1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-09 00:00:01 +01:00

updated infrastructure page structure

Signed-off-by: Andrii Podriez <andrey5577990@gmail.com>
This commit is contained in:
Andrii Podriez 2024-07-25 13:53:26 +02:00 committed by al3xa23
parent 67a5484ca3
commit 4284fc1876
6 changed files with 1018 additions and 83 deletions

View file

@ -0,0 +1,248 @@
/**
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { Form, Col, Row } from 'react-bootstrap';
import Dialog from '../../../common/dialogs/dialog';
import ParametersEditor from '../../../common/parameters-editor';
import NotificationsDataManager from "../../../common/data-managers/notifications-data-manager";
import NotificationsFactory from "../../../common/data-managers/notifications-factory";
class EditICDialog extends React.Component {
valid = true;
constructor(props) {
super(props);
this.state = {
name: '',
websocketurl: '',
apiurl: '',
location: '',
description: '',
type: '',
category: '',
icstate: '',
managedexternally: false,
startparameterschema: {}
};
}
onClose(canceled) {
if (canceled === false) {
if (this.valid) {
let data = JSON.parse(JSON.stringify(this.props.ic));
if (this.state.name != null && this.state.name !== "" && this.state.name !== this.props.ic.name) {
data.name = this.state.name;
}
data.websocketurl = this.state.websocketurl;
data.apiurl = this.state.apiurl;
if (this.state.location != null && this.state.location !== this.props.ic.location) {
data.location = this.state.location;
}
if (this.state.description != null && this.state.description !== this.props.ic.description) {
data.description = this.state.description;
}
if (this.state.type != null && this.state.type !== "" && this.state.type !== this.props.ic.type) {
data.type = this.state.type;
}
if (this.state.category != null && this.state.category !== "" && this.state.category !== this.props.ic.category) {
data.category = this.state.category;
}
if (this.state.icstate != null && this.state.icstate !== "" && this.state.icstate !== this.props.ic.state) {
data.state = this.state.icstate;
}
if (Object.keys(this.state.startparameterschema).length === 0 && this.state.startparameterschema.constructor === Object) {
data.startparameterschema = this.state.startparameterschema;
}
data.managedexternally = this.state.managedexternally;
this.props.onClose(data);
this.setState({managedexternally: false});
}
} else {
this.props.onClose();
this.setState({managedexternally: false});
}
}
handleChange(e) {
if(e.target.id === "managedexternally"){
this.setState({ managedexternally : !this.state.managedexternally});
}
else{
this.setState({ [e.target.id]: e.target.value });
}
}
handleStartParameterSchemaChange(data) {
this.setState({ startparameterschema: data });
}
resetState() {
this.setState({
name: this.props.ic.name,
websocketurl: this.props.ic.websocketurl,
apiurl: this.props.ic.apiurl,
type: this.props.ic.type,
location: this.props.ic.location,
description: this.props.ic.description,
category: this.props.ic.category,
icstate: this.props.ic.state,
managedexternally: false,
startparameterschema: this.props.ic.startparameterschema || {},
});
}
selectStartParamsFile(event) {
const file = event.target.files[0];
if (!file.type.match('application/json')) {
console.error("Not a json file. Will not process file '" + file.name + "'.")
NotificationsDataManager.addNotification(NotificationsFactory.UPDATE_ERROR("Not a json file. Will not process file \'" + file.name + "\'."));
return;
}
let reader = new FileReader();
reader.readAsText(file);
reader.onload = event => {
const params = JSON.parse(reader.result);
this.setState({ startparameterschema: params})
}
};
render() {
let typeOptions = [];
switch(this.state.category){
case "simulator":
typeOptions = ["dummy","generic","dpsim","rtlab","rscad","rtlab","kubernetes"];
break;
case "manager":
typeOptions = ["villas-node","villas-relay","generic"];
break;
case "gateway":
typeOptions = ["villas-node","villas-relay"];
break;
case "service":
typeOptions = ["ems","custom"];
break;
case "equipment":
typeOptions = ["chroma-emulator","chroma-loads","sma-sunnyboy","fleps","sonnenbatterie"];
break;
default:
typeOptions =[];
}
let stateOptions = ["idle", "starting", "running", "pausing", "paused", "resuming", "stopping", "resetting", "error", "gone", "shuttingdown", "shutdown"];
return (
<Dialog
show={this.props.show}
title="Edit Infrastructure Component"
buttonTitle="Save"
onClose={(c) => this.onClose(c)}
onReset={() => this.resetState()}
valid={this.valid}
>
<Form>
<Form.Label column={false}>UUID: {this.props.ic.uuid}</Form.Label>
<Form.Group controlId="name" style={{marginBottom: '15px'}}>
<Form.Label column={false}>Name</Form.Label>
<Form.Control type="text" placeholder={this.props.ic.name} value={this.state.name} onChange={(e) => this.handleChange(e)} />
<Form.Control.Feedback />
</Form.Group>
<Form.Group controlId="category" style={{marginBottom: '15px'}}>
<Form.Label column={false}>Category</Form.Label>
<Form.Control as="select" value={this.state.category} onChange={(e) => this.handleChange(e)}>
<option>simulator</option>
<option>service</option>
<option>gateway</option>
<option>equipment</option>
<option>manager</option>
</Form.Control>
</Form.Group>
<Form.Group controlId="type" style={{marginBottom: '15px'}}>
<Form.Label column={false}>Type</Form.Label>
<Form.Control as="select" value={this.state.type} onChange={(e) => this.handleChange(e)}>
<option default>Select type</option>
{typeOptions.map((name,index) => (
<option key={index}>{name}</option>
))}
</Form.Control>
</Form.Group>
{
this.state.type === "dummy" ?
<Form.Group controlId="icstate" style={{marginBottom: '15px'}}>
<Form.Label column={false}>State</Form.Label>
<Form.Control as="select" value={this.state.icstate} onChange={(e) => this.handleChange(e)}>
<option default>Select State</option>
{stateOptions.map((name,index) => (
<option key={index}>{name}</option>
))}
</Form.Control>
</Form.Group>
: <></>
}
<Form.Group controlId="websocketurl" style={{marginBottom: '15px'}}>
<Form.Label column={false}>Websocket URL</Form.Label>
<Form.Control type="text" placeholder={this.props.ic.websocketurl} value={this.state.websocketurl} onChange={(e) => this.handleChange(e)} />
<Form.Control.Feedback />
</Form.Group>
<Form.Group controlId="apiurl" style={{marginBottom: '15px'}}>
<Form.Label column={false}>API URL</Form.Label>
<Form.Control type="text" placeholder={this.props.ic.apiurl} value={this.state.apiurl} onChange={(e) => this.handleChange(e)} />
<Form.Control.Feedback />
</Form.Group>
<Form.Group controlId="location" style={{marginBottom: '15px'}}>
<Form.Label column={false}>Location</Form.Label>
<Form.Control type="text" placeholder={this.props.ic.location} value={this.state.location || '' } onChange={(e) => this.handleChange(e)} />
<Form.Control.Feedback />
</Form.Group>
<Form.Group controlId="description">
<Form.Label column={false}>Description</Form.Label>
<Form.Control type="text" placeholder={this.props.ic.description} value={this.state.description || '' } onChange={(e) => this.handleChange(e)} />
<Form.Control.Feedback />
</Form.Group>
<hr/>
<Form.Group controlId='startParameterSchema' >
<Form.Label column={false}>Start parameter schema of IC</Form.Label>
<ParametersEditor
content={this.state.startparameterschema}
onChange={(data) => this.handleStartParameterSchemaChange(data)}
disabled={true}
/>
<Form.Label style={{marginTop: '15px'}} column={false}>Select JSON file to update start parameter schema: </Form.Label>
<Form.Control type='file' onChange={(event) => this.selectStartParamsFile(event)} />
</Form.Group>
</Form>
</Dialog>
);
}
}
export default EditICDialog;

View file

@ -0,0 +1,148 @@
/**
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { Form } from 'react-bootstrap';
import _ from 'lodash';
import Dialog from '../../../common/dialogs/dialog';
class ImportICDialog extends React.Component {
valid = false;
imported = false;
constructor(props) {
super(props);
this.state = {
name: '',
websocketurl: '',
uuid: ''
};
}
onClose(canceled) {
if (canceled === false) {
if (this.valid) {
const data = {
properties: {
name: this.state.name
},
uuid: this.state.uuid
};
if (this.state.websocketurl != null && this.state.websocketurl !== "" && this.state.websocketurl !== 'http://') {
data.websocketurl = this.state.websocketurl;
}
this.props.onClose(data);
}
} else {
this.props.onClose();
}
}
handleChange(e) {
this.setState({ [e.target.id]: e.target.value });
}
resetState() {
this.setState({ name: '', websocketurl: 'http://', uuid: '' });
}
loadFile(fileList) {
// get file
const file = fileList[0];
if (!file.type.match('application/json')) {
return;
}
// create file reader
const reader = new FileReader();
const self = this;
reader.onload = function(event) {
// read component
const ic = JSON.parse(event.target.result);
self.imported = true;
self.setState({
name: _.get(ic, 'properties.name') || _.get(ic, 'rawProperties.name'),
websocketurl: _.get(ic, 'websocketurl'),
uuid: ic.uuid
});
};
reader.readAsText(file);
}
validateForm(target) {
// check all controls
let name = true;
let uuid = true;
if (this.state.name === '') {
name = false;
}
if (this.state.uuid === '') {
uuid = false;
}
this.valid = name || uuid;
// return state to control
if (target === 'name') return name ? "success" : "error";
if (target === 'uuid') return uuid ? "success" : "error";
}
render() {
return (
<Dialog
show={this.props.show}
title="Import Infrastructure Component"
buttonTitle="Add"
onClose={(c) => this.onClose(c)}
onReset={() => this.resetState()}
valid={this.valid}
>
<Form>
<Form.Group controlId="file">
<Form.Label>Infrastructure Component File</Form.Label>
<Form.Control type="file" onChange={(e) => this.loadFile(e.target.files)} />
</Form.Group>
<Form.Group controlId="name" valid={this.validateForm('name')}>
<Form.Label>Name</Form.Label>
<Form.Control type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<Form.Control.Feedback />
</Form.Group>
<Form.Group controlId="websocketurl">
<Form.Label>Websocket URL</Form.Label>
<Form.Control type="text" placeholder="Enter websocketurl" value={this.state.websocketurl} onChange={(e) => this.handleChange(e)} />
<Form.Control.Feedback />
</Form.Group>
<Form.Group controlId="uuid" valid={this.validateForm('uuid')}>
<Form.Label>UUID</Form.Label>
<Form.Control type="text" placeholder="Enter uuid" value={this.state.uuid} onChange={(e) => this.handleChange(e)} />
<Form.Control.Feedback />
</Form.Group>
</Form>
</Dialog>
);
}
}
export default ImportICDialog;

View file

@ -0,0 +1,354 @@
/**
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import { Form as BForm, OverlayTrigger, Tooltip} from 'react-bootstrap';
import Dialog from '../../../common/dialogs/dialog';
import ParametersEditor from '../../../common/parameters-editor';
import Form from "@rjsf/core";
import $RefParser from '@apidevtools/json-schema-ref-parser';
class NewICDialog extends React.Component {
valid = false;
constructor(props) {
super(props);
this.state = {
name: '',
websocketurl: '',
apiurl: '',
uuid: '',
type: '',
category: '',
managedexternally: false,
description: '',
location: '',
manager: '',
properties: {},
schema: {},
formData: {},
};
}
onClose(canceled) {
if (canceled === false) {
if (this.valid) {
const parameters = {
name: this.state.name,
type: this.state.type,
category: this.state.category,
uuid: this.state.uuid,
location: this.state.location,
description: this.state.description,
}
const data = {
managedexternally: this.state.managedexternally,
manager: this.state.manager,
name: this.state.name,
type: this.state.type,
category: this.state.category,
uuid: this.state.uuid,
description: this.state.description,
location: this.state.location,
parameters: parameters
};
// Add custom properties
if (this.state.managedexternally)
Object.assign(parameters, this.state.properties);
if (this.state.websocketurl != null && this.state.websocketurl !== "") {
parameters.websocketurl = this.state.websocketurl;
data.websocketurl = this.state.websocketurl;
}
if (this.state.apiurl != null && this.state.apiurl !== "") {
parameters.apiurl = this.state.apiurl;
data.apiurl = this.state.apiurl;
}
this.props.onClose(data);
this.setState({managedexternally: false});
}
} else {
this.props.onClose();
this.setState({managedexternally: false});
}
}
handleChange(e) {
if(e.target.id === "managedexternally"){
this.setState({ managedexternally : !this.state.managedexternally});
}
else{
this.setState({ [e.target.id]: e.target.value });
}
}
setManager(e) {
this.setState({ [e.target.id]: e.target.value });
if (this.props.managers) {
let schema = this.props.managers.find(m => m.uuid === e.target.value).createparameterschema
if (schema) {
$RefParser.dereference(schema, (err, deref) => {
if (err) {
console.error(err)
}
else {
this.setState({schema: schema})
}
})
}
}
}
handlePropertiesChange = properties => {
this.setState({
properties: properties
});
};
handleFormChange({formData}) {
this.setState({properties: formData, formData: formData})
}
resetState() {
this.setState({
name: '',
websocketurl: '',
apiurl: '',
uuid: this.uuidv4(),
type: '',
category: '',
managedexternally: false,
description: '',
location: '',
properties: {},
});
}
validateForm(target) {
if (this.state.managedexternally) {
this.valid = this.state.manager !== '';
return this.state.manager !== '' ? "success" : "error";
}
// check all controls
let name = true;
let uuid = true;
let type = true;
let category = true;
if (this.state.name === '') {
name = false;
}
if (this.state.uuid === '') {
uuid = false;
}
if (this.state.type === '') {
type = false;
}
if (this.state.category === '') {
category = false;
}
this.valid = name && uuid && type && category;
// return state to control
if (target === 'name') return name ? "success" : "error";
if (target === 'uuid') return uuid ? "success" : "error";
if (target === 'type') return type ? "success" : "error";
if (target === 'category') return category ? "success" : "error";
return this.valid;
}
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);
});
}
render() {
let typeOptions = [];
switch(this.state.category){
case "simulator":
typeOptions = ["dummy","generic","dpsim","rtlab","rscad","rtlab","kubernetes"];
break;
case "manager":
typeOptions = ["villas-node","villas-relay","generic","kubernetes"];
break;
case "gateway":
typeOptions = ["villas-node","villas-relay"];
break;
case "service":
typeOptions = ["ems","custom"];
break;
case "equipment":
typeOptions = ["chroma-emulator","chroma-loads","sma-sunnyboy","fleps","sonnenbatterie"];
break;
default:
typeOptions =[];
}
let managerOptions = [];
managerOptions.push(<option key={-1} value={"Select manager"} defaultChecked={true}>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()}
>
{this.props.managers.length > 0 ?
<BForm>
<BForm.Group controlId="managedexternally">
<OverlayTrigger key="-1" placement={'left'}
overlay={<Tooltip id={`tooltip-${"me"}`}>An externally managed component is created and
managed by an IC manager via AMQP</Tooltip>}>
<BForm.Check type={"checkbox"} label={"Use manager"} defaultChecked={this.state.managedexternally}
onChange={e => this.handleChange(e)}>
</BForm.Check>
</OverlayTrigger>
</BForm.Group>
</BForm> : <></>
}
{this.state.managedexternally === true ?
<>
<BForm>
<BForm.Group controlId="manager" valid={this.validateForm('manager')}>
<OverlayTrigger key="0" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<BForm.Label>Manager to create new IC *</BForm.Label>
</OverlayTrigger>
<BForm.Control as="select" value={this.state.manager} onChange={(e) => this.setManager(e)}>
{managerOptions}
</BForm.Control>
</BForm.Group>
</BForm>
{this.state.schema ?
<Form
schema={this.state.schema}
formData={this.state.properties}
id='jsonFormData'
onChange={({formData}) => this.handleFormChange({formData})}
children={true} // hides submit button
/>
:
<BForm>
<BForm.Group controlId="properties">
<BForm.Label>Create Properties</BForm.Label>
<ParametersEditor
content={this.state.properties}
onChange={(data) => this.handlePropertiesChange(data)}
/>
</BForm.Group>
</BForm>
}
</>
:
<BForm>
<BForm.Group controlId="name" valid={this.validateForm('name')}>
<OverlayTrigger key="1" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<BForm.Label>Name *</BForm.Label>
</OverlayTrigger>
<BForm.Control type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<BForm.Control.Feedback />
</BForm.Group>
<BForm.Group controlId="uuid" valid={this.validateForm('uuid')}>
<BForm.Label>UUID</BForm.Label>
<BForm.Control type="text" placeholder="Enter uuid" value={this.state.uuid}
onChange={(e) => this.handleChange(e)}/>
<BForm.Control.Feedback/>
</BForm.Group>
<BForm.Group controlId="location">
<BForm.Label>Location</BForm.Label>
<BForm.Control type="text" placeholder="Enter Location" value={this.state.location} onChange={(e) => this.handleChange(e)} />
<BForm.Control.Feedback />
</BForm.Group>
<BForm.Group controlId="description">
<BForm.Label>Description</BForm.Label>
<BForm.Control type="text" placeholder="Enter Description" value={this.state.description} onChange={(e) => this.handleChange(e)} />
<BForm.Control.Feedback />
</BForm.Group>
<BForm.Group controlId="category" valid={this.validateForm('category')}>
<OverlayTrigger key="2" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<BForm.Label>Category of component *</BForm.Label>
</OverlayTrigger>
<BForm.Control as="select" value={this.state.category} onChange={(e) => this.handleChange(e)}>
<option default>Select category</option>
<option value="simulator">Simulator</option>
<option value="service">Service</option>
<option value="gateway">Gateway</option>
<option value="equipment">Equipment</option>
<option value="manager">Manager</option>
</BForm.Control>
</BForm.Group>
<BForm.Group controlId="type" valid={this.validateForm('type')}>
<OverlayTrigger key="3" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<BForm.Label>Type of component *</BForm.Label>
</OverlayTrigger>
<BForm.Control as="select" value={this.state.type} onChange={(e) => this.handleChange(e)}>
<option default>Select type</option>
{typeOptions.map((name,index) => (
<option key={index}>{name}</option>
))}
</BForm.Control>
</BForm.Group>
<BForm.Group controlId="websocketurl">
<BForm.Label>Websocket URL</BForm.Label>
<BForm.Control type="text" placeholder="https://" value={this.state.websocketurl} onChange={(e) => this.handleChange(e)} />
<BForm.Control.Feedback />
</BForm.Group>
<BForm.Group controlId="apiurl">
<BForm.Label>API URL</BForm.Label>
<BForm.Control type="text" placeholder="https://" value={this.state.apiurl} onChange={(e) => this.handleChange(e)} />
<BForm.Control.Feedback />
</BForm.Group>
</BForm>
}
</Dialog>
);
}
}
export default NewICDialog;

View file

@ -0,0 +1,114 @@
/**
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import { Form, Row, Col } from 'react-bootstrap';
import DateTimePicker from 'react-datetime-picker';
import ActionBoardButtonGroup from '../../common/buttons/action-board-button-group';
import classNames from 'classnames';
import { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { sessionToken } from '../../localStorage';
import { clearCheckedICs, deleteIC, loadAllICs, sendActionToIC } from '../../store/icSlice';
const ICActionBoard = (props) => {
const dispatch = useDispatch();
const checkedICsIds = useSelector(state => state.infrastructure.checkedICsIds);
let pickedTime = new Date();
pickedTime.setMinutes(5 * Math.round(pickedTime.getMinutes() / 5 + 1));
const [time, setTime] = useState(pickedTime);
const onReset = () => {
let newAction = {};
newAction['action'] = 'reset';
newAction['when'] = time;
checkedICsIds.forEach((id) => {
dispatch(sendActionToIC({token: sessionToken, id: id, actions: newAction}));
});
}
const onRecreate = () => {
let newAction = {};
newAction['action'] = 'create';
newAction['when'] = time;
checkedICsIds.forEach((id) => {
dispatch(sendActionToIC({token: sessionToken, id: id, actions: newAction}));
});
}
const onDelete = () => {
checkedICsIds.forEach((id) => {
console.log(id)
dispatch(deleteIC({token: sessionToken, id: id}));
});
dispatch(clearCheckedICs());
dispatch(loadAllICs({token: sessionToken}));
}
const onShutdown = () => {
let newAction = {};
newAction['action'] = 'shutdown';
newAction['when'] = time;
checkedICsIds.forEach((id) => {
dispatch(sendActionToIC({token: sessionToken, id: id, actions: newAction}));
});
}
return (<div className={classNames('section', 'box')}>
<Row className='align-items-center'>
<Col style={{padding: '10px'}} md='auto' lg='auto'>
<Form>
<DateTimePicker
onChange={(newTime) => setTime(newTime)}
value={time}
disableClock={true}
/>
</Form>
</Col>
<Col style={{padding: '20px'}} md='auto' lg='auto'>
<ActionBoardButtonGroup
disabled={checkedICsIds.length == 0}
onReset={() => onReset()}
onShutdown={() => onShutdown()}
onDelete={() => onDelete()}
onRecreate={() => onRecreate()}
paused={false}
/>
</Col>
{false ?
<Col style={{padding: '20px'}} md='auto' lg='auto'>
<Form.Group controlId="resultCheck">
<Form.Check
type="checkbox"
label="Create Result"
checked={false}
onChange={null}
/>
</Form.Group>
</Col> : <></>
}
</Row>
<small className="text-muted">Select time for synced command execution</small>
</div>);
}
export default ICActionBoard;

View file

@ -15,41 +15,25 @@
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import { useSelector, useDispatch } from "react-redux";
import { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import {Table, ButtonColumn, CheckboxColumn, DataColumn, LabelColumn, LinkColumn } from '../../common/table';
import { Badge } from 'react-bootstrap';
import FileSaver from 'file-saver';
import _ from 'lodash';
import moment from 'moment'
import IconToggleButton from "../../common/buttons/icon-toggle-button";
import { checkICsByCategory } from "../../store/icSlice";
import { useState } from "react";
import { updateCheckedICs, openDeleteModal, openEditModal } from "../../store/icSlice";
import { stateLabelStyle } from "./styles";
import { currentUser } from "../../localStorage";
//a Table of IC components of specific category from props.category
//titled with props.title
const ICCategoryTable = (props) => {
const dispatch = useDispatch();
const ics = useSelector(state => state.infrastructure.ICsArray);
let currentUser = JSON.parse(localStorage.getItem("currentUser"));
const checkedICs = useSelector(state => state.infrastructure.checkedICsArray);
const [isGenericDisplayed, setIsGenericDisplayed] = useState(false)
const exportIC = (index) => {
// filter properties
let ic = Object.assign({}, ics[index]);
delete ic.id;
// show save dialog
const blob = new Blob([JSON.stringify(ic, null, 2)], { type: 'application/json' });
FileSaver.saveAs(blob, 'ic-' + (_.get(ic, 'name') || 'undefined') + '.json');
}
const [isGenericDisplayed, setIsGenericDisplayed] = useState(false);
const modifyUptimeColumn = (uptime, component) => {
if (uptime >= 0) {
@ -89,8 +73,6 @@ const ICCategoryTable = (props) => {
return 99;
}
}
//if category of the table is manager we need to filter the generic-typed ics
//according to the state of IconToggleButton
let tableData = [];
@ -113,29 +95,63 @@ const ICCategoryTable = (props) => {
}
})
const isLocalIC = (index, ics) => {
let ic = ics[index]
const [checkedICs, setCheckedICs] = useState({});
const [areAllICsChecked, setAreAllICsChecked] = useState(false);
useEffect(() => {
if(tableData.length > 0){
for(let ic in tableData){
if(!checkedICs.hasOwnProperty(tableData[ic].id) && !isLocalIC(tableData[ic])){
setCheckedICs(prevState => ({...prevState, [tableData[ic].id]: false}));
}
}
}
}, [tableData])
useEffect(() => {
dispatch(updateCheckedICs(checkedICs));
//every time some ic is checked we need to check wether or not all ics are checked afterwards
if(Object.keys(checkedICs).length > 0){
setAreAllICsChecked(Object.values(checkedICs).every((value)=> value))
}
}, [checkedICs])
const exportIC = (index) => {
// filter properties
let toExport = {...tableData[index]};
delete toExport.id;
const fileName = toExport.name.replace(/\s+/g, '-').toLowerCase();
// show save dialog
const blob = new Blob([JSON.stringify(toExport, null, 2)], { type: 'application/json' });
FileSaver.saveAs(blob, 'ic-' + fileName + '.json');
}
const isLocalIC = (ic) => {
return !ic.managedexternally
}
const checkAllICs = (ics, title) => {
//TODO
}
const isICchecked = (ic) => {
//TODO
return false
}
const areAllChecked = (title) => {
//TODO
return false
}
const onICChecked = (ic, event) => {
//TODO
const checkAllICs = () => {
if(areAllICsChecked){
for(const id in checkedICs){
setCheckedICs(prevState => ({...prevState, [id]: false}));
}
} else {
for(const id in checkedICs){
setCheckedICs(prevState => ({...prevState, [id]: true}));
}
}
}
const toggleCheck = (id) => {
setCheckedICs(prevState => {
return {
...prevState, [id]: !prevState[id]
}
})
}
return (<div>
<h2>
{props.title}
@ -157,11 +173,11 @@ const ICCategoryTable = (props) => {
<Table data={tableData}>
<CheckboxColumn
enableCheckAll
onCheckAll={() => checkAllICs(ics, props.title)}
allChecked={areAllChecked(props.title)}
checkboxDisabled={(index) => isLocalIC(index, ics) === true}
checked={(ic) => isICchecked(ic)}
onChecked={(ic, event) => onICChecked(ic, event)}
onCheckAll={() => checkAllICs()}
allChecked={areAllICsChecked}
checkboxDisabled={(index) => isLocalIC(tableData[index])}
checked={(ic) => checkedICs[ic.id]}
onChecked={(ic, event) => toggleCheck(ic.id)}
width='30'
/>
{currentUser.role === "Admin" ?
@ -202,13 +218,13 @@ const ICCategoryTable = (props) => {
width='150'
align='right'
editButton
showEditButton ={(index) => isLocalIC(index, ics)}
showEditButton ={(index) => isLocalIC(tableData[index])}
exportButton
deleteButton
showDeleteButton = {null}
onEdit={index => {}}
onEdit={index => {dispatch(openEditModal(tableData[index]))}}
onExport={index => exportIC(index)}
onDelete={index => {}}
onDelete={index => {dispatch(openDeleteModal(tableData[index]))}}
/>
:
<ButtonColumn

View file

@ -18,33 +18,32 @@
import { useEffect, useState } from "react"
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { Badge } from 'react-bootstrap';
import { loadAllICs, loadICbyId } from "../../store/icSlice";
import { set } from "lodash";
import { loadAllICs, loadICbyId, addIC, sendActionToIC, closeDeleteModal, closeEditModal, editIC, deleteIC } from "../../store/icSlice";
import IconButton from "../../common/buttons/icon-button";
import ICCategoryTable from "./ic-category-table";
import { sessionToken, currentUser } from "../../localStorage";
import ICActionBoard from "./ic-action-board";
import { buttonStyle, iconStyle } from "./styles";
import NewICDialog from "./dialogs/new-ic-dialog";
import ImportICDialog from "./dialogs/import-ic-dialog";
import EditICDialog from "./dialogs/edit-ic-dialog";
import DeleteDialog from "../../common/dialogs/delete-dialog";
import NotificationsDataManager from "../../common/data-managers/notifications-data-manager";
import NotificationsFactory from "../../common/data-managers/notifications-factory";
const Infrastructure = (props) => {
const dispatch = useDispatch();
const ICsArray = useSelector(state => state.infrastructure.ICsArray);
const ics = useSelector(state => state.infrastructure.ICsArray);
const externalICs = ics.filter(ic => ic.managedexternally === true);
//track status of the modals
const [isEditModalOpened, setIsEditModalOpened] = useState(false);
const [isNewModalOpened, setIsNewModalOpened] = useState(false);
const [isImportModalOpened, setIsImportModalOpened] = useState(false);
const [isDeleteModalOpened, setIsDeleteModalOpened] = useState(false);
const [isICModalOpened, setIsICModalOpened] = useState(false);
const [checkedICs, setCheckedICs] = useState([]);
const currentUser = JSON.parse(localStorage.getItem("currentUser"));
useEffect(() => {
//load array of ics and start a timer for periodic refresh
dispatch(loadAllICs({token: sessionToken}));
@ -62,21 +61,69 @@ const Infrastructure = (props) => {
}
}
const buttonStyle = {
marginLeft: '10px',
}
const iconStyle = {
height: '30px',
width: '30px'
//modal actions and selectors
const isEditModalOpened = useSelector(state => state.infrastructure.isEditModalOpened);
const isDeleteModalOpened = useSelector(state => state.infrastructure.isDeleteModalOpened);
const editModalIC = useSelector(state => state.infrastructure.editModalIC);
const deleteModalIC = useSelector(state => state.infrastructure.deleteModalIC);
const onNewModalClose = (data) => {
setIsNewModalOpened(false);
console.log("Adding ic. External: ", !data.managedexternally)
if(data){
if(!data.managedexternally){
dispatch(addIC({token: sessionToken, ic: data})).then(res => dispatch(loadAllICs({token: sessionToken})));
}else {
// externally managed IC: dispatch create action to selected manager
let newAction = {};
newAction["action"] = "create";
newAction["parameters"] = data.parameters;
newAction["when"] = new Date();
// find the manager IC
const managerIC = ics.find(ic => ic.uuid === data.manager)
if (managerIC === null || managerIC === undefined) {
NotificationsDataManager.addNotification(NotificationsFactory.ADD_ERROR("Could not find manager IC with UUID " + data.manager));
return;
}
dispatch(sendActionToIC({token: sessionToken, id: managerIC.id, actions: newAction})).then(res => dispatch(loadAllICs({token: sessionToken})));
}
}
}
const onImportModalClose = (data) => {
setIsImportModalOpened(false);
dispatch(addIC({token: sessionToken, ic: data})).then(res => dispatch(loadAllICs({token: sessionToken})));
}
const onEditModalClose = (data) => {
if(data){
//some changes where done
dispatch(editIC({token: sessionToken, ic: data})).then(res => dispatch(loadAllICs({token: sessionToken})));
}
dispatch(closeEditModal(data));
}
const onCloseDeleteModal = (isDeleteConfirmed) => {
if(isDeleteConfirmed){
dispatch(deleteIC({token: sessionToken, id:deleteModalIC.id})).then(res => dispatch(loadAllICs({token: sessionToken})));
}
dispatch(closeDeleteModal());
}
//getting list of managers for the new IC modal
const managers = ics.filter(ic => ic.category === "manager");
return (
<div>
<div className='section'>
<h1>Infrastructure
{//TODO
/* {currentUser.role === "Admin" ?
{currentUser.role === "Admin" ?
<span className='icon-button'>
<IconButton
childKey={1}
@ -96,13 +143,12 @@ const Infrastructure = (props) => {
/>
</span>
:
<span />
} */}
<span />}
</h1>
<ICCategoryTable
title={"IC Managers"}
category={"manager"}
category={"manager"}
/>
<ICCategoryTable
@ -125,16 +171,25 @@ const Infrastructure = (props) => {
category={"equipment"}
/>
{currentUser.role === "Admin" ? <ICActionBoard /> : null}
</div>
{//TODO
/* <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)} /> */}
<NewICDialog show={isNewModalOpened} onClose={data => onNewModalClose(data)} managers={managers} />
<ImportICDialog show={isImportModalOpened} onClose={data => onImportModalClose(data)} />
<EditICDialog
show={isEditModalOpened}
onClose={data => onEditModalClose(data)}
ic={editModalIC ? editModalIC : {}}
/>
<DeleteDialog
title="infrastructure-component"
name={deleteModalIC ? deleteModalIC.name : 'Unknown'}
show={isDeleteModalOpened}
onClose={(e) => onCloseDeleteModal(e)}
/>
</div>
);
}
export default Infrastructure;
export default Infrastructure;