Merge pull request #129 from stephb9959/dev

Version 2.5.25
This commit is contained in:
Charles
2022-01-17 22:04:47 +01:00
committed by GitHub
13 changed files with 505 additions and 352 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "ucentral-client", "name": "ucentral-client",
"version": "2.5.24", "version": "2.5.27",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ucentral-client", "name": "ucentral-client",
"version": "2.5.24", "version": "2.5.27",
"dependencies": { "dependencies": {
"@coreui/coreui": "^3.4.0", "@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1", "@coreui/icons": "^2.0.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "ucentral-client", "name": "ucentral-client",
"version": "2.5.24", "version": "2.5.27",
"dependencies": { "dependencies": {
"@coreui/coreui": "^3.4.0", "@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1", "@coreui/icons": "^2.0.1",

View File

@@ -804,6 +804,7 @@
"mode": "Modus", "mode": "Modus",
"network_diagram": "Netzwerkdiagramm", "network_diagram": "Netzwerkdiagramm",
"radios": "Radios", "radios": "Radios",
"title": "WLAN-Analyse" "title": "WLAN-Analyse",
"vendor": "Verkäufer"
} }
} }

View File

@@ -804,6 +804,7 @@
"mode": "Mode", "mode": "Mode",
"network_diagram": "Network Diagram", "network_diagram": "Network Diagram",
"radios": "Radios", "radios": "Radios",
"title": "Wi-Fi Analysis" "title": "Wi-Fi Analysis",
"vendor": "Vendor"
} }
} }

View File

@@ -804,6 +804,7 @@
"mode": "Modo", "mode": "Modo",
"network_diagram": "Diagrama de Red", "network_diagram": "Diagrama de Red",
"radios": "Radios", "radios": "Radios",
"title": "Análisis de Wi-Fi" "title": "Análisis de Wi-Fi",
"vendor": "Vendedor"
} }
} }

View File

@@ -804,6 +804,7 @@
"mode": "Mode", "mode": "Mode",
"network_diagram": "Diagramme de réseau", "network_diagram": "Diagramme de réseau",
"radios": "Radios", "radios": "Radios",
"title": "Analyse Wi-Fi" "title": "Analyse Wi-Fi",
"vendor": "vendeur"
} }
} }

View File

@@ -804,6 +804,7 @@
"mode": "Modo", "mode": "Modo",
"network_diagram": "Diagrama de rede", "network_diagram": "Diagrama de rede",
"radios": "Rádios", "radios": "Rádios",
"title": "Análise de Wi-Fi" "title": "Análise de Wi-Fi",
"vendor": "fornecedor"
} }
} }

View File

@@ -2,9 +2,8 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
CWidgetDropdown, CCardHeader,
CRow, CCardBody,
CCol,
CButton, CButton,
CDataTable, CDataTable,
CCard, CCard,
@@ -262,31 +261,47 @@ const DeviceCommands = () => {
return ( return (
<div> <div>
<CWidgetDropdown <CCard className="m-0">
className="m-0" <CCardHeader className="dark-header">
inverse="true" <div className="d-flex flex-row-reverse align-items-center">
color="gradient-primary" <div className="pl-2">
header={t('commands.title')} <CPopover content={t('common.refresh')}>
footerSlot={ <CButton
<div className="pb-1 px-3"> size="sm"
<CRow className="mb-2"> color="info"
<CCol> onClick={getCommands}
From: disabled={startError || endError}
<DatePicker includeTime onChange={(date) => modifyStart(date)} /> >
<CFormText color="danger" hidden={!startError}> <CIcon content={cilSync} />
{t('common.invalid_date_explanation')} </CButton>
</CFormText> </CPopover>
</CCol> </div>
<CCol> <div className="pl-2">
To: <DatePicker
<DatePicker includeTime onChange={(date) => modifyEnd(date)} /> includeTime
onChange={(date) => modifyEnd(date)}
value={end ? new Date(end) : undefined}
/>
<CFormText color="danger" hidden={!endError}> <CFormText color="danger" hidden={!endError}>
{t('common.invalid_date_explanation')} {t('common.invalid_date_explanation')}
</CFormText> </CFormText>
</CCol> </div>
</CRow> To:
<CCard> <div className="pl-2">
<div className="overflow-auto" style={{ height: '200px' }}> <DatePicker
includeTime
onChange={(date) => modifyStart(date)}
value={start ? new Date(start) : undefined}
/>
<CFormText color="danger" hidden={!startError}>
{t('common.invalid_date_explanation')}
</CFormText>
</div>
From:
</div>
</CCardHeader>
<CCardBody className="p-1">
<div className="overflow-auto" style={{ height: 'calc(100vh - 620px)' }}>
<CDataTable <CDataTable
addTableClasses="ignore-overflow table-sm" addTableClasses="ignore-overflow table-sm"
border border
@@ -400,17 +415,8 @@ const DeviceCommands = () => {
</div> </div>
)} )}
</div> </div>
</CCardBody>
</CCard> </CCard>
</div>
}
>
<div className="text-right float-right">
<CButton onClick={refreshCommands} size="sm">
<CIcon name="cil-sync" content={cilSync} className="text-white" size="2xl" />
</CButton>
</div>
</CWidgetDropdown>
<WifiScanResultModalWidget <WifiScanResultModalWidget
show={showScanModal} show={showScanModal}
toggle={toggleScanModal} toggle={toggleScanModal}

