mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
				synced 2025-10-30 18:27:53 +00:00 
			
		
		
		
	Merge pull request #208 from stephb9959/main
[WIFI-13315] Wi-Fi analysis fixes
This commit is contained in:
		
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-client", |   "name": "ucentral-client", | ||||||
|   "version": "3.0.1(2)", |   "version": "3.0.1(5)", | ||||||
|   "lockfileVersion": 3, |   "lockfileVersion": 3, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "packages": { |   "packages": { | ||||||
|     "": { |     "": { | ||||||
|       "name": "ucentral-client", |       "name": "ucentral-client", | ||||||
|       "version": "3.0.1(2)", |       "version": "3.0.1(5)", | ||||||
|       "license": "ISC", |       "license": "ISC", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@chakra-ui/anatomy": "^2.1.1", |         "@chakra-ui/anatomy": "^2.1.1", | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-client", |   "name": "ucentral-client", | ||||||
|   "version": "3.0.1(2)", |   "version": "3.0.1(5)", | ||||||
|   "description": "", |   "description": "", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "main": "index.tsx", |   "main": "index.tsx", | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import { AxiosError } from 'models/Axios'; | |||||||
| import { DeviceRttyApiResponse, GatewayDevice, WifiScanCommand, WifiScanResult } from 'models/Device'; | import { DeviceRttyApiResponse, GatewayDevice, WifiScanCommand, WifiScanResult } from 'models/Device'; | ||||||
| import { Note } from 'models/Note'; | import { Note } from 'models/Note'; | ||||||
| import { PageInfo } from 'models/Table'; | import { PageInfo } from 'models/Table'; | ||||||
|  | import { DeviceCommandHistory } from './Commands'; | ||||||
|  |  | ||||||
| export const DEVICE_PLATFORMS = ['ALL', 'AP', 'SWITCH'] as const; | export const DEVICE_PLATFORMS = ['ALL', 'AP', 'SWITCH'] as const; | ||||||
| export type DevicePlatform = (typeof DEVICE_PLATFORMS)[number]; | export type DevicePlatform = (typeof DEVICE_PLATFORMS)[number]; | ||||||
| @@ -461,3 +462,29 @@ export const useDeleteDeviceBatch = () => { | |||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | export type PowerCyclePort = { | ||||||
|  |   /** Ex.: Ethernet0 */ | ||||||
|  |   name: string; | ||||||
|  |   /** Cycle length in MS. Default is 10 000 */ | ||||||
|  |   cycle?: number; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export type PowerCycleRequest = { | ||||||
|  |   serial: string; | ||||||
|  |   when: number; | ||||||
|  |   ports: PowerCyclePort[]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const usePowerCycle = () => { | ||||||
|  |   const queryClient = useQueryClient(); | ||||||
|  |   return useMutation( | ||||||
|  |     (request: PowerCycleRequest) => | ||||||
|  |       axiosGw.post(`device/${request.serial}/powercycle`, request).then((res) => res.data as DeviceCommandHistory), | ||||||
|  |     { | ||||||
|  |       onSettled: () => { | ||||||
|  |         queryClient.invalidateQueries(['commands']); | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -129,11 +129,21 @@ export type DeviceStatistics = { | |||||||
|     channel: number; |     channel: number; | ||||||
|     band?: string[]; |     band?: string[]; | ||||||
|     channel_width: string; |     channel_width: string; | ||||||
|     noise: number; |     noise?: number; | ||||||
|     phy: string; |     phy: string; | ||||||
|     receive_ms: number; |     receive_ms: number; | ||||||
|     transmit_ms: number; |     transmit_ms: number; | ||||||
|  |     temperature?: number; | ||||||
|     tx_power: number; |     tx_power: number; | ||||||
|  |     frequency?: number[]; | ||||||
|  |     survey?: { | ||||||
|  |       busy: number; | ||||||
|  |       frequency: number; | ||||||
|  |       noise: number; | ||||||
|  |       time: number; | ||||||
|  |       time_rx: number; | ||||||
|  |       time_tx: number; | ||||||
|  |     }[]; | ||||||
|   }[]; |   }[]; | ||||||
|   dynamic_vlans?: { |   dynamic_vlans?: { | ||||||
|     vid: number; |     vid: number; | ||||||
|   | |||||||
							
								
								
									
										70
									
								
								src/pages/Device/SwitchPortExamination/Actions.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/pages/Device/SwitchPortExamination/Actions.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | import * as React from 'react'; | ||||||
|  | import { IconButton, Tooltip, useToast } from '@chakra-ui/react'; | ||||||
|  | import { Power } from '@phosphor-icons/react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import { usePowerCycle } from 'hooks/Network/Devices'; | ||||||
|  | import { useNotification } from 'hooks/useNotification'; | ||||||
|  | import { DeviceLinkState } from 'hooks/Network/Statistics'; | ||||||
|  |  | ||||||
|  | type Props = { | ||||||
|  |   state: DeviceLinkState & { name: string }; | ||||||
|  |   deviceSerialNumber: string; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const LinkStateTableActions = ({ state, deviceSerialNumber }: Props) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const powerCycle = usePowerCycle(); | ||||||
|  |   const toast = useToast(); | ||||||
|  |   const { successToast, apiErrorToast } = useNotification(); | ||||||
|  |  | ||||||
|  |   const onPowerCycle = () => { | ||||||
|  |     powerCycle.mutate( | ||||||
|  |       { serial: deviceSerialNumber, when: 0, ports: [{ name: state.name, cycle: 10 * 1000 }] }, | ||||||
|  |       { | ||||||
|  |         onSuccess: (data) => { | ||||||
|  |           if (data.errorCode === 0) { | ||||||
|  |             successToast({ | ||||||
|  |               description: `Power cycle started for port ${state.name} for 10s`, | ||||||
|  |             }); | ||||||
|  |           } else if (data.errorCode === 1) { | ||||||
|  |             toast({ | ||||||
|  |               id: `powercycle-warning-${deviceSerialNumber}`, | ||||||
|  |               title: 'Warning', | ||||||
|  |               description: `${data?.errorText ?? 'Unknown Warning'}`, | ||||||
|  |               status: 'warning', | ||||||
|  |               duration: 5000, | ||||||
|  |               isClosable: true, | ||||||
|  |               position: 'top-right', | ||||||
|  |             }); | ||||||
|  |           } else { | ||||||
|  |             toast({ | ||||||
|  |               id: `powercycle-error-${deviceSerialNumber}`, | ||||||
|  |               title: t('common.error'), | ||||||
|  |               description: `${data?.errorText ?? 'Unknown Error'} (Code ${data.errorCode})`, | ||||||
|  |               status: 'error', | ||||||
|  |               duration: 5000, | ||||||
|  |               isClosable: true, | ||||||
|  |               position: 'top-right', | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         onError: (e) => apiErrorToast({ e }), | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <Tooltip label="Power Cycle" placement="auto-start"> | ||||||
|  |       <IconButton | ||||||
|  |         aria-label="Power Cycle" | ||||||
|  |         icon={<Power size={20} />} | ||||||
|  |         colorScheme="green" | ||||||
|  |         onClick={onPowerCycle} | ||||||
|  |         isLoading={powerCycle.isLoading} | ||||||
|  |         size="xs" | ||||||
|  |       /> | ||||||
|  |     </Tooltip> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default LinkStateTableActions; | ||||||
| @@ -5,18 +5,23 @@ import DataCell from 'components/TableCells/DataCell'; | |||||||
| import { DataGridColumn, useDataGrid } from 'components/DataTables/DataGrid/useDataGrid'; | import { DataGridColumn, useDataGrid } from 'components/DataTables/DataGrid/useDataGrid'; | ||||||
| import { DataGrid } from 'components/DataTables/DataGrid'; | import { DataGrid } from 'components/DataTables/DataGrid'; | ||||||
| import { uppercaseFirstLetter } from 'helpers/stringHelper'; | import { uppercaseFirstLetter } from 'helpers/stringHelper'; | ||||||
|  | import LinkStateTableActions from './Actions'; | ||||||
|  |  | ||||||
| type Row = DeviceLinkState & { name: string }; | type Row = DeviceLinkState & { name: string }; | ||||||
| const dataCell = (v: number) => <DataCell bytes={v} />; | const dataCell = (v: number) => <DataCell bytes={v} />; | ||||||
|  | const actionCell = (row: Row, serialNumber: string) => ( | ||||||
|  |   <LinkStateTableActions state={row} deviceSerialNumber={serialNumber} /> | ||||||
|  | ); | ||||||
|  |  | ||||||
| type Props = { | type Props = { | ||||||
|   statistics?: Row[]; |   statistics?: Row[]; | ||||||
|   refetch: () => void; |   refetch: () => void; | ||||||
|   isFetching: boolean; |   isFetching: boolean; | ||||||
|   type: 'upstream' | 'downstream'; |   type: 'upstream' | 'downstream'; | ||||||
|  |   serialNumber: string; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const LinkStateTable = ({ statistics, refetch, isFetching, type }: Props) => { | const LinkStateTable = ({ statistics, refetch, isFetching, type, serialNumber }: Props) => { | ||||||
|   const tableController = useDataGrid({ |   const tableController = useDataGrid({ | ||||||
|     tableSettingsId: 'switch.link-state.table', |     tableSettingsId: 'switch.link-state.table', | ||||||
|     defaultOrder: [ |     defaultOrder: [ | ||||||
| @@ -31,6 +36,8 @@ const LinkStateTable = ({ statistics, refetch, isFetching, type }: Props) => { | |||||||
|       'tx_bytes', |       'tx_bytes', | ||||||
|       'tx_dropped', |       'tx_dropped', | ||||||
|       'tx_error', |       'tx_error', | ||||||
|  |       'tx_packets', | ||||||
|  |       'actions', | ||||||
|     ], |     ], | ||||||
|     defaultSortBy: [{ id: 'name', desc: false }], |     defaultSortBy: [{ id: 'name', desc: false }], | ||||||
|   }); |   }); | ||||||
| @@ -144,6 +151,12 @@ const LinkStateTable = ({ statistics, refetch, isFetching, type }: Props) => { | |||||||
|           customWidth: '35px', |           customWidth: '35px', | ||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
|  |       { | ||||||
|  |         id: 'actions', | ||||||
|  |         header: '', | ||||||
|  |         accessorKey: '', | ||||||
|  |         cell: ({ cell }) => actionCell(cell.row.original, serialNumber), | ||||||
|  |       }, | ||||||
|     ], |     ], | ||||||
|     [], |     [], | ||||||
|   ); |   ); | ||||||
|   | |||||||
| @@ -69,6 +69,7 @@ const SwitchPortExamination = ({ serialNumber }: Props) => { | |||||||
|                   refetch={getStats.refetch} |                   refetch={getStats.refetch} | ||||||
|                   isFetching={getStats.isFetching} |                   isFetching={getStats.isFetching} | ||||||
|                   type="upstream" |                   type="upstream" | ||||||
|  |                   serialNumber={serialNumber} | ||||||
|                 /> |                 /> | ||||||
|               ) : ( |               ) : ( | ||||||
|                 <Spinner size="xl" /> |                 <Spinner size="xl" /> | ||||||
| @@ -81,6 +82,7 @@ const SwitchPortExamination = ({ serialNumber }: Props) => { | |||||||
|                   refetch={getStats.refetch} |                   refetch={getStats.refetch} | ||||||
|                   isFetching={getStats.isFetching} |                   isFetching={getStats.isFetching} | ||||||
|                   type="downstream" |                   type="downstream" | ||||||
|  |                   serialNumber={serialNumber} | ||||||
|                 /> |                 /> | ||||||
|               ) : ( |               ) : ( | ||||||
|                 <Spinner size="xl" /> |                 <Spinner size="xl" /> | ||||||
|   | |||||||
| @@ -166,7 +166,7 @@ const WifiAnalysisAssociationsTable = ({ data, ouis, isSingle }: Props) => { | |||||||
|         customWidth: '35px', |         customWidth: '35px', | ||||||
|       }, |       }, | ||||||
|     ], |     ], | ||||||
|     [t], |     [t, ouis], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|   | |||||||
| @@ -17,14 +17,18 @@ export type ParsedRadio = { | |||||||
|   activeMs: string; |   activeMs: string; | ||||||
|   busyMs: string; |   busyMs: string; | ||||||
|   receiveMs: string; |   receiveMs: string; | ||||||
|  |   sendMs: string; | ||||||
|   phy: string; |   phy: string; | ||||||
|  |   frequency: string; | ||||||
|  |   temperature: string; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| type Props = { | type Props = { | ||||||
|   data?: ParsedRadio[]; |   data?: ParsedRadio[]; | ||||||
|  |   isSingle?: boolean; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const WifiAnalysisRadioTable = ({ data }: Props) => { | const WifiAnalysisRadioTable = ({ data, isSingle }: Props) => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]); |   const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]); | ||||||
|  |  | ||||||
| @@ -44,19 +48,27 @@ const WifiAnalysisRadioTable = ({ data }: Props) => { | |||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         id: 'channel', |         id: 'channel', | ||||||
|         Header: 'Ch', |         Header: 'Ch.', | ||||||
|         Footer: '', |         Footer: '', | ||||||
|         accessor: 'channel', |         accessor: 'channel', | ||||||
|         customWidth: '35px', |         customWidth: '35px', | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         id: 'channelWidth', |         id: 'channelWidth', | ||||||
|         Header: t('controller.wifi.channel_width'), |         Header: 'Ch. W', | ||||||
|         Footer: '', |         Footer: '', | ||||||
|         accessor: 'channelWidth', |         accessor: 'channelWidth', | ||||||
|         customWidth: '35px', |         customWidth: '35px', | ||||||
|         disableSortBy: true, |         disableSortBy: true, | ||||||
|       }, |       }, | ||||||
|  |       { | ||||||
|  |         id: 'tx-power', | ||||||
|  |         Header: 'Tx Pow.', | ||||||
|  |         Footer: '', | ||||||
|  |         accessor: 'txPower', | ||||||
|  |         customWidth: '35px', | ||||||
|  |         disableSortBy: true, | ||||||
|  |       }, | ||||||
|       { |       { | ||||||
|         id: 'noise', |         id: 'noise', | ||||||
|         Header: t('controller.wifi.noise'), |         Header: t('controller.wifi.noise'), | ||||||
| @@ -67,25 +79,49 @@ const WifiAnalysisRadioTable = ({ data }: Props) => { | |||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         id: 'activeMs', |         id: 'activeMs', | ||||||
|         Header: t('controller.wifi.active_ms'), |         Header: 'Active (ms)', | ||||||
|         Footer: '', |         Footer: '', | ||||||
|         accessor: 'activeMs', |         accessor: 'activeMs', | ||||||
|         customWidth: '35px', |         customWidth: '105px', | ||||||
|         disableSortBy: true, |         disableSortBy: true, | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         id: 'busyMs', |         id: 'busyMs', | ||||||
|         Header: t('controller.wifi.busy_ms'), |         Header: 'Busy (ms)', | ||||||
|         Footer: '', |         Footer: '', | ||||||
|         accessor: 'busyMs', |         accessor: 'busyMs', | ||||||
|         customWidth: '35px', |         customWidth: '105px', | ||||||
|         disableSortBy: true, |         disableSortBy: true, | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         id: 'receiveMs', |         id: 'receiveMs', | ||||||
|         Header: t('controller.wifi.receive_ms'), |         Header: 'Receive (ms)', | ||||||
|         Footer: '', |         Footer: '', | ||||||
|         accessor: 'receiveMs', |         accessor: 'receiveMs', | ||||||
|  |         customWidth: '105px', | ||||||
|  |         disableSortBy: true, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         id: 'sendMs', | ||||||
|  |         Header: 'Send (ms)', | ||||||
|  |         Footer: '', | ||||||
|  |         accessor: 'sendMs', | ||||||
|  |         customWidth: '105px', | ||||||
|  |         disableSortBy: true, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         id: 'temperature', | ||||||
|  |         Header: 'Temp.', | ||||||
|  |         Footer: '', | ||||||
|  |         accessor: 'temperature', | ||||||
|  |         customWidth: '35px', | ||||||
|  |         disableSortBy: true, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         id: 'frequency', | ||||||
|  |         Header: 'Frequency', | ||||||
|  |         Footer: '', | ||||||
|  |         accessor: 'frequency', | ||||||
|         customWidth: '35px', |         customWidth: '35px', | ||||||
|         disableSortBy: true, |         disableSortBy: true, | ||||||
|       }, |       }, | ||||||
| @@ -97,7 +133,7 @@ const WifiAnalysisRadioTable = ({ data }: Props) => { | |||||||
|     <> |     <> | ||||||
|       <Flex> |       <Flex> | ||||||
|         <Heading size="sm" mt={2} my="auto"> |         <Heading size="sm" mt={2} my="auto"> | ||||||
|           {t('configurations.radios')} ({data?.length}) |           {isSingle ? 'Radio' : `${t('configurations.radios')} (${data?.length})`} | ||||||
|         </Heading> |         </Heading> | ||||||
|         <Spacer /> |         <Spacer /> | ||||||
|         <ColumnPicker |         <ColumnPicker | ||||||
|   | |||||||
| @@ -16,11 +16,29 @@ type Props = { | |||||||
|   serialNumber: string; |   serialNumber: string; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const parseRadios = (t: (str: string) => string, data: { data: DeviceStatistics; recorded: number }) => { | const parseRadios = (_: (str: string) => string, data: { data: DeviceStatistics; recorded: number }) => { | ||||||
|   const radios: ParsedRadio[] = []; |   const radios: ParsedRadio[] = []; | ||||||
|   if (data.data.radios) { |   if (data.data.radios) { | ||||||
|     for (let i = 0; i < data.data.radios.length; i += 1) { |     for (let i = 0; i < data.data.radios.length; i += 1) { | ||||||
|       const radio = data.data.radios[i]; |       const radio = data.data.radios[i]; | ||||||
|  |       let temperature = radio?.temperature; | ||||||
|  |       if (temperature) temperature = temperature > 1000 ? Math.round(temperature / 1000) : temperature; | ||||||
|  |  | ||||||
|  |       const tempNoise = radio?.noise ?? radio?.survey?.[0]?.noise; | ||||||
|  |       const noise = tempNoise ? parseDbm(tempNoise) : '-'; | ||||||
|  |  | ||||||
|  |       const tempActiveMs = radio?.survey?.[0]?.time ?? radio?.active_ms; | ||||||
|  |       const activeMs = tempActiveMs?.toLocaleString() ?? '-'; | ||||||
|  |  | ||||||
|  |       const tempBusyMs = radio?.survey?.[0]?.busy ?? radio?.busy_ms; | ||||||
|  |       const busyMs = tempBusyMs?.toLocaleString() ?? '-'; | ||||||
|  |  | ||||||
|  |       const tempReceiveMs = radio?.survey?.[0]?.time_rx ?? radio?.receive_ms; | ||||||
|  |       const receiveMs = tempReceiveMs?.toLocaleString() ?? '-'; | ||||||
|  |  | ||||||
|  |       const tempSendMs = radio?.survey?.[0]?.time_tx; | ||||||
|  |       const sendMs = tempSendMs?.toLocaleString() ?? '-'; | ||||||
|  |  | ||||||
|       if (radio) { |       if (radio) { | ||||||
|         radios.push({ |         radios.push({ | ||||||
|           recorded: data.recorded, |           recorded: data.recorded, | ||||||
| @@ -29,12 +47,15 @@ const parseRadios = (t: (str: string) => string, data: { data: DeviceStatistics; | |||||||
|           deductedBand: radio.channel && radio.channel > 16 ? '5G' : '2G', |           deductedBand: radio.channel && radio.channel > 16 ? '5G' : '2G', | ||||||
|           channel: radio.channel, |           channel: radio.channel, | ||||||
|           channelWidth: radio.channel_width, |           channelWidth: radio.channel_width, | ||||||
|           noise: radio.noise ? parseDbm(radio.noise) : '-', |           noise, | ||||||
|           txPower: radio.tx_power ?? '-', |           txPower: radio.tx_power ?? '-', | ||||||
|           activeMs: compactSecondsToDetailed(radio?.active_ms ? Math.floor(radio.active_ms / 1000) : 0, t), |           activeMs, | ||||||
|           busyMs: compactSecondsToDetailed(radio?.busy_ms ? Math.floor(radio.busy_ms / 1000) : 0, t), |           busyMs, | ||||||
|           receiveMs: compactSecondsToDetailed(radio?.receive_ms ? Math.floor(radio.receive_ms / 1000) : 0, t), |           receiveMs, | ||||||
|  |           sendMs, | ||||||
|           phy: radio.phy, |           phy: radio.phy, | ||||||
|  |           temperature: temperature ? temperature.toString() : '-', | ||||||
|  |           frequency: radio.frequency?.join(', ') ?? '-', | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -325,11 +325,8 @@ const DevicePageWrapper = ({ serialNumber }: Props) => { | |||||||
|           <DeviceSummary serialNumber={serialNumber} /> |           <DeviceSummary serialNumber={serialNumber} /> | ||||||
|           <DeviceDetails serialNumber={serialNumber} /> |           <DeviceDetails serialNumber={serialNumber} /> | ||||||
|           <DeviceStatisticsCard serialNumber={serialNumber} /> |           <DeviceStatisticsCard serialNumber={serialNumber} /> | ||||||
|           {getDevice.data?.deviceType === 'AP' ? ( |           {getDevice.data?.deviceType === 'AP' ? <WifiAnalysisCard serialNumber={serialNumber} /> : null} | ||||||
|             <WifiAnalysisCard serialNumber={serialNumber} /> |           {getDevice.data?.deviceType === 'SWITCH' ? <SwitchPortExamination serialNumber={serialNumber} /> : null} | ||||||
|           ) : ( |  | ||||||
|             <SwitchPortExamination serialNumber={serialNumber} /> |  | ||||||
|           )} |  | ||||||
|           <DeviceLogsCard serialNumber={serialNumber} /> |           <DeviceLogsCard serialNumber={serialNumber} /> | ||||||
|           {getDevice.data && getDevice.data?.hasRADIUSSessions > 0 ? ( |           {getDevice.data && getDevice.data?.hasRADIUSSessions > 0 ? ( | ||||||
|             <RadiusClientsCard serialNumber={serialNumber} /> |             <RadiusClientsCard serialNumber={serialNumber} /> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Charles Bourque
					Charles Bourque