diff --git a/src/containers/AccessPointDetails/components/General/components/Advanced/index.js b/src/containers/AccessPointDetails/components/General/components/Advanced/index.js new file mode 100644 index 0000000..d9d30ad --- /dev/null +++ b/src/containers/AccessPointDetails/components/General/components/Advanced/index.js @@ -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 = ( + + ); + + const defaultOptionsBoolean = ( + + ); + + 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 ( + + ); + }; + + const renderConditionalItem = (label, obj = {}, dataIndex, dependency) => ( + +
+ {sortRadioTypes(Object.keys(obj)).map(key => { + const isEnabled = childProfiles.rf?.[0]?.details?.rfConfigMap?.[key]?.[dependency]; + + if (isEnabled) { + return ( + + ); + } + return ( + + ); + })} +
+
+ ); + + const renderInputItem = (dataIndex, key, label, options = {}) => ( + ({ + 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)); + }, + }), + ]} + > + + + ); + + const renderOptionItem = (dataIndex, key, label, options = {}) => { + return ( + + {typeof options.dropdown === 'function' ? options.dropdown(key) : options.dropdown} + + ); + }; + + const renderChannelItem = label => { + return ( + +
+ {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: ( + + ), + } + : { + dataIndex: 'manualChannelNumber', + addOnText: 'Manual', + dependencies: ['radioMap', key, 'manualBackupChannelNumber'], + }; + } + if (label === 'Backup Channel') { + channel = isEnabled + ? { + dataIndex: 'backupChannelNumber', + addOnText: ( + + ), + } + : { + 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 ( + ({ + 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(); + }, + }), + ]} + > + + + ); + })} +
+
+ ); + }; + + const renderBandwidthLabels = () => ( + +
+ {sortRadioTypes(Object.keys(radioMap)).map(radio => ( + + ))} +
+
+ ); + + return ( + <> + {renderItem(' ', data?.details?.radioMap, 'radioType')} +

Radio Specific Parameters:

+ {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 => ( + + ), + } + )} + {renderItem( + 'Multicast Rate (Mbps)', + advancedRadioMap, + ['multicastRate', 'value'], + renderOptionItem, + { + mapName: 'advancedRadioMap', + dropdown: ( + + ), + } + )} + {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; diff --git a/src/containers/AccessPointDetails/components/General/index.js b/src/containers/AccessPointDetails/components/General/index.js index 85df25f..48bbb54 100644 --- a/src/containers/AccessPointDetails/components/General/index.js +++ b/src/containers/AccessPointDetails/components/General/index.js @@ -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 = ( - - ); - - const defaultOptionsBoolean = ( - - ); - - const renderItem = (label, obj = {}, dataIndex, renderInput, options = {}) => { - if (extraFields.some(field => field.label === label)) { - return null; - } - return ( - - ); - }; - - const renderConditionalItem = (label, obj = {}, dataIndex, dependency) => ( - -
- {sortRadioTypes(Object.keys(obj)).map(key => { - const isEnabled = childProfiles.rf?.[0]?.details?.rfConfigMap?.[key]?.[dependency]; - - if (isEnabled) { - return ( - - ); - } - return ( - - ); - })} -
-
- ); - - const renderInputItem = (dataIndex, key, label, options = {}) => ( - ({ - 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)); - }, - }), - ]} - > - - - ); - - const renderOptionItem = (dataIndex, key, label, options = {}) => { - return ( - - {typeof options.dropdown === 'function' ? options.dropdown(key) : options.dropdown} - - ); - }; - - const renderChannelItem = label => { - return ( - -
- {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: ( - - ), - } - : { - dataIndex: 'manualChannelNumber', - addOnText: 'Manual', - dependencies: ['radioMap', key, 'manualBackupChannelNumber'], - }; - } - if (label === 'Backup Channel') { - channel = isEnabled - ? { - dataIndex: 'backupChannelNumber', - addOnText: ( - - ), - } - : { - 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 ( - ({ - 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(); - }, - }), - ]} - > - - - ); - })} -
-
- ); - }; - - const renderBandwidthLabels = () => ( - -
- {sortRadioTypes(Object.keys(radioMap)).map(radio => ( - - ))} -
-
- ); - if (errorProfiles) { return ( - {renderItem(' ', data?.details?.radioMap, 'radioType')} -

Radio Specific Parameters:

- {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 => ( - - ), - } - )} - {renderItem( - 'Multicast Rate (Mbps)', - advancedRadioMap, - ['multicastRate', 'value'], - renderOptionItem, - { - mapName: 'advancedRadioMap', - dropdown: ( - - ), - } - )} - {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, - } - )} +
diff --git a/src/containers/AccessPointDetails/index.module.scss b/src/containers/AccessPointDetails/index.module.scss index 3a3d7ef..be5379a 100644 --- a/src/containers/AccessPointDetails/index.module.scss +++ b/src/containers/AccessPointDetails/index.module.scss @@ -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; +} diff --git a/src/index.js b/src/index.js index 6fa4271..31358d5 100644 --- a/src/index.js +++ b/src/index.js @@ -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';