View File

@@ -20,7 +20,7 @@ import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import 'react-widgets/styles.css'; import 'react-widgets/styles.css';
import { useAuth, useDevice } from 'ucentral-libs'; import { useAuth, useDevice, useToast } from 'ucentral-libs';
import { checkIfJson } from 'utils/helper'; import { checkIfJson } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus'; import eventBus from 'utils/eventBus';
@@ -29,6 +29,7 @@ import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
const ConfigureModal = ({ show, toggleModal }) => { const ConfigureModal = ({ show, toggleModal }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
const { addToast } = useToast();
const { deviceSerialNumber } = useDevice(); const { deviceSerialNumber } = useDevice();
const [hadSuccess, setHadSuccess] = useState(false); const [hadSuccess, setHadSuccess] = useState(false);
const [hadFailure, setHadFailure] = useState(false); const [hadFailure, setHadFailure] = useState(false);
@@ -91,7 +92,13 @@ const ConfigureModal = ({ show, toggleModal }) => {
{ headers }, { headers },
) )
.then(() => { .then(() => {
setHadSuccess(true); addToast({
title: t('common.success'),
body: t('commands.command_success'),
color: 'success',
autohide: true,
});
toggleModal();
}) })
.catch(() => { .catch(() => {
setResponseBody('Error while submitting command!'); setResponseBody('Error while submitting command!');

View File

@@ -1,17 +1,17 @@
/* eslint-disable-rule prefer-destructuring */ /* eslint-disable-rule prefer-destructuring */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { import {
CWidgetDropdown, CCardBody,
CButton, CButton,
CDataTable, CDataTable,
CCard, CCardHeader,
CRow,
CCol,
CProgress,
CPopover, CPopover,
CCard,
CFormText,
CBadge,
} from '@coreui/react'; } from '@coreui/react';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import { cilTrash } from '@coreui/icons'; import { cilSync, cilTrash } from '@coreui/icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker'; import DatePicker from 'react-widgets/DatePicker';
import { dateToUnix } from 'utils/helper'; import { dateToUnix } from 'utils/helper';
@@ -27,7 +27,9 @@ const DeviceHealth = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [healthChecks, setHealthChecks] = useState([]); const [healthChecks, setHealthChecks] = useState([]);
const [start, setStart] = useState(''); const [start, setStart] = useState('');
const [startError, setStartError] = useState(false);
const [end, setEnd] = useState(''); const [end, setEnd] = useState('');
const [endError, setEndError] = useState(false);
const [logLimit, setLogLimit] = useState(25); const [logLimit, setLogLimit] = useState(25);
const [loadingMore, setLoadingMore] = useState(false); const [loadingMore, setLoadingMore] = useState(false);
const [showLoadingMore, setShowLoadingMore] = useState(true); const [showLoadingMore, setShowLoadingMore] = useState(true);
@@ -40,13 +42,26 @@ const DeviceHealth = () => {
}; };
const modifyStart = (value) => { const modifyStart = (value) => {
try {
new Date(value).toISOString();
setStartError(false);
setStart(value); setStart(value);
} catch (e) {
setStart('');
setStartError(true);
}
}; };
const modifyEnd = (value) => { const modifyEnd = (value) => {
try {
new Date(value).toISOString();
setEndError(false);
setEnd(value); setEnd(value);
} catch (e) {
setEnd('');
setEndError(true);
}
}; };
const showMoreLogs = () => { const showMoreLogs = () => {
setLogLimit(logLimit + 50); setLogLimit(logLimit + 50);
}; };
@@ -128,14 +143,14 @@ const DeviceHealth = () => {
const tempSanityLevel = sortedHealthchecks[healthChecks.length - 1].sanity; const tempSanityLevel = sortedHealthchecks[healthChecks.length - 1].sanity;
setSanityLevel(tempSanityLevel); setSanityLevel(tempSanityLevel);
if (tempSanityLevel === 100) { if (tempSanityLevel === 100) {
setBarColor('gradient-success'); setBarColor('success');
} else if (tempSanityLevel >= 90) { } else if (tempSanityLevel >= 90) {
setBarColor('gradient-warning'); setBarColor('warning');
} else { } else {
setBarColor('gradient-danger'); setBarColor('danger');
} }
} else { } else {
setBarColor('gradient-dark'); setBarColor('dark');
} }
}, [healthChecks]); }, [healthChecks]);
@@ -156,30 +171,61 @@ const DeviceHealth = () => {
}, []); }, []);
return ( return (
<CWidgetDropdown <CCard className="m-0">
className="m-0" <CCardHeader className="dark-header">
header={t('health.title')} <div className="float-left align-middle pt-1">
text={sanityLevel ? `${sanityLevel}%` : t('common.unknown')} <h4>
value={sanityLevel ?? 100} <CBadge color={barColor} className="my-0">
color={barColor} {sanityLevel ? `${sanityLevel}%` : `${t('common.unknown')} Sanity Level`}
inverse="true" </CBadge>
footerSlot={ </h4>
<div className="pb-1 px-3"> </div>
<CProgress className="mb-3" color="white" value={sanityLevel ?? 0} /> <div className="d-flex flex-row-reverse align-items-center">
<CRow className="mb-3"> <div className="pl-2">
<CCol> <CPopover content={t('common.refresh')}>
{t('common.from')} <CButton
: size="sm"
<DatePicker includeTime onChange={(date) => modifyStart(date)} /> color="info"
</CCol> onClick={getDeviceHealth}
<CCol> disabled={startError || endError}
{t('common.to')} >
: <CIcon content={cilSync} />
<DatePicker includeTime onChange={(date) => modifyEnd(date)} /> </CButton>
</CCol> </CPopover>
</CRow> </div>
<CCard className="p-0"> <div className="pl-2">
<div className="overflow-auto" style={{ height: '200px' }}> <DatePicker
includeTime
onChange={(date) => modifyEnd(date)}
value={end ? new Date(end) : undefined}
/>
<CFormText color="danger" hidden={!endError}>
{t('common.invalid_date_explanation')}
</CFormText>
</div>
To:
<div className="pl-2">
<DatePicker
includeTime
onChange={(date) => modifyStart(date)}
value={start ? new Date(start) : undefined}
/>
<CFormText color="danger" hidden={!startError}>
{t('common.invalid_date_explanation')}
</CFormText>
</div>
From:
<div className="px-2">
<CPopover content={t('common.delete')}>
<CButton onClick={toggleDeleteModal} size="sm" color="danger">
<CIcon name="cil-trash" content={cilTrash} />
</CButton>
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody className="p-1">
<div className="overflow-auto" style={{ height: 'calc(100vh - 620px)' }}>
<CDataTable <CDataTable
addTableClasses="ignore-overflow table-sm" addTableClasses="ignore-overflow table-sm"
border border
@@ -214,25 +260,15 @@ const DeviceHealth = () => {
/> />
</div> </div>
)} )}
</div>
</CCard>
<DeleteLogModal <DeleteLogModal
serialNumber={deviceSerialNumber} serialNumber={deviceSerialNumber}
object="healthchecks" object="logs"
show={showDeleteModal} show={showDeleteModal}
toggle={toggleDeleteModal} toggle={toggleDeleteModal}
/> />
</div> </div>
} </CCardBody>
> </CCard>
<div className="text-right float-right">
<CPopover content={t('common.delete')}>
<CButton onClick={toggleDeleteModal} size="sm">
<CIcon name="cil-trash" content={cilTrash} className="text-white" size="2xl" />
</CButton>
</CPopover>
</div>
</CWidgetDropdown>
); );
}; };

