mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs.git
synced 2025-10-29 18:02:21 +00:00
Version 1.0.26
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ucentral-libs",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.26",
|
||||
"main": "dist/index.js",
|
||||
"source": "src/index.js",
|
||||
"engines": {
|
||||
|
||||
@@ -256,7 +256,7 @@ const AddContactForm = ({ t, disable, fields, updateField, updateFieldWithKey, e
|
||||
<CInvalidFeedback>{t('common.required')}</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="phones">
|
||||
Landlines
|
||||
{t('location.phones')}
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CreatableSelect
|
||||
@@ -267,11 +267,11 @@ const AddContactForm = ({ t, disable, fields, updateField, updateFieldWithKey, e
|
||||
components={{ NoOptionsMessage }}
|
||||
options={[]}
|
||||
value={fields.phones.value.map((opt) => ({ value: opt, label: opt }))}
|
||||
placeholder={t('common.type_for_options')}
|
||||
placeholder="+1(202)555-0103"
|
||||
/>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="phones">
|
||||
Mobiles
|
||||
{t('location.mobiles')}
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CreatableSelect
|
||||
@@ -282,7 +282,7 @@ const AddContactForm = ({ t, disable, fields, updateField, updateFieldWithKey, e
|
||||
components={{ NoOptionsMessage }}
|
||||
options={[]}
|
||||
value={fields.mobiles.value.map((opt) => ({ value: opt, label: opt }))}
|
||||
placeholder={t('common.type_for_options')}
|
||||
placeholder="+1(202)555-0103"
|
||||
/>
|
||||
</CCol>
|
||||
<CLabel sm="2" col htmlFor="description">
|
||||
|
||||
@@ -130,7 +130,7 @@ const AddLocationForm = ({
|
||||
</div>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="phones">
|
||||
Landlines
|
||||
{t('location.phones')}
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CreatableSelect
|
||||
@@ -141,11 +141,11 @@ const AddLocationForm = ({
|
||||
components={{ NoOptionsMessage }}
|
||||
options={[]}
|
||||
value={fields.phones.value.map((opt) => ({ value: opt, label: opt }))}
|
||||
placeholder={t('common.type_for_options')}
|
||||
placeholder="+1(202)555-0103"
|
||||
/>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="phones">
|
||||
Mobiles
|
||||
{t('location.mobiles')}
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CreatableSelect
|
||||
@@ -156,7 +156,7 @@ const AddLocationForm = ({
|
||||
components={{ NoOptionsMessage }}
|
||||
options={[]}
|
||||
value={fields.mobiles.value.map((opt) => ({ value: opt, label: opt }))}
|
||||
placeholder={t('common.type_for_options')}
|
||||
placeholder="+1(202)555-0103"
|
||||
/>
|
||||
</CCol>
|
||||
<CCol className="mb-3" sm="12">
|
||||
|
||||
542
src/components/AddSimulationForm/index.js
Normal file
542
src/components/AddSimulationForm/index.js
Normal file
@@ -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 (
|
||||
<CForm>
|
||||
<CRow>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="name">
|
||||
{t('user.name')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="name"
|
||||
type="text"
|
||||
required
|
||||
value={fields.name.value}
|
||||
onChange={updateField}
|
||||
invalid={fields.name.error}
|
||||
disabled={disable}
|
||||
maxLength="50"
|
||||
/>
|
||||
<CFormText hidden={!fields.name.error} color={fields.name.error ? 'danger' : ''}>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="gateway">
|
||||
{t('simulation.gateway')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="gateway"
|
||||
type="text"
|
||||
required
|
||||
value={fields.gateway.value}
|
||||
onChange={updateField}
|
||||
invalid={fields.gateway.error}
|
||||
disabled={disable}
|
||||
maxLength="50"
|
||||
/>
|
||||
<CFormText hidden={!fields.gateway.error} color={fields.gateway.error ? 'danger' : ''}>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="certificate">
|
||||
{t('common.certificate')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInputFile
|
||||
className="mt-1"
|
||||
key={certKey}
|
||||
id="file-input"
|
||||
name="file-input"
|
||||
accept=".pem"
|
||||
onChange={(e) => handleCertFile(e.target.files[0])}
|
||||
/>
|
||||
<CFormText
|
||||
hidden={!fields.certificate.error}
|
||||
color={fields.certificate.error ? 'danger' : ''}
|
||||
>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="key">
|
||||
{t('common.key')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInputFile
|
||||
className="mt-1"
|
||||
key={keyKey}
|
||||
id="file-input"
|
||||
name="file-input"
|
||||
accept=".pem"
|
||||
onChange={(e) => handleKeyFile(e.target.files[0])}
|
||||
/>
|
||||
<CFormText hidden={!fields.key.error} color={fields.key.error ? 'danger' : ''}>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="macPrefix">
|
||||
{t('simulation.mac_prefix')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="macPrefix"
|
||||
type="text"
|
||||
required
|
||||
value={fields.macPrefix.value}
|
||||
onChange={updateField}
|
||||
invalid={fields.macPrefix.error}
|
||||
disabled={disable}
|
||||
maxLength="50"
|
||||
/>
|
||||
<CFormText
|
||||
hidden={!fields.macPrefix.error}
|
||||
color={fields.macPrefix.error ? 'danger' : ''}
|
||||
>
|
||||
{t('simulation.prefix_length')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="devices">
|
||||
{t('common.devices')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="devices"
|
||||
type="number"
|
||||
required
|
||||
value={fields.devices.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.devices.value < fields.devices.min || fields.devices.value > fields.devices.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.devices.min,
|
||||
max: fields.devices.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="healthCheckInterval">
|
||||
{t('simulation.healtcheck_interval')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="healthCheckInterval"
|
||||
type="number"
|
||||
required
|
||||
value={fields.healthCheckInterval.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.healthCheckInterval.value < fields.healthCheckInterval.min ||
|
||||
fields.healthCheckInterval.value > fields.healthCheckInterval.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.healthCheckInterval.min,
|
||||
max: fields.healthCheckInterval.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="stateInterval">
|
||||
{t('simulation.state_interval')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="stateInterval"
|
||||
type="number"
|
||||
required
|
||||
value={fields.stateInterval.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.stateInterval.value < fields.stateInterval.min ||
|
||||
fields.stateInterval.value > fields.stateInterval.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.stateInterval.min,
|
||||
max: fields.stateInterval.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="clientInterval">
|
||||
{t('simulation.client_interval')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="clientInterval"
|
||||
type="number"
|
||||
required
|
||||
value={fields.clientInterval.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.clientInterval.value < fields.clientInterval.min ||
|
||||
fields.clientInterval.value > fields.clientInterval.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.clientInterval.min,
|
||||
max: fields.clientInterval.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="reconnectInterval">
|
||||
{t('simulation.reconnect_interval')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="reconnectInterval"
|
||||
type="number"
|
||||
required
|
||||
value={fields.reconnectInterval.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.reconnectInterval.value < fields.reconnectInterval.min ||
|
||||
fields.reconnectInterval.value > fields.reconnectInterval.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.reconnectInterval.min,
|
||||
max: fields.reconnectInterval.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="minAssociations">
|
||||
{t('simulation.min_associations')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="minAssociations"
|
||||
type="number"
|
||||
required
|
||||
value={fields.minAssociations.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.minAssociations.value < fields.minAssociations.min ||
|
||||
fields.minAssociations.value > fields.minAssociations.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.minAssociations.min,
|
||||
max: fields.minAssociations.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="maxAssociations">
|
||||
{t('simulation.max_associations')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="maxAssociations"
|
||||
type="number"
|
||||
required
|
||||
value={fields.maxAssociations.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.maxAssociations.value < fields.maxAssociations.min ||
|
||||
fields.maxAssociations.value > fields.maxAssociations.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.maxAssociations.min,
|
||||
max: fields.maxAssociations.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="minClients">
|
||||
{t('simulation.min_clients')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="minClients"
|
||||
type="number"
|
||||
required
|
||||
value={fields.minClients.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.minClients.value < fields.minClients.min ||
|
||||
fields.minClients.value > fields.minClients.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.minClients.min,
|
||||
max: fields.minClients.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="maxClients">
|
||||
{t('simulation.max_clients')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="maxClients"
|
||||
type="number"
|
||||
required
|
||||
value={fields.maxClients.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.maxClients.value < fields.maxClients.min ||
|
||||
fields.maxClients.value > fields.maxClients.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.maxClients.min,
|
||||
max: fields.maxClients.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="simulationLength">
|
||||
{t('simulation.length')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="simulationLength"
|
||||
type="number"
|
||||
required
|
||||
value={fields.simulationLength.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.simulationLength.value < fields.simulationLength.min ||
|
||||
fields.simulationLength.value > fields.simulationLength.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.simulationLength.min,
|
||||
max: fields.simulationLength.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="threads">
|
||||
{t('simulation.threads')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="threads"
|
||||
type="number"
|
||||
required
|
||||
value={fields.threads.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.threads.value < fields.threads.min || fields.threads.value > fields.threads.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.threads.min,
|
||||
max: fields.threads.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="keepAlive">
|
||||
{t('simulation.keep_alive')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="keepAlive"
|
||||
type="number"
|
||||
required
|
||||
value={fields.keepAlive.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.keepAlive.value < fields.keepAlive.min ||
|
||||
fields.keepAlive.value > fields.keepAlive.max
|
||||
}
|
||||
disabled={disable}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.keepAlive.min,
|
||||
max: fields.keepAlive.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="deviceType">
|
||||
{t('configuration.device_type')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CSelect
|
||||
custom
|
||||
id="deviceType"
|
||||
type="text"
|
||||
required
|
||||
value={fields.deviceType.value}
|
||||
onChange={updateField}
|
||||
invalid={fields.deviceType.error}
|
||||
disabled={disable}
|
||||
maxLength="50"
|
||||
>
|
||||
<option value="cig_wf160d">cig_wf160d</option>
|
||||
<option value="cig_wf188">cig_wf188</option>
|
||||
<option value="cig_wf188n">cig_wf188n</option>
|
||||
<option value="cig_wf194c">cig_wf194c</option>
|
||||
<option value="cig_wf194c4">cig_wf194c4</option>
|
||||
<option value="edgecore_eap101">edgecore_eap101</option>
|
||||
<option value="edgecore_eap102">edgecore_eap102</option>
|
||||
<option value="edgecore_ecs4100-12ph">edgecore_ecs4100-12ph</option>
|
||||
<option value="edgecore_ecw5211">edgecore_ecw5211</option>
|
||||
<option value="edgecore_ecw5410">edgecore_ecw5410</option>
|
||||
<option value="edgecore_oap100">edgecore_oap100</option>
|
||||
<option value="edgecore_spw2ac1200">edgecore_spw2ac1200</option>
|
||||
<option value="edgecore_spw2ac1200-lan-poe">edgecore_spw2ac1200-lan-poe</option>
|
||||
<option value="edgecore_ssw2ac2600">edgecore_ssw2ac2600</option>
|
||||
<option value="hfcl_ion4.yml">hfcl_ion4.yml</option>
|
||||
<option value="indio_um-305ac">indio_um-305ac</option>
|
||||
<option value="linksys_e8450-ubi">linksys_e8450-ubi</option>
|
||||
<option value="linksys_ea6350">linksys_ea6350</option>
|
||||
<option value="linksys_ea6350-v4">linksys_ea6350-v4</option>
|
||||
<option value="linksys_ea8300">linksys_ea8300</option>
|
||||
<option value="mikrotik_nand">mikrotik_nand</option>
|
||||
<option value="tp-link_ec420-g1">tp-link_ec420-g1</option>
|
||||
<option value="tplink_cpe210_v3">tplink_cpe210_v3</option>
|
||||
<option value="tplink_cpe510_v3">tplink_cpe510_v3</option>
|
||||
<option value="tplink_eap225_outdoor_v1">tplink_eap225_outdoor_v1</option>
|
||||
<option value="tplink_ec420">tplink_ec420</option>
|
||||
<option value="tplink_ex227">tplink_ex227</option>
|
||||
<option value="tplink_ex228">tplink_ex228</option>
|
||||
<option value="tplink_ex447">tplink_ex447</option>
|
||||
<option value="wallys_dr40x9">wallys_dr40x9</option>
|
||||
</CSelect>
|
||||
<CFormText
|
||||
hidden={!fields.deviceType.error}
|
||||
color={fields.deviceType.error ? 'danger' : ''}
|
||||
>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CForm>
|
||||
);
|
||||
};
|
||||
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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
<CIcon content={cilPlus} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
<CPopover content={t('common.save')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={closeAndSave}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
className="ml-2"
|
||||
onClick={closeAndSave}
|
||||
disabled={disabled}
|
||||
>
|
||||
<CIcon content={cilSave} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
@@ -145,6 +153,7 @@ CustomMultiModal.propTypes = {
|
||||
noTable: PropTypes.bool,
|
||||
toggleAdd: PropTypes.func,
|
||||
reset: PropTypes.func,
|
||||
disabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
CustomMultiModal.defaultProps = {
|
||||
|
||||
@@ -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 (
|
||||
<div className="py-1 pb-0 mb-0">
|
||||
<h6 className="mt-1 float-left">{label}</h6>
|
||||
<div className="text-right">
|
||||
<CButtonClose onClick={toggle} style={{ color: 'white' }} />
|
||||
<CButtonClose onClick={toggle} style={{ color: 'white' }} disabled={disabled} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -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;
|
||||
|
||||
@@ -104,7 +104,7 @@ const ConfigurationTable = ({
|
||||
deleteConfig={deleteConfig}
|
||||
hideTooltips={hideTooltips}
|
||||
/>
|
||||
<CPopover content={t('configuration.view_config')}>
|
||||
<CPopover content={t('common.details')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
|
||||
64
src/components/ConfirmStopEditingButton/index.js
Normal file
64
src/components/ConfirmStopEditingButton/index.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
CAlert,
|
||||
CButton,
|
||||
CModal,
|
||||
CModalBody,
|
||||
CModalHeader,
|
||||
CModalTitle,
|
||||
CPopover,
|
||||
} from '@coreui/react';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { cilX } from '@coreui/icons';
|
||||
import useToggle from '../../hooks/useToggle';
|
||||
|
||||
const ConfirmStopEditingButton = ({ t, stopEditing, disabled }) => {
|
||||
const [show, toggleShow] = useToggle();
|
||||
|
||||
const toggleAndStop = () => {
|
||||
toggleShow();
|
||||
stopEditing();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CPopover content={t('common.stop_editing')}>
|
||||
<CButton disabled={disabled} color="dark" onClick={toggleShow} className="ml-2">
|
||||
<CIcon name="cil-x" content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
<CModal show={show} onClose={toggleShow}>
|
||||
<CModalHeader className="p-1">
|
||||
<CModalTitle className="pl-1 pt-1 text-dark">{t('common.stop_editing')}</CModalTitle>
|
||||
<div className="text-right">
|
||||
<CPopover content={t('common.close')}>
|
||||
<CButton color="primary" variant="outline" className="ml-2" onClick={toggleShow}>
|
||||
<CIcon content={cilX} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<CAlert color="danger">{t('common.confirm_stop_editing')}</CAlert>
|
||||
<div className="text-center">
|
||||
<CButton className="mr-2" color="danger" onClick={toggleAndStop}>
|
||||
{t('common.stop_editing')}
|
||||
</CButton>
|
||||
<CButton className="ml-2" color="light" onClick={toggleShow}>
|
||||
{t('common.go_back')}
|
||||
</CButton>
|
||||
</div>
|
||||
</CModalBody>
|
||||
</CModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ConfirmStopEditingButton.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
stopEditing: PropTypes.func.isRequired,
|
||||
disabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default ConfirmStopEditingButton;
|
||||
@@ -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}
|
||||
/>
|
||||
<CPopover content={t('common.edit')}>
|
||||
<CPopover content={t('common.details')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
@@ -128,7 +128,7 @@ const ContactTable = ({
|
||||
onClick={() => toggleEditModal(item.id)}
|
||||
style={{ width: '33px', height: '30px' }}
|
||||
>
|
||||
<CIcon name="cil-pencil" content={cilPencil} size="sm" />
|
||||
<CIcon name="cil-magnifying-glass" content={cilMagnifyingGlass} size="sm" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</CButtonToolbar>
|
||||
|
||||
@@ -14,7 +14,7 @@ const CopyToClipboardButton = ({ t, content, size }) => {
|
||||
|
||||
return (
|
||||
<CPopover content={t('common.copy_to_clipboard')}>
|
||||
<CButton onClick={copyToClipboard} size={size}>
|
||||
<CButton onClick={copyToClipboard} size={size} className="py-0">
|
||||
<CIcon content={cilClone} />
|
||||
{' '}
|
||||
{result || ''}
|
||||
|
||||
@@ -77,10 +77,11 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats })
|
||||
</div>
|
||||
<CRow>
|
||||
<CCol lg="2" xl="1" xxl="1">
|
||||
<CLabel>{t('common.serial_number')}: </CLabel>
|
||||
<CLabel>{t('common.serial_num')}: </CLabel>
|
||||
</CCol>
|
||||
<CCol className="border-right" lg="2" xl="3" xxl="3">
|
||||
{deviceConfig?.serialNumber}
|
||||
{' '}
|
||||
<CopyToClipboardButton t={t} size="sm" content={deviceConfig?.serialNumber} />
|
||||
</CCol>
|
||||
<CCol lg="2" xl="1" xxl="1">
|
||||
@@ -88,6 +89,7 @@ const DeviceDetails = ({ t, loading, getData, status, deviceConfig, lastStats })
|
||||
</CCol>
|
||||
<CCol lg="2" xl="3" xxl="3">
|
||||
{getPassword()}
|
||||
{' '}
|
||||
<HideTextButton t={t} toggle={toggleShowPassword} show={showPassword} />
|
||||
<CopyToClipboardButton
|
||||
t={t}
|
||||
|
||||
@@ -52,10 +52,10 @@ const DeviceListTable = ({
|
||||
deleteStatus,
|
||||
}) => {
|
||||
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 = <CIcon size="md" name="cil-arrow-circle-top" content={cilArrowCircleTop} />;
|
||||
let icon = <CIcon name="cil-arrow-circle-top" content={cilArrowCircleTop} />;
|
||||
let color = 'secondary';
|
||||
if (latest !== undefined) {
|
||||
text = t('firmware.newer_firmware_available');
|
||||
color = 'warning';
|
||||
|
||||
if (latest) {
|
||||
icon = <CIcon size="md" name="cil-check-circle" content={cilCheckCircle} />;
|
||||
icon = <CIcon name="cil-check-circle" content={cilCheckCircle} />;
|
||||
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
|
||||
}
|
||||
/>
|
||||
</CCol>
|
||||
<CCol>
|
||||
@@ -185,75 +187,82 @@ const DeviceListTable = ({
|
||||
const tooltipId = createUuid();
|
||||
|
||||
return (
|
||||
<CPopover content={t('common.delete_device')}>
|
||||
<div>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
className="mx-1"
|
||||
data-tip
|
||||
data-for={tooltipId}
|
||||
data-event="click"
|
||||
style={{ width: '33px', height: '30px' }}
|
||||
>
|
||||
<CIcon name="cil-trash" content={cilTrash} size="sm" />
|
||||
</CButton>
|
||||
<ReactTooltip
|
||||
id={tooltipId}
|
||||
place="top"
|
||||
effect="solid"
|
||||
globalEventOff="click"
|
||||
clickable
|
||||
className={[styles.deleteTooltip, 'tooltipRight'].join(' ')}
|
||||
border
|
||||
borderColor="#321fdb"
|
||||
arrowColor="white"
|
||||
overridePosition={({ left, top }) => {
|
||||
const element = document.getElementById(tooltipId);
|
||||
const tooltipWidth = element ? element.offsetWidth : 0;
|
||||
const newLeft = left - tooltipWidth * 0.25;
|
||||
return { top, left: newLeft };
|
||||
}}
|
||||
>
|
||||
<CCardHeader color="primary" className={styles.tooltipHeader}>
|
||||
{t('common.device_delete', { serialNumber })}
|
||||
<CButtonClose
|
||||
style={{ color: 'white' }}
|
||||
onClick={(e) => {
|
||||
e.target.parentNode.parentNode.classList.remove('show');
|
||||
hideTooltips();
|
||||
}}
|
||||
/>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<LoadingButton
|
||||
data-toggle="dropdown"
|
||||
variant="outline"
|
||||
color="danger"
|
||||
label={t('common.confirm')}
|
||||
isLoadingLabel={t('user.deleting')}
|
||||
isLoading={deleteStatus.loading}
|
||||
action={() => deleteDevice(serialNumber)}
|
||||
block
|
||||
disabled={deleteStatus.loading}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardBody>
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
</CPopover>
|
||||
<div>
|
||||
<CPopover content={t('common.delete_device')}>
|
||||
<div>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
className="mx-1"
|
||||
data-tip
|
||||
data-for={tooltipId}
|
||||
data-event="click"
|
||||
style={{ width: '33px', height: '30px' }}
|
||||
>
|
||||
<CIcon name="cil-trash" content={cilTrash} size="sm" />
|
||||
</CButton>
|
||||
</div>
|
||||
</CPopover>
|
||||
<ReactTooltip
|
||||
id={tooltipId}
|
||||
place="top"
|
||||
effect="solid"
|
||||
globalEventOff="click"
|
||||
clickable
|
||||
className={[styles.deleteTooltip, 'tooltipRight'].join(' ')}
|
||||
border
|
||||
borderColor="#321fdb"
|
||||
arrowColor="white"
|
||||
overridePosition={({ left, top }) => {
|
||||
const element = document.getElementById(tooltipId);
|
||||
const tooltipWidth = element ? element.offsetWidth : 0;
|
||||
const newLeft = left - tooltipWidth * 0.25;
|
||||
return { top, left: newLeft };
|
||||
}}
|
||||
>
|
||||
<CCardHeader color="primary" className={styles.tooltipHeader}>
|
||||
{t('common.device_delete', { serialNumber })}
|
||||
<CButtonClose
|
||||
className="p-0 mb-1"
|
||||
style={{ color: 'white' }}
|
||||
onClick={(e) => {
|
||||
e.target.parentNode.parentNode.classList.remove('show');
|
||||
hideTooltips();
|
||||
}}
|
||||
/>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<LoadingButton
|
||||
data-toggle="dropdown"
|
||||
variant="outline"
|
||||
color="danger"
|
||||
label={t('common.confirm')}
|
||||
isLoadingLabel={t('user.deleting')}
|
||||
isLoading={deleteStatus.loading}
|
||||
action={(e) => {
|
||||
e.target.parentNode.parentNode.parentNode.parentNode.classList.remove('show');
|
||||
hideTooltips();
|
||||
deleteDevice(serialNumber);
|
||||
}}
|
||||
block
|
||||
disabled={deleteStatus.loading}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardBody>
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CCard className="m-0 p-0">
|
||||
<CCardHeader className="dark-header">
|
||||
<CCardHeader className="p-0">
|
||||
<div className="float-left" style={{ width: '400px' }}>
|
||||
{searchBar}
|
||||
</div>
|
||||
@@ -335,7 +344,7 @@ const DeviceListTable = ({
|
||||
<CButtonToolbar
|
||||
role="group"
|
||||
className="justify-content-center"
|
||||
style={{ width: '170px' }}
|
||||
style={{ width: '180px' }}
|
||||
>
|
||||
<CPopover content={t('actions.connect')}>
|
||||
<CButton
|
||||
|
||||
@@ -19,10 +19,12 @@
|
||||
font-size: 0.875rem !important;
|
||||
font-weight: 400 !important;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
|
||||
width: 300px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.tooltipHeader {
|
||||
padding-left: 5px;
|
||||
padding-right: 10px;
|
||||
border-top-left-radius: 1rem !important;
|
||||
border-top-right-radius: 1rem !important;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Select, { components } from 'react-select';
|
||||
|
||||
const DeviceSearchBar = ({ t, search, results, history }) => {
|
||||
const DeviceSearchBar = ({ t, search, results, history, action }) => {
|
||||
const [selected, setSelected] = useState('');
|
||||
const NoOptionsMessage = (props) => (
|
||||
<components.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;
|
||||
|
||||
@@ -119,11 +119,18 @@ const DeviceStatusCard = ({
|
||||
errorField(t)
|
||||
) : (
|
||||
<div>
|
||||
{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)
|
||||
: '-'}
|
||||
%
|
||||
</div>
|
||||
)}
|
||||
</CCol>
|
||||
|
||||
@@ -191,6 +191,14 @@ const EditConfigurationForm = ({
|
||||
/>
|
||||
</div>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" md="5" lg="2" xl="2" xxl="1" col htmlFor="name">
|
||||
<div>{t('common.created')}:</div>
|
||||
</CLabel>
|
||||
<CCol md="7" lg="4" xl="4" xxl="5">
|
||||
<p className="mt-2 mb-0">
|
||||
<FormattedDate date={fields.created.value} />
|
||||
</p>
|
||||
</CCol>
|
||||
<CLabel col className="mb-2" md="5" lg="2" xl="2" xxl="1" htmlFor="firmwareRCOnly">
|
||||
Only Release Candidates
|
||||
</CLabel>
|
||||
@@ -206,14 +214,6 @@ const EditConfigurationForm = ({
|
||||
disabled={!editing || fields.firmwareUpgrade.value === 'no'}
|
||||
/>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" md="5" lg="2" xl="2" xxl="1" col htmlFor="name">
|
||||
<div>{t('common.created')}:</div>
|
||||
</CLabel>
|
||||
<CCol md="7" lg="4" xl="4" xxl="5">
|
||||
<p className="mt-2 mb-0">
|
||||
<FormattedDate date={fields.created.value} />
|
||||
</p>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" md="5" lg="2" xl="2" xxl="1" col htmlFor="name">
|
||||
<div>{t('common.modified')}:</div>
|
||||
</CLabel>
|
||||
|
||||
@@ -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 (
|
||||
<CForm>
|
||||
<CRow>
|
||||
@@ -344,7 +352,7 @@ const EditContactForm = ({
|
||||
)}
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="phones">
|
||||
Landlines
|
||||
{t('location.phones')}
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CreatableSelect
|
||||
@@ -355,11 +363,11 @@ const EditContactForm = ({
|
||||
components={{ NoOptionsMessage }}
|
||||
options={[]}
|
||||
value={fields.phones.value.map((opt) => ({ value: opt, label: opt }))}
|
||||
placeholder={t('common.type_for_options')}
|
||||
placeholder="+1(202)555-0103"
|
||||
/>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="phones">
|
||||
Mobiles
|
||||
{t('location.mobiles')}
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CreatableSelect
|
||||
@@ -370,70 +378,72 @@ const EditContactForm = ({
|
||||
components={{ NoOptionsMessage }}
|
||||
options={[]}
|
||||
value={fields.mobiles.value.map((opt) => ({ value: opt, label: opt }))}
|
||||
placeholder={t('common.type_for_options')}
|
||||
placeholder="+1(202)555-0103"
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CFormGroup row className="pb-1">
|
||||
<CLabel sm="2" col htmlFor="title">
|
||||
{t('entity.selected_entity')}
|
||||
{t('entity.entity')}
|
||||
</CLabel>
|
||||
<CCol sm="4" className="pt-2">
|
||||
<h6>
|
||||
<CCol sm="4">
|
||||
<CButton className="pl-0" color="link" onClick={toggleDropdown} disabled={!editing}>
|
||||
{fields.entity.value === '' ? t('entity.need_select_entity') : fields.entityName.value}
|
||||
</h6>
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
{hideEntities ? null : (
|
||||
<div className="overflow-auto border mb-3" style={{ height: '200px' }}>
|
||||
<CInput
|
||||
className="w-50 mb-2"
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
/>
|
||||
<CDataTable
|
||||
items={entities}
|
||||
fields={columns}
|
||||
hover
|
||||
tableFilterValue={filter}
|
||||
border
|
||||
scopedSlots={{
|
||||
name: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/entity/${item.id}`}
|
||||
>
|
||||
{item.name}
|
||||
</CLink>
|
||||
</td>
|
||||
),
|
||||
created: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<FormattedDate date={item.created} />
|
||||
</td>
|
||||
),
|
||||
actions: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CPopover content={t('entity.select_entity')}>
|
||||
<CButton
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
onClick={() => selectEntity(item)}
|
||||
disabled={!editing}
|
||||
<CCollapse show={showDropdown}>
|
||||
<div className="overflow-auto border mb-3" style={{ height: '200px' }}>
|
||||
<CInput
|
||||
className="w-50 mb-2"
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
/>
|
||||
<CDataTable
|
||||
items={entities}
|
||||
fields={columns}
|
||||
hover
|
||||
tableFilterValue={filter}
|
||||
border
|
||||
scopedSlots={{
|
||||
name: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/entity/${item.id}`}
|
||||
>
|
||||
{t('common.select')}
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{item.name}
|
||||
</CLink>
|
||||
</td>
|
||||
),
|
||||
created: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<FormattedDate date={item.created} />
|
||||
</td>
|
||||
),
|
||||
actions: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CPopover content={t('entity.select_entity')}>
|
||||
<CButton
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
onClick={() => selectEntity(item)}
|
||||
disabled={!editing}
|
||||
>
|
||||
{t('common.select')}
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CCollapse>
|
||||
)}
|
||||
</CForm>
|
||||
);
|
||||
|
||||
@@ -80,6 +80,14 @@ const EditEntityForm = ({
|
||||
/>
|
||||
</div>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" md="5" lg="2" xl="2" xxl="1" col htmlFor="name">
|
||||
<div>{t('common.created')}:</div>
|
||||
</CLabel>
|
||||
<CCol md="7" lg="4" xl="4" xxl="5">
|
||||
<div className="mt-2 mb-0">
|
||||
<FormattedDate date={fields.created.value} />
|
||||
</div>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" md="5" lg="2" xl="2" xxl="1" col htmlFor="sourceIp">
|
||||
<div>{t('entity.ip_detection')}:</div>
|
||||
</CLabel>
|
||||
@@ -100,14 +108,6 @@ const EditEntityForm = ({
|
||||
</div>
|
||||
)}
|
||||
</CCol>
|
||||
<CLabel className="mb-2" md="5" lg="2" xl="2" xxl="1" col htmlFor="name">
|
||||
<div>{t('common.created')}:</div>
|
||||
</CLabel>
|
||||
<CCol md="7" lg="4" xl="4" xxl="5">
|
||||
<div className="mt-2 mb-0">
|
||||
<FormattedDate date={fields.created.value} />
|
||||
</div>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" md="5" lg="2" xl="2" xxl="1" col htmlFor="name">
|
||||
<div>{t('common.modified')}:</div>
|
||||
</CLabel>
|
||||
|
||||
@@ -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,
|
||||
}) => (
|
||||
<CForm>
|
||||
<CFormGroup row className="mb-1">
|
||||
<CCol>
|
||||
<CLabel htmlFor="serialNumber">{t('common.serial_number')}</CLabel>
|
||||
</CCol>
|
||||
<CCol sm="8">{fields.serialNumber.value}</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row className="mb-1">
|
||||
<CLabel col htmlFor="name">
|
||||
{t('user.name')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="8">
|
||||
{editing ? (
|
||||
<div>
|
||||
<CInput
|
||||
id="name"
|
||||
type="text"
|
||||
required
|
||||
value={fields.name.value}
|
||||
onChange={updateField}
|
||||
invalid={fields.name.error}
|
||||
disabled={disable}
|
||||
maxLength="50"
|
||||
/>
|
||||
<CFormText hidden={!fields.name.error} color={fields.name.error ? 'danger' : ''}>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</div>
|
||||
) : (
|
||||
<p className="mt-2 mb-0">{fields.name.value}</p>
|
||||
)}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row className="mb-1">
|
||||
<CLabel col htmlFor="description">
|
||||
{t('user.description')}
|
||||
</CLabel>
|
||||
<CCol sm="8">
|
||||
{editing ? (
|
||||
<div>
|
||||
<CInput
|
||||
id="description"
|
||||
type="text"
|
||||
required
|
||||
value={fields.description.value}
|
||||
onChange={updateField}
|
||||
disabled={disable}
|
||||
maxLength="50"
|
||||
hideEntities,
|
||||
batchSetField,
|
||||
}) => {
|
||||
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 (
|
||||
<CForm>
|
||||
<CFormGroup row className="mb-1">
|
||||
<CCol>
|
||||
<CLabel htmlFor="serialNumber">{t('common.serial_number')}</CLabel>
|
||||
</CCol>
|
||||
<CCol sm="8">{fields.serialNumber.value}</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row className="mb-1">
|
||||
<CLabel col htmlFor="name">
|
||||
{t('user.name')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="8">
|
||||
{editing ? (
|
||||
<div>
|
||||
<CInput
|
||||
id="name"
|
||||
type="text"
|
||||
required
|
||||
value={fields.name.value}
|
||||
onChange={updateField}
|
||||
invalid={fields.name.error}
|
||||
disabled={disable}
|
||||
maxLength="50"
|
||||
/>
|
||||
<CFormText hidden={!fields.name.error} color={fields.name.error ? 'danger' : ''}>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</div>
|
||||
) : (
|
||||
<p className="mt-2 mb-0">{fields.name.value}</p>
|
||||
)}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row className="mb-1">
|
||||
<CLabel col htmlFor="description">
|
||||
{t('user.description')}
|
||||
</CLabel>
|
||||
<CCol sm="8">
|
||||
{editing ? (
|
||||
<div>
|
||||
<CInput
|
||||
id="description"
|
||||
type="text"
|
||||
required
|
||||
value={fields.description.value}
|
||||
onChange={updateField}
|
||||
disabled={disable}
|
||||
maxLength="50"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<p className="mt-2 mb-0">{fields.description.value}</p>
|
||||
)}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row className="mb-1">
|
||||
<CLabel col htmlFor="deviceType">
|
||||
{t('firmware.device_type')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="8">
|
||||
<div style={{ width: '250px' }}>
|
||||
<Select
|
||||
styles={selectStyles}
|
||||
id="deviceType"
|
||||
value={{ value: fields.deviceType.value, label: fields.deviceType.value }}
|
||||
onChange={(v) => updateFieldDirectly('deviceType', { value: v.value })}
|
||||
options={deviceTypes.map((v) => ({ value: v, label: v }))}
|
||||
isDisabled={disable || !editing}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<p className="mt-2 mb-0">{fields.description.value}</p>
|
||||
)}
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CFormGroup row className="mb-1">
|
||||
<CLabel col htmlFor="deviceType">
|
||||
{t('firmware.device_type')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="8">
|
||||
<div style={{ width: '250px' }}>
|
||||
<Select
|
||||
styles={selectStyles}
|
||||
id="deviceType"
|
||||
value={{ value: fields.deviceType.value, label: fields.deviceType.value }}
|
||||
onChange={(v) => updateFieldDirectly('deviceType', { value: v.value })}
|
||||
options={deviceTypes.map((v) => ({ value: v, label: v }))}
|
||||
isDisabled={disable || !editing}
|
||||
/>
|
||||
</div>
|
||||
<CFormText
|
||||
hidden={!fields.deviceType.error}
|
||||
color={fields.deviceType.error ? 'danger' : ''}
|
||||
>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CRow className="mb-1">
|
||||
<CLabel sm="4" col htmlFor="rrm">
|
||||
RRM
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="8">
|
||||
<div style={{ width: '120px' }}>
|
||||
<Select
|
||||
id="rrm"
|
||||
styles={selectStyles}
|
||||
value={{ value: fields.rrm.value, label: fields.rrm.value }}
|
||||
onChange={(v) => updateFieldDirectly('rrm', { value: v.value, error: false })}
|
||||
options={[
|
||||
{ label: 'on', value: 'on' },
|
||||
{ label: 'off', value: 'off' },
|
||||
{ label: 'inherit', value: 'inherit' },
|
||||
]}
|
||||
isDisabled={disable || !editing}
|
||||
/>
|
||||
</div>
|
||||
<CFormText hidden={!fields.rrm.error} color={fields.rrm.error ? 'danger' : ''}>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CForm>
|
||||
);
|
||||
<CFormText
|
||||
hidden={!fields.deviceType.error}
|
||||
color={fields.deviceType.error ? 'danger' : ''}
|
||||
>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<CRow className="mb-1">
|
||||
<CLabel sm="4" col htmlFor="rrm">
|
||||
RRM
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="8">
|
||||
<div style={{ width: '120px' }}>
|
||||
<Select
|
||||
id="rrm"
|
||||
styles={selectStyles}
|
||||
value={{ value: fields.rrm.value, label: fields.rrm.value }}
|
||||
onChange={(v) => updateFieldDirectly('rrm', { value: v.value, error: false })}
|
||||
options={[
|
||||
{ label: 'on', value: 'on' },
|
||||
{ label: 'off', value: 'off' },
|
||||
{ label: 'inherit', value: 'inherit' },
|
||||
]}
|
||||
isDisabled={disable || !editing}
|
||||
/>
|
||||
</div>
|
||||
<CFormText hidden={!fields.rrm.error} color={fields.rrm.error ? 'danger' : ''}>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CFormGroup row className="pb-1">
|
||||
<CLabel sm="4" col htmlFor="title">
|
||||
{getEntityLabel()}
|
||||
</CLabel>
|
||||
<CCol sm="8">
|
||||
<CButton className="pl-0" color="link" onClick={toggleDropdown} disabled={!editing}>
|
||||
{getEntityValue()}
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
{hideEntities ? null : (
|
||||
<CCollapse show={showDropdown}>
|
||||
<div className="overflow-auto border mb-3" style={{ height: '200px' }}>
|
||||
<h5>{t('entity.entities')}</h5>
|
||||
<CInput
|
||||
className="w-50 mb-2"
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
value={entityFilter}
|
||||
onChange={(e) => setEntityFilter(e.target.value)}
|
||||
/>
|
||||
<CDataTable
|
||||
items={entities}
|
||||
fields={columns}
|
||||
hover
|
||||
tableFilterValue={entityFilter}
|
||||
border
|
||||
scopedSlots={{
|
||||
name: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/entity/${item.id}`}
|
||||
>
|
||||
{item.name}
|
||||
</CLink>
|
||||
</td>
|
||||
),
|
||||
created: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<FormattedDate date={item.created} />
|
||||
</td>
|
||||
),
|
||||
actions: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CPopover content={t('entity.select_entity')}>
|
||||
<CButton
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
onClick={() => selectEntity(item)}
|
||||
disabled={!editing}
|
||||
>
|
||||
{t('common.select')}
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<h5>{t('entity.venues')}</h5>
|
||||
<CInput
|
||||
className="w-50 mb-2"
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
value={venueFilter}
|
||||
onChange={(e) => setVenueFilter(e.target.value)}
|
||||
/>
|
||||
<CDataTable
|
||||
items={venues}
|
||||
fields={columns}
|
||||
hover
|
||||
tableFilterValue={venueFilter}
|
||||
border
|
||||
scopedSlots={{
|
||||
name: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/entity/${item.id}`}
|
||||
>
|
||||
{item.name}
|
||||
</CLink>
|
||||
</td>
|
||||
),
|
||||
created: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<FormattedDate date={item.created} />
|
||||
</td>
|
||||
),
|
||||
actions: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CPopover content={t('entity.select_entity')}>
|
||||
<CButton
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
onClick={() => selectVenue(item)}
|
||||
disabled={!editing}
|
||||
>
|
||||
{t('common.select')}
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CCollapse>
|
||||
)}
|
||||
</CForm>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
@@ -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 (
|
||||
<CForm>
|
||||
<CRow>
|
||||
@@ -153,7 +161,7 @@ const EditLocationForm = ({
|
||||
)}
|
||||
</CCol>
|
||||
<CLabel className="mb-3" sm="2" col htmlFor="phones">
|
||||
Landlines
|
||||
{t('location.phones')}
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CreatableSelect
|
||||
@@ -164,11 +172,11 @@ const EditLocationForm = ({
|
||||
components={{ NoOptionsMessage }}
|
||||
options={[]}
|
||||
value={fields.phones.value.map((opt) => ({ value: opt, label: opt }))}
|
||||
placeholder={t('common.type_for_options')}
|
||||
placeholder="+1(202)555-0103"
|
||||
/>
|
||||
</CCol>
|
||||
<CLabel className="mb-3" sm="2" col htmlFor="phones">
|
||||
Mobiles
|
||||
<CLabel className="mb-3" sm="2" col htmlFor="mobiles">
|
||||
{t('location.mobiles')}
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CreatableSelect
|
||||
@@ -179,7 +187,7 @@ const EditLocationForm = ({
|
||||
components={{ NoOptionsMessage }}
|
||||
options={[]}
|
||||
value={fields.mobiles.value.map((opt) => ({ value: opt, label: opt }))}
|
||||
placeholder={t('common.type_for_options')}
|
||||
placeholder="+1(202)555-0103"
|
||||
/>
|
||||
</CCol>
|
||||
<CCol className="mb-3" sm="12">
|
||||
@@ -339,63 +347,65 @@ const EditLocationForm = ({
|
||||
</CRow>
|
||||
<CFormGroup row className="pt-2 pb-1">
|
||||
<CLabel sm="2" col htmlFor="title">
|
||||
{t('entity.selected_entity')}
|
||||
{t('entity.entity')}
|
||||
</CLabel>
|
||||
<CCol sm="4" className="pt-2">
|
||||
<h6>
|
||||
<CCol sm="4">
|
||||
<CButton className="pl-0" color="link" onClick={toggleDropdown} disabled={!editing}>
|
||||
{fields.entity.value === '' ? t('entity.need_select_entity') : fields.entityName.value}
|
||||
</h6>
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CFormGroup>
|
||||
<div className="overflow-auto border mb-1" style={{ height: '200px' }}>
|
||||
<CInput
|
||||
className="w-50 mb-2"
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
/>
|
||||
<CDataTable
|
||||
items={entities}
|
||||
fields={columns}
|
||||
hover
|
||||
tableFilterValue={filter}
|
||||
border
|
||||
scopedSlots={{
|
||||
name: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/configuration/${item.id}`}
|
||||
>
|
||||
{item.name}
|
||||
</CLink>
|
||||
</td>
|
||||
),
|
||||
created: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<FormattedDate date={item.created} />
|
||||
</td>
|
||||
),
|
||||
actions: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CPopover content={t('entity.select_entity')}>
|
||||
<CButton
|
||||
disabled={!editing}
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
onClick={() => selectEntity(item)}
|
||||
<CCollapse show={showDropdown}>
|
||||
<div className="overflow-auto border mb-1" style={{ height: '200px' }}>
|
||||
<CInput
|
||||
className="w-50 mb-2"
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
/>
|
||||
<CDataTable
|
||||
items={entities}
|
||||
fields={columns}
|
||||
hover
|
||||
tableFilterValue={filter}
|
||||
border
|
||||
scopedSlots={{
|
||||
name: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/configuration/${item.id}`}
|
||||
>
|
||||
{t('common.select')}
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{item.name}
|
||||
</CLink>
|
||||
</td>
|
||||
),
|
||||
created: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<FormattedDate date={item.created} />
|
||||
</td>
|
||||
),
|
||||
actions: (item) => (
|
||||
<td className="align-middle p-1">
|
||||
<CPopover content={t('entity.select_entity')}>
|
||||
<CButton
|
||||
disabled={!editing}
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
onClick={() => selectEntity(item)}
|
||||
>
|
||||
{t('common.select')}
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CCollapse>
|
||||
</CForm>
|
||||
);
|
||||
};
|
||||
|
||||
554
src/components/EditSimulationForm/index.js
Normal file
554
src/components/EditSimulationForm/index.js
Normal file
@@ -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 (
|
||||
<CForm>
|
||||
<CRow>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="name">
|
||||
{t('user.name')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="name"
|
||||
type="text"
|
||||
required
|
||||
value={fields.name.value}
|
||||
onChange={updateField}
|
||||
invalid={fields.name.error}
|
||||
disabled={disable || !editing}
|
||||
maxLength="50"
|
||||
/>
|
||||
<CFormText hidden={!fields.name.error} color={fields.name.error ? 'danger' : ''}>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="gateway">
|
||||
{t('simulation.gateway')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="gateway"
|
||||
type="text"
|
||||
required
|
||||
value={fields.gateway.value}
|
||||
onChange={updateField}
|
||||
invalid={fields.gateway.error}
|
||||
disabled={disable || !editing}
|
||||
maxLength="50"
|
||||
/>
|
||||
<CFormText hidden={!fields.gateway.error} color={fields.gateway.error ? 'danger' : ''}>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="certificate">
|
||||
{t('common.certificate')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
{editing ? (
|
||||
<div>
|
||||
<CInputFile
|
||||
className="mt-1"
|
||||
key={certKey}
|
||||
id="file-input"
|
||||
name="file-input"
|
||||
accept=".pem"
|
||||
onChange={(e) => handleCertFile(e.target.files[0])}
|
||||
/>
|
||||
<CFormText
|
||||
hidden={!fields.certificate.error}
|
||||
color={fields.certificate.error ? 'danger' : ''}
|
||||
>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</div>
|
||||
) : (
|
||||
<div className="pt-1 mt-1">{t('simulation.valid_cert')}</div>
|
||||
)}
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="key">
|
||||
{t('common.key')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
{editing ? (
|
||||
<div>
|
||||
<CInputFile
|
||||
className="mt-1"
|
||||
key={keyKey}
|
||||
id="file-input"
|
||||
name="file-input"
|
||||
accept=".pem"
|
||||
onChange={(e) => handleKeyFile(e.target.files[0])}
|
||||
/>
|
||||
<CFormText hidden={!fields.key.error} color={fields.key.error ? 'danger' : ''}>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</div>
|
||||
) : (
|
||||
<div className="pt-1 mt-1">{t('simulation.valid_key')}</div>
|
||||
)}
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="macPrefix">
|
||||
{t('simulation.mac_prefix')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="macPrefix"
|
||||
type="text"
|
||||
required
|
||||
value={fields.macPrefix.value}
|
||||
onChange={updateField}
|
||||
invalid={fields.macPrefix.error}
|
||||
disabled={disable || !editing}
|
||||
maxLength="50"
|
||||
/>
|
||||
<CFormText
|
||||
hidden={!fields.macPrefix.error}
|
||||
color={fields.macPrefix.error ? 'danger' : ''}
|
||||
>
|
||||
{t('simulation.prefix_length')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="devices">
|
||||
{t('common.devices')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="devices"
|
||||
type="number"
|
||||
required
|
||||
value={fields.devices.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.devices.value < fields.devices.min || fields.devices.value > fields.devices.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CFormText hidden={!fields.devices.error} color={fields.devices.error ? 'danger' : ''}>
|
||||
{t('common.required')}
|
||||
</CFormText>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="healthCheckInterval">
|
||||
{t('simulation.healtcheck_interval')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="healthCheckInterval"
|
||||
type="number"
|
||||
required
|
||||
value={fields.healthCheckInterval.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.healthCheckInterval.value < fields.healthCheckInterval.min ||
|
||||
fields.healthCheckInterval.value > fields.healthCheckInterval.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.healthCheckInterval.min,
|
||||
max: fields.healthCheckInterval.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="stateInterval">
|
||||
{t('simulation.state_interval')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="stateInterval"
|
||||
type="number"
|
||||
required
|
||||
value={fields.stateInterval.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.stateInterval.value < fields.stateInterval.min ||
|
||||
fields.stateInterval.value > fields.stateInterval.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.stateInterval.min,
|
||||
max: fields.stateInterval.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="clientInterval">
|
||||
{t('simulation.client_interval')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="clientInterval"
|
||||
type="number"
|
||||
required
|
||||
value={fields.clientInterval.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.clientInterval.value < fields.clientInterval.min ||
|
||||
fields.clientInterval.value > fields.clientInterval.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.clientInterval.min,
|
||||
max: fields.clientInterval.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="reconnectInterval">
|
||||
{t('simulation.reconnect_interval')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="reconnectInterval"
|
||||
type="number"
|
||||
required
|
||||
value={fields.reconnectInterval.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.reconnectInterval.value < fields.reconnectInterval.min ||
|
||||
fields.reconnectInterval.value > fields.reconnectInterval.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.reconnectInterval.min,
|
||||
max: fields.reconnectInterval.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="minAssociations">
|
||||
{t('simulation.min_associations')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="minAssociations"
|
||||
type="number"
|
||||
required
|
||||
value={fields.minAssociations.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.minAssociations.value < fields.minAssociations.min ||
|
||||
fields.minAssociations.value > fields.minAssociations.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.minAssociations.min,
|
||||
max: fields.minAssociations.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="maxAssociations">
|
||||
{t('simulation.max_associations')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="maxAssociations"
|
||||
type="number"
|
||||
required
|
||||
value={fields.maxAssociations.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.maxAssociations.value < fields.maxAssociations.min ||
|
||||
fields.maxAssociations.value > fields.maxAssociations.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.maxAssociations.min,
|
||||
max: fields.maxAssociations.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="minClients">
|
||||
{t('simulation.min_clients')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="minClients"
|
||||
type="number"
|
||||
required
|
||||
value={fields.minClients.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.minClients.value < fields.minClients.min ||
|
||||
fields.minClients.value > fields.minClients.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.minClients.min,
|
||||
max: fields.minClients.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="maxClients">
|
||||
{t('simulation.max_clients')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="maxClients"
|
||||
type="number"
|
||||
required
|
||||
value={fields.maxClients.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.maxClients.value < fields.maxClients.min ||
|
||||
fields.maxClients.value > fields.maxClients.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.maxClients.min,
|
||||
max: fields.maxClients.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="simulationLength">
|
||||
{t('simulation.length')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="simulationLength"
|
||||
type="number"
|
||||
required
|
||||
value={fields.simulationLength.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.simulationLength.value < fields.simulationLength.min ||
|
||||
fields.simulationLength.value > fields.simulationLength.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.simulationLength.min,
|
||||
max: fields.simulationLength.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="threads">
|
||||
{t('simulation.threads')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="threads"
|
||||
type="number"
|
||||
required
|
||||
value={fields.threads.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.threads.value < fields.threads.min || fields.threads.value > fields.threads.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.threads.min,
|
||||
max: fields.threads.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="keepAlive">
|
||||
{t('simulation.keep_alive')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="keepAlive"
|
||||
type="number"
|
||||
required
|
||||
value={fields.keepAlive.value}
|
||||
onChange={updateField}
|
||||
invalid={
|
||||
fields.keepAlive.value < fields.keepAlive.min ||
|
||||
fields.keepAlive.value > fields.keepAlive.max
|
||||
}
|
||||
disabled={disable || !editing}
|
||||
pattern="[0-9]*"
|
||||
style={{ width: '100px' }}
|
||||
/>
|
||||
<CInvalidFeedback>
|
||||
{t('common.min_max', {
|
||||
min: fields.keepAlive.min,
|
||||
max: fields.keepAlive.max,
|
||||
})}
|
||||
</CInvalidFeedback>
|
||||
</CCol>
|
||||
<CLabel className="mb-2" sm="2" col htmlFor="deviceType">
|
||||
{t('configuration.device_type')}
|
||||
<RequiredAsterisk />
|
||||
</CLabel>
|
||||
<CCol sm="4">
|
||||
<CSelect
|
||||
custom
|
||||
id="deviceType"
|
||||
type="text"
|
||||
required
|
||||
value={fields.deviceType.value}
|
||||
onChange={updateField}
|
||||
invalid={fields.deviceType.error}
|
||||
disabled={disable || !editing}
|
||||
maxLength="50"
|
||||
>
|
||||
<option value="cig_wf160d">cig_wf160d</option>
|
||||
<option value="cig_wf188">cig_wf188</option>
|
||||
<option value="cig_wf188n">cig_wf188n</option>
|
||||
<option value="cig_wf194c">cig_wf194c</option>
|
||||
<option value="cig_wf194c4">cig_wf194c4</option>
|
||||
<option value="edgecore_eap101">edgecore_eap101</option>
|
||||
<option value="edgecore_eap102">edgecore_eap102</option>
|
||||
<option value="edgecore_ecs4100-12ph">edgecore_ecs4100-12ph</option>
|
||||
<option value="edgecore_ecw5211">edgecore_ecw5211</option>
|
||||
<option value="edgecore_ecw5410">edgecore_ecw5410</option>
|
||||
<option value="edgecore_oap100">edgecore_oap100</option>
|
||||
<option value="edgecore_spw2ac1200">edgecore_spw2ac1200</option>
|
||||
<option value="edgecore_spw2ac1200-lan-poe">edgecore_spw2ac1200-lan-poe</option>
|
||||
<option value="edgecore_ssw2ac2600">edgecore_ssw2ac2600</option>
|
||||
<option value="hfcl_ion4.yml">hfcl_ion4.yml</option>
|
||||
<option value="indio_um-305ac">indio_um-305ac</option>
|
||||
<option value="linksys_e8450-ubi">linksys_e8450-ubi</option>
|
||||
<option value="linksys_ea6350">linksys_ea6350</option>
|
||||
<option value="linksys_ea6350-v4">linksys_ea6350-v4</option>
|
||||
<option value="linksys_ea8300">linksys_ea8300</option>
|
||||
<option value="mikrotik_nand">mikrotik_nand</option>
|
||||
<option value="tp-link_ec420-g1">tp-link_ec420-g1</option>
|
||||
<option value="tplink_cpe210_v3">tplink_cpe210_v3</option>
|
||||
<option value="tplink_cpe510_v3">tplink_cpe510_v3</option>
|
||||
<option value="tplink_eap225_outdoor_v1">tplink_eap225_outdoor_v1</option>
|
||||
<option value="tplink_ec420">tplink_ec420</option>
|
||||
<option value="tplink_ex227">tplink_ex227</option>
|
||||
<option value="tplink_ex228">tplink_ex228</option>
|
||||
<option value="tplink_ex447">tplink_ex447</option>
|
||||
<option value="wallys_dr40x9">wallys_dr40x9</option>
|
||||
</CSelect>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CForm>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
81
src/components/EntityTree/index.js
Normal file
81
src/components/EntityTree/index.js
Normal file
@@ -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 (
|
||||
<div style={{ height: '80vh', width: '100%' }}>
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onElementClick={onClick}
|
||||
deleteKeyCode={null}
|
||||
onLoad={onLoad}
|
||||
snapToGrid
|
||||
snapGrid={[10, 10]}
|
||||
>
|
||||
<div className="float-left">
|
||||
<div
|
||||
className="align-middle text-center mx-auto"
|
||||
style={{ backgroundColor: '#0F0A0A', color: 'white', width: '150px' }}
|
||||
>
|
||||
<h4 className="align-middle mb-0 font-weight-bold">Root</h4>
|
||||
</div>
|
||||
<div
|
||||
className="align-middle text-center mx-auto"
|
||||
style={{ backgroundColor: '#2292A4', color: 'white', width: '150px' }}
|
||||
>
|
||||
<h4 className="align-middle mb-0 font-weight-bold">Entity</h4>
|
||||
</div>
|
||||
<div
|
||||
className="align-middle text-center mx-auto"
|
||||
style={{ backgroundColor: '#F5EFED', width: '150px' }}
|
||||
>
|
||||
<h4 className="align-middle mb-0 font-weight-bold">Venue</h4>
|
||||
</div>
|
||||
</div>
|
||||
<MiniMap
|
||||
nodeColor={(n) => {
|
||||
if (n.style?.background) return n.style.background;
|
||||
|
||||
return '#fff';
|
||||
}}
|
||||
nodeBorderRadius={5}
|
||||
/>
|
||||
<Controls />
|
||||
<Background color="#aaa" gap={20} />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -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}
|
||||
>
|
||||
<CIcon content={cilCloudUpload} />
|
||||
</CButton>
|
||||
@@ -125,6 +134,7 @@ FileToStringButton.propTypes = {
|
||||
explanations: PropTypes.string.isRequired,
|
||||
acceptedFileTypes: PropTypes.string.isRequired,
|
||||
size: PropTypes.string,
|
||||
disabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
FileToStringButton.defaultProps = {
|
||||
|
||||
@@ -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 }) => (
|
||||
<CCardBody className="p-1">
|
||||
<CRow>
|
||||
<CCol sm="2">Created</CCol>
|
||||
<CCol sm="4">{prettyDate(fields.created.value)}</CCol>
|
||||
<CCol sm="2">Release</CCol>
|
||||
<CCol sm="2">{t('firmware.release')}</CCol>
|
||||
<CCol sm="4">{fields.release.value}</CCol>
|
||||
<CCol sm="2">{t('common.created')}</CCol>
|
||||
<CCol sm="4">{prettyDate(fields.created.value)}</CCol>
|
||||
</CRow>
|
||||
<CRow className="my-3">
|
||||
<CCol sm="2">Image</CCol>
|
||||
<CCol sm="4">{fields.image.value}</CCol>
|
||||
<CCol sm="2">Image Date</CCol>
|
||||
<CCol sm="2">{t('firmware.image_date')}</CCol>
|
||||
<CCol sm="4">{prettyDate(fields.imageDate.value)}</CCol>
|
||||
<CCol sm="2">{t('firmware.size')}</CCol>
|
||||
<CCol sm="4">{cleanBytesString(fields.size.value)}</CCol>
|
||||
</CRow>
|
||||
<CRow className="my-3">
|
||||
<CCol sm="2">Revision</CCol>
|
||||
<CCol sm="2">{t('firmware.image')}</CCol>
|
||||
<CCol sm="4">{fields.image.value}</CCol>
|
||||
<CCol sm="2">{t('firmware.revision')}</CCol>
|
||||
<CCol sm="4">{fields.revision.value}</CCol>
|
||||
<CCol sm="2">Size</CCol>
|
||||
<CCol sm="4">{cleanBytesString(fields.size.value)}</CCol>
|
||||
</CRow>
|
||||
<CRow className="my-3">
|
||||
<CCol sm="2">URI</CCol>
|
||||
<CCol sm="4">{fields.uri.value}</CCol>
|
||||
<CCol sm="2">Owner</CCol>
|
||||
<CCol sm="4">{fields.owner.value === '' ? t('common.unknown') : fields.owner.value}</CCol>
|
||||
</CRow>
|
||||
<CRow className="my-3">
|
||||
<CCol sm="2" className="mt-2">
|
||||
Description
|
||||
{t('user.description')}
|
||||
</CCol>
|
||||
<CCol sm="4">
|
||||
{editing ? (
|
||||
@@ -46,9 +41,6 @@ const FirmwareDetailsForm = ({ t, fields, updateFieldsWithId, addNote, editing }
|
||||
<p className="mt-2 mb-0">{fields.description.value}</p>
|
||||
)}
|
||||
</CCol>
|
||||
<CCol>
|
||||
<NotesTable t={t} notes={fields.notes.value} addNote={addNote} editable={editing} />
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardBody>
|
||||
);
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
<CCard className="m-0">
|
||||
<CCardHeader className="dark-header">
|
||||
<CCardHeader className="p-1">
|
||||
<div className="d-flex flex-row-reverse">
|
||||
<div className="px-3">
|
||||
<CSwitch
|
||||
@@ -113,7 +113,7 @@ const FirmwareList = ({
|
||||
),
|
||||
uri: (item) => (
|
||||
<td className="align-middle">
|
||||
<div style={{ width: 'calc(45vw)' }}>
|
||||
<div style={{ width: 'calc(50vw)' }}>
|
||||
<div className="text-truncate align-middle">
|
||||
<CopyToClipboardButton key={item.uri} t={t} size="sm" content={item.uri} />
|
||||
<CPopover content={item.uri}>
|
||||
@@ -125,14 +125,16 @@ const FirmwareList = ({
|
||||
),
|
||||
show_details: (item) => (
|
||||
<td className="text-center align-middle">
|
||||
<CButton
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
onClick={() => toggleEditModal(item.id)}
|
||||
>
|
||||
<CIcon content={cilSearch} />
|
||||
</CButton>
|
||||
<CPopover content={t('common.details')}>
|
||||
<CButton
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
onClick={() => toggleEditModal(item.id)}
|
||||
>
|
||||
<CIcon content={cilSearch} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CPopover } from '@coreui/react';
|
||||
import { formatDaysAgo, prettyDate } from 'utils/formatting';
|
||||
|
||||
const FormattedDate = ({ date }) => (
|
||||
<CPopover content={prettyDate(date)}>
|
||||
<CPopover content={prettyDate(date)} advancedOptions={{ animation: false }}>
|
||||
<span className="d-inline-block">{date === 0 ? '-' : formatDaysAgo(date)}</span>
|
||||
</CPopover>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { CButton, CPopover } from '@coreui/react';
|
||||
|
||||
const HideTextButton = ({ t, toggle, show, size }) => (
|
||||
<CPopover content={t('user.show_hide_password')}>
|
||||
<CButton onClick={toggle} size={size} className="pt-0">
|
||||
<CButton onClick={toggle} size={size} className="py-0">
|
||||
<CIcon name={show ? 'cil-envelope-open' : 'cil-envelope-closed'} />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
|
||||
@@ -203,7 +203,7 @@ const InventoryTable = ({
|
||||
<CIcon name="cil-spreadsheet" content={cilSpreadsheet} size="sm" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
<CPopover content="View Tag">
|
||||
<CPopover content={t('common.details')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
|
||||
@@ -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 LocationTable = ({
|
||||
deleteLocation={deleteLocation}
|
||||
hideTooltips={hideTooltips}
|
||||
/>
|
||||
<CPopover content={t('common.edit')}>
|
||||
<CPopover content={t('common.details')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
@@ -128,7 +128,7 @@ const LocationTable = ({
|
||||
onClick={() => toggleEditModal(item.id)}
|
||||
style={{ width: '33px', height: '30px' }}
|
||||
>
|
||||
<CIcon name="cil-pencil" content={cilPencil} size="sm" />
|
||||
<CIcon name="cil-magnifying-glass" content={cilMagnifyingGlass} size="sm" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</CButtonToolbar>
|
||||
|
||||
273
src/components/SimulationTable/index.js
Normal file
273
src/components/SimulationTable/index.js
Normal file
@@ -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 (
|
||||
<CPopover content={t('common.delete')}>
|
||||
<div>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
className="mx-1"
|
||||
data-tip
|
||||
data-for={tooltipId}
|
||||
data-event="click"
|
||||
style={{ width: '33px', height: '30px' }}
|
||||
>
|
||||
<CIcon name="cil-trash" content={cilTrash} size="sm" />
|
||||
</CButton>
|
||||
<ReactTooltip
|
||||
id={tooltipId}
|
||||
place="top"
|
||||
effect="solid"
|
||||
globalEventOff="click"
|
||||
clickable
|
||||
className={[styles.deleteTooltip, 'tooltipRight'].join(' ')}
|
||||
border
|
||||
borderColor="#321fdb"
|
||||
arrowColor="white"
|
||||
overridePosition={({ left, top }) => {
|
||||
const element = document.getElementById(tooltipId);
|
||||
const tooltipWidth = element ? element.offsetWidth : 0;
|
||||
const newLeft = left - tooltipWidth * 0.25;
|
||||
return { top, left: newLeft };
|
||||
}}
|
||||
>
|
||||
<CCardHeader color="primary" className={styles.tooltipHeader}>
|
||||
{t('simulation.delete_simulation', { name })}
|
||||
<CButtonClose
|
||||
style={{ color: 'white' }}
|
||||
onClick={(e) => {
|
||||
e.target.parentNode.parentNode.classList.remove('show');
|
||||
hideTooltips();
|
||||
}}
|
||||
/>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<CButton
|
||||
data-toggle="dropdown"
|
||||
variant="outline"
|
||||
color="danger"
|
||||
onClick={() => deleteSimulation(id)}
|
||||
block
|
||||
>
|
||||
{t('common.confirm')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardBody>
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
</CPopover>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CCard className="m-0 p-0">
|
||||
<CCardBody className="p-0">
|
||||
<CDataTable
|
||||
addTableClasses="ignore-overflow table-sm"
|
||||
items={simulations ?? []}
|
||||
fields={columns}
|
||||
hover
|
||||
border
|
||||
loading={loading}
|
||||
scopedSlots={{
|
||||
name: (item) => <td className="align-middle">{item.name}</td>,
|
||||
gateway: (item) => <td className="align-middle">{item.gateway}</td>,
|
||||
deviceType: (item) => <td className="align-middle">{item.deviceType}</td>,
|
||||
devices: (item) => <td className="align-middle">{item.devices}</td>,
|
||||
macPrefix: (item) => <td className="align-middle">{item.macPrefix}</td>,
|
||||
healthCheckInterval: (item) => (
|
||||
<td className="align-middle">{item.healthCheckInterval}</td>
|
||||
),
|
||||
stateInterval: (item) => <td className="align-middle">{item.stateInterval}</td>,
|
||||
minAssociations: (item) => <td className="align-middle">{item.minAssociations}</td>,
|
||||
maxAssociations: (item) => <td className="align-middle">{item.maxAssociations}</td>,
|
||||
minClients: (item) => <td className="align-middle">{item.minClients}</td>,
|
||||
maxClients: (item) => <td className="align-middle">{item.maxClients}</td>,
|
||||
simulationLength: (item) => <td className="align-middle">{item.simulationLength}</td>,
|
||||
actions: (item) => (
|
||||
<td className="text-center align-middle">
|
||||
<CButtonToolbar
|
||||
role="group"
|
||||
className="justify-content-center"
|
||||
style={{ width: '150px' }}
|
||||
>
|
||||
<CPopover content={t('simulation.run_simulation')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
className="mx-1"
|
||||
style={{ width: '33px', height: '30px' }}
|
||||
onClick={() => startSim(item.id)}
|
||||
>
|
||||
<CIcon name="cil-media-play" content={cilMediaPlay} size="sm" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
{deleteButton(item)}
|
||||
<CPopover content={t('configuration.details')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
className="mx-1"
|
||||
style={{ width: '33px', height: '30px' }}
|
||||
onClick={() => toggleEdit(item.id)}
|
||||
>
|
||||
<CIcon name="cil-search" content={cilSearch} size="sm" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</CButtonToolbar>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<div className="d-flex flex-row pl-3">
|
||||
<div className="pr-3">
|
||||
<ReactPaginate
|
||||
previousLabel="← Previous"
|
||||
nextLabel="Next →"
|
||||
pageCount={pageCount}
|
||||
onPageChange={updatePage}
|
||||
forcePage={Number(currentPage)}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName="pagination"
|
||||
pageClassName="page-item"
|
||||
pageLinkClassName="page-link"
|
||||
previousClassName="page-item"
|
||||
previousLinkClassName="page-link"
|
||||
nextClassName="page-item"
|
||||
nextLinkClassName="page-link"
|
||||
activeClassName="active"
|
||||
/>
|
||||
</div>
|
||||
<p className="pr-2 mt-1">{t('common.items_per_page')}</p>
|
||||
<div style={{ width: '100px' }} className="px-2">
|
||||
<CSelect
|
||||
custom
|
||||
defaultValue={simulationsPerPage}
|
||||
onChange={(e) => updateSimulationsPerPage(e.target.value)}
|
||||
disabled={loading}
|
||||
>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
</CSelect>
|
||||
</div>
|
||||
</div>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
||||
16
src/components/SimulationTable/index.module.scss
Normal file
16
src/components/SimulationTable/index.module.scss
Normal file
@@ -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;
|
||||
}
|
||||
@@ -169,6 +169,7 @@ const UserListTable = ({
|
||||
onClick={() => handleDeleteClick(item.Id)}
|
||||
color="primary"
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
>
|
||||
<CIcon content={cilTrash} size="sm" />
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -30,6 +30,8 @@ const Header = ({
|
||||
user,
|
||||
avatar,
|
||||
hideBreadcrumb,
|
||||
extraButton,
|
||||
hideSidebarButton,
|
||||
}) => {
|
||||
const [translatedRoutes, setTranslatedRoutes] = useState(routes);
|
||||
|
||||
@@ -49,14 +51,25 @@ const Header = ({
|
||||
|
||||
return (
|
||||
<CHeader withSubheader>
|
||||
<CToggler inHeader className="ml-md-3 d-lg-none" onClick={toggleSidebarMobile} />
|
||||
<CToggler inHeader className="ml-3 d-md-down-none" onClick={toggleSidebar} />
|
||||
{hideSidebarButton ? null : (
|
||||
<>
|
||||
<CToggler inHeader className="ml-md-3 d-lg-none" onClick={toggleSidebarMobile} />
|
||||
<CToggler inHeader className="ml-3 d-md-down-none" onClick={toggleSidebar} />
|
||||
</>
|
||||
)}
|
||||
<CHeaderBrand className="mx-auto d-lg-none" to="/">
|
||||
<img src={logo} alt="OpenWifi" />
|
||||
<img
|
||||
src={logo}
|
||||
alt="OpenWifi"
|
||||
className="c-sidebar-brand-full"
|
||||
style={{ height: '75px', width: '175px' }}
|
||||
/>
|
||||
</CHeaderBrand>
|
||||
|
||||
<CHeaderNav className="d-md-down-none mr-auto" />
|
||||
|
||||
<CHeaderNav>{extraButton}</CHeaderNav>
|
||||
|
||||
<CHeaderNav className="px-3">
|
||||
<LanguageSwitcher i18n={i18n} />
|
||||
</CHeaderNav>
|
||||
@@ -78,12 +91,14 @@ const Header = ({
|
||||
</CDropdown>
|
||||
</CHeaderNav>
|
||||
|
||||
<CSubheader hidden={hideBreadcrumb} className="px-3 justify-content-between">
|
||||
<CBreadcrumbRouter
|
||||
className="border-0 c-subheader-nav m-0 px-0 px-md-3"
|
||||
routes={translatedRoutes}
|
||||
/>
|
||||
</CSubheader>
|
||||
{hideBreadcrumb ? null : (
|
||||
<CSubheader hidden={hideBreadcrumb} className="px-3 justify-content-between">
|
||||
<CBreadcrumbRouter
|
||||
className="border-0 c-subheader-nav m-0 px-0 px-md-3"
|
||||
routes={translatedRoutes}
|
||||
/>
|
||||
</CSubheader>
|
||||
)}
|
||||
</CHeader>
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
}) => (
|
||||
<CSidebar show={showSidebar} onShowChange={(val) => setShowSidebar(val)}>
|
||||
<CSidebarBrand className="d-md-down-none" to={redirectTo}>
|
||||
<img
|
||||
className={[styles.sidebarImgFull, 'c-sidebar-brand-full'].join(' ')}
|
||||
style={{ height: logoHeight ?? undefined, width: logoWidth ?? undefined }}
|
||||
src={logo}
|
||||
alt="OpenWifi"
|
||||
/>
|
||||
<img
|
||||
className={[styles.sidebarImgMinimized, 'c-sidebar-brand-minimized'].join(' ')}
|
||||
style={{ height: logoHeight ?? undefined, width: logoWidth ?? undefined }}
|
||||
src={logo}
|
||||
alt="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);
|
||||
|
||||
Reference in New Issue
Block a user