From aa4b0edfa324968bf9ff2aca4ea18ea989ccc9e1 Mon Sep 17 00:00:00 2001
From: Andrii Podriez <andrey5577990@gmail.com>
Date: Thu, 25 Jul 2024 13:53:26 +0200
Subject: [PATCH] updated infrastructure page structure

Signed-off-by: Andrii Podriez <andrey5577990@gmail.com>
---
 .../infrastructure/dialogs/edit-ic-dialog.js  | 248 ++++++++++++
 .../dialogs/import-ic-dialog.js               | 148 ++++++++
 .../infrastructure/dialogs/new-ic-dialog.js   | 354 ++++++++++++++++++
 src/pages/infrastructure/ic-action-board.js   | 114 ++++++
 src/pages/infrastructure/ic-category-table.js | 116 +++---
 src/pages/infrastructure/infrastructure.js    | 121 ++++--
 6 files changed, 1018 insertions(+), 83 deletions(-)
 create mode 100644 src/pages/infrastructure/dialogs/edit-ic-dialog.js
 create mode 100644 src/pages/infrastructure/dialogs/import-ic-dialog.js
 create mode 100644 src/pages/infrastructure/dialogs/new-ic-dialog.js
 create mode 100644 src/pages/infrastructure/ic-action-board.js

diff --git a/src/pages/infrastructure/dialogs/edit-ic-dialog.js b/src/pages/infrastructure/dialogs/edit-ic-dialog.js
new file mode 100644
index 0000000..9a87e7d
--- /dev/null
+++ b/src/pages/infrastructure/dialogs/edit-ic-dialog.js
@@ -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;
diff --git a/src/pages/infrastructure/dialogs/import-ic-dialog.js b/src/pages/infrastructure/dialogs/import-ic-dialog.js
new file mode 100644
index 0000000..e8db469
--- /dev/null
+++ b/src/pages/infrastructure/dialogs/import-ic-dialog.js
@@ -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;
diff --git a/src/pages/infrastructure/dialogs/new-ic-dialog.js b/src/pages/infrastructure/dialogs/new-ic-dialog.js
new file mode 100644
index 0000000..a4380e7
--- /dev/null
+++ b/src/pages/infrastructure/dialogs/new-ic-dialog.js
@@ -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;
diff --git a/src/pages/infrastructure/ic-action-board.js b/src/pages/infrastructure/ic-action-board.js
new file mode 100644
index 0000000..f330777
--- /dev/null
+++ b/src/pages/infrastructure/ic-action-board.js
@@ -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;
diff --git a/src/pages/infrastructure/ic-category-table.js b/src/pages/infrastructure/ic-category-table.js
index dce214a..3e3a38a 100644
--- a/src/pages/infrastructure/ic-category-table.js
+++ b/src/pages/infrastructure/ic-category-table.js
@@ -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
diff --git a/src/pages/infrastructure/infrastructure.js b/src/pages/infrastructure/infrastructure.js
index a69211d..92d9018 100644
--- a/src/pages/infrastructure/infrastructure.js
+++ b/src/pages/infrastructure/infrastructure.js
@@ -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;
\ No newline at end of file
+export default Infrastructure;