import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import { useAuth } from 'ucentral-libs'; import axiosInstance from 'utils/axiosInstance'; import NetworkDiagram from 'components/NetworkDiagram'; import { cleanBytesString, prettyDate, compactSecondsToDetailed } from 'utils/helper'; import { CButton, CCard, CCardBody, CCardHeader, CCol, CModal, CModalHeader, CModalBody, CModalTitle, CRow, CPopover, } from '@coreui/react'; import CIcon from '@coreui/icons-react'; import { cilSync, cilX } from '@coreui/icons'; import RadioAnalysisTable from './RadioAnalysis'; import WifiAnalysisTable from './WifiAnalysis'; const parseDbm = (value) => { if (!value) return '-'; if (value > -150 && value < 100) return value; return (4294967295 - value) * -1; }; const WifiAnalysis = () => { const { t } = useTranslation(); const { deviceId } = useParams(); const { currentToken, endpoints } = useAuth(); const [loading, setLoading] = useState(false); const [showModal, setShowModal] = useState(false); const [tableTime, setTableTime] = useState(''); const [parsedAssociationStats, setParsedAssociationStats] = useState([]); const [selectedAssociationStats, setSelectedAssociationStats] = useState(null); const [parsedRadioStats, setParsedRadioStats] = useState([]); const [selectedRadioStats, setSelectedRadioStats] = useState(null); const [range, setRange] = useState(19); const toggleModal = () => { setShowModal(!showModal); }; const secondsToLabel = (seconds) => compactSecondsToDetailed(seconds, t('common.day'), t('common.days'), t('common.seconds')); const extractIp = (json, station) => { const ips = { ipV4: [], ipV6: [], }; for (const obj of json.interfaces) { if ('clients' in obj) { for (const client of obj.clients) { if (client.mac === station) { ips.ipV4 = ips.ipV4.concat(client.ipv4_addresses ?? []); ips.ipV6 = ips.ipV6.concat(client.ipv6_addresses ?? []); } } } } return ips; }; const getVendors = async (bssids) => { setLoading(true); setRange(19); const options = { headers: { Accept: 'application/json', Authorization: `Bearer ${currentToken}`, }, }; return axiosInstance .get(`${endpoints.owgw}/api/v1/ouis?macList=${bssids.join(',')}`, options) .then((response) => { const newObj = bssids; for (const tag of response.data.tagList) { newObj[tag.tag] = tag.value === '' ? '-' : tag.value; } return newObj; }) .catch(() => ({})); }; const parseAssociationStats = async (json) => { const newParsedAssociationStats = []; const newParsedRadioStats = []; const bssidObj = {}; for (const stat of json.data) { const associations = []; const radios = []; const timeStamp = prettyDate(stat.recorded); if (stat.data.radios !== undefined) { for (let i = 0; i < stat.data.radios.length; i += 1) { const radio = stat.data.radios[i]; radios.push({ timeStamp, radio: i, channel: radio.channel, channelWidth: radio.channel_width, noise: radio.noise ? parseDbm(radio.noise) : '-', txPower: radio.tx_power ?? '-', activeMs: secondsToLabel(radio?.active_ms ? Math.floor(radio.active_ms / 1000) : 0), busyMs: secondsToLabel(radio?.busy_ms ? Math.floor(radio.busy_ms / 1000) : 0), receiveMs: secondsToLabel(radio?.receive_ms ? Math.floor(radio.receive_ms / 1000) : 0), }); } newParsedRadioStats.push(radios); } // Looping through the interfaces for (const deviceInterface of stat.data.interfaces) { if ('ssids' in deviceInterface) { for (const ssid of deviceInterface.ssids) { // Information common between all associations const radioInfo = { found: false, }; if (ssid.phy !== undefined) { radioInfo.radio = stat.data.radios.findIndex((element) => element.phy === ssid.phy); radioInfo.found = radioInfo.radio !== undefined; radioInfo.radioIndex = radioInfo.radio; } if (!radioInfo.found && ssid.radio !== undefined) { const radioArray = ssid.radio.$ref.split('/'); const radioIndex = radioArray !== undefined ? radioArray[radioArray.length - 1] : '-'; radioInfo.found = stat.data.radios[radioIndex] !== undefined; radioInfo.radio = radioIndex; radioInfo.radioIndex = radioIndex; } if (!radioInfo.found) { radioInfo.radio = '-'; } if ('associations' in ssid) { for (const association of ssid.associations) { bssidObj[association.station] = 0; const data = { radio: radioInfo, ...extractIp(stat.data, association.station), station: association.station, ssid: ssid.ssid, rssi: association.rssi ? parseDbm(association.rssi) : '-', mode: ssid.mode, rxBytes: cleanBytesString(association.rx_bytes, 0), rxRate: association.rx_rate.bitrate, rxMcs: association.rx_rate.mcs ?? '-', rxNss: association.rx_rate.nss ?? '-', txBytes: cleanBytesString(association.tx_bytes, 0), txMcs: association.tx_rate.mcs ?? '-', txNss: association.tx_rate.nss ?? '-', txRate: association.tx_rate.bitrate ?? '-', timeStamp, }; associations.push(data); } } } } } newParsedAssociationStats.push(associations); } // Adding Vendor info to associations const vendors = await getVendors(Object.keys(bssidObj)); for (let i = 0; i < newParsedAssociationStats.length; i += 1) { for (let y = 0; y < newParsedAssociationStats[i].length; y += 1) { newParsedAssociationStats[i][y].vendor = vendors[newParsedAssociationStats[i][y].station] ?? '-'; } } // Radio Stats const ascOrderedRadioStats = newParsedRadioStats.reverse(); setParsedRadioStats(ascOrderedRadioStats); setSelectedRadioStats(ascOrderedRadioStats[ascOrderedRadioStats.length - 1]); const ascOrderedAssociationStats = newParsedAssociationStats.reverse(); setParsedAssociationStats(ascOrderedAssociationStats); setSelectedAssociationStats(ascOrderedAssociationStats[ascOrderedAssociationStats.length - 1]); setRange(ascOrderedRadioStats.length > 0 ? ascOrderedRadioStats.length - 1 : 0); setTableTime( ascOrderedRadioStats.length > 0 ? ascOrderedRadioStats[ascOrderedRadioStats.length - 1][0]?.timeStamp : '', ); setLoading(false); }; const getLatestAssociationStats = () => { setLoading(true); setRange(19); const options = { headers: { Accept: 'application/json', Authorization: `Bearer ${currentToken}`, }, }; axiosInstance .get(`${endpoints.owgw}/api/v1/device/${deviceId}/statistics?newest=true&limit=20`, options) .then((response) => { parseAssociationStats(response.data); }) .catch(() => { setLoading(false); }); }; const updateSelectedStats = (index) => { setTableTime(parsedRadioStats[index][0].timeStamp); setSelectedAssociationStats(parsedAssociationStats[index]); setSelectedRadioStats(parsedRadioStats[index]); }; useEffect(() => { if (deviceId && deviceId.length > 0) { getLatestAssociationStats(); } }, [deviceId]); return (