From 0ad902376b16b11a40fe8dd2edcd58b34e7c6517 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 10 Nov 2021 09:36:12 -0500 Subject: [PATCH] Version 1.0.26 --- package-lock.json | 4 +- package.json | 2 +- src/components/AddContactForm/index.js | 8 +- src/components/AddLocationForm/index.js | 8 +- src/components/AddSimulationForm/index.js | 542 +++++++++++++++++ src/components/AddressEditor/index.js | 10 +- .../Configuration/CustomMultiModal/index.js | 11 +- .../Configuration/SectionToggler/index.js | 5 +- src/components/ConfigurationTable/index.js | 2 +- .../ConfirmStopEditingButton/index.js | 64 ++ src/components/ContactTable/index.js | 6 +- src/components/CopyToClipboardButton/index.js | 2 +- src/components/DeviceDetails/index.js | 4 +- src/components/DeviceListTable/index.js | 147 ++--- .../DeviceListTable/index.module.scss | 4 +- src/components/DeviceSearchBar/index.js | 11 +- src/components/DeviceStatusCard/index.js | 17 +- src/components/EditConfigurationForm/index.js | 16 +- src/components/EditContactForm/index.js | 124 ++-- src/components/EditEntityForm/index.js | 16 +- src/components/EditInventoryTagForm/index.js | 404 +++++++++---- src/components/EditLocationForm/index.js | 126 ++-- src/components/EditSimulationForm/index.js | 554 ++++++++++++++++++ src/components/EntityTree/index.js | 81 +++ src/components/FileToStringButton/index.js | 12 +- src/components/FirmwareDetailsForm/index.js | 31 +- src/components/FirmwareList/index.js | 24 +- src/components/FormattedDate/index.js | 2 +- src/components/HideTextButton/index.js | 2 +- src/components/InventoryTable/index.js | 2 +- src/components/LocationTable/index.js | 6 +- src/components/SimulationTable/index.js | 273 +++++++++ .../SimulationTable/index.module.scss | 16 + src/components/UserListTable/index.js | 1 + src/index.js | 5 + src/layout/Header/index.js | 37 +- src/layout/Sidebar/index.js | 19 +- 37 files changed, 2212 insertions(+), 386 deletions(-) create mode 100644 src/components/AddSimulationForm/index.js create mode 100644 src/components/ConfirmStopEditingButton/index.js create mode 100644 src/components/EditSimulationForm/index.js create mode 100644 src/components/EntityTree/index.js create mode 100644 src/components/SimulationTable/index.js create mode 100644 src/components/SimulationTable/index.module.scss diff --git a/package-lock.json b/package-lock.json index 88fa604..e025ca1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ucentral-libs", - "version": "1.0.1", + "version": "1.0.26", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ucentral-libs", - "version": "1.0.1", + "version": "1.0.26", "license": "BSD-3-Clause", "dependencies": { "@coreui/coreui": "^3.4.0", diff --git a/package.json b/package.json index 52da25c..a856d0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ucentral-libs", - "version": "1.0.1", + "version": "1.0.26", "main": "dist/index.js", "source": "src/index.js", "engines": { diff --git a/src/components/AddContactForm/index.js b/src/components/AddContactForm/index.js index 9689dfd..7d43b91 100644 --- a/src/components/AddContactForm/index.js +++ b/src/components/AddContactForm/index.js @@ -256,7 +256,7 @@ const AddContactForm = ({ t, disable, fields, updateField, updateFieldWithKey, e {t('common.required')} - Landlines + {t('location.phones')} ({ value: opt, label: opt }))} - placeholder={t('common.type_for_options')} + placeholder="+1(202)555-0103" /> - Mobiles + {t('location.mobiles')} ({ value: opt, label: opt }))} - placeholder={t('common.type_for_options')} + placeholder="+1(202)555-0103" /> diff --git a/src/components/AddLocationForm/index.js b/src/components/AddLocationForm/index.js index fc5f02d..4cde5c3 100644 --- a/src/components/AddLocationForm/index.js +++ b/src/components/AddLocationForm/index.js @@ -130,7 +130,7 @@ const AddLocationForm = ({ - Landlines + {t('location.phones')} ({ value: opt, label: opt }))} - placeholder={t('common.type_for_options')} + placeholder="+1(202)555-0103" /> - Mobiles + {t('location.mobiles')} ({ value: opt, label: opt }))} - placeholder={t('common.type_for_options')} + placeholder="+1(202)555-0103" /> diff --git a/src/components/AddSimulationForm/index.js b/src/components/AddSimulationForm/index.js new file mode 100644 index 0000000..b0204b7 --- /dev/null +++ b/src/components/AddSimulationForm/index.js @@ -0,0 +1,542 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { + CForm, + CInput, + CLabel, + CCol, + CFormText, + CRow, + CInputFile, + CInvalidFeedback, + CSelect, +} from '@coreui/react'; +import RequiredAsterisk from '../RequiredAsterisk'; + +const validatePem = (value) => + (value.includes('---BEGIN CERTIFICATE---') && value.includes('---END CERTIFICATE---')) || + (value.includes('---BEGIN PRIVATE KEY---') && value.includes('---END PRIVATE KEY---')); + +const AddSimulationForm = ({ t, show, disable, fields, updateField, updateFieldWithKey }) => { + const [certKey, setCertKey] = useState(0); + const [keyKey, setKeyKey] = useState(0); + + let fileReader; + + const handleCertFileRead = () => { + updateFieldWithKey('certificate', { error: false }); + const content = fileReader.result; + if (content && validatePem(content)) { + updateFieldWithKey('certificate', { value: content }); + } else { + updateFieldWithKey('certificate', { error: true }); + } + }; + + const handleCertFile = (file) => { + fileReader = new FileReader(); + fileReader.onloadend = handleCertFileRead; + fileReader.readAsText(file); + }; + + const handleKeyFileRead = () => { + updateFieldWithKey('key', { error: false }); + const content = fileReader.result; + if (content && validatePem(content)) { + updateFieldWithKey('key', { value: content }); + } else { + updateFieldWithKey('key', { error: true }); + } + }; + + const handleKeyFile = (file) => { + fileReader = new FileReader(); + fileReader.onloadend = handleKeyFileRead; + fileReader.readAsText(file); + }; + + useEffect(() => { + if (show) { + setCertKey(certKey + 1); + setKeyKey(keyKey + 1); + } + }, [show]); + + return ( + + + + {t('user.name')} + + + + + + + + {t('simulation.gateway')} + + + + + + + + {t('common.certificate')} + + + + handleCertFile(e.target.files[0])} + /> + + + + {t('common.key')} + + + + handleKeyFile(e.target.files[0])} + /> + + + + {t('simulation.mac_prefix')} + + + + + + + + {t('common.devices')} + + + + fields.devices.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.devices.min, + max: fields.devices.max, + })} + + + + {t('simulation.healtcheck_interval')} + + + + fields.healthCheckInterval.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.healthCheckInterval.min, + max: fields.healthCheckInterval.max, + })} + + + + {t('simulation.state_interval')} + + + + fields.stateInterval.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.stateInterval.min, + max: fields.stateInterval.max, + })} + + + + {t('simulation.client_interval')} + + + + fields.clientInterval.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.clientInterval.min, + max: fields.clientInterval.max, + })} + + + + {t('simulation.reconnect_interval')} + + + + fields.reconnectInterval.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.reconnectInterval.min, + max: fields.reconnectInterval.max, + })} + + + + {t('simulation.min_associations')} + + + + fields.minAssociations.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.minAssociations.min, + max: fields.minAssociations.max, + })} + + + + {t('simulation.max_associations')} + + + + fields.maxAssociations.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.maxAssociations.min, + max: fields.maxAssociations.max, + })} + + + + {t('simulation.min_clients')} + + + + fields.minClients.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.minClients.min, + max: fields.minClients.max, + })} + + + + {t('simulation.max_clients')} + + + + fields.maxClients.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.maxClients.min, + max: fields.maxClients.max, + })} + + + + {t('simulation.length')} + + + + fields.simulationLength.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.simulationLength.min, + max: fields.simulationLength.max, + })} + + + + {t('simulation.threads')} + + + + fields.threads.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.threads.min, + max: fields.threads.max, + })} + + + + {t('simulation.keep_alive')} + + + + fields.keepAlive.max + } + disabled={disable} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.keepAlive.min, + max: fields.keepAlive.max, + })} + + + + {t('configuration.device_type')} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; +AddSimulationForm.propTypes = { + t: PropTypes.func.isRequired, + show: PropTypes.bool.isRequired, + disable: PropTypes.bool.isRequired, + fields: PropTypes.instanceOf(Object).isRequired, + updateField: PropTypes.func.isRequired, + updateFieldWithKey: PropTypes.func.isRequired, +}; + +export default AddSimulationForm; diff --git a/src/components/AddressEditor/index.js b/src/components/AddressEditor/index.js index 568ed34..7e1f256 100644 --- a/src/components/AddressEditor/index.js +++ b/src/components/AddressEditor/index.js @@ -6,7 +6,7 @@ import _ from 'lodash'; import formatGoogleAddress from 'utils/formatGoogleAddress'; import selectStyles from 'utils/selectStyles'; -const AddressEditor = ({ t, currentToken, endpoint, setAddress, show }) => { +const AddressEditor = ({ t, currentToken, endpoint, setAddress, show, disabled }) => { const [tempValue, setTempValue] = useState(''); const [socket, setSocket] = useState(null); const [results, setResults] = useState([]); @@ -25,7 +25,7 @@ const AddressEditor = ({ t, currentToken, endpoint, setAddress, show }) => { const onChange = useCallback( (v) => { - setWaitingSearch(v); + if (v.length >= 4) setWaitingSearch(v); }, [setWaitingSearch], ); @@ -115,6 +115,7 @@ const AddressEditor = ({ t, currentToken, endpoint, setAddress, show }) => { placeholder={t('location.search')} onInputChange={handleTyping} onChange={(property) => changeAddress(property)} + isDisabled={disabled} /> ); }; @@ -125,6 +126,11 @@ AddressEditor.propTypes = { endpoint: PropTypes.string.isRequired, setAddress: PropTypes.func.isRequired, show: PropTypes.bool.isRequired, + disabled: PropTypes.bool, +}; + +AddressEditor.defaultProps = { + disabled: false, }; export default AddressEditor; diff --git a/src/components/Configuration/CustomMultiModal/index.js b/src/components/Configuration/CustomMultiModal/index.js index 384fc2a..dfba77d 100644 --- a/src/components/Configuration/CustomMultiModal/index.js +++ b/src/components/Configuration/CustomMultiModal/index.js @@ -32,6 +32,7 @@ const CustomMultiModal = ({ noTable, toggleAdd, reset, + disabled, }) => { const [show, toggle] = useToggle(); @@ -82,12 +83,19 @@ const CustomMultiModal = ({ variant="outline" className="ml-2" onClick={toggleAdd} + disabled={disabled} > - + @@ -145,6 +153,7 @@ CustomMultiModal.propTypes = { noTable: PropTypes.bool, toggleAdd: PropTypes.func, reset: PropTypes.func, + disabled: PropTypes.bool.isRequired, }; CustomMultiModal.defaultProps = { diff --git a/src/components/Configuration/SectionToggler/index.js b/src/components/Configuration/SectionToggler/index.js index bb1012b..0400d2c 100644 --- a/src/components/Configuration/SectionToggler/index.js +++ b/src/components/Configuration/SectionToggler/index.js @@ -2,14 +2,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import { CButtonClose } from '@coreui/react'; -const SectionToggler = ({ id, label, field, updateField }) => { +const SectionToggler = ({ id, label, field, updateField, disabled }) => { const toggle = () => updateField(id, { enabled: !field.enabled }); return (
{label}
- +
); @@ -20,6 +20,7 @@ SectionToggler.propTypes = { field: PropTypes.instanceOf(Object).isRequired, label: PropTypes.string.isRequired, updateField: PropTypes.func.isRequired, + disabled: PropTypes.bool.isRequired, }; export default SectionToggler; diff --git a/src/components/ConfigurationTable/index.js b/src/components/ConfigurationTable/index.js index e200dd5..dd2dd96 100644 --- a/src/components/ConfigurationTable/index.js +++ b/src/components/ConfigurationTable/index.js @@ -104,7 +104,7 @@ const ConfigurationTable = ({ deleteConfig={deleteConfig} hideTooltips={hideTooltips} /> - + { + const [show, toggleShow] = useToggle(); + + const toggleAndStop = () => { + toggleShow(); + stopEditing(); + }; + + return ( + <> + + + + + + + + {t('common.stop_editing')} +
+ + + + + +
+
+ + {t('common.confirm_stop_editing')} +
+ + {t('common.stop_editing')} + + + {t('common.go_back')} + +
+
+
+ + ); +}; + +ConfirmStopEditingButton.propTypes = { + t: PropTypes.func.isRequired, + stopEditing: PropTypes.func.isRequired, + disabled: PropTypes.bool.isRequired, +}; + +export default ConfirmStopEditingButton; diff --git a/src/components/ContactTable/index.js b/src/components/ContactTable/index.js index c18cf6c..863d195 100644 --- a/src/components/ContactTable/index.js +++ b/src/components/ContactTable/index.js @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { CButton, CDataTable, CLink, CPopover, CButtonToolbar } from '@coreui/react'; -import { cilPencil, cilPlus } from '@coreui/icons'; +import { cilMagnifyingGlass, cilPlus } from '@coreui/icons'; import CIcon from '@coreui/icons-react'; import ReactTooltip from 'react-tooltip'; import DeleteButton from './DeleteButton'; @@ -118,7 +118,7 @@ const ContactTable = ({ deleteContact={deleteContact} hideTooltips={hideTooltips} /> - + toggleEditModal(item.id)} style={{ width: '33px', height: '30px' }} > - + diff --git a/src/components/CopyToClipboardButton/index.js b/src/components/CopyToClipboardButton/index.js index b399932..051c970 100644 --- a/src/components/CopyToClipboardButton/index.js +++ b/src/components/CopyToClipboardButton/index.js @@ -14,7 +14,7 @@ const CopyToClipboardButton = ({ t, content, size }) => { return ( - + {' '} {result || ''} diff --git a/src/components/DeviceDetails/index.js b/src/components/DeviceDetails/index.js index f162078..ee73552 100644 --- a/src/components/DeviceDetails/index.js +++ b/src/components/DeviceDetails/index.js @@ -77,10 +77,11 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats }) - {t('common.serial_number')}: + {t('common.serial_num')}: {deviceConfig?.serialNumber} + {' '} @@ -88,6 +89,7 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats }) {getPassword()} + {' '} { const columns = [ - { key: 'deviceType', label: '', filter: false, sorter: false, _style: { width: '3%' } }, + { key: 'deviceType', label: '', filter: false, sorter: false, _style: { width: '1%' } }, { key: 'serialNumber', label: t('common.serial_number'), _style: { width: '6%' } }, { key: 'firmware', label: t('firmware.revision') }, - { key: 'firmware_button', label: '', filter: false, _style: { width: '5%' } }, + { key: 'firmware_button', label: '', filter: false, _style: { width: '1%' } }, { key: 'compatible', label: t('common.type'), filter: false, _style: { width: '13%' } }, { key: 'txBytes', label: 'Tx', filter: false, _style: { width: '14%' } }, { key: 'rxBytes', label: 'Rx', filter: false, _style: { width: '14%' } }, @@ -92,14 +92,14 @@ const DeviceListTable = ({ const tooltipId = createUuid(); let text = t('firmware.unknown_firmware_status'); let upgradeText = t('firmware.upgrade_to_latest'); - let icon = ; + let icon = ; let color = 'secondary'; if (latest !== undefined) { text = t('firmware.newer_firmware_available'); color = 'warning'; if (latest) { - icon = ; + icon = ; text = t('firmware.latest_version_installed'); upgradeText = t('firmware.reinstall_latest'); color = 'success'; @@ -147,7 +147,9 @@ const DeviceListTable = ({ isLoading={upgradeStatus.loading} action={() => upgradeToLatest(device)} block - disabled={upgradeStatus.loading} + disabled={ + upgradeStatus.loading && upgradeStatus.serialNumber === device.serialNumber + } /> @@ -185,75 +187,82 @@ const DeviceListTable = ({ const tooltipId = createUuid(); return ( - -
- - - - { - const element = document.getElementById(tooltipId); - const tooltipWidth = element ? element.offsetWidth : 0; - const newLeft = left - tooltipWidth * 0.25; - return { top, left: newLeft }; - }} - > - - {t('common.device_delete', { serialNumber })} - { - e.target.parentNode.parentNode.classList.remove('show'); - hideTooltips(); - }} - /> - - - - - deleteDevice(serialNumber)} - block - disabled={deleteStatus.loading} - /> - - - - -
-
+
+ +
+ + + +
+
+ { + const element = document.getElementById(tooltipId); + const tooltipWidth = element ? element.offsetWidth : 0; + const newLeft = left - tooltipWidth * 0.25; + return { top, left: newLeft }; + }} + > + + {t('common.device_delete', { serialNumber })} + { + e.target.parentNode.parentNode.classList.remove('show'); + hideTooltips(); + }} + /> + + + + + { + e.target.parentNode.parentNode.parentNode.parentNode.classList.remove('show'); + hideTooltips(); + deleteDevice(serialNumber); + }} + block + disabled={deleteStatus.loading} + /> + + + + +
); }; return ( <> - +
{searchBar}
@@ -335,7 +344,7 @@ const DeviceListTable = ({ { +const DeviceSearchBar = ({ t, search, results, history, action }) => { const [selected, setSelected] = useState(''); const NoOptionsMessage = (props) => ( @@ -25,7 +25,9 @@ const DeviceSearchBar = ({ t, search, results, history }) => { inputValue={selected} placeholder={t('common.search')} onInputChange={onInputChange} - onChange={(property) => history.push(`/devices/${property.value}`)} + onChange={(property) => + action === null ? history.push(`/devices/${property.value}`) : action(property.value) + } /> ); }; @@ -35,6 +37,11 @@ DeviceSearchBar.propTypes = { search: PropTypes.func.isRequired, results: PropTypes.instanceOf(Array).isRequired, history: PropTypes.instanceOf(Object).isRequired, + action: PropTypes.func, +}; + +DeviceSearchBar.defaultProps = { + action: null, }; export default DeviceSearchBar; diff --git a/src/components/DeviceStatusCard/index.js b/src/components/DeviceStatusCard/index.js index 0e5913f..1886684 100644 --- a/src/components/DeviceStatusCard/index.js +++ b/src/components/DeviceStatusCard/index.js @@ -119,11 +119,18 @@ const DeviceStatusCard = ({ errorField(t) ) : (
- {lastStats?.unit?.load[0] ? (lastStats?.unit?.load[0] * 100).toFixed(2) : '-'}% - {' / '} - {lastStats?.unit?.load[1] ? (lastStats?.unit?.load[1] * 100).toFixed(2) : '-'}% - {' / '} - {lastStats?.unit?.load[2] ? (lastStats?.unit?.load[2] * 100).toFixed(2) : '-'}% + {lastStats?.unit?.load[0] !== undefined + ? (lastStats?.unit?.load[0] * 100).toFixed(2) + : '-'} + %{' / '} + {lastStats?.unit?.load[1] !== undefined + ? (lastStats?.unit?.load[1] * 100).toFixed(2) + : '-'} + %{' / '} + {lastStats?.unit?.load[2] !== undefined + ? (lastStats?.unit?.load[2] * 100).toFixed(2) + : '-'} + %
)}
diff --git a/src/components/EditConfigurationForm/index.js b/src/components/EditConfigurationForm/index.js index 59cee61..8fb9dd8 100644 --- a/src/components/EditConfigurationForm/index.js +++ b/src/components/EditConfigurationForm/index.js @@ -191,6 +191,14 @@ const EditConfigurationForm = ({ />
+ +
{t('common.created')}:
+
+ +

+ +

+
Only Release Candidates @@ -206,14 +214,6 @@ const EditConfigurationForm = ({ disabled={!editing || fields.firmwareUpgrade.value === 'no'} /> - -
{t('common.created')}:
-
- -

- -

-
{t('common.modified')}:
diff --git a/src/components/EditContactForm/index.js b/src/components/EditContactForm/index.js index 5596d74..63e9982 100644 --- a/src/components/EditContactForm/index.js +++ b/src/components/EditContactForm/index.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import Select from 'react-select'; import CreatableSelect from 'react-select/creatable'; @@ -14,10 +14,12 @@ import { CLink, CPopover, CButton, + CCollapse, } from '@coreui/react'; import FormattedDate from '../FormattedDate'; import RequiredAsterisk from '../RequiredAsterisk'; import selectStyles from '../../utils/selectStyles'; +import useToggle from '../../hooks/useToggle'; const EditContactForm = ({ t, @@ -30,6 +32,7 @@ const EditContactForm = ({ hideEntities, editing, }) => { + const [showDropdown, toggleDropdown, setShowDropdown] = useToggle(false); const [filter, setFilter] = useState(''); const onPhonesChange = (v) => updateFieldWithKey('phones', { value: v.map((obj) => obj.value) }); @@ -52,8 +55,13 @@ const EditContactForm = ({ { id: 'entity', value: id }, { id: 'entityName', value: name }, ]); + toggleDropdown(); }; + useEffect(() => { + if (!editing) setShowDropdown(false); + }, [editing]); + return ( @@ -344,7 +352,7 @@ const EditContactForm = ({ )} - Landlines + {t('location.phones')} ({ value: opt, label: opt }))} - placeholder={t('common.type_for_options')} + placeholder="+1(202)555-0103" /> - Mobiles + {t('location.mobiles')} ({ value: opt, label: opt }))} - placeholder={t('common.type_for_options')} + placeholder="+1(202)555-0103" /> - {t('entity.selected_entity')} + {t('entity.entity')} - -
+ + {fields.entity.value === '' ? t('entity.need_select_entity') : fields.entityName.value} -
+
{hideEntities ? null : ( -
- setFilter(e.target.value)} - /> - ( - - `/entity/${item.id}`} - > - {item.name} - - - ), - created: (item) => ( - - - - ), - actions: (item) => ( - - - selectEntity(item)} - disabled={!editing} + +
+ setFilter(e.target.value)} + /> + ( + + `/entity/${item.id}`} > - {t('common.select')} - - - - ), - }} - /> -
+ {item.name} + + + ), + created: (item) => ( + + + + ), + actions: (item) => ( + + + selectEntity(item)} + disabled={!editing} + > + {t('common.select')} + + + + ), + }} + /> +
+ )}
); diff --git a/src/components/EditEntityForm/index.js b/src/components/EditEntityForm/index.js index 3dcc72a..f42e56d 100644 --- a/src/components/EditEntityForm/index.js +++ b/src/components/EditEntityForm/index.js @@ -80,6 +80,14 @@ const EditEntityForm = ({ /> + +
{t('common.created')}:
+
+ +
+ +
+
{t('entity.ip_detection')}:
@@ -100,14 +108,6 @@ const EditEntityForm = ({ )} - -
{t('common.created')}:
-
- -
- -
-
{t('common.modified')}:
diff --git a/src/components/EditInventoryTagForm/index.js b/src/components/EditInventoryTagForm/index.js index 5d3e66c..253b76b 100644 --- a/src/components/EditInventoryTagForm/index.js +++ b/src/components/EditInventoryTagForm/index.js @@ -1,9 +1,24 @@ -import React from 'react'; -import { CForm, CInput, CLabel, CCol, CFormGroup, CFormText, CRow } from '@coreui/react'; +import React, { useEffect, useState } from 'react'; +import { + CForm, + CDataTable, + CLink, + CInput, + CLabel, + CCol, + CFormGroup, + CFormText, + CRow, + CButton, + CCollapse, + CPopover, +} from '@coreui/react'; import Select from 'react-select'; import PropTypes from 'prop-types'; +import FormattedDate from '../FormattedDate'; import RequiredAsterisk from '../RequiredAsterisk'; import selectStyles from '../../utils/selectStyles'; +import useToggle from '../../hooks/useToggle'; const EditInventoryTagForm = ({ t, @@ -11,116 +26,281 @@ const EditInventoryTagForm = ({ fields, updateField, updateFieldDirectly, + entities, + venues, deviceTypes, editing, -}) => ( - - - - {t('common.serial_number')} - - {fields.serialNumber.value} - - - - {t('user.name')} - - - - {editing ? ( -
- - -
- ) : ( -

{fields.name.value}

- )} -
-
- - - {t('user.description')} - - - {editing ? ( -
- { + const [showDropdown, toggleDropdown, setShowDropdown] = useToggle(false); + const [entityFilter, setEntityFilter] = useState(''); + const [venueFilter, setVenueFilter] = useState(''); + + const selectEntity = ({ id, name }) => { + batchSetField([ + { id: 'entity', value: id }, + { id: 'entityName', value: name }, + { id: 'venue', value: '' }, + { id: 'venueName', value: '' }, + ]); + toggleDropdown(); + }; + + const selectVenue = ({ id, name }) => { + batchSetField([ + { id: 'venue', value: id }, + { id: 'venueName', value: name }, + { id: 'entity', value: '' }, + { id: 'entityName', value: '' }, + ]); + toggleDropdown(); + }; + + const columns = [ + { key: 'created', label: t('common.created'), _style: { width: '20%' }, filter: false }, + { key: 'name', label: t('user.name'), _style: { width: '25%' }, filter: false }, + { key: 'description', label: t('user.description'), _style: { width: '50%' } }, + { key: 'actions', label: '', _style: { width: '15%' }, filter: false }, + ]; + + const getEntityLabel = () => { + if (fields.entity.value !== '') return t('entity.entity'); + if (fields.venue.value !== '') return t('inventory.venue'); + return `${t('entity.entity')}/${t('inventory.venue')}`; + }; + + const getEntityValue = () => { + if (fields.entity.value !== '') return fields.entityName.value; + if (fields.venue.value !== '') return fields.venueName.value; + return t('entity.need_select_entity'); + }; + + useEffect(() => { + if (!editing) setShowDropdown(false); + }, [editing]); + + return ( + + + + {t('common.serial_number')} + + {fields.serialNumber.value} + + + + {t('user.name')} + + + + {editing ? ( +
+ + +
+ ) : ( +

{fields.name.value}

+ )} +
+
+ + + {t('user.description')} + + + {editing ? ( +
+ +
+ ) : ( +

{fields.description.value}

+ )} +
+
+ + + {t('firmware.device_type')} + + + +
+ updateFieldDirectly('deviceType', { value: v.value })} - options={deviceTypes.map((v) => ({ value: v, label: v }))} - isDisabled={disable || !editing} - /> -
- -
-
- - - RRM - - - -
- updateFieldDirectly('rrm', { value: v.value, error: false })} + options={[ + { label: 'on', value: 'on' }, + { label: 'off', value: 'off' }, + { label: 'inherit', value: 'inherit' }, + ]} + isDisabled={disable || !editing} + /> +
+ +
+
+ + + {getEntityLabel()} + + + + {getEntityValue()} + + + + {hideEntities ? null : ( + +
+
{t('entity.entities')}
+ setEntityFilter(e.target.value)} + /> + ( + + `/entity/${item.id}`} + > + {item.name} + + + ), + created: (item) => ( + + + + ), + actions: (item) => ( + + + selectEntity(item)} + disabled={!editing} + > + {t('common.select')} + + + + ), + }} + /> +
{t('entity.venues')}
+ setVenueFilter(e.target.value)} + /> + ( + + `/entity/${item.id}`} + > + {item.name} + + + ), + created: (item) => ( + + + + ), + actions: (item) => ( + + + selectVenue(item)} + disabled={!editing} + > + {t('common.select')} + + + + ), + }} + /> +
+
+ )} +
+ ); +}; EditInventoryTagForm.propTypes = { t: PropTypes.func.isRequired, @@ -130,6 +310,14 @@ EditInventoryTagForm.propTypes = { updateFieldDirectly: PropTypes.func.isRequired, deviceTypes: PropTypes.instanceOf(Array).isRequired, editing: PropTypes.bool.isRequired, + entities: PropTypes.instanceOf(Array).isRequired, + venues: PropTypes.instanceOf(Array).isRequired, + hideEntities: PropTypes.bool, + batchSetField: PropTypes.func.isRequired, +}; + +EditInventoryTagForm.defaultProps = { + hideEntities: false, }; export default EditInventoryTagForm; diff --git a/src/components/EditLocationForm/index.js b/src/components/EditLocationForm/index.js index 47fe2dd..874ce8b 100644 --- a/src/components/EditLocationForm/index.js +++ b/src/components/EditLocationForm/index.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import Select from 'react-select'; import CreatableSelect from 'react-select/creatable'; @@ -14,10 +14,12 @@ import { CLink, CPopover, CButton, + CCollapse, } from '@coreui/react'; import countryList from 'utils/countryList'; import FormattedDate from '../FormattedDate'; import RequiredAsterisk from '../RequiredAsterisk'; +import useToggle from '../../hooks/useToggle'; const EditLocationForm = ({ t, @@ -30,6 +32,7 @@ const EditLocationForm = ({ batchSetField, editing, }) => { + const [showDropdown, toggleDropdown, setShowDropdown] = useToggle(false); const [filter, setFilter] = useState(''); const onPhonesChange = (v) => updateFieldWithKey('phones', { value: v.map((obj) => obj.value) }); @@ -52,8 +55,13 @@ const EditLocationForm = ({ { id: 'entity', value: id }, { id: 'entityName', value: name }, ]); + toggleDropdown(); }; + useEffect(() => { + if (!editing) setShowDropdown(false); + }, [editing]); + return ( @@ -153,7 +161,7 @@ const EditLocationForm = ({ )} - Landlines + {t('location.phones')} ({ value: opt, label: opt }))} - placeholder={t('common.type_for_options')} + placeholder="+1(202)555-0103" /> - - Mobiles + + {t('location.mobiles')} ({ value: opt, label: opt }))} - placeholder={t('common.type_for_options')} + placeholder="+1(202)555-0103" /> @@ -339,63 +347,65 @@ const EditLocationForm = ({ - {t('entity.selected_entity')} + {t('entity.entity')} - -
+ + {fields.entity.value === '' ? t('entity.need_select_entity') : fields.entityName.value} -
+
-
- setFilter(e.target.value)} - /> - ( - - `/configuration/${item.id}`} - > - {item.name} - - - ), - created: (item) => ( - - - - ), - actions: (item) => ( - - - selectEntity(item)} + +
+ setFilter(e.target.value)} + /> + ( + + `/configuration/${item.id}`} > - {t('common.select')} - - - - ), - }} - /> -
+ {item.name} + + + ), + created: (item) => ( + + + + ), + actions: (item) => ( + + + selectEntity(item)} + > + {t('common.select')} + + + + ), + }} + /> +
+
); }; diff --git a/src/components/EditSimulationForm/index.js b/src/components/EditSimulationForm/index.js new file mode 100644 index 0000000..36cb108 --- /dev/null +++ b/src/components/EditSimulationForm/index.js @@ -0,0 +1,554 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { + CForm, + CInput, + CLabel, + CCol, + CFormText, + CRow, + CInputFile, + CInvalidFeedback, + CSelect, +} from '@coreui/react'; +import RequiredAsterisk from '../RequiredAsterisk'; + +const validatePem = (value) => + (value.includes('---BEGIN CERTIFICATE---') && value.includes('---END CERTIFICATE---')) || + (value.includes('---BEGIN PRIVATE KEY---') && value.includes('---END PRIVATE KEY---')); + +const EditSimulationForm = ({ + t, + show, + disable, + fields, + updateField, + updateFieldWithKey, + editing, +}) => { + const [certKey, setCertKey] = useState(0); + const [keyKey, setKeyKey] = useState(0); + + let fileReader; + + const handleCertFileRead = () => { + const content = fileReader.result; + if (content && validatePem(content)) { + updateFieldWithKey('certificate', { value: content, error: false }); + } else { + updateFieldWithKey('certificate', { error: true }); + } + }; + + const handleCertFile = (file) => { + fileReader = new FileReader(); + fileReader.onloadend = handleCertFileRead; + fileReader.readAsText(file); + }; + + const handleKeyFileRead = () => { + updateFieldWithKey('key', { error: false }); + const content = fileReader.result; + if (content && validatePem(content)) { + updateFieldWithKey('key', { value: content, error: false }); + } else { + updateFieldWithKey('key', { error: true }); + } + }; + + const handleKeyFile = (file) => { + fileReader = new FileReader(); + fileReader.onloadend = handleKeyFileRead; + fileReader.readAsText(file); + }; + + useEffect(() => { + if (show) { + setCertKey(certKey + 1); + setKeyKey(keyKey + 1); + } + }, [show]); + + return ( + + + + {t('user.name')} + + + + + + + + {t('simulation.gateway')} + + + + + + + + {t('common.certificate')} + + + + {editing ? ( +
+ handleCertFile(e.target.files[0])} + /> + +
+ ) : ( +
{t('simulation.valid_cert')}
+ )} +
+ + {t('common.key')} + + + + {editing ? ( +
+ handleKeyFile(e.target.files[0])} + /> + +
+ ) : ( +
{t('simulation.valid_key')}
+ )} +
+ + {t('simulation.mac_prefix')} + + + + + + + + {t('common.devices')} + + + + fields.devices.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + + + {t('simulation.healtcheck_interval')} + + + + fields.healthCheckInterval.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.healthCheckInterval.min, + max: fields.healthCheckInterval.max, + })} + + + + {t('simulation.state_interval')} + + + + fields.stateInterval.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.stateInterval.min, + max: fields.stateInterval.max, + })} + + + + {t('simulation.client_interval')} + + + + fields.clientInterval.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.clientInterval.min, + max: fields.clientInterval.max, + })} + + + + {t('simulation.reconnect_interval')} + + + + fields.reconnectInterval.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.reconnectInterval.min, + max: fields.reconnectInterval.max, + })} + + + + {t('simulation.min_associations')} + + + + fields.minAssociations.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.minAssociations.min, + max: fields.minAssociations.max, + })} + + + + {t('simulation.max_associations')} + + + + fields.maxAssociations.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.maxAssociations.min, + max: fields.maxAssociations.max, + })} + + + + {t('simulation.min_clients')} + + + + fields.minClients.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.minClients.min, + max: fields.minClients.max, + })} + + + + {t('simulation.max_clients')} + + + + fields.maxClients.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.maxClients.min, + max: fields.maxClients.max, + })} + + + + {t('simulation.length')} + + + + fields.simulationLength.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.simulationLength.min, + max: fields.simulationLength.max, + })} + + + + {t('simulation.threads')} + + + + fields.threads.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.threads.min, + max: fields.threads.max, + })} + + + + {t('simulation.keep_alive')} + + + + fields.keepAlive.max + } + disabled={disable || !editing} + pattern="[0-9]*" + style={{ width: '100px' }} + /> + + {t('common.min_max', { + min: fields.keepAlive.min, + max: fields.keepAlive.max, + })} + + + + {t('configuration.device_type')} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ); +}; + +EditSimulationForm.propTypes = { + t: PropTypes.func.isRequired, + show: PropTypes.bool.isRequired, + disable: PropTypes.bool.isRequired, + fields: PropTypes.instanceOf(Object).isRequired, + updateField: PropTypes.func.isRequired, + updateFieldWithKey: PropTypes.func.isRequired, + editing: PropTypes.bool.isRequired, +}; + +export default EditSimulationForm; diff --git a/src/components/EntityTree/index.js b/src/components/EntityTree/index.js new file mode 100644 index 0000000..eb89afc --- /dev/null +++ b/src/components/EntityTree/index.js @@ -0,0 +1,81 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ReactFlow, { removeElements, MiniMap, Controls, Background } from 'react-flow-renderer'; + +const EntityTree = ({ elements, setElements, history, toggle, setReactFlowInstance }) => { + const onElementsRemove = (elementsToRemove) => { + setElements((els) => removeElements(elementsToRemove, els)); + }; + + const onClick = (e, el) => { + const split = el.id.split('/'); + const type = split[0]; + + if (type === 'entity' || type === 'world') { + toggle(); + history.push(`/entity/${split[1]}`); + } else if (type === 'venue') { + toggle(); + history.push(`/venue/${split[1]}`); + } + }; + + const onLoad = (instance) => { + setReactFlowInstance(instance); + }; + + return ( +
+ +
+
+

Root

+
+
+

Entity

+
+
+

Venue

+
+
+ { + if (n.style?.background) return n.style.background; + + return '#fff'; + }} + nodeBorderRadius={5} + /> + + +
+
+ ); +}; + +EntityTree.propTypes = { + elements: PropTypes.instanceOf(Array).isRequired, + setElements: PropTypes.func.isRequired, + history: PropTypes.instanceOf(Object).isRequired, + toggle: PropTypes.func.isRequired, + setReactFlowInstance: PropTypes.func.isRequired, +}; + +export default React.memo(EntityTree); diff --git a/src/components/FileToStringButton/index.js b/src/components/FileToStringButton/index.js index 6e61e5c..358f63f 100644 --- a/src/components/FileToStringButton/index.js +++ b/src/components/FileToStringButton/index.js @@ -18,7 +18,15 @@ const validatePem = (value) => (value.includes('---BEGIN CERTIFICATE---') && value.includes('---END CERTIFICATE---')) || (value.includes('---BEGIN PRIVATE KEY---') && value.includes('---END PRIVATE KEY---')); -const FileToStringButton = ({ t, save, title, explanations, acceptedFileTypes, size }) => { +const FileToStringButton = ({ + t, + save, + title, + explanations, + acceptedFileTypes, + size, + disabled, +}) => { const [show, toggle] = useToggle(false); const [value, setValue] = useState(''); const [fileName, setFileName] = useState(''); @@ -74,6 +82,7 @@ const FileToStringButton = ({ t, save, title, explanations, acceptedFileTypes, s variant="outline" style={{ height: '35px', width: '35px' }} size={size} + disabled={disabled} > @@ -125,6 +134,7 @@ FileToStringButton.propTypes = { explanations: PropTypes.string.isRequired, acceptedFileTypes: PropTypes.string.isRequired, size: PropTypes.string, + disabled: PropTypes.bool.isRequired, }; FileToStringButton.defaultProps = { diff --git a/src/components/FirmwareDetailsForm/index.js b/src/components/FirmwareDetailsForm/index.js index 3643b2e..94f2d17 100644 --- a/src/components/FirmwareDetailsForm/index.js +++ b/src/components/FirmwareDetailsForm/index.js @@ -2,37 +2,32 @@ import React from 'react'; import PropTypes from 'prop-types'; import { CCardBody, CCol, CInput, CRow } from '@coreui/react'; import { prettyDate, cleanBytesString } from '../../utils/formatting'; -import NotesTable from '../NotesTable'; -const FirmwareDetailsForm = ({ t, fields, updateFieldsWithId, addNote, editing }) => ( +const FirmwareDetailsForm = ({ t, fields, updateFieldsWithId, editing }) => ( - Created - {prettyDate(fields.created.value)} - Release + {t('firmware.release')} {fields.release.value} + {t('common.created')} + {prettyDate(fields.created.value)} - Image - {fields.image.value} - Image Date + {t('firmware.image_date')} {prettyDate(fields.imageDate.value)} + {t('firmware.size')} + {cleanBytesString(fields.size.value)} - Revision + {t('firmware.image')} + {fields.image.value} + {t('firmware.revision')} {fields.revision.value} - Size - {cleanBytesString(fields.size.value)} URI {fields.uri.value} - Owner - {fields.owner.value === '' ? t('common.unknown') : fields.owner.value} - - - Description + {t('user.description')} {editing ? ( @@ -46,9 +41,6 @@ const FirmwareDetailsForm = ({ t, fields, updateFieldsWithId, addNote, editing }

{fields.description.value}

)}
- - -
); @@ -57,7 +49,6 @@ FirmwareDetailsForm.propTypes = { t: PropTypes.func.isRequired, fields: PropTypes.instanceOf(Object).isRequired, updateFieldsWithId: PropTypes.func.isRequired, - addNote: PropTypes.func.isRequired, editing: PropTypes.bool.isRequired, }; export default FirmwareDetailsForm; diff --git a/src/components/FirmwareList/index.js b/src/components/FirmwareList/index.js index 004ac34..ad66e01 100644 --- a/src/components/FirmwareList/index.js +++ b/src/components/FirmwareList/index.js @@ -38,7 +38,7 @@ const FirmwareList = ({ { key: 'size', label: t('firmware.size'), _style: { width: '1%' } }, { key: 'revision', label: t('firmware.revision'), _style: { width: '1%' } }, { key: 'uri', label: 'URI' }, - { key: 'show_details', label: '', _style: { width: '5%' } }, + { key: 'show_details', label: '', _style: { width: '1%' } }, ]; const getShortRevision = (revision) => { @@ -54,7 +54,7 @@ const FirmwareList = ({ return ( - +
( -
+
@@ -125,14 +125,16 @@ const FirmwareList = ({ ), show_details: (item) => ( - toggleEditModal(item.id)} - > - - + + toggleEditModal(item.id)} + > + + + ), }} diff --git a/src/components/FormattedDate/index.js b/src/components/FormattedDate/index.js index a3ae1e3..321d8ad 100644 --- a/src/components/FormattedDate/index.js +++ b/src/components/FormattedDate/index.js @@ -4,7 +4,7 @@ import { CPopover } from '@coreui/react'; import { formatDaysAgo, prettyDate } from 'utils/formatting'; const FormattedDate = ({ date }) => ( - + {date === 0 ? '-' : formatDaysAgo(date)} ); diff --git a/src/components/HideTextButton/index.js b/src/components/HideTextButton/index.js index 0416983..fa2e0e4 100644 --- a/src/components/HideTextButton/index.js +++ b/src/components/HideTextButton/index.js @@ -5,7 +5,7 @@ import { CButton, CPopover } from '@coreui/react'; const HideTextButton = ({ t, toggle, show, size }) => ( - + diff --git a/src/components/InventoryTable/index.js b/src/components/InventoryTable/index.js index 147df48..ec3de3a 100644 --- a/src/components/InventoryTable/index.js +++ b/src/components/InventoryTable/index.js @@ -203,7 +203,7 @@ const InventoryTable = ({ - + - + toggleEditModal(item.id)} style={{ width: '33px', height: '30px' }} > - + diff --git a/src/components/SimulationTable/index.js b/src/components/SimulationTable/index.js new file mode 100644 index 0000000..3c2dc88 --- /dev/null +++ b/src/components/SimulationTable/index.js @@ -0,0 +1,273 @@ +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import ReactPaginate from 'react-paginate'; +import { + CCardBody, + CDataTable, + CButton, + CCard, + CCardHeader, + CRow, + CCol, + CPopover, + CSelect, + CButtonToolbar, + CButtonClose, +} from '@coreui/react'; +import { cilTrash, cilSearch, cilMediaPlay } from '@coreui/icons'; +import CIcon from '@coreui/icons-react'; +import ReactTooltip from 'react-tooltip'; +import { v4 as createUuid } from 'uuid'; +import styles from './index.module.scss'; + +const SimulationTable = ({ + currentPage, + simulations, + simulationsPerPage, + loading, + updateSimulationsPerPage, + pageCount, + updatePage, + t, + deleteSimulation, + toggleEdit, + startSim, +}) => { + const columns = [ + { key: 'name', label: t('user.name'), filter: false, sorter: false, _style: { width: '10%' } }, + { + key: 'gateway', + label: t('simulation.gateway'), + filter: false, + sorter: false, + _style: { width: '10%' }, + }, + { key: 'deviceType', label: t('firmware.device_type'), filter: false, sorter: false }, + { key: 'devices', label: t('common.devices'), filter: false, sorter: false }, + { key: 'macPrefix', label: t('simulation.mac_prefix'), filter: false, sorter: false }, + { key: 'stateInterval', label: t('simulation.state_interval'), filter: false, sorter: false }, + { + key: 'minAssociations', + label: t('simulation.min_associations'), + filter: false, + sorter: false, + }, + { + key: 'maxAssociations', + label: t('simulation.max_associations'), + filter: false, + sorter: false, + }, + { key: 'minClients', label: t('simulation.min_clients'), filter: false, sorter: false }, + { key: 'maxClients', label: t('simulation.max_clients'), filter: false, sorter: false }, + { key: 'simulationLength', label: t('simulation.length'), filter: false, sorter: false }, + { key: 'actions', label: '', filter: false, sorter: false, _style: { width: '1%' } }, + ]; + + const hideTooltips = () => ReactTooltip.hide(); + + const escFunction = (event) => { + if (event.keyCode === 27) { + hideTooltips(); + } + }; + + useEffect(() => { + document.addEventListener('keydown', escFunction, false); + + return () => { + document.removeEventListener('keydown', escFunction, false); + }; + }, []); + + const deleteButton = ({ name, id }) => { + const tooltipId = createUuid(); + + return ( + +
+ + + + { + const element = document.getElementById(tooltipId); + const tooltipWidth = element ? element.offsetWidth : 0; + const newLeft = left - tooltipWidth * 0.25; + return { top, left: newLeft }; + }} + > + + {t('simulation.delete_simulation', { name })} + { + e.target.parentNode.parentNode.classList.remove('show'); + hideTooltips(); + }} + /> + + + + + deleteSimulation(id)} + block + > + {t('common.confirm')} + + + + + +
+
+ ); + }; + + return ( + <> + + + {item.name}, + gateway: (item) => {item.gateway}, + deviceType: (item) => {item.deviceType}, + devices: (item) => {item.devices}, + macPrefix: (item) => {item.macPrefix}, + healthCheckInterval: (item) => ( + {item.healthCheckInterval} + ), + stateInterval: (item) => {item.stateInterval}, + minAssociations: (item) => {item.minAssociations}, + maxAssociations: (item) => {item.maxAssociations}, + minClients: (item) => {item.minClients}, + maxClients: (item) => {item.maxClients}, + simulationLength: (item) => {item.simulationLength}, + actions: (item) => ( + + + + startSim(item.id)} + > + + + + {deleteButton(item)} + + toggleEdit(item.id)} + > + + + + + + ), + }} + /> +
+
+ +
+

{t('common.items_per_page')}

+
+ updateSimulationsPerPage(e.target.value)} + disabled={loading} + > + + + + +
+
+
+
+ + ); +}; + +SimulationTable.propTypes = { + startSim: PropTypes.func.isRequired, + currentPage: PropTypes.string, + simulations: PropTypes.instanceOf(Array).isRequired, + updateSimulationsPerPage: PropTypes.func.isRequired, + pageCount: PropTypes.number.isRequired, + updatePage: PropTypes.func.isRequired, + simulationsPerPage: PropTypes.string.isRequired, + t: PropTypes.func.isRequired, + loading: PropTypes.bool.isRequired, + toggleEdit: PropTypes.func.isRequired, + deleteSimulation: PropTypes.func.isRequired, +}; + +SimulationTable.defaultProps = { + currentPage: '0', +}; + +export default React.memo(SimulationTable); diff --git a/src/components/SimulationTable/index.module.scss b/src/components/SimulationTable/index.module.scss new file mode 100644 index 0000000..5e7facb --- /dev/null +++ b/src/components/SimulationTable/index.module.scss @@ -0,0 +1,16 @@ +.deleteTooltip { + opacity: 1 !important; + padding: 0px 0px 0px 0px !important; + border-radius: 1rem !important; + background-color: #fff !important; + border-color: #321fdb !important; + font-size: 0.875rem !important; + font-weight: 400 !important; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important; + width: 300px; +} + +.tooltipHeader { + border-top-left-radius: 1rem !important; + border-top-right-radius: 1rem !important; +} diff --git a/src/components/UserListTable/index.js b/src/components/UserListTable/index.js index fedf762..9272e2f 100644 --- a/src/components/UserListTable/index.js +++ b/src/components/UserListTable/index.js @@ -169,6 +169,7 @@ const UserListTable = ({ onClick={() => handleDeleteClick(item.Id)} color="primary" variant="outline" + shape="square" size="sm" > diff --git a/src/index.js b/src/index.js index 3b35229..2ca1e8b 100644 --- a/src/index.js +++ b/src/index.js @@ -14,6 +14,7 @@ export { default as AddEntityForm } from './components/AddEntityForm'; export { default as AddInventoryTagForm } from './components/AddInventoryTagForm'; export { default as AddLocationForm } from './components/AddLocationForm'; export { default as AddressEditor } from './components/AddressEditor'; +export { default as AddSimulationForm } from './components/AddSimulationForm'; export { default as ApiStatusCard } from './components/ApiStatusCard'; export { default as Avatar } from './components/Avatar'; export { default as CompactNotesTable } from './components/CompactNotesTable'; @@ -29,6 +30,7 @@ export { default as ConfigurationSelect } from './components/Configuration/Selec export { default as ConfigurationStringField } from './components/Configuration/StringField'; export { default as ConfigurationToggle } from './components/Configuration/Toggle'; export { default as ConfirmFooter } from './components/ConfirmFooter'; +export { default as ConfirmStopEditingButton } from './components/ConfirmStopEditingButton'; export { default as ContactTable } from './components/ContactTable'; export { default as CopyToClipboardButton } from './components/CopyToClipboardButton'; export { default as CreateUserForm } from './components/CreateUserForm'; @@ -45,8 +47,10 @@ export { default as EditEntityForm } from './components/EditEntityForm'; export { default as EditInventoryTagForm } from './components/EditInventoryTagForm'; export { default as EditLocationForm } from './components/EditLocationForm'; export { default as EditMyProfile } from './components/EditMyProfile'; +export { default as EditSimulationForm } from './components/EditSimulationForm'; export { default as EditUserForm } from './components/EditUserForm'; export { default as EditUserModal } from './components/EditUserModal'; +export { default as EntityTree } from './components/EntityTree'; export { default as EventQueueModal } from './components/EventQueueModal'; export { default as InventoryTable } from './components/InventoryTable'; export { default as FileToStringButton } from './components/FileToStringButton'; @@ -62,6 +66,7 @@ export { default as LoadingButton } from './components/LoadingButton'; export { default as NetworkDiagram } from './components/NetworkDiagram'; export { default as NotesTable } from './components/NotesTable'; export { default as RadioAnalysisTable } from './components/RadioAnalysisTable'; +export { default as SimulationTable } from './components/SimulationTable'; export { default as UserListTable } from './components/UserListTable'; export { default as VenueTable } from './components/VenueTable'; export { default as WifiAnalysisTable } from './components/WifiAnalysisTable'; diff --git a/src/layout/Header/index.js b/src/layout/Header/index.js index 2ba0ddf..cd664b2 100644 --- a/src/layout/Header/index.js +++ b/src/layout/Header/index.js @@ -30,6 +30,8 @@ const Header = ({ user, avatar, hideBreadcrumb, + extraButton, + hideSidebarButton, }) => { const [translatedRoutes, setTranslatedRoutes] = useState(routes); @@ -49,14 +51,25 @@ const Header = ({ return ( - - + {hideSidebarButton ? null : ( + <> + + + + )} - OpenWifi + OpenWifi + {extraButton} + @@ -78,12 +91,14 @@ const Header = ({ - + {hideBreadcrumb ? null : ( + + )} ); }; @@ -101,10 +116,14 @@ Header.propTypes = { user: PropTypes.instanceOf(Object).isRequired, avatar: PropTypes.string.isRequired, hideBreadcrumb: PropTypes.bool, + extraButton: PropTypes.node, + hideSidebarButton: PropTypes.bool, }; Header.defaultProps = { + extraButton: null, hideBreadcrumb: false, + hideSidebarButton: false, }; export default React.memo(Header); diff --git a/src/layout/Sidebar/index.js b/src/layout/Sidebar/index.js index 4687900..f3fe95e 100644 --- a/src/layout/Sidebar/index.js +++ b/src/layout/Sidebar/index.js @@ -13,16 +13,26 @@ import { import PropTypes from 'prop-types'; import styles from './index.module.scss'; -const Sidebar = ({ showSidebar, setShowSidebar, logo, options, redirectTo }) => ( +const Sidebar = ({ + showSidebar, + setShowSidebar, + logo, + options, + redirectTo, + logoHeight, + logoWidth, +}) => ( setShowSidebar(val)}> OpenWifi OpenWifi @@ -48,6 +58,13 @@ Sidebar.propTypes = { logo: PropTypes.string.isRequired, options: PropTypes.arrayOf(Object).isRequired, redirectTo: PropTypes.string.isRequired, + logoHeight: PropTypes.string, + logoWidth: PropTypes.string, +}; + +Sidebar.defaultProps = { + logoHeight: null, + logoWidth: null, }; export default React.memo(Sidebar);