View File

@@ -1,18 +1,17 @@
/* eslint-disable-rule prefer-destructuring */ /* eslint-disable-rule prefer-destructuring */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { import {
CWidgetDropdown, CCardHeader,
CRow, CCardBody,
CCol,
CCollapse, CCollapse,
CButton, CButton,
CDataTable, CDataTable,
CCard, CCard,
CCardBody,
CPopover, CPopover,
CFormText,
} from '@coreui/react'; } from '@coreui/react';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import { cilTrash } from '@coreui/icons'; import { cilSync, cilTrash } from '@coreui/icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker'; import DatePicker from 'react-widgets/DatePicker';
import { dateToUnix } from 'utils/helper'; import { dateToUnix } from 'utils/helper';
@@ -29,7 +28,9 @@ const DeviceLogs = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [logs, setLogs] = useState([]); const [logs, setLogs] = useState([]);
const [start, setStart] = useState(''); const [start, setStart] = useState('');
const [startError, setStartError] = useState(false);
const [end, setEnd] = useState(''); const [end, setEnd] = useState('');
const [endError, setEndError] = useState(false);
const [logLimit, setLogLimit] = useState(25); const [logLimit, setLogLimit] = useState(25);
const [loadingMore, setLoadingMore] = useState(false); const [loadingMore, setLoadingMore] = useState(false);
const [showLoadingMore, setShowLoadingMore] = useState(true); const [showLoadingMore, setShowLoadingMore] = useState(true);
@@ -40,11 +41,25 @@ const DeviceLogs = () => {
}; };
const modifyStart = (value) => { const modifyStart = (value) => {
try {
new Date(value).toISOString();
setStartError(false);
setStart(value); setStart(value);
} catch (e) {
setStart('');
setStartError(true);
}
}; };
const modifyEnd = (value) => { const modifyEnd = (value) => {
try {
new Date(value).toISOString();
setEndError(false);
setEnd(value); setEnd(value);
} catch (e) {
setEnd('');
setEndError(true);
}
}; };
const showMoreLogs = () => { const showMoreLogs = () => {
@@ -167,25 +182,49 @@ const DeviceLogs = () => {
return ( return (
<div> <div>
<CWidgetDropdown <CCard className="m-0">
className="m-0" <CCardHeader className="dark-header">
inverse="true" <div className="d-flex flex-row-reverse align-items-center">
color="gradient-info" <div className="pl-2">
header={t('device_logs.title')} <CPopover content={t('common.refresh')}>
footerSlot={ <CButton size="sm" color="info" onClick={getLogs} disabled={startError || endError}>
<div className="pb-1 px-3"> <CIcon content={cilSync} />
<CRow className="mb-3"> </CButton>
<CCol> </CPopover>
{t('common.from')} </div>
<DatePicker includeTime onChange={(date) => modifyStart(date)} /> <div className="pl-2">
</CCol> <DatePicker
<CCol> includeTime
{t('common.to')} onChange={(date) => modifyEnd(date)}
<DatePicker includeTime onChange={(date) => modifyEnd(date)} /> value={end ? new Date(end) : undefined}
</CCol> />
</CRow> <CFormText color="danger" hidden={!endError}>
<CCard> {t('common.invalid_date_explanation')}
<div className="overflow-auto" style={{ height: '250px' }}> </CFormText>
</div>
To:
<div className="pl-2">
<DatePicker
includeTime
onChange={(date) => modifyStart(date)}
value={start ? new Date(start) : undefined}
/>
<CFormText color="danger" hidden={!startError}>
{t('common.invalid_date_explanation')}
</CFormText>
</div>
From:
<div className="px-2">
<CPopover content={t('common.delete')}>
<CButton onClick={toggleDeleteModal} size="sm" color="danger">
<CIcon name="cil-trash" content={cilTrash} />
</CButton>
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody className="p-1">
<div className="overflow-auto" style={{ height: 'calc(100vh - 620px)' }}>
<CDataTable <CDataTable
addTableClasses="ignore-overflow table-sm" addTableClasses="ignore-overflow table-sm"
border border
@@ -240,18 +279,8 @@ const DeviceLogs = () => {
</div> </div>
)} )}
</div> </div>
</CCardBody>
</CCard> </CCard>
</div>
}
>
<div className="text-right float-right">
<CPopover content={t('common.delete')}>
<CButton onClick={toggleDeleteModal} size="sm">
<CIcon name="cil-trash" content={cilTrash} className="text-white" size="2xl" />
</CButton>
</CPopover>
</div>
</CWidgetDropdown>
<DeleteLogModal <DeleteLogModal
serialNumber={deviceSerialNumber} serialNumber={deviceSerialNumber}
object="logs" object="logs"

View File

@@ -22,10 +22,11 @@ const WifiAnalysisTable = ({ t, data, loading }) => {
setShow(!show); setShow(!show);
}; };
const columns = [ const columns = [
{ key: 'radio', label: '#', _style: { width: '5%' } }, { key: 'radio', label: '#', _style: { width: '1%' } },
{ key: 'bssid', label: 'BSSID', _style: { width: '14%' } }, { key: 'bssid', label: 'BSSID', _style: { width: '1%' } },
{ key: 'mode', label: t('wifi_analysis.mode'), _style: { width: '9%' }, sorter: false }, { key: 'vendor', label: t('wifi_analysis.vendor'), _style: { width: '15%' }, sorter: false },
{ key: 'ssid', label: 'SSID', _style: { width: '17%' } }, { key: 'mode', label: t('wifi_analysis.mode'), _style: { width: '1%' }, sorter: false },
{ key: 'ssid', label: 'SSID', _style: { width: '15%' } },
{ key: 'rssi', label: 'RSSI', _style: { width: '5%' }, sorter: false }, { key: 'rssi', label: 'RSSI', _style: { width: '5%' }, sorter: false },
{ key: 'rxRate', label: 'Rx Rate', _style: { width: '7%' }, sorter: false }, { key: 'rxRate', label: 'Rx Rate', _style: { width: '7%' }, sorter: false },
{ key: 'rxBytes', label: 'Rx', _style: { width: '7%' }, sorter: false }, { key: 'rxBytes', label: 'Rx', _style: { width: '7%' }, sorter: false },
@@ -33,26 +34,39 @@ const WifiAnalysisTable = ({ t, data, loading }) => {
{ key: 'rxNss', label: 'Rx NSS', _style: { width: '6%' }, sorter: false }, { key: 'rxNss', label: 'Rx NSS', _style: { width: '6%' }, sorter: false },
{ key: 'txRate', label: 'Tx Rate', _style: { width: '7%' }, sorter: false }, { key: 'txRate', label: 'Tx Rate', _style: { width: '7%' }, sorter: false },
{ key: 'txBytes', label: 'Tx', _style: { width: '7%' }, sorter: false }, { key: 'txBytes', label: 'Tx', _style: { width: '7%' }, sorter: false },
{ key: 'ips', label: 'IP', _style: { width: '6%' }, sorter: false }, { key: 'ips', label: 'IP', _style: { width: '1%' }, sorter: false },
]; ];
const centerIfEmpty = (value) => ( const centerIfEmpty = (value) => (
<td className={!value || value === '' || value === '-' ? 'text-center' : ''}>{value}</td> <td
className={
!value || value === '' || value === '-'
? 'text-center align-middle'
: 'text-right align-middle'
}
>
{value}
</td>
); );
const displayIp = (ssid, v4, v6) => { const displayIp = (ssid, v4, v6) => {
const count = v4.length + v6.length; const count = v4.length + v6.length;
return ( return (
<td className="ignore-overflow text-center"> <td className="ignore-overflow text-center align-middle">
{count > 0 ? ( {count > 0 ? (
<CPopover content="View"> <CPopover content="View">
<CButton color="primary" size="sm" onClick={() => toggle(ssid, v4, v6)}> <CButton
color="primary"
size="sm"
onClick={() => toggle(ssid, v4, v6)}
className="py-1"
>
{count} {count}
</CButton> </CButton>
</CPopover> </CPopover>
) : ( ) : (
<p>{count}</p> count
)} )}
</td> </td>
); );
@@ -70,8 +84,23 @@ const WifiAnalysisTable = ({ t, data, loading }) => {
sorter sorter
sorterValue={{ column: 'radio', asc: true }} sorterValue={{ column: 'radio', asc: true }}
scopedSlots={{ scopedSlots={{
radio: (item) => <td className="text-center">{item.radio.radio}</td>, bssid: (item) => (
<td
className="text-center align-middle"
style={{ fontFamily: 'monospace', fontSize: '0.96rem' }}
>
{item.bssid}
</td>
),
radio: (item) => <td className="text-center align-middle">{item.radio.radio}</td>,
ssid: (item) => <td className="align-middle">{item.ssid}</td>,
mode: (item) => <td className="align-middle">{item.mode}</td>,
vendor: (item) => <td className="align-middle">{item.vendor}</td>,
rxMcs: (item) => centerIfEmpty(item.rxMcs), rxMcs: (item) => centerIfEmpty(item.rxMcs),
rxRate: (item) => centerIfEmpty(item.rxRate),
rxBytes: (item) => centerIfEmpty(item.rxBytes),
txRate: (item) => centerIfEmpty(item.txRate),
txBytes: (item) => centerIfEmpty(item.txBytes),
rxNss: (item) => centerIfEmpty(item.rxNss), rxNss: (item) => centerIfEmpty(item.rxNss),
rssi: (item) => centerIfEmpty(item.rssi), rssi: (item) => centerIfEmpty(item.rssi),
ips: (item) => displayIp(item.ssid, item.ipV4, item.ipV6), ips: (item) => displayIp(item.ssid, item.ipV4, item.ipV6),

View File

@@ -19,7 +19,7 @@ import {
CPopover, CPopover,
} from '@coreui/react'; } from '@coreui/react';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import { cilX } from '@coreui/icons'; import { cilSync, cilX } from '@coreui/icons';
import RadioAnalysisTable from './RadioAnalysis'; import RadioAnalysisTable from './RadioAnalysis';
import WifiAnalysisTable from './WifiAnalysis'; import WifiAnalysisTable from './WifiAnalysis';
@@ -67,9 +67,33 @@ const WifiAnalysis = () => {
return ips; return ips;
}; };
const parseAssociationStats = (json) => { 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 newParsedAssociationStats = [];
const newParsedRadioStats = []; const newParsedRadioStats = [];
const bssidObj = {};
for (const stat of json.data) { for (const stat of json.data) {
const associations = []; const associations = [];
@@ -123,6 +147,8 @@ const WifiAnalysis = () => {
if ('associations' in ssid) { if ('associations' in ssid) {
for (const association of ssid.associations) { for (const association of ssid.associations) {
bssidObj[association.bssid] = 0;
const data = { const data = {
radio: radioInfo, radio: radioInfo,
...extractIp(stat.data, association.bssid), ...extractIp(stat.data, association.bssid),
@@ -149,6 +175,16 @@ const WifiAnalysis = () => {
newParsedAssociationStats.push(associations); 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].bssid] ?? '-';
}
}
// Radio Stats // Radio Stats
const ascOrderedRadioStats = newParsedRadioStats.reverse(); const ascOrderedRadioStats = newParsedRadioStats.reverse();
setParsedRadioStats(ascOrderedRadioStats); setParsedRadioStats(ascOrderedRadioStats);
@@ -203,14 +239,19 @@ const WifiAnalysis = () => {
return ( return (
<div> <div>
<CCard> <CCard>
<CCardHeader className="dark-header"> <CCardHeader className="dark-header d-flex flex-row-reverse align-items-center">
<CRow> <div className="pl-2">
<CCol className="text-right"> <CPopover content={t('common.refresh')}>
<CButton size="sm" color="info" onClick={getLatestAssociationStats}>
<CIcon content={cilSync} />
</CButton>
</CPopover>
</div>
<div>
<CButton color="info" size="sm" onClick={toggleModal}> <CButton color="info" size="sm" onClick={toggleModal}>
{t('wifi_analysis.network_diagram')} {t('wifi_analysis.network_diagram')}
</CButton> </CButton>
</CCol> </div>
</CRow>
</CCardHeader> </CCardHeader>
<CCardBody> <CCardBody>
<CRow className="mb-4"> <CRow className="mb-4">