mirror of
https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
synced 2025-11-02 11:17: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",
|
"name": "ucentral-client",
|
||||||
"version": "2.10.0(42)",
|
"version": "2.10.0(44)",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.10.0(42)",
|
"version": "2.10.0(44)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/icons": "^2.0.18",
|
"@chakra-ui/icons": "^2.0.18",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.10.0(42)",
|
"version": "2.10.0(44)",
|
||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type DeviceInterfaceStatistics = {
|
|||||||
ack_signal_avg: number;
|
ack_signal_avg: number;
|
||||||
bssid: string;
|
bssid: string;
|
||||||
connected: number;
|
connected: number;
|
||||||
|
dynamic_vlan?: number;
|
||||||
inactive: number;
|
inactive: number;
|
||||||
ipaddr_v4: string;
|
ipaddr_v4: string;
|
||||||
rssi: number;
|
rssi: number;
|
||||||
@@ -117,6 +118,13 @@ export type DeviceStatistics = {
|
|||||||
transmit_ms: number;
|
transmit_ms: number;
|
||||||
tx_power: number;
|
tx_power: number;
|
||||||
}[];
|
}[];
|
||||||
|
dynamic_vlans?: {
|
||||||
|
vid: number;
|
||||||
|
rx_bytes: number;
|
||||||
|
rx_packets: number;
|
||||||
|
tx_bytes: number;
|
||||||
|
tx_packets: number;
|
||||||
|
}[];
|
||||||
unit?: {
|
unit?: {
|
||||||
load: [number, number, number];
|
load: [number, number, number];
|
||||||
localtime: number;
|
localtime: number;
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const RestrictionsCard = ({ serialNumber }: Props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null}
|
) : null}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody p={0} display="block">
|
<CardBody display="block">
|
||||||
<Flex mt={2}>
|
<Flex mt={2}>
|
||||||
<Heading size="sm" mr={2} my="auto">
|
<Heading size="sm" mr={2} my="auto">
|
||||||
{t('restrictions.countries')}:
|
{t('restrictions.countries')}:
|
||||||
|
|||||||
@@ -29,22 +29,44 @@ const getDivisionFactor = (maxBytes: number) => {
|
|||||||
return { factor: 1024 * 1024 * 1024, unit: 'GB' };
|
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 = {
|
type Props = {
|
||||||
data: {
|
data: {
|
||||||
tx: number[];
|
tx: number[];
|
||||||
rx: number[];
|
rx: number[];
|
||||||
|
packetsRx: number[];
|
||||||
|
packetsTx: number[];
|
||||||
recorded: number[];
|
recorded: number[];
|
||||||
maxTx: number;
|
|
||||||
maxRx: 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 { colorMode } = useColorMode();
|
||||||
|
|
||||||
const { factor, unit } = getDivisionFactor(data.maxTx);
|
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()),
|
labels: data.recorded.map((recorded) => new Date(recorded * 1000).toLocaleTimeString()),
|
||||||
datasets: [
|
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 (
|
return (
|
||||||
<Line
|
<Line
|
||||||
@@ -89,7 +143,10 @@ const InterfaceChart = ({ data }: Props) => {
|
|||||||
intersect: false,
|
intersect: false,
|
||||||
|
|
||||||
callbacks: {
|
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: {
|
ticks: {
|
||||||
color: colorMode === 'dark' ? 'white' : undefined,
|
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,
|
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 DeviceMemoryChart from './MemoryChart';
|
||||||
import { useStatisticsCard } from './useStatisticsCard';
|
import { useStatisticsCard } from './useStatisticsCard';
|
||||||
import ViewLastStatsModal from './ViewLastStatsModal';
|
import ViewLastStatsModal from './ViewLastStatsModal';
|
||||||
|
import VlanChart from './VlanChart';
|
||||||
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
import { RefreshButton } from 'components/Buttons/RefreshButton';
|
||||||
import { Card } from 'components/Containers/Card';
|
import { Card } from 'components/Containers/Card';
|
||||||
import { CardBody } from 'components/Containers/Card/CardBody';
|
import { CardBody } from 'components/Containers/Card/CardBody';
|
||||||
@@ -42,6 +43,7 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
const { time, setTime, parsedData, isLoading, selected, onSelectInterface, refresh } = useStatisticsCard({
|
const { time, setTime, parsedData, isLoading, selected, onSelectInterface, refresh } = useStatisticsCard({
|
||||||
serialNumber,
|
serialNumber,
|
||||||
});
|
});
|
||||||
|
const [formatChosen, setFormatChosen] = React.useState<'bytes' | 'packets'>('bytes');
|
||||||
|
|
||||||
const setNewTime = (start: Date, end: Date) => {
|
const setNewTime = (start: Date, end: Date) => {
|
||||||
setTime({ start, end });
|
setTime({ start, end });
|
||||||
@@ -50,15 +52,29 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
setTime(undefined);
|
setTime(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onFormatChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
setFormatChosen(e.target.value as 'bytes' | 'packets');
|
||||||
|
};
|
||||||
|
|
||||||
const interfaces = React.useMemo(() => {
|
const interfaces = React.useMemo(() => {
|
||||||
if (!parsedData) return undefined;
|
if (!parsedData) return undefined;
|
||||||
|
|
||||||
return Object.entries(parsedData.interfaces).map(([name, data]) => (
|
return Object.entries(parsedData.interfaces).map(([name, data]) => (
|
||||||
<Box hidden={name !== selected} key={uuid()}>
|
<Box hidden={name !== selected} key={uuid()}>
|
||||||
<InterfaceChart data={data} />
|
<InterfaceChart data={data} format={formatChosen} />
|
||||||
</Box>
|
</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(() => {
|
const memory = React.useMemo(() => {
|
||||||
if (!parsedData) return undefined;
|
if (!parsedData) return undefined;
|
||||||
|
|
||||||
@@ -76,7 +92,13 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
<Heading size="md">{t('configurations.statistics')}</Heading>
|
<Heading size="md">{t('configurations.statistics')}</Heading>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<HStack>
|
<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
|
{parsedData?.interfaces
|
||||||
? Object.keys(parsedData.interfaces).map((v) => (
|
? Object.keys(parsedData.interfaces).map((v) => (
|
||||||
<option value={v} key={uuid()}>
|
<option value={v} key={uuid()}>
|
||||||
@@ -84,6 +106,13 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
</option>
|
</option>
|
||||||
))
|
))
|
||||||
: null}
|
: 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>
|
<option value="memory">{t('statistics.memory')}</option>
|
||||||
</Select>
|
</Select>
|
||||||
<StatisticsCardDatePickers defaults={time} setTime={setNewTime} onClear={onClear} />
|
<StatisticsCardDatePickers defaults={time} setTime={setNewTime} onClear={onClear} />
|
||||||
@@ -123,6 +152,7 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
<Box>
|
<Box>
|
||||||
{selected === 'memory' && memory}
|
{selected === 'memory' && memory}
|
||||||
{interfaces}
|
{interfaces}
|
||||||
|
{vlans}
|
||||||
</Box>
|
</Box>
|
||||||
</LoadingOverlay>
|
</LoadingOverlay>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -37,9 +37,21 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
const parsedData = React.useMemo(() => {
|
const parsedData = React.useMemo(() => {
|
||||||
if (!getStats.data && !getCustomStats.data) return undefined;
|
if (!getStats.data && !getCustomStats.data) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
const data: Record<
|
const data: Record<
|
||||||
string,
|
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 = {
|
const memoryData = {
|
||||||
used: [] as number[],
|
used: [] as number[],
|
||||||
@@ -49,8 +61,29 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
total: [] as number[],
|
total: [] as number[],
|
||||||
recorded: [] 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 previousRx: { [key: string]: number } = {};
|
||||||
const previousTx: { [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;
|
let dataToLoop = getCustomStats.data ?? getStats.data?.data;
|
||||||
if (dataToLoop && !getCustomStats.data) {
|
if (dataToLoop && !getCustomStats.data) {
|
||||||
@@ -67,8 +100,17 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
}
|
}
|
||||||
previousRx[inter.name] = inter.counters?.rx_bytes ?? 0;
|
previousRx[inter.name] = inter.counters?.rx_bytes ?? 0;
|
||||||
previousTx[inter.name] = inter.counters?.tx_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 {
|
} else {
|
||||||
|
// Memory
|
||||||
const newMem = extractMemory(stat.data);
|
const newMem = extractMemory(stat.data);
|
||||||
memoryData.used.push(newMem.used ?? 0);
|
memoryData.used.push(newMem.used ?? 0);
|
||||||
memoryData.buffered.push(newMem.buffered ?? 0);
|
memoryData.buffered.push(newMem.buffered ?? 0);
|
||||||
@@ -77,18 +119,100 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
memoryData.total.push(newMem.total ?? 0);
|
memoryData.total.push(newMem.total ?? 0);
|
||||||
memoryData.recorded.push(stat.recorded);
|
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 ?? []) {
|
for (const inter of stat.data.interfaces ?? []) {
|
||||||
const isInterUpstream = inter.name?.substring(0, 2) === 'up';
|
const isInterUpstream = inter.name?.substring(0, 2) === 'up';
|
||||||
let rx = inter.counters?.rx_bytes ?? 0;
|
let rx = inter.counters?.rx_bytes ?? 0;
|
||||||
let tx = inter.counters?.tx_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']) {
|
if (inter['counters-aggregate']) {
|
||||||
rx = inter['counters-aggregate'].rx_bytes;
|
rx = inter['counters-aggregate'].rx_bytes;
|
||||||
tx = inter['counters-aggregate'].tx_bytes;
|
tx = inter['counters-aggregate'].tx_bytes;
|
||||||
|
packetsRx = inter['counters-aggregate'].rx_packets;
|
||||||
|
packetsTx = inter['counters-aggregate'].tx_packets;
|
||||||
} else if (isInterUpstream) {
|
} else if (isInterUpstream) {
|
||||||
for (const ssid of inter.ssids ?? []) {
|
for (const ssid of inter.ssids ?? []) {
|
||||||
rx += ssid.counters?.rx_bytes ?? 0;
|
rx += ssid.counters?.rx_bytes ?? 0;
|
||||||
tx += ssid.counters?.tx_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;
|
if (rxDelta < 0) rxDelta = 0;
|
||||||
let txDelta = tx - (previousTx[inter.name] ?? 0);
|
let txDelta = tx - (previousTx[inter.name] ?? 0);
|
||||||
if (txDelta < 0) txDelta = 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)
|
if (data[inter.name] === undefined)
|
||||||
data[inter.name] = {
|
data[inter.name] = {
|
||||||
rx: [rxDelta],
|
rx: [rxDelta],
|
||||||
tx: [txDelta],
|
tx: [txDelta],
|
||||||
|
packetsRx: [packetsRxDelta],
|
||||||
|
packetsTx: [packetsTxDelta],
|
||||||
recorded: [stat.recorded],
|
recorded: [stat.recorded],
|
||||||
maxTx: txDelta,
|
maxTx: 0,
|
||||||
maxRx: rxDelta,
|
maxRx: 0,
|
||||||
|
maxPacketsRx: 0,
|
||||||
|
maxPacketsTx: 0,
|
||||||
};
|
};
|
||||||
else {
|
else {
|
||||||
if (data[inter.name] && !data[inter.name]?.removed && data[inter.name]?.recorded.length === 1) {
|
if (data[inter.name] && !data[inter.name]?.removed && data[inter.name]?.recorded.length === 1) {
|
||||||
data[inter.name]?.tx.shift();
|
data[inter.name]?.tx.shift();
|
||||||
data[inter.name]?.rx.shift();
|
data[inter.name]?.rx.shift();
|
||||||
|
data[inter.name]?.packetsTx.shift();
|
||||||
|
data[inter.name]?.packetsRx.shift();
|
||||||
data[inter.name]?.recorded.shift();
|
data[inter.name]?.recorded.shift();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
data[inter.name].maxRx = rxDelta;
|
data[inter.name].maxRx = rxDelta;
|
||||||
@@ -119,22 +254,37 @@ export const useStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
|
|
||||||
data[inter.name]?.rx.push(rxDelta);
|
data[inter.name]?.rx.push(rxDelta);
|
||||||
data[inter.name]?.tx.push(txDelta);
|
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);
|
data[inter.name]?.recorded.push(stat.recorded);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (data[inter.name] !== undefined && txDelta > data[inter.name].maxTx) data[inter.name].maxTx = txDelta;
|
if (data[inter.name] !== undefined && txDelta > data[inter.name].maxTx) data[inter.name].maxTx = txDelta;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (data[inter.name] !== undefined && rxDelta > data[inter.name].maxRx) data[inter.name].maxRx = rxDelta;
|
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;
|
previousRx[inter.name] = rx;
|
||||||
previousTx[inter.name] = tx;
|
previousTx[inter.name] = tx;
|
||||||
|
previousPacketsRx[inter.name] = packetsRx;
|
||||||
|
previousPacketsTx[inter.name] = packetsTx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
interfaces: data,
|
interfaces: data,
|
||||||
memory: memoryData,
|
memory: memoryData,
|
||||||
|
vlans: vlanData,
|
||||||
};
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}, [getStats.data, getCustomStats.data]);
|
}, [getStats.data, getCustomStats.data]);
|
||||||
|
|
||||||
const refresh = React.useCallback(() => {
|
const refresh = React.useCallback(() => {
|
||||||
|
|||||||
@@ -26,14 +26,16 @@ export type ParsedAssociation = {
|
|||||||
txMcs: number | string;
|
txMcs: number | string;
|
||||||
txNss: number | string;
|
txNss: number | string;
|
||||||
recorded: number;
|
recorded: number;
|
||||||
|
dynamicVlan?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data?: ParsedAssociation[];
|
data?: ParsedAssociation[];
|
||||||
ouis?: Record<string, string>;
|
ouis?: Record<string, string>;
|
||||||
|
isSingle?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const WifiAnalysisAssocationsTable = ({ data, ouis }: Props) => {
|
const WifiAnalysisAssocationsTable = ({ data, ouis, isSingle }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]);
|
const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]);
|
||||||
|
|
||||||
@@ -56,7 +58,7 @@ const WifiAnalysisAssocationsTable = ({ data, ouis }: Props) => {
|
|||||||
Header: '',
|
Header: '',
|
||||||
Footer: '',
|
Footer: '',
|
||||||
accessor: 'radio.index',
|
accessor: 'radio.index',
|
||||||
Cell: ({ cell }) => indexCell(cell.row.original),
|
Cell: ({ cell }) => indexCell(cell.row.original) ?? '',
|
||||||
customWidth: '35px',
|
customWidth: '35px',
|
||||||
alwaysShow: true,
|
alwaysShow: true,
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
@@ -79,6 +81,14 @@ const WifiAnalysisAssocationsTable = ({ data, ouis }: Props) => {
|
|||||||
customWidth: '35px',
|
customWidth: '35px',
|
||||||
disableSortBy: true,
|
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',
|
id: 'mode',
|
||||||
Header: t('controller.wifi.mode'),
|
Header: t('controller.wifi.mode'),
|
||||||
@@ -151,7 +161,7 @@ const WifiAnalysisAssocationsTable = ({ data, ouis }: Props) => {
|
|||||||
<>
|
<>
|
||||||
<Flex mt={2}>
|
<Flex mt={2}>
|
||||||
<Heading size="sm" my="auto">
|
<Heading size="sm" my="auto">
|
||||||
{t('devices.associations')} ({data?.length})
|
{isSingle ? 'Association' : `${t('devices.associations')} (${data?.length})`}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<ColumnPicker
|
<ColumnPicker
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ const parseAssociations = (data: { data: DeviceStatistics; recorded: number }, r
|
|||||||
txMcs: association.tx_rate.mcs ?? '-',
|
txMcs: association.tx_rate.mcs ?? '-',
|
||||||
txNss: association.tx_rate.nss ?? '-',
|
txNss: association.tx_rate.nss ?? '-',
|
||||||
recorded: data.recorded,
|
recorded: data.recorded,
|
||||||
|
dynamicVlan: association.dynamic_vlan,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user