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,
|
Empty,
|
||||||
Typography,
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { Card } from 'components/Skeleton';
|
import { Card } from 'components/Skeleton';
|
||||||
import { Input, Select, RoleProtectedBtn } from 'components/WithRoles';
|
import { Input, Select, RoleProtectedBtn } from 'components/WithRoles';
|
||||||
import _ from 'lodash';
|
|
||||||
import ThemeContext from 'contexts/ThemeContext';
|
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 { pageLayout } from 'utils/form';
|
||||||
import { USER_FRIENDLY_RATES, USER_FRIENDLY_BANDWIDTHS } from './constants';
|
|
||||||
|
import Advanced from './components/Advanced';
|
||||||
|
|
||||||
import styles from '../../index.module.scss';
|
import styles from '../../index.module.scss';
|
||||||
|
|
||||||
@@ -112,80 +110,8 @@ const General = ({
|
|||||||
longitude,
|
longitude,
|
||||||
serial,
|
serial,
|
||||||
lastModifiedTimestamp,
|
lastModifiedTimestamp,
|
||||||
details: { advancedRadioMap = {}, radioMap = {} } = {},
|
|
||||||
} = data;
|
} = 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 = () => {
|
const handleOnSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.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) {
|
if (errorProfiles) {
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -605,141 +273,13 @@ const General = ({
|
|||||||
{extraGeneralCards}
|
{extraGeneralCards}
|
||||||
<Collapse expandIconPosition="right">
|
<Collapse expandIconPosition="right">
|
||||||
<Panel header="Advanced Settings" name="settings">
|
<Panel header="Advanced Settings" name="settings">
|
||||||
{renderItem(' ', data?.details?.radioMap, 'radioType')}
|
<Advanced
|
||||||
<p>Radio Specific Parameters:</p>
|
extraFields={extraFields}
|
||||||
{renderBandwidthLabels()}
|
childProfiles={childProfiles}
|
||||||
{renderItem('Enable Radio', advancedRadioMap, ['radioAdminState'], renderOptionItem, {
|
data={data}
|
||||||
dropdown: defaultOptions,
|
radioTypes={radioTypes}
|
||||||
mapName: 'advancedRadioMap',
|
form={form}
|
||||||
})}
|
/>
|
||||||
{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,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</Panel>
|
</Panel>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -34,87 +34,88 @@
|
|||||||
:global(.ant-form-item) {
|
:global(.ant-form-item) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.InlineBlockDiv {
|
.InlineBlockDiv {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
:global(.ant-avatar) {
|
:global(.ant-avatar) {
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.WifiIcon {
|
|
||||||
background-color: transparent;
|
|
||||||
color: #35a649;
|
|
||||||
border: none;
|
|
||||||
font-size: 40px;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.backButton {
|
.WifiIcon {
|
||||||
margin-top: 5px;
|
background-color: transparent;
|
||||||
}
|
color: #35a649;
|
||||||
|
border: none;
|
||||||
|
font-size: 40px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.InlineDiv {
|
.backButton {
|
||||||
display: flex;
|
margin-top: 5px;
|
||||||
flex-wrap: wrap;
|
}
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
|
|
||||||
.InlineDiv > * {
|
.InlineDiv {
|
||||||
margin: 0 10px 0 0;
|
display: flex;
|
||||||
}
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
.InlineBetweenDiv {
|
.InlineDiv > * {
|
||||||
display: flex;
|
flex: 1;
|
||||||
align-items: center;
|
margin: 0 10px 0 0;
|
||||||
justify-content: space-between;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.saveButton {
|
.InlineBetweenDiv {
|
||||||
margin-top: 10px;
|
display: flex;
|
||||||
min-width: 139px;
|
align-items: center;
|
||||||
max-width: 100%;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.InlineEndDiv {
|
.saveButton {
|
||||||
display: flex;
|
margin-top: 10px;
|
||||||
justify-content: flex-end;
|
min-width: 139px;
|
||||||
}
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
.InlineEndDiv {
|
||||||
font-weight: bold !important;
|
display: flex;
|
||||||
font-size: 16px;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
.spanStyle {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.UpgradeState {
|
p {
|
||||||
margin-left: 20px;
|
font-weight: bold !important;
|
||||||
}
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.spanStyle {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.HeaderDiv {
|
.UpgradeState {
|
||||||
display: flex;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.troubleshootBtnsDiv {
|
.HeaderDiv {
|
||||||
& > * {
|
display: flex;
|
||||||
margin-right: 10px;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabContentContainer {
|
.troubleshootBtnsDiv {
|
||||||
margin-top: 10px;
|
& > * {
|
||||||
}
|
margin-right: 10px;
|
||||||
|
|
||||||
.Row {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 ClientDeviceDetails } from 'containers/ClientDeviceDetails';
|
||||||
export { default as NetworkTableContainer } from 'containers/NetworkTableContainer';
|
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 ThemeProvider } from 'contexts/ThemeProvider';
|
||||||
export { default as RolesProvider } from 'contexts/RolesProvider';
|
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 LineGraphTooltip } from 'components/GraphTooltips/LineGraphTooltip';
|
||||||
export { default as PieGraphTooltip } from 'components/GraphTooltips/PieGraphTooltip';
|
export { default as PieGraphTooltip } from 'components/GraphTooltips/PieGraphTooltip';
|
||||||
export { default as Skeleton } from 'components/Skeleton';
|
export { default as Skeleton } from 'components/Skeleton';
|
||||||
|
export { default as Timer } from 'components/Timer';
|
||||||
export {
|
export {
|
||||||
Table as SkeletonTable,
|
Table as SkeletonTable,
|
||||||
List as SkeletonList,
|
List as SkeletonList,
|
||||||
Card as SkeletonCard,
|
Card as SkeletonCard,
|
||||||
} from 'components/Skeleton';
|
} from 'components/Skeleton';
|
||||||
export { default as DisabledText } from 'components/DisabledText';
|
export { default as DisabledText } from 'components/DisabledText';
|
||||||
export { default as Timer } from 'components/Timer';
|
|
||||||
|
|
||||||
export { default as WithRoles } from 'components/WithRoles';
|
export { default as WithRoles } from 'components/WithRoles';
|
||||||
export { Input, Select, Switch, RoleProtectedBtn } from 'components/WithRoles';
|
export { Input, Select, Switch, RoleProtectedBtn } from 'components/WithRoles';
|
||||||
|
|||||||
Reference in New Issue
Block a user