mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
synced 2025-10-30 02:12:33 +00:00
273 lines
9.1 KiB
JavaScript
273 lines
9.1 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useParams } from 'react-router-dom';
|
|
import { WifiAnalysisTable, RadioAnalysisTable, 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 { cilX } from '@coreui/icons';
|
|
|
|
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, bssid) => {
|
|
const ips = {
|
|
ipV4: [],
|
|
ipV6: [],
|
|
};
|
|
for (const obj of json.interfaces) {
|
|
if ('clients' in obj) {
|
|
for (const client of obj.clients) {
|
|
if (client.mac === bssid) {
|
|
ips.ipV4 = ips.ipV4.concat(client.ipv4_addresses ?? []);
|
|
ips.ipV6 = ips.ipV6.concat(client.ipv6_addresses ?? []);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ips;
|
|
};
|
|
|
|
const parseAssociationStats = (json) => {
|
|
const newParsedAssociationStats = [];
|
|
const newParsedRadioStats = [];
|
|
|
|
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 ('counters' in deviceInterface && '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) {
|
|
const data = {
|
|
radio: radioInfo,
|
|
...extractIp(stat.data, association.bssid),
|
|
bssid: association.bssid,
|
|
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);
|
|
}
|
|
|
|
// 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 (
|
|
<div>
|
|
<CCard>
|
|
<CCardHeader className="dark-header">
|
|
<CRow>
|
|
<CCol className="text-right">
|
|
<CButton color="info" size="sm" onClick={toggleModal}>
|
|
{t('wifi_analysis.network_diagram')}
|
|
</CButton>
|
|
</CCol>
|
|
</CRow>
|
|
</CCardHeader>
|
|
<CCardBody>
|
|
<CRow className="mb-4">
|
|
<CCol className="text-center">
|
|
<input
|
|
type="range"
|
|
style={{ width: '80%' }}
|
|
className="form-range"
|
|
min="0"
|
|
max={range}
|
|
step="1"
|
|
onChange={(e) => updateSelectedStats(e.target.value)}
|
|
defaultValue={range}
|
|
disabled={!selectedRadioStats}
|
|
/>
|
|
<h5>
|
|
{t('common.timestamp')}: {tableTime}
|
|
</h5>
|
|
</CCol>
|
|
</CRow>
|
|
<div className="overflow-auto" style={{ height: 'calc(100vh - 300px)' }}>
|
|
<h5 className="pb-3 text-center">{t('wifi_analysis.radios')}</h5>
|
|
<RadioAnalysisTable data={selectedRadioStats ?? []} loading={loading} range={range} />
|
|
<h5 className="pt-5 pb-3 text-center">{t('wifi_analysis.associations')}</h5>
|
|
<WifiAnalysisTable
|
|
t={t}
|
|
data={selectedAssociationStats ?? []}
|
|
loading={loading}
|
|
range={range}
|
|
/>
|
|
</div>
|
|
</CCardBody>
|
|
</CCard>
|
|
<CModal size="xl" show={showModal} onClose={toggleModal}>
|
|
<CModalHeader className="p-1">
|
|
<CModalTitle className="pl-1 pt-1">{t('wifi_analysis.network_diagram')}</CModalTitle>
|
|
<div className="text-right">
|
|
<CPopover content={t('common.close')}>
|
|
<CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
|
|
<CIcon content={cilX} />
|
|
</CButton>
|
|
</CPopover>
|
|
</div>
|
|
</CModalHeader>
|
|
<CModalBody>
|
|
{showModal ? (
|
|
<NetworkDiagram
|
|
show={showModal}
|
|
radios={selectedRadioStats}
|
|
associations={selectedAssociationStats}
|
|
/>
|
|
) : (
|
|
<div />
|
|
)}
|
|
</CModalBody>
|
|
</CModal>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default WifiAnalysis;
|