mirror of
https://github.com/Telecominfraproject/wlan-cloud-ui-library.git
synced 2025-11-04 12:47:55 +00:00
Merge pull request #325 from Telecominfraproject/feature/NETEXP-2883-ap-details-redesign
NOJIRA: Move advanced settings to its own component
This commit is contained in:
@@ -0,0 +1,507 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Form, Select as AntdSelect } from 'antd';
|
||||
import { Input, Select } from 'components/WithRoles';
|
||||
import _ from 'lodash';
|
||||
import DisabledText from 'components/DisabledText';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
|
||||
import { sortRadioTypes } from 'utils/sortRadioTypes';
|
||||
import { USER_FRIENDLY_RATES, USER_FRIENDLY_BANDWIDTHS } from '../../constants';
|
||||
|
||||
import styles from '../../../../index.module.scss';
|
||||
|
||||
const { Option } = AntdSelect;
|
||||
|
||||
const Advanced = ({ extraFields, childProfiles, data, radioTypes, form, Item }) => {
|
||||
const { details: { advancedRadioMap = {}, radioMap = {} } = {} } = data;
|
||||
|
||||
const defaultOptions = (
|
||||
<Select className={styles.Field}>
|
||||
<Option value="enabled">Enabled</Option>
|
||||
<Option value="disabled">Disabled</Option>
|
||||
</Select>
|
||||
);
|
||||
|
||||
const defaultOptionsBoolean = (
|
||||
<Select className={styles.Field}>
|
||||
<Option value="true">Enabled</Option>
|
||||
<Option value="false">Disabled</Option>
|
||||
</Select>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.details) {
|
||||
const currentRadios = Object.keys(advancedRadioMap);
|
||||
const formData = {
|
||||
advancedRadioMap: {},
|
||||
radioMap: {},
|
||||
};
|
||||
|
||||
currentRadios.forEach(radio => {
|
||||
formData.advancedRadioMap[radio] = {
|
||||
radioAdminState: advancedRadioMap[radio]?.radioAdminState || 'disabled',
|
||||
deauthAttackDetection: advancedRadioMap[radio]?.deauthAttackDetection ? 'true' : 'false',
|
||||
uapsdState: advancedRadioMap[radio]?.uapsdState || 'disabled',
|
||||
managementRate: {
|
||||
value: advancedRadioMap[radio]?.managementRate?.value || 'rate1mbps',
|
||||
},
|
||||
multicastRate: {
|
||||
value: advancedRadioMap[radio]?.multicastRate?.value || 'rate6mbps',
|
||||
},
|
||||
bestApSettings: {
|
||||
value: {
|
||||
dropInSnrPercentage:
|
||||
advancedRadioMap[radio]?.bestApSettings?.value?.dropInSnrPercentage || 0,
|
||||
minLoadFactor: advancedRadioMap[radio]?.bestApSettings?.value?.minLoadFactor || 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
formData.radioMap[radio] = {
|
||||
rxCellSizeDb: {
|
||||
value: radioMap[radio]?.rxCellSizeDb?.value || 0,
|
||||
},
|
||||
probeResponseThresholdDb: {
|
||||
value: radioMap[radio]?.probeResponseThresholdDb?.value || 0,
|
||||
},
|
||||
clientDisconnectThresholdDb: {
|
||||
value: radioMap[radio]?.clientDisconnectThresholdDb?.value || 0,
|
||||
},
|
||||
eirpTxPower: {
|
||||
value: radioMap[radio]?.eirpTxPower?.value || 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
form.setFieldsValue({ ...formData });
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.details) {
|
||||
const currentRadios = Object.keys(advancedRadioMap);
|
||||
const formData = {
|
||||
radioMap: {},
|
||||
};
|
||||
currentRadios.forEach(radio => {
|
||||
const isEnabled =
|
||||
childProfiles.rf?.[0]?.details?.rfConfigMap?.[radio]?.autoChannelSelection;
|
||||
formData.radioMap[radio] = {
|
||||
[isEnabled ? 'channelNumber' : 'manualChannelNumber']: isEnabled
|
||||
? radioMap[radio]?.channelNumber
|
||||
: radioMap[radio]?.manualChannelNumber,
|
||||
[isEnabled ? 'backupChannelNumber' : 'manualBackupChannelNumber']: isEnabled
|
||||
? radioMap[radio]?.backupChannelNumber
|
||||
: radioMap[radio]?.manualBackupChannelNumber,
|
||||
};
|
||||
});
|
||||
|
||||
form.setFieldsValue({ ...formData });
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const renderItem = (label, obj = {}, dataIndex, renderInput, options = {}) => {
|
||||
if (extraFields.some(field => field.label === label)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Item
|
||||
label={label}
|
||||
colon={dataIndex !== 'radioType'}
|
||||
key={label}
|
||||
hidden={options.hidden ?? false}
|
||||
>
|
||||
<div className={styles.InlineDiv}>
|
||||
{sortRadioTypes(Object.keys(obj)).map(i =>
|
||||
renderInput ? (
|
||||
renderInput(dataIndex, i, label, options)
|
||||
) : (
|
||||
<span key={i} className={styles.spanStyle}>
|
||||
{dataIndex === 'radioType'
|
||||
? radioTypes?.[obj[i]?.[dataIndex]]
|
||||
: obj[i]?.[dataIndex]}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
const renderConditionalItem = (label, obj = {}, dataIndex, dependency) => (
|
||||
<Item label={label} key={label}>
|
||||
<div className={styles.InlineDiv}>
|
||||
{sortRadioTypes(Object.keys(obj)).map(key => {
|
||||
const isEnabled = childProfiles.rf?.[0]?.details?.rfConfigMap?.[key]?.[dependency];
|
||||
|
||||
if (isEnabled) {
|
||||
return (
|
||||
<DisabledText
|
||||
key={key}
|
||||
value={
|
||||
USER_FRIENDLY_RATES[obj[key]?.[dataIndex]?.value] ||
|
||||
obj[key]?.[dataIndex]?.value ||
|
||||
'N/A'
|
||||
}
|
||||
title={`The ${radioTypes[key]} radio has "${_.startCase(
|
||||
dependency
|
||||
)}" enabled in the RF Profile`}
|
||||
text="Auto"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<DisabledText
|
||||
key={key}
|
||||
value={
|
||||
USER_FRIENDLY_RATES[
|
||||
childProfiles.rf?.[0]?.details?.rfConfigMap?.[key]?.[dataIndex]
|
||||
] || childProfiles.rf?.[0]?.details?.rfConfigMap?.[key]?.[dataIndex || 'N/A']
|
||||
}
|
||||
title={`The ${radioTypes[key]} radio has "${_.startCase(
|
||||
dependency
|
||||
)}" disabled in the RF Profile`}
|
||||
text="Profile"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Item>
|
||||
);
|
||||
|
||||
const renderInputItem = (dataIndex, key, label, options = {}) => (
|
||||
<Item
|
||||
key={options.mapName + key + dataIndex}
|
||||
name={[options.mapName, key, ...dataIndex]}
|
||||
rules={[
|
||||
{ required: true, message: options.error },
|
||||
({ getFieldValue }) => ({
|
||||
validator(_rule, value) {
|
||||
if (
|
||||
!value ||
|
||||
(getFieldValue([options.mapName, key, ...dataIndex]) <= options.max &&
|
||||
getFieldValue([options.mapName, key, ...dataIndex]) >= options.min)
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(options.error));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
className={styles.Field}
|
||||
placeholder={`Enter ${label} for ${radioTypes[key]}`}
|
||||
type="number"
|
||||
min={options.min}
|
||||
max={options.max}
|
||||
addonAfter={options?.addOnText ? options?.addOnText : ''}
|
||||
/>
|
||||
</Item>
|
||||
);
|
||||
|
||||
const renderOptionItem = (dataIndex, key, label, options = {}) => {
|
||||
return (
|
||||
<Item
|
||||
key={key + dataIndex}
|
||||
name={[options.mapName, key, ...dataIndex]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: `Enter ${label} for ${radioTypes[key]}`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{typeof options.dropdown === 'function' ? options.dropdown(key) : options.dropdown}
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
const renderChannelItem = label => {
|
||||
return (
|
||||
<Item label={label} colon={false}>
|
||||
<div className={styles.InlineDiv}>
|
||||
{sortRadioTypes(Object.keys(radioMap)).map(key => {
|
||||
const isEnabled = childProfiles.rf?.[0]?.details?.rfConfigMap[key].autoChannelSelection;
|
||||
|
||||
let channel;
|
||||
if (label === 'Active Channel') {
|
||||
channel = isEnabled
|
||||
? {
|
||||
dataIndex: 'channelNumber',
|
||||
addOnText: (
|
||||
<Tooltip
|
||||
text="Auto"
|
||||
title={`The ${radioTypes[key]} radio has "Auto Channel Selection" enabled in the RF Profile`}
|
||||
/>
|
||||
),
|
||||
}
|
||||
: {
|
||||
dataIndex: 'manualChannelNumber',
|
||||
addOnText: 'Manual',
|
||||
dependencies: ['radioMap', key, 'manualBackupChannelNumber'],
|
||||
};
|
||||
}
|
||||
if (label === 'Backup Channel') {
|
||||
channel = isEnabled
|
||||
? {
|
||||
dataIndex: 'backupChannelNumber',
|
||||
addOnText: (
|
||||
<Tooltip
|
||||
text="Auto"
|
||||
title={`The ${radioTypes[key]} radio has "Auto Channel Selection" enabled in the RF Profile`}
|
||||
/>
|
||||
),
|
||||
}
|
||||
: {
|
||||
dataIndex: 'manualBackupChannelNumber',
|
||||
addOnText: 'Manual',
|
||||
dependencies: ['radioMap', key, 'manualChannelNumber'],
|
||||
};
|
||||
}
|
||||
|
||||
const powerLevels = data?.details?.radioMap?.[key]?.allowedChannelsPowerLevels ?? [];
|
||||
|
||||
const allowedChannels = powerLevels
|
||||
.filter(item => {
|
||||
if (channel.dataIndex === 'manualBackupChannelNumber') {
|
||||
return !item.dfs;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
.map(item => item?.channelNumber)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
return (
|
||||
<Item
|
||||
key={`radioMap${key}${channel.dataIndex}`}
|
||||
name={['radioMap', key, channel.dataIndex]}
|
||||
dependencies={[channel.dependencies]}
|
||||
rules={[
|
||||
{ required: true, message: `Allowed Channels: ${allowedChannels.join(', ')}` },
|
||||
({ getFieldValue }) => ({
|
||||
validator(_rule, value) {
|
||||
if (!isEnabled) {
|
||||
if (
|
||||
parseInt(getFieldValue(['radioMap', key, 'manualChannelNumber']), 10) ===
|
||||
parseInt(
|
||||
getFieldValue(['radioMap', key, 'manualBackupChannelNumber']),
|
||||
10
|
||||
)
|
||||
) {
|
||||
return Promise.reject(
|
||||
new Error('Active and backup channels must be different')
|
||||
);
|
||||
}
|
||||
const channelNumber = parseInt(
|
||||
getFieldValue(['radioMap', key, channel.dataIndex]),
|
||||
10
|
||||
);
|
||||
if (!value || allowedChannels.includes(channelNumber)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error(`Allowed Channels: ${allowedChannels.join(', ')}`)
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
className={styles.Field}
|
||||
placeholder={`Enter ${label} for ${radioTypes[key]}`}
|
||||
type="number"
|
||||
min={Math.min(...allowedChannels)}
|
||||
max={Math.max(...allowedChannels)}
|
||||
addonAfter={channel.addOnText}
|
||||
disabled={isEnabled}
|
||||
/>
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
const renderBandwidthLabels = () => (
|
||||
<Item label="Channel Bandwidth">
|
||||
<div className={styles.InlineDiv}>
|
||||
{sortRadioTypes(Object.keys(radioMap)).map(radio => (
|
||||
<DisabledText
|
||||
key={radio}
|
||||
value={
|
||||
USER_FRIENDLY_BANDWIDTHS[
|
||||
childProfiles?.rf?.[0]?.details?.rfConfigMap?.[radio].channelBandwidth
|
||||
] ?? 'N/A'
|
||||
}
|
||||
showTooltip={false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Item>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderItem(' ', data?.details?.radioMap, 'radioType')}
|
||||
<p>Radio Specific Parameters:</p>
|
||||
{renderBandwidthLabels()}
|
||||
{renderItem('Enable Radio', advancedRadioMap, ['radioAdminState'], renderOptionItem, {
|
||||
dropdown: defaultOptions,
|
||||
mapName: 'advancedRadioMap',
|
||||
})}
|
||||
{renderItem(
|
||||
'Deauth Attack Detection',
|
||||
advancedRadioMap,
|
||||
['deauthAttackDetection'],
|
||||
renderOptionItem,
|
||||
{
|
||||
mapName: 'advancedRadioMap',
|
||||
dropdown: defaultOptionsBoolean,
|
||||
}
|
||||
)}
|
||||
{renderItem('UAPSD', advancedRadioMap, ['uapsdState'], renderOptionItem, {
|
||||
mapName: 'advancedRadioMap',
|
||||
dropdown: defaultOptions,
|
||||
})}
|
||||
{renderChannelItem('Active Channel')}
|
||||
{renderChannelItem('Backup Channel')}
|
||||
{extraFields.map(field =>
|
||||
renderConditionalItem(field.label, field.obj, field.dataIndex, field.dependencies)
|
||||
)}
|
||||
{renderItem(
|
||||
'Management Rate (Mbps)',
|
||||
advancedRadioMap,
|
||||
['managementRate', 'value'],
|
||||
renderOptionItem,
|
||||
{
|
||||
mapName: 'advancedRadioMap',
|
||||
dropdown: key => (
|
||||
<Select className={styles.Field}>
|
||||
{key === 'is2dot4GHz' && (
|
||||
<>
|
||||
<Option value="rate1mbps">1</Option>
|
||||
<Option value="rate2mbps">2</Option>
|
||||
<Option value="rate5dot5mbps">5.5</Option>
|
||||
</>
|
||||
)}
|
||||
<Option value="rate6mbps">6</Option>
|
||||
<Option value="rate9mbps">9</Option>
|
||||
{key === 'is2dot4GHz' && <Option value="rate11mbps">11</Option>}
|
||||
<Option value="rate12mbps">12</Option>
|
||||
<Option value="rate18mbps">18</Option>
|
||||
<Option value="rate24mbps">24</Option>
|
||||
</Select>
|
||||
),
|
||||
}
|
||||
)}
|
||||
{renderItem(
|
||||
'Multicast Rate (Mbps)',
|
||||
advancedRadioMap,
|
||||
['multicastRate', 'value'],
|
||||
renderOptionItem,
|
||||
{
|
||||
mapName: 'advancedRadioMap',
|
||||
dropdown: (
|
||||
<Select className={styles.Field}>
|
||||
<Option value="rate6mbps">6</Option>
|
||||
<Option value="rate9mbps">9</Option>
|
||||
<Option value="rate12mbps">12</Option>
|
||||
<Option value="rate18mbps">18</Option>
|
||||
<Option value="rate24mbps">24</Option>
|
||||
<Option value="rate36mbps">36</Option>
|
||||
<Option value="rate48mbps">48</Option>
|
||||
<Option value="rate54mbps">54</Option>
|
||||
</Select>
|
||||
),
|
||||
}
|
||||
)}
|
||||
{renderItem(
|
||||
'Probe Response Threshold',
|
||||
radioMap,
|
||||
['probeResponseThresholdDb', 'value'],
|
||||
renderInputItem,
|
||||
{
|
||||
min: -100,
|
||||
max: -40,
|
||||
error: '-100 - -40 dBm',
|
||||
addOnText: 'dBm',
|
||||
mapName: 'radioMap',
|
||||
}
|
||||
)}
|
||||
{renderItem(
|
||||
'Client Disconnect Threshold',
|
||||
radioMap,
|
||||
['clientDisconnectThresholdDb', 'value'],
|
||||
renderInputItem,
|
||||
{
|
||||
min: -100,
|
||||
max: 0,
|
||||
error: '-100 - 0 dBm',
|
||||
addOnText: 'dBm',
|
||||
mapName: 'radioMap',
|
||||
}
|
||||
)}
|
||||
{renderItem('EIRP Tx Power', radioMap, ['eirpTxPower', 'value'], renderInputItem, {
|
||||
min: 1,
|
||||
max: 32,
|
||||
error: '1 - 32 dBm',
|
||||
addOnText: 'dBm',
|
||||
mapName: 'radioMap',
|
||||
})}
|
||||
|
||||
{renderItem(
|
||||
'SNR',
|
||||
advancedRadioMap,
|
||||
['bestApSettings', 'value', 'dropInSnrPercentage'],
|
||||
renderInputItem,
|
||||
{
|
||||
min: 0,
|
||||
max: 100,
|
||||
error: '0 - 100%',
|
||||
addOnText: '% Drop',
|
||||
mapName: 'advancedRadioMap',
|
||||
hidden: true,
|
||||
}
|
||||
)}
|
||||
{renderItem(
|
||||
'Min Load',
|
||||
advancedRadioMap,
|
||||
['bestApSettings', 'value', 'minLoadFactor'],
|
||||
renderInputItem,
|
||||
{
|
||||
min: 0,
|
||||
max: 100,
|
||||
error: '0 - 100%',
|
||||
addOnText: '%',
|
||||
mapName: 'advancedRadioMap',
|
||||
hidden: true,
|
||||
}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Advanced.propTypes = {
|
||||
extraFields: PropTypes.instanceOf(Array),
|
||||
data: PropTypes.instanceOf(Object),
|
||||
childProfiles: PropTypes.instanceOf(Object),
|
||||
radioTypes: PropTypes.instanceOf(Object),
|
||||
form: PropTypes.instanceOf(Object),
|
||||
Item: PropTypes.node,
|
||||
};
|
||||
|
||||
Advanced.defaultProps = {
|
||||
extraFields: [],
|
||||
data: {},
|
||||
childProfiles: {},
|
||||
radioTypes: {},
|
||||
form: null,
|
||||
Item: Form.Item,
|
||||
};
|
||||
|
||||
export default Advanced;
|
||||
@@ -11,16 +11,14 @@ import {
|
||||
Empty,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Card } from 'components/Skeleton';
|
||||
import { Input, Select, RoleProtectedBtn } from 'components/WithRoles';
|
||||
import _ from 'lodash';
|
||||
import ThemeContext from 'contexts/ThemeContext';
|
||||
import DisabledText from 'components/DisabledText';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
|
||||
import { sortRadioTypes } from 'utils/sortRadioTypes';
|
||||
import { pageLayout } from 'utils/form';
|
||||
import { USER_FRIENDLY_RATES, USER_FRIENDLY_BANDWIDTHS } from './constants';
|
||||
|
||||
import Advanced from './components/Advanced';
|
||||
|
||||
import styles from '../../index.module.scss';
|
||||
|
||||
@@ -112,80 +110,8 @@ const General = ({
|
||||
longitude,
|
||||
serial,
|
||||
lastModifiedTimestamp,
|
||||
details: { advancedRadioMap = {}, radioMap = {} } = {},
|
||||
} = data;
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.details) {
|
||||
const currentRadios = Object.keys(advancedRadioMap);
|
||||
const formData = {
|
||||
advancedRadioMap: {},
|
||||
radioMap: {},
|
||||
};
|
||||
|
||||
currentRadios.forEach(radio => {
|
||||
formData.advancedRadioMap[radio] = {
|
||||
radioAdminState: advancedRadioMap[radio]?.radioAdminState || 'disabled',
|
||||
deauthAttackDetection: advancedRadioMap[radio]?.deauthAttackDetection ? 'true' : 'false',
|
||||
uapsdState: advancedRadioMap[radio]?.uapsdState || 'disabled',
|
||||
managementRate: {
|
||||
value: advancedRadioMap[radio]?.managementRate?.value || 'rate1mbps',
|
||||
},
|
||||
multicastRate: {
|
||||
value: advancedRadioMap[radio]?.multicastRate?.value || 'rate6mbps',
|
||||
},
|
||||
bestApSettings: {
|
||||
value: {
|
||||
dropInSnrPercentage:
|
||||
advancedRadioMap[radio]?.bestApSettings?.value?.dropInSnrPercentage || 0,
|
||||
minLoadFactor: advancedRadioMap[radio]?.bestApSettings?.value?.minLoadFactor || 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
formData.radioMap[radio] = {
|
||||
rxCellSizeDb: {
|
||||
value: radioMap[radio]?.rxCellSizeDb?.value || 0,
|
||||
},
|
||||
probeResponseThresholdDb: {
|
||||
value: radioMap[radio]?.probeResponseThresholdDb?.value || 0,
|
||||
},
|
||||
clientDisconnectThresholdDb: {
|
||||
value: radioMap[radio]?.clientDisconnectThresholdDb?.value || 0,
|
||||
},
|
||||
eirpTxPower: {
|
||||
value: radioMap[radio]?.eirpTxPower?.value || 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
form.setFieldsValue({ ...formData });
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.details) {
|
||||
const currentRadios = Object.keys(advancedRadioMap);
|
||||
const formData = {
|
||||
radioMap: {},
|
||||
};
|
||||
currentRadios.forEach(radio => {
|
||||
const isEnabled =
|
||||
childProfiles.rf?.[0]?.details?.rfConfigMap?.[radio]?.autoChannelSelection;
|
||||
formData.radioMap[radio] = {
|
||||
[isEnabled ? 'channelNumber' : 'manualChannelNumber']: isEnabled
|
||||
? radioMap[radio]?.channelNumber
|
||||
: radioMap[radio]?.manualChannelNumber,
|
||||
[isEnabled ? 'backupChannelNumber' : 'manualBackupChannelNumber']: isEnabled
|
||||
? radioMap[radio]?.backupChannelNumber
|
||||
: radioMap[radio]?.manualBackupChannelNumber,
|
||||
};
|
||||
});
|
||||
|
||||
form.setFieldsValue({ ...formData });
|
||||
}
|
||||
}, [selectedProfile]);
|
||||
|
||||
const handleOnSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
@@ -235,264 +161,6 @@ const General = ({
|
||||
});
|
||||
};
|
||||
|
||||
const defaultOptions = (
|
||||
<Select className={styles.Field}>
|
||||
<Option value="enabled">Enabled</Option>
|
||||
<Option value="disabled">Disabled</Option>
|
||||
</Select>
|
||||
);
|
||||
|
||||
const defaultOptionsBoolean = (
|
||||
<Select className={styles.Field}>
|
||||
<Option value="true">Enabled</Option>
|
||||
<Option value="false">Disabled</Option>
|
||||
</Select>
|
||||
);
|
||||
|
||||
const renderItem = (label, obj = {}, dataIndex, renderInput, options = {}) => {
|
||||
if (extraFields.some(field => field.label === label)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Item
|
||||
label={label}
|
||||
colon={dataIndex !== 'radioType'}
|
||||
key={label}
|
||||
hidden={options.hidden ?? false}
|
||||
>
|
||||
<div className={styles.InlineDiv}>
|
||||
{sortRadioTypes(Object.keys(obj)).map(i =>
|
||||
renderInput ? (
|
||||
renderInput(dataIndex, i, label, options)
|
||||
) : (
|
||||
<span key={i} className={styles.spanStyle}>
|
||||
{dataIndex === 'radioType'
|
||||
? radioTypes?.[obj[i]?.[dataIndex]]
|
||||
: obj[i]?.[dataIndex]}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
const renderConditionalItem = (label, obj = {}, dataIndex, dependency) => (
|
||||
<Item label={label} key={label}>
|
||||
<div className={styles.InlineDiv}>
|
||||
{sortRadioTypes(Object.keys(obj)).map(key => {
|
||||
const isEnabled = childProfiles.rf?.[0]?.details?.rfConfigMap?.[key]?.[dependency];
|
||||
|
||||
if (isEnabled) {
|
||||
return (
|
||||
<DisabledText
|
||||
key={key}
|
||||
value={
|
||||
USER_FRIENDLY_RATES[obj[key]?.[dataIndex]?.value] ||
|
||||
obj[key]?.[dataIndex]?.value ||
|
||||
'N/A'
|
||||
}
|
||||
title={`The ${radioTypes[key]} radio has "${_.startCase(
|
||||
dependency
|
||||
)}" enabled in the RF Profile`}
|
||||
text="Auto"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<DisabledText
|
||||
key={key}
|
||||
value={
|
||||
USER_FRIENDLY_RATES[
|
||||
childProfiles.rf?.[0]?.details?.rfConfigMap?.[key]?.[dataIndex]
|
||||
] || childProfiles.rf?.[0]?.details?.rfConfigMap?.[key]?.[dataIndex || 'N/A']
|
||||
}
|
||||
title={`The ${radioTypes[key]} radio has "${_.startCase(
|
||||
dependency
|
||||
)}" disabled in the RF Profile`}
|
||||
text="Profile"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Item>
|
||||
);
|
||||
|
||||
const renderInputItem = (dataIndex, key, label, options = {}) => (
|
||||
<Item
|
||||
key={options.mapName + key + dataIndex}
|
||||
name={[options.mapName, key, ...dataIndex]}
|
||||
rules={[
|
||||
{ required: true, message: options.error },
|
||||
({ getFieldValue }) => ({
|
||||
validator(_rule, value) {
|
||||
if (
|
||||
!value ||
|
||||
(getFieldValue([options.mapName, key, ...dataIndex]) <= options.max &&
|
||||
getFieldValue([options.mapName, key, ...dataIndex]) >= options.min)
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(options.error));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
className={styles.Field}
|
||||
placeholder={`Enter ${label} for ${radioTypes[key]}`}
|
||||
type="number"
|
||||
min={options.min}
|
||||
max={options.max}
|
||||
addonAfter={options?.addOnText ? options?.addOnText : ''}
|
||||
/>
|
||||
</Item>
|
||||
);
|
||||
|
||||
const renderOptionItem = (dataIndex, key, label, options = {}) => {
|
||||
return (
|
||||
<Item
|
||||
key={key + dataIndex}
|
||||
name={[options.mapName, key, ...dataIndex]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: `Enter ${label} for ${radioTypes[key]}`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{typeof options.dropdown === 'function' ? options.dropdown(key) : options.dropdown}
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
const renderChannelItem = label => {
|
||||
return (
|
||||
<Item label={label} colon={false}>
|
||||
<div className={styles.InlineDiv}>
|
||||
{sortRadioTypes(Object.keys(radioMap)).map(key => {
|
||||
const isEnabled = childProfiles.rf?.[0]?.details?.rfConfigMap[key].autoChannelSelection;
|
||||
|
||||
let channel;
|
||||
if (label === 'Active Channel') {
|
||||
channel = isEnabled
|
||||
? {
|
||||
dataIndex: 'channelNumber',
|
||||
addOnText: (
|
||||
<Tooltip
|
||||
text="Auto"
|
||||
title={`The ${radioTypes[key]} radio has "Auto Channel Selection" enabled in the RF Profile`}
|
||||
/>
|
||||
),
|
||||
}
|
||||
: {
|
||||
dataIndex: 'manualChannelNumber',
|
||||
addOnText: 'Manual',
|
||||
dependencies: ['radioMap', key, 'manualBackupChannelNumber'],
|
||||
};
|
||||
}
|
||||
if (label === 'Backup Channel') {
|
||||
channel = isEnabled
|
||||
? {
|
||||
dataIndex: 'backupChannelNumber',
|
||||
addOnText: (
|
||||
<Tooltip
|
||||
text="Auto"
|
||||
title={`The ${radioTypes[key]} radio has "Auto Channel Selection" enabled in the RF Profile`}
|
||||
/>
|
||||
),
|
||||
}
|
||||
: {
|
||||
dataIndex: 'manualBackupChannelNumber',
|
||||
addOnText: 'Manual',
|
||||
dependencies: ['radioMap', key, 'manualChannelNumber'],
|
||||
};
|
||||
}
|
||||
|
||||
const powerLevels = data?.details?.radioMap?.[key]?.allowedChannelsPowerLevels ?? [];
|
||||
|
||||
const allowedChannels = powerLevels
|
||||
.filter(item => {
|
||||
if (channel.dataIndex === 'manualBackupChannelNumber') {
|
||||
return !item.dfs;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
.map(item => item?.channelNumber)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
return (
|
||||
<Item
|
||||
key={`radioMap${key}${channel.dataIndex}`}
|
||||
name={['radioMap', key, channel.dataIndex]}
|
||||
dependencies={[channel.dependencies]}
|
||||
rules={[
|
||||
{ required: true, message: `Allowed Channels: ${allowedChannels.join(', ')}` },
|
||||
({ getFieldValue }) => ({
|
||||
validator(_rule, value) {
|
||||
if (!isEnabled) {
|
||||
if (
|
||||
parseInt(getFieldValue(['radioMap', key, 'manualChannelNumber']), 10) ===
|
||||
parseInt(
|
||||
getFieldValue(['radioMap', key, 'manualBackupChannelNumber']),
|
||||
10
|
||||
)
|
||||
) {
|
||||
return Promise.reject(
|
||||
new Error('Active and backup channels must be different')
|
||||
);
|
||||
}
|
||||
const channelNumber = parseInt(
|
||||
getFieldValue(['radioMap', key, channel.dataIndex]),
|
||||
10
|
||||
);
|
||||
if (!value || allowedChannels.includes(channelNumber)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error(`Allowed Channels: ${allowedChannels.join(', ')}`)
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
className={styles.Field}
|
||||
placeholder={`Enter ${label} for ${radioTypes[key]}`}
|
||||
type="number"
|
||||
min={Math.min(...allowedChannels)}
|
||||
max={Math.max(...allowedChannels)}
|
||||
addonAfter={channel.addOnText}
|
||||
disabled={isEnabled}
|
||||
/>
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
const renderBandwidthLabels = () => (
|
||||
<Item label="Channel Bandwidth">
|
||||
<div className={styles.InlineDiv}>
|
||||
{sortRadioTypes(Object.keys(radioMap)).map(radio => (
|
||||
<DisabledText
|
||||
key={radio}
|
||||
value={
|
||||
USER_FRIENDLY_BANDWIDTHS[
|
||||
childProfiles?.rf?.[0]?.details?.rfConfigMap?.[radio].channelBandwidth
|
||||
] ?? 'N/A'
|
||||
}
|
||||
showTooltip={false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Item>
|
||||
);
|
||||
|
||||
if (errorProfiles) {
|
||||
return (
|
||||
<Alert
|
||||
@@ -605,141 +273,13 @@ const General = ({
|
||||
{extraGeneralCards}
|
||||
<Collapse expandIconPosition="right">
|
||||
<Panel header="Advanced Settings" name="settings">
|
||||
{renderItem(' ', data?.details?.radioMap, 'radioType')}
|
||||
<p>Radio Specific Parameters:</p>
|
||||
{renderBandwidthLabels()}
|
||||
{renderItem('Enable Radio', advancedRadioMap, ['radioAdminState'], renderOptionItem, {
|
||||
dropdown: defaultOptions,
|
||||
mapName: 'advancedRadioMap',
|
||||
})}
|
||||
{renderItem(
|
||||
'Deauth Attack Detection',
|
||||
advancedRadioMap,
|
||||
['deauthAttackDetection'],
|
||||
renderOptionItem,
|
||||
{
|
||||
mapName: 'advancedRadioMap',
|
||||
dropdown: defaultOptionsBoolean,
|
||||
}
|
||||
)}
|
||||
{renderItem('UAPSD', advancedRadioMap, ['uapsdState'], renderOptionItem, {
|
||||
mapName: 'advancedRadioMap',
|
||||
dropdown: defaultOptions,
|
||||
})}
|
||||
{renderChannelItem('Active Channel')}
|
||||
{renderChannelItem('Backup Channel')}
|
||||
{extraFields.map(field =>
|
||||
renderConditionalItem(field.label, field.obj, field.dataIndex, field.dependencies)
|
||||
)}
|
||||
{renderItem(
|
||||
'Management Rate (Mbps)',
|
||||
advancedRadioMap,
|
||||
['managementRate', 'value'],
|
||||
renderOptionItem,
|
||||
{
|
||||
mapName: 'advancedRadioMap',
|
||||
dropdown: key => (
|
||||
<Select className={styles.Field}>
|
||||
{key === 'is2dot4GHz' && (
|
||||
<>
|
||||
<Option value="rate1mbps">1</Option>
|
||||
<Option value="rate2mbps">2</Option>
|
||||
<Option value="rate5dot5mbps">5.5</Option>
|
||||
</>
|
||||
)}
|
||||
<Option value="rate6mbps">6</Option>
|
||||
<Option value="rate9mbps">9</Option>
|
||||
{key === 'is2dot4GHz' && <Option value="rate11mbps">11</Option>}
|
||||
<Option value="rate12mbps">12</Option>
|
||||
<Option value="rate18mbps">18</Option>
|
||||
<Option value="rate24mbps">24</Option>
|
||||
</Select>
|
||||
),
|
||||
}
|
||||
)}
|
||||
{renderItem(
|
||||
'Multicast Rate (Mbps)',
|
||||
advancedRadioMap,
|
||||
['multicastRate', 'value'],
|
||||
renderOptionItem,
|
||||
{
|
||||
mapName: 'advancedRadioMap',
|
||||
dropdown: (
|
||||
<Select className={styles.Field}>
|
||||
<Option value="rate6mbps">6</Option>
|
||||
<Option value="rate9mbps">9</Option>
|
||||
<Option value="rate12mbps">12</Option>
|
||||
<Option value="rate18mbps">18</Option>
|
||||
<Option value="rate24mbps">24</Option>
|
||||
<Option value="rate36mbps">36</Option>
|
||||
<Option value="rate48mbps">48</Option>
|
||||
<Option value="rate54mbps">54</Option>
|
||||
</Select>
|
||||
),
|
||||
}
|
||||
)}
|
||||
{renderItem(
|
||||
'Probe Response Threshold',
|
||||
radioMap,
|
||||
['probeResponseThresholdDb', 'value'],
|
||||
renderInputItem,
|
||||
{
|
||||
min: -100,
|
||||
max: -40,
|
||||
error: '-100 - -40 dBm',
|
||||
addOnText: 'dBm',
|
||||
mapName: 'radioMap',
|
||||
}
|
||||
)}
|
||||
{renderItem(
|
||||
'Client Disconnect Threshold',
|
||||
radioMap,
|
||||
['clientDisconnectThresholdDb', 'value'],
|
||||
renderInputItem,
|
||||
{
|
||||
min: -100,
|
||||
max: 0,
|
||||
error: '-100 - 0 dBm',
|
||||
addOnText: 'dBm',
|
||||
mapName: 'radioMap',
|
||||
}
|
||||
)}
|
||||
{renderItem('EIRP Tx Power', radioMap, ['eirpTxPower', 'value'], renderInputItem, {
|
||||
min: 1,
|
||||
max: 32,
|
||||
error: '1 - 32 dBm',
|
||||
addOnText: 'dBm',
|
||||
mapName: 'radioMap',
|
||||
})}
|
||||
|
||||
{renderItem(
|
||||
'SNR',
|
||||
advancedRadioMap,
|
||||
['bestApSettings', 'value', 'dropInSnrPercentage'],
|
||||
renderInputItem,
|
||||
{
|
||||
min: 0,
|
||||
max: 100,
|
||||
error: '0 - 100%',
|
||||
addOnText: '% Drop',
|
||||
mapName: 'advancedRadioMap',
|
||||
hidden: true,
|
||||
}
|
||||
)}
|
||||
{renderItem(
|
||||
'Min Load',
|
||||
advancedRadioMap,
|
||||
['bestApSettings', 'value', 'minLoadFactor'],
|
||||
renderInputItem,
|
||||
{
|
||||
min: 0,
|
||||
max: 100,
|
||||
error: '0 - 100%',
|
||||
addOnText: '%',
|
||||
mapName: 'advancedRadioMap',
|
||||
hidden: true,
|
||||
}
|
||||
)}
|
||||
<Advanced
|
||||
extraFields={extraFields}
|
||||
childProfiles={childProfiles}
|
||||
data={data}
|
||||
radioTypes={radioTypes}
|
||||
form={form}
|
||||
/>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
</Form>
|
||||
|
||||
@@ -34,87 +34,88 @@
|
||||
:global(.ant-form-item) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.InlineBlockDiv {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
:global(.ant-avatar) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.WifiIcon {
|
||||
background-color: transparent;
|
||||
color: #35a649;
|
||||
border: none;
|
||||
font-size: 40px;
|
||||
vertical-align: middle;
|
||||
.InlineBlockDiv {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
:global(.ant-avatar) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.backButton {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.WifiIcon {
|
||||
background-color: transparent;
|
||||
color: #35a649;
|
||||
border: none;
|
||||
font-size: 40px;
|
||||
vertical-align: middle;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.InlineDiv {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.backButton {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.InlineDiv > * {
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
.InlineDiv {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.InlineBetweenDiv {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.InlineDiv > * {
|
||||
flex: 1;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.saveButton {
|
||||
margin-top: 10px;
|
||||
min-width: 139px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.InlineBetweenDiv {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.InlineEndDiv {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.saveButton {
|
||||
margin-top: 10px;
|
||||
min-width: 139px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
p {
|
||||
font-weight: bold !important;
|
||||
font-size: 16px;
|
||||
}
|
||||
.spanStyle {
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.InlineEndDiv {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.UpgradeState {
|
||||
margin-left: 20px;
|
||||
}
|
||||
p {
|
||||
font-weight: bold !important;
|
||||
font-size: 16px;
|
||||
}
|
||||
.spanStyle {
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.HeaderDiv {
|
||||
display: flex;
|
||||
}
|
||||
.UpgradeState {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.troubleshootBtnsDiv {
|
||||
& > * {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.HeaderDiv {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tabContentContainer {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.Row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
.troubleshootBtnsDiv {
|
||||
& > * {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.tabContentContainer {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.Row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ export { default as BlockedList } from 'containers/BlockedList';
|
||||
|
||||
export { default as ClientDeviceDetails } from 'containers/ClientDeviceDetails';
|
||||
export { default as NetworkTableContainer } from 'containers/NetworkTableContainer';
|
||||
export { default as APFirmware } from 'containers/AccessPointDetails/components/Firmware';
|
||||
export { default as APAdvanced } from 'containers/AccessPointDetails/components/General/components/Advanced';
|
||||
|
||||
export { default as ThemeProvider } from 'contexts/ThemeProvider';
|
||||
export { default as RolesProvider } from 'contexts/RolesProvider';
|
||||
@@ -42,13 +44,13 @@ export { default as ScrollToTop } from 'components/ScrollToTop';
|
||||
export { default as LineGraphTooltip } from 'components/GraphTooltips/LineGraphTooltip';
|
||||
export { default as PieGraphTooltip } from 'components/GraphTooltips/PieGraphTooltip';
|
||||
export { default as Skeleton } from 'components/Skeleton';
|
||||
export { default as Timer } from 'components/Timer';
|
||||
export {
|
||||
Table as SkeletonTable,
|
||||
List as SkeletonList,
|
||||
Card as SkeletonCard,
|
||||
} from 'components/Skeleton';
|
||||
export { default as DisabledText } from 'components/DisabledText';
|
||||
export { default as Timer } from 'components/Timer';
|
||||
|
||||
export { default as WithRoles } from 'components/WithRoles';
|
||||
export { Input, Select, Switch, RoleProtectedBtn } from 'components/WithRoles';
|
||||
|
||||
Reference in New Issue
Block a user