mirror of
https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
synced 2025-10-30 17:57:46 +00:00
[WIFI-12614] Dynamic VLAN support
Signed-off-by: Charles <charles.bourque96@gmail.com>
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ucentral-client",
|
||||
"version": "2.10.0(42)",
|
||||
"version": "2.10.0(44)",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ucentral-client",
|
||||
"version": "2.10.0(42)",
|
||||
"version": "2.10.0(44)",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@chakra-ui/icons": "^2.0.18",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ucentral-client",
|
||||
"version": "2.10.0(42)",
|
||||
"version": "2.10.0(44)",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"main": "index.tsx",
|
||||
|
||||
@@ -39,6 +39,7 @@ type DeviceInterfaceStatistics = {
|
||||
ack_signal_avg: number;
|
||||
bssid: string;
|
||||
connected: number;
|
||||
dynamic_vlan?: number;
|
||||
inactive: number;
|
||||
ipaddr_v4: string;
|
||||
rssi: number;
|
||||
@@ -117,6 +118,13 @@ export type DeviceStatistics = {
|
||||
transmit_ms: number;
|
||||
tx_power: number;
|
||||
}[];
|
||||
dynamic_vlans?: {
|
||||
vid: number;
|
||||
rx_bytes: number;
|
||||
rx_packets: number;
|
||||
tx_bytes: number;
|
||||
tx_packets: number;
|
||||
}[];
|
||||
unit?: {
|
||||
load: [number, number, number];
|
||||
localtime: number;
|
||||
|
||||
@@ -69,7 +69,7 @@ const RestrictionsCard = ({ serialNumber }: Props) => {
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</CardHeader>
|
||||
<CardBody p={0} display="block">
|
||||
<CardBody display="block">
|
||||
<Flex mt={2}>
|
||||
<Heading size="sm" mr={2} my="auto">
|
||||
{t('restrictions.countries')}:
|
||||
|
||||
@@ -29,22 +29,44 @@ const getDivisionFactor = (maxBytes: number) => {
|
||||
return { factor: 1024 * 1024 * 1024, unit: 'GB' };
|
||||
};
|
||||
|
||||
const getDivisionFactorPackets = (maxPackets: number) => {
|
||||
if (maxPackets < 1000) {
|
||||
return { factor: 1, unit: '' };
|
||||
}
|
||||
if (maxPackets < 1000 * 1000) {
|
||||
return { factor: 1000, unit: 'K' };
|
||||
}
|
||||
if (maxPackets < 1000 * 1000 * 1000) {
|
||||
return { factor: 1000 * 1000, unit: 'M' };
|
||||
}
|
||||
return { factor: 1000 * 1000 * 1000, unit: 'G' };
|
||||
};
|
||||
|
||||
type Props = {
|
||||
data: {
|
||||
tx: number[];
|
||||
rx: number[];
|
||||
packetsRx: number[];
|
||||
packetsTx: number[];
|
||||
recorded: number[];
|
||||
maxTx: number;
|
||||
maxRx: number;
|
||||
maxTx: number;
|
||||
maxPacketsRx: number;
|
||||
maxPacketsTx: number;
|
||||
removed?: boolean;
|
||||
};
|
||||
format: 'bytes' | 'packets';
|
||||
};
|
||||
|
||||
const InterfaceChart = ({ data }: Props) => {
|
||||
const InterfaceChart = ({ data, format }: Props) => {
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
const { factor, unit } = getDivisionFactor(data.maxTx);
|
||||
const packetsFactor = getDivisionFactorPackets(
|
||||
data.maxPacketsTx > data.maxPacketsRx ? data.maxPacketsTx : data.maxPacketsRx,
|
||||
);
|
||||
|
||||
const points: ChartData<'line', string[], string> = {
|
||||
const bytesPoints: ChartData<'line', string[], string> = {
|
||||
labels: data.recorded.map((recorded) => new Date(recorded * 1000).toLocaleTimeString()),
|
||||
datasets: [
|
||||
{
|
||||
@@ -69,6 +91,38 @@ const InterfaceChart = ({ data }: Props) => {
|
||||
},
|
||||
],
|
||||
};
|
||||
const packetPoints: ChartData<'line', string[], string> = {
|
||||
labels: data.recorded.map((recorded) => new Date(recorded * 1000).toLocaleTimeString()),
|
||||
datasets: [
|
||||
{
|
||||
// Real 'Tx', but shown as 'Rx'
|
||||
label: 'Tx',
|
||||
data: data.packetsRx.map((rx) => rx.toString()),
|
||||
borderColor: colorMode === 'light' ? 'rgba(99, 179, 237, 1)' : 'rgba(190, 227, 248, 1)', // blue-300 - blue-100
|
||||
backgroundColor: colorMode === 'light' ? 'rgba(99, 179, 237, 0.3)' : 'rgba(190, 227, 248, 0.3)', // blue-300 - blue-100
|
||||
tension: 0.5,
|
||||
pointRadius: 0,
|
||||
fill: 'start',
|
||||
},
|
||||
{
|
||||
// Real 'Tx', but shown as 'Rx'
|
||||
label: 'Rx',
|
||||
data: data.packetsTx.map((tx) => tx.toString()),
|
||||
borderColor: colorMode === 'light' ? 'rgba(72, 187, 120, 1)' : 'rgba(154, 230, 180, 1)', // green-400 - green-200
|
||||
backgroundColor: colorMode === 'light' ? 'rgba(72, 187, 120, 0.3)' : 'rgba(154, 230, 180, 0.3)', // green-400 - green-200
|
||||
tension: 0.5,
|
||||
pointRadius: 0,
|
||||
fill: 'start',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const packetsTick = (value: number) => {
|
||||
if (packetsFactor.factor === 1) {
|
||||
return value.toLocaleString();
|
||||
}
|
||||
return `${(value / packetsFactor.factor).toLocaleString()}${packetsFactor.unit}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Line
|
||||
@@ -89,7 +143,10 @@ const InterfaceChart = ({ data }: Props) => {
|
||||
intersect: false,
|
||||
|
||||
callbacks: {
|
||||
label: (context) => `${context.dataset.label}: ${context.formattedValue} ${unit}`,
|
||||
label:
|
||||
format === 'bytes'
|
||||
? (context) => `${context.dataset.label}: ${context.formattedValue} ${unit}`
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -109,7 +166,10 @@ const InterfaceChart = ({ data }: Props) => {
|
||||
},
|
||||
ticks: {
|
||||
color: colorMode === 'dark' ? 'white' : undefined,
|
||||
callback: (tickValue) => `${tickValue} ${unit}`,
|
||||
callback:
|
||||
format === 'bytes'
|
||||
? (tickValue) => `${tickValue} ${unit}`
|
||||
: (tickValue) => (typeof tickValue === 'number' ? packetsTick(tickValue) : tickValue),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -118,7 +178,7 @@ const InterfaceChart = ({ data }: Props) => {
|
||||
intersect: true,
|
||||
},
|
||||
}}
|
||||
data={points}
|
||||
data={format === 'bytes' ? bytesPoints : packetPoints}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
186
src/pages/Device/StatisticsCard/VlanChart.tsx
Normal file
186
src/pages/Device/StatisticsCard/VlanChart.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import * as React from 'react';
|
||||
import { useColorMode } from '@chakra-ui/react';
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
ChartData,
|
||||
Filler,
|
||||
} from 'chart.js';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
|
||||
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, Filler);
|
||||
|
||||
const getDivisionFactor = (maxBytes: number) => {
|
||||
if (maxBytes < 1024) {
|
||||
return { factor: 1, unit: 'B' };
|
||||
}
|
||||
if (maxBytes < 1024 * 1024) {
|
||||
return { factor: 1024, unit: 'KB' };
|
||||
}
|
||||
if (maxBytes < 1024 * 1024 * 1024) {
|
||||
return { factor: 1024 * 1024, unit: 'MB' };
|
||||
}
|
||||
return { factor: 1024 * 1024 * 1024, unit: 'GB' };
|
||||
};
|
||||
|
||||
const getDivisionFactorPackets = (maxPackets: number) => {
|
||||
if (maxPackets < 1000) {
|
||||
return { factor: 1, unit: '' };
|
||||
}
|
||||
if (maxPackets < 1000 * 1000) {
|
||||
return { factor: 1000, unit: 'K' };
|
||||
}
|
||||
if (maxPackets < 1000 * 1000 * 1000) {
|
||||
return { factor: 1000 * 1000, unit: 'M' };
|
||||
}
|
||||
return { factor: 1000 * 1000 * 1000, unit: 'G' };
|
||||
};
|
||||
|
||||
type Props = {
|
||||
data: {
|
||||
tx: number[];
|
||||
rx: number[];
|
||||
packetsRx: number[];
|
||||
packetsTx: number[];
|
||||
recorded: number[];
|
||||
maxRx: number;
|
||||
maxTx: number;
|
||||
maxPacketsRx: number;
|
||||
maxPacketsTx: number;
|
||||
removed?: boolean;
|
||||
};
|
||||
format: 'bytes' | 'packets';
|
||||
};
|
||||
|
||||
const VlanChart = ({ data, format }: Props) => {
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
const { factor, unit } = getDivisionFactor(data.maxTx);
|
||||
const packetsFactor = getDivisionFactorPackets(
|
||||
data.maxPacketsTx > data.maxPacketsRx ? data.maxPacketsTx : data.maxPacketsRx,
|
||||
);
|
||||
|
||||
const bytesPoints: ChartData<'line', string[], string> = {
|
||||
labels: data.recorded.map((recorded) => new Date(recorded * 1000).toLocaleTimeString()),
|
||||
datasets: [
|
||||
{
|
||||
// Real 'Tx', but shown as 'Rx'
|
||||
label: 'Tx',
|
||||
data: data.rx.map((tx) => (Math.floor((tx / factor) * 100) / 100).toFixed(2)),
|
||||
borderColor: colorMode === 'light' ? 'rgba(99, 179, 237, 1)' : 'rgba(190, 227, 248, 1)', // blue-300 - blue-100
|
||||
backgroundColor: colorMode === 'light' ? 'rgba(99, 179, 237, 0.3)' : 'rgba(190, 227, 248, 0.3)', // blue-300 - blue-100
|
||||
tension: 0.5,
|
||||
pointRadius: 0,
|
||||
fill: 'start',
|
||||
},
|
||||
{
|
||||
// Real 'Rx', but shown as 'Tx'
|
||||
label: 'Rx',
|
||||
data: data.tx.map((rx) => (Math.floor((rx / factor) * 100) / 100).toFixed(2)),
|
||||
borderColor: colorMode === 'light' ? 'rgba(72, 187, 120, 1)' : 'rgba(154, 230, 180, 1)', // green-400 - green-200
|
||||
backgroundColor: colorMode === 'light' ? 'rgba(72, 187, 120, 0.3)' : 'rgba(154, 230, 180, 0.3)', // green-400 - green-200
|
||||
tension: 0.5,
|
||||
pointRadius: 0,
|
||||
fill: 'start',
|
||||
},
|
||||
],
|
||||
};
|
||||
const packetPoints: ChartData<'line', string[], string> = {
|
||||
labels: data.recorded.map((recorded) => new Date(recorded * 1000).toLocaleTimeString()),
|
||||
datasets: [
|
||||
{
|
||||
// Real 'Tx', but shown as 'Rx'
|
||||
label: 'Tx',
|
||||
data: data.packetsRx.map((rx) => rx.toString()),
|
||||
borderColor: colorMode === 'light' ? 'rgba(99, 179, 237, 1)' : 'rgba(190, 227, 248, 1)', // blue-300 - blue-100
|
||||
backgroundColor: colorMode === 'light' ? 'rgba(99, 179, 237, 0.3)' : 'rgba(190, 227, 248, 0.3)', // blue-300 - blue-100
|
||||
tension: 0.5,
|
||||
pointRadius: 0,
|
||||
fill: 'start',
|
||||
},
|
||||
{
|
||||
// Real 'Tx', but shown as 'Rx'
|
||||
label: 'Rx',
|
||||
data: data.packetsTx.map((tx) => tx.toString()),
|
||||
borderColor: colorMode === 'light' ? 'rgba(72, 187, 120, 1)' : 'rgba(154, 230, 180, 1)', // green-400 - green-200
|
||||
backgroundColor: colorMode === 'light' ? 'rgba(72, 187, 120, 0.3)' : 'rgba(154, 230, 180, 0.3)', // green-400 - green-200
|
||||
tension: 0.5,
|
||||
pointRadius: 0,
|
||||
fill: 'start',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const packetsTick = (value: number) => {
|
||||
if (packetsFactor.factor === 1) {
|
||||
return value.toLocaleString();
|
||||
}
|
||||
return `${(value / packetsFactor.factor).toLocaleString()}${packetsFactor.unit}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Line
|
||||
options={{
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top' as const,
|
||||
labels: {
|
||||
color: colorMode === 'dark' ? 'white' : undefined,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
mode: 'index',
|
||||
position: 'nearest',
|
||||
intersect: false,
|
||||
|
||||
callbacks: {
|
||||
label:
|
||||
format === 'bytes'
|
||||
? (context) => `${context.dataset.label}: ${context.formattedValue} ${unit}`
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
color: colorMode === 'dark' ? 'white' : undefined,
|
||||
},
|
||||
ticks: {
|
||||
color: colorMode === 'dark' ? 'white' : undefined,
|
||||
maxRotation: 10,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
grid: {
|
||||
color: colorMode === 'dark' ? 'white' : undefined,
|
||||
},
|
||||
ticks: {
|
||||
color: colorMode === 'dark' ? 'white' : undefined,
|
||||
callback:
|
||||
format === 'bytes'
|
||||
? (tickValue) => `${tickValue} ${unit}`
|
||||
: (tickValue) => (typeof tickValue === 'number' ? packetsTick(tickValue) : tickValue),
|
||||
},
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
mode: 'nearest',
|
||||
intersect: true,
|
||||
},
|
||||
}}
|
||||
data={format === 'bytes' ? bytesPoints : packetPoints}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(VlanChart);
|
||||
@@ -8,6 +8,7 @@ import InterfaceChart from './InterfaceChart';
|
||||
import DeviceMemoryChart from './MemoryChart';
|
||||
import { useStatisticsCard } from './useStatisticsCard';
|
||||
import ViewLastStatsModal from './ViewLastStatsModal';
|
||||
import VlanChart from './VlanChart';
|
||||
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
||||
import { Card } from 'components/Containers/Card';
|
||||
import { CardBody } from 'components/Containers/Card/CardBody';
|
||||
@@ -42,6 +43,7 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
||||
const { time, setTime, parsedData, isLoading, selected, onSelectInterface, refresh } = useStatisticsCard({
|
||||
serialNumber,
|
||||
});
|
||||
const [formatChosen, setFormatChosen] = React.useState<'bytes' | 'packets'>('bytes');
|
||||
|
||||
const setNewTime = (start: Date, end: Date) => {
|
||||
setTime({ start, end });
|
||||
@@ -50,15 +52,29 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
||||
setTime(undefined);
|
||||
};
|
||||
|
||||
const onFormatChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setFormatChosen(e.target.value as 'bytes' | 'packets');
|
||||
};
|
||||
|
||||
const interfaces = React.useMemo(() => {
|
||||
if (!parsedData) return undefined;
|
||||
|
||||
return Object.entries(parsedData.interfaces).map(([name, data]) => (
|
||||
<Box hidden={name !== selected} key={uuid()}>
|
||||
<InterfaceChart data={data} />
|
||||
<InterfaceChart data={data} format={formatChosen} />
|
||||
</Box>
|
||||
));
|
||||
}, [parsedData, selected]);
|
||||
}, [parsedData, selected, formatChosen]);
|
||||
const vlans = React.useMemo(() => {
|
||||
if (!parsedData) return undefined;
|
||||
|
||||
return Object.entries(parsedData.vlans).map(([name, data]) => (
|
||||
<Box hidden={`VLAN-${name}` !== selected} key={uuid()}>
|
||||
<VlanChart data={data} format={formatChosen} />
|
||||
</Box>
|
||||
));
|
||||
}, [parsedData, selected, formatChosen]);
|
||||
|
||||
const memory = React.useMemo(() => {
|
||||
if (!parsedData) return undefined;
|
||||
|
||||
@@ -76,7 +92,13 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
||||
<Heading size="md">{t('configurations.statistics')}</Heading>
|
||||
<Spacer />
|
||||
<HStack>
|
||||
<Select value={selected} onChange={onSelectInterface}>
|
||||
{selected === 'memory' ? null : (
|
||||
<Select value={formatChosen} onChange={onFormatChange} w="112px">
|
||||
<option value="bytes">Data</option>
|
||||
<option value="packets">Packets</option>
|
||||
</Select>
|
||||
)}
|
||||
<Select value={selected} onChange={onSelectInterface} w="unset">
|
||||
{parsedData?.interfaces
|
||||
? Object.keys(parsedData.interfaces).map((v) => (
|
||||
<option value={v} key={uuid()}>
|
||||
@@ -84,6 +106,13 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
||||
</option>
|
||||
))
|
||||
: null}
|
||||
{parsedData?.vlans
|
||||
? Object.keys(parsedData.vlans).map((v) => (
|
||||
<option value={`VLAN-${v}`} key={uuid()}>
|
||||
VLAN - {v}
|
||||
</option>
|
||||
))
|
||||
: null}
|
||||
<option value="memory">{t('statistics.memory')}</option>
|
||||
</Select>
|
||||
<StatisticsCardDatePickers defaults={time} setTime={setNewTime} onClear={onClear} />
|
||||
@@ -123,6 +152,7 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
||||
<Box>
|
||||
{selected === 'memory' && memory}
|
||||
{interfaces}
|
||||
{vlans}
|
||||
</Box>
|
||||
</LoadingOverlay>
|
||||
)}
|
||||
|
||||
@@ -37,9 +37,21 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
||||
const parsedData = React.useMemo(() => {
|
||||
if (!getStats.data && !getCustomStats.data) return undefined;
|
||||
|
||||
try {
|
||||
const data: Record<
|
||||
string,
|
||||
{ tx: number[]; rx: number[]; recorded: number[]; maxRx: number; maxTx: number; removed?: boolean }
|
||||
{
|
||||
tx: number[];
|
||||
rx: number[];
|
||||
packetsRx: number[];
|
||||
packetsTx: number[];
|
||||
recorded: number[];
|
||||
maxRx: number;
|
||||
maxTx: number;
|
||||
maxPacketsRx: number;
|
||||
maxPacketsTx: number;
|
||||
removed?: boolean;
|
||||
}
|
||||
> = {};
|
||||
const memoryData = {
|
||||
used: [] as number[],
|
||||
@@ -49,8 +61,29 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
||||
total: [] as number[],
|
||||
recorded: [] as number[],
|
||||
};
|
||||
const vlanData: Record<
|
||||
string,
|
||||
{
|
||||
tx: number[];
|
||||
rx: number[];
|
||||
packetsRx: number[];
|
||||
packetsTx: number[];
|
||||
recorded: number[];
|
||||
maxRx: number;
|
||||
maxTx: number;
|
||||
maxPacketsRx: number;
|
||||
maxPacketsTx: number;
|
||||
removed?: boolean;
|
||||
}
|
||||
> = {};
|
||||
const previousRx: { [key: string]: number } = {};
|
||||
const previousTx: { [key: string]: number } = {};
|
||||
const previousPacketsRx: { [key: string]: number } = {};
|
||||
const previousPacketsTx: { [key: string]: number } = {};
|
||||
const previousVlanRx: { [key: string]: number } = {};
|
||||
const previousVlanTx: { [key: string]: number } = {};
|
||||
const previousVlanPacketsRx: { [key: string]: number } = {};
|
||||
const previousVlanPacketsTx: { [key: string]: number } = {};
|
||||
|
||||
let dataToLoop = getCustomStats.data ?? getStats.data?.data;
|
||||
if (dataToLoop && !getCustomStats.data) {
|
||||
@@ -67,8 +100,17 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
||||
}
|
||||
previousRx[inter.name] = inter.counters?.rx_bytes ?? 0;
|
||||
previousTx[inter.name] = inter.counters?.tx_bytes ?? 0;
|
||||
previousPacketsRx[inter.name] = inter.counters?.rx_packets ?? 0;
|
||||
previousPacketsTx[inter.name] = inter.counters?.tx_packets ?? 0;
|
||||
}
|
||||
for (const vlan of stat.data.dynamic_vlans ?? []) {
|
||||
previousVlanRx[vlan.vid] = vlan.rx_bytes ?? 0;
|
||||
previousVlanTx[vlan.vid] = vlan.tx_bytes ?? 0;
|
||||
previousVlanPacketsRx[vlan.vid] = vlan.rx_packets ?? 0;
|
||||
previousVlanPacketsTx[vlan.vid] = vlan.tx_packets ?? 0;
|
||||
}
|
||||
} else {
|
||||
// Memory
|
||||
const newMem = extractMemory(stat.data);
|
||||
memoryData.used.push(newMem.used ?? 0);
|
||||
memoryData.buffered.push(newMem.buffered ?? 0);
|
||||
@@ -77,18 +119,100 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
||||
memoryData.total.push(newMem.total ?? 0);
|
||||
memoryData.recorded.push(stat.recorded);
|
||||
|
||||
// Vlans
|
||||
for (const vlan of stat.data.dynamic_vlans ?? []) {
|
||||
const rx = vlan.rx_bytes ?? 0;
|
||||
const tx = vlan.tx_bytes ?? 0;
|
||||
const packetsRx = vlan.rx_packets ?? 0;
|
||||
const packetsTx = vlan?.tx_packets ?? 0;
|
||||
|
||||
let rxDelta = rx - (previousVlanRx[vlan.vid] ?? 0);
|
||||
if (rxDelta < 0) rxDelta = 0;
|
||||
let txDelta = tx - (previousVlanTx[vlan.vid] ?? 0);
|
||||
if (txDelta < 0) txDelta = 0;
|
||||
let packetsRxDelta = packetsRx - (previousVlanPacketsRx[vlan.vid] ?? 0);
|
||||
if (packetsRxDelta < 0) packetsRxDelta = 0;
|
||||
let packetsTxDelta = packetsTx - (previousVlanPacketsTx[vlan.vid] ?? 0);
|
||||
if (packetsTxDelta < 0) packetsTxDelta = 0;
|
||||
|
||||
if (vlanData[vlan.vid] === undefined)
|
||||
vlanData[vlan.vid] = {
|
||||
rx: [rxDelta],
|
||||
tx: [txDelta],
|
||||
packetsRx: [packetsRxDelta],
|
||||
packetsTx: [packetsTxDelta],
|
||||
recorded: [stat.recorded],
|
||||
maxTx: 0,
|
||||
maxRx: 0,
|
||||
maxPacketsRx: 0,
|
||||
maxPacketsTx: 0,
|
||||
};
|
||||
else {
|
||||
if (vlanData[vlan.vid] && !vlanData[vlan.vid]?.removed && vlanData[vlan.vid]?.recorded.length === 1) {
|
||||
vlanData[vlan.vid]?.tx.shift();
|
||||
vlanData[vlan.vid]?.rx.shift();
|
||||
vlanData[vlan.vid]?.packetsTx.shift();
|
||||
vlanData[vlan.vid]?.packetsRx.shift();
|
||||
vlanData[vlan.vid]?.recorded.shift();
|
||||
// @ts-ignore
|
||||
vlanData[vlan.vid].maxRx = rxDelta;
|
||||
// @ts-ignore
|
||||
vlanData[vlan.vid].maxTx = txDelta;
|
||||
// @ts-ignore
|
||||
vlanData[vlan.vid].removed = true;
|
||||
}
|
||||
|
||||
vlanData[vlan.vid]?.rx.push(rxDelta);
|
||||
vlanData[vlan.vid]?.tx.push(txDelta);
|
||||
vlanData[vlan.vid]?.packetsRx.push(packetsRxDelta);
|
||||
vlanData[vlan.vid]?.packetsTx.push(packetsTxDelta);
|
||||
vlanData[vlan.vid]?.recorded.push(stat.recorded);
|
||||
// @ts-ignore
|
||||
if (vlanData[vlan.vid] !== undefined && txDelta > vlanData[vlan.vid].maxTx) {
|
||||
// @ts-ignore
|
||||
vlanData[vlan.vid].maxTx = txDelta;
|
||||
}
|
||||
// @ts-ignore
|
||||
if (vlanData[vlan.vid] !== undefined && rxDelta > vlanData[vlan.vid].maxRx) {
|
||||
// @ts-ignore
|
||||
vlanData[vlan.vid].maxRx = rxDelta;
|
||||
}
|
||||
// @ts-ignore
|
||||
if (vlanData[vlan.vid] !== undefined && packetsTxDelta > vlanData[vlan.vid].maxPacketsTx) {
|
||||
// @ts-ignore
|
||||
vlanData[vlan.vid].maxPacketsTx = packetsTxDelta;
|
||||
}
|
||||
// @ts-ignore
|
||||
if (vlanData[vlan.vid] !== undefined && packetsRxDelta > vlanData[vlan.vid].maxPacketsRx) {
|
||||
// @ts-ignore
|
||||
vlanData[vlan.vid].maxPacketsRx = packetsRxDelta;
|
||||
}
|
||||
}
|
||||
previousVlanRx[vlan.vid] = rx;
|
||||
previousVlanTx[vlan.vid] = tx;
|
||||
previousVlanPacketsRx[vlan.vid] = packetsRx;
|
||||
previousVlanPacketsTx[vlan.vid] = packetsTx;
|
||||
}
|
||||
|
||||
// Interfaces
|
||||
for (const inter of stat.data.interfaces ?? []) {
|
||||
const isInterUpstream = inter.name?.substring(0, 2) === 'up';
|
||||
let rx = inter.counters?.rx_bytes ?? 0;
|
||||
let tx = inter.counters?.tx_bytes ?? 0;
|
||||
let packetsRx = inter.counters?.rx_packets ?? 0;
|
||||
let packetsTx = inter.counters?.tx_packets ?? 0;
|
||||
|
||||
if (inter['counters-aggregate']) {
|
||||
rx = inter['counters-aggregate'].rx_bytes;
|
||||
tx = inter['counters-aggregate'].tx_bytes;
|
||||
packetsRx = inter['counters-aggregate'].rx_packets;
|
||||
packetsTx = inter['counters-aggregate'].tx_packets;
|
||||
} else if (isInterUpstream) {
|
||||
for (const ssid of inter.ssids ?? []) {
|
||||
rx += ssid.counters?.rx_bytes ?? 0;
|
||||
tx += ssid.counters?.tx_bytes ?? 0;
|
||||
packetsRx += ssid.counters?.rx_packets ?? 0;
|
||||
packetsTx += ssid.counters?.tx_packets ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,18 +220,29 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
||||
if (rxDelta < 0) rxDelta = 0;
|
||||
let txDelta = tx - (previousTx[inter.name] ?? 0);
|
||||
if (txDelta < 0) txDelta = 0;
|
||||
let packetsRxDelta = packetsRx - (previousPacketsRx[inter.name] ?? 0);
|
||||
if (packetsRxDelta < 0) packetsRxDelta = 0;
|
||||
let packetsTxDelta = packetsTx - (previousPacketsTx[inter.name] ?? 0);
|
||||
if (packetsTxDelta < 0) packetsTxDelta = 0;
|
||||
|
||||
if (data[inter.name] === undefined)
|
||||
data[inter.name] = {
|
||||
rx: [rxDelta],
|
||||
tx: [txDelta],
|
||||
packetsRx: [packetsRxDelta],
|
||||
packetsTx: [packetsTxDelta],
|
||||
recorded: [stat.recorded],
|
||||
maxTx: txDelta,
|
||||
maxRx: rxDelta,
|
||||
maxTx: 0,
|
||||
maxRx: 0,
|
||||
maxPacketsRx: 0,
|
||||
maxPacketsTx: 0,
|
||||
};
|
||||
else {
|
||||
if (data[inter.name] && !data[inter.name]?.removed && data[inter.name]?.recorded.length === 1) {
|
||||
data[inter.name]?.tx.shift();
|
||||
data[inter.name]?.rx.shift();
|
||||
data[inter.name]?.packetsTx.shift();
|
||||
data[inter.name]?.packetsRx.shift();
|
||||
data[inter.name]?.recorded.shift();
|
||||
// @ts-ignore
|
||||
data[inter.name].maxRx = rxDelta;
|
||||
@@ -119,22 +254,37 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
||||
|
||||
data[inter.name]?.rx.push(rxDelta);
|
||||
data[inter.name]?.tx.push(txDelta);
|
||||
data[inter.name]?.packetsRx.push(packetsRxDelta);
|
||||
data[inter.name]?.packetsTx.push(packetsTxDelta);
|
||||
data[inter.name]?.recorded.push(stat.recorded);
|
||||
// @ts-ignore
|
||||
if (data[inter.name] !== undefined && txDelta > data[inter.name].maxTx) data[inter.name].maxTx = txDelta;
|
||||
// @ts-ignore
|
||||
if (data[inter.name] !== undefined && rxDelta > data[inter.name].maxRx) data[inter.name].maxRx = rxDelta;
|
||||
// @ts-ignore
|
||||
if (data[inter.name] !== undefined && packetsTxDelta > data[inter.name].maxPacketsTx)
|
||||
// @ts-ignore
|
||||
data[inter.name].maxPacketsTx = packetsTxDelta;
|
||||
// @ts-ignore
|
||||
if (data[inter.name] !== undefined && packetsRxDelta > data[inter.name].maxPacketsRx)
|
||||
// @ts-ignore
|
||||
data[inter.name].maxPacketsRx = packetsRxDelta;
|
||||
}
|
||||
previousRx[inter.name] = rx;
|
||||
previousTx[inter.name] = tx;
|
||||
previousPacketsRx[inter.name] = packetsRx;
|
||||
previousPacketsTx[inter.name] = packetsTx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
interfaces: data,
|
||||
memory: memoryData,
|
||||
vlans: vlanData,
|
||||
};
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}, [getStats.data, getCustomStats.data]);
|
||||
|
||||
const refresh = React.useCallback(() => {
|
||||
|
||||
@@ -26,14 +26,16 @@ export type ParsedAssociation = {
|
||||
txMcs: number | string;
|
||||
txNss: number | string;
|
||||
recorded: number;
|
||||
dynamicVlan?: number;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
data?: ParsedAssociation[];
|
||||
ouis?: Record<string, string>;
|
||||
isSingle?: boolean;
|
||||
};
|
||||
|
||||
const WifiAnalysisAssocationsTable = ({ data, ouis }: Props) => {
|
||||
const WifiAnalysisAssocationsTable = ({ data, ouis, isSingle }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]);
|
||||
|
||||
@@ -56,7 +58,7 @@ const WifiAnalysisAssocationsTable = ({ data, ouis }: Props) => {
|
||||
Header: '',
|
||||
Footer: '',
|
||||
accessor: 'radio.index',
|
||||
Cell: ({ cell }) => indexCell(cell.row.original),
|
||||
Cell: ({ cell }) => indexCell(cell.row.original) ?? '',
|
||||
customWidth: '35px',
|
||||
alwaysShow: true,
|
||||
disableSortBy: true,
|
||||
@@ -79,6 +81,14 @@ const WifiAnalysisAssocationsTable = ({ data, ouis }: Props) => {
|
||||
customWidth: '35px',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
id: 'dynamicVlan',
|
||||
Header: 'VLAN',
|
||||
Footer: '',
|
||||
Cell: (v) => (v.cell.row.original.dynamicVlan !== undefined ? `${v.cell.row.original.dynamicVlan}` : '-'),
|
||||
accessor: 'txBytes',
|
||||
customWidth: '35px',
|
||||
},
|
||||
{
|
||||
id: 'mode',
|
||||
Header: t('controller.wifi.mode'),
|
||||
@@ -151,7 +161,7 @@ const WifiAnalysisAssocationsTable = ({ data, ouis }: Props) => {
|
||||
<>
|
||||
<Flex mt={2}>
|
||||
<Heading size="sm" my="auto">
|
||||
{t('devices.associations')} ({data?.length})
|
||||
{isSingle ? 'Association' : `${t('devices.associations')} (${data?.length})`}
|
||||
</Heading>
|
||||
<Spacer />
|
||||
<ColumnPicker
|
||||
|
||||
@@ -83,6 +83,7 @@ const parseAssociations = (data: { data: DeviceStatistics; recorded: number }, r
|
||||
txMcs: association.tx_rate.mcs ?? '-',
|
||||
txNss: association.tx_rate.nss ?? '-',
|
||||
recorded: data.recorded,
|
||||
dynamicVlan: association.dynamic_vlan,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user