mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
synced 2025-10-30 02:12:33 +00:00
Merge pull request #130 from stephb9959/main
[WIFI-11251] Now fetching device statistics in batches of 100
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.8.0(17)",
|
"version": "2.8.0(18)",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.8.0(17)",
|
"version": "2.8.0(18)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/icons": "^2.0.11",
|
"@chakra-ui/icons": "^2.0.11",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.8.0(17)",
|
"version": "2.8.0(18)",
|
||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
|
|||||||
@@ -189,29 +189,61 @@ export const useGetMacOuis = ({ macs, onError }: { macs?: string[]; onError?: (e
|
|||||||
onError,
|
onError,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getStatsBetweenTimestamps = (limit: number, start?: number, end?: number, serialNumber?: string) => async () =>
|
const getStatsBetweenTimestamps = async (params: {
|
||||||
|
start?: number;
|
||||||
|
end?: number;
|
||||||
|
serialNumber?: string;
|
||||||
|
offset: number;
|
||||||
|
}) =>
|
||||||
axiosGw
|
axiosGw
|
||||||
.get(`device/${serialNumber}/statistics?startDate=${start}&endDate=${end}&limit=${limit}`)
|
.get(
|
||||||
.then((response) => response.data) as Promise<{
|
`device/${params.serialNumber}/statistics?startDate=${params.start}&endDate=${params.end}&offset=${params.offset}&limit=100`,
|
||||||
data: { data: DeviceStatistics; UUID: string; recorded: number }[];
|
)
|
||||||
}>;
|
.then((response) => response.data.data as { data: DeviceStatistics; UUID: string; recorded: number }[]);
|
||||||
|
|
||||||
|
const getStatsBetweenTimestampsCount = async (params: { start?: number; end?: number; serialNumber?: string }) =>
|
||||||
|
axiosGw
|
||||||
|
.get(`device/${params.serialNumber}/statistics?startDate=${params.start}&endDate=${params.end}&countOnly=true`)
|
||||||
|
.then((response) => response.data.count as number)
|
||||||
|
.catch(() => 0);
|
||||||
|
|
||||||
|
const getStatsBetweenTimestampsWithProgress =
|
||||||
|
(params: { start?: number; end?: number; serialNumber?: string }, setProgress?: (pct: number) => void) =>
|
||||||
|
async () => {
|
||||||
|
const { start, end, serialNumber } = params;
|
||||||
|
if (setProgress) setProgress(0);
|
||||||
|
const count = await getStatsBetweenTimestampsCount(params);
|
||||||
|
let allStats: { data: DeviceStatistics; UUID: string; recorded: number }[] = [];
|
||||||
|
let offset = 0;
|
||||||
|
let latestResponse: { data: DeviceStatistics; UUID: string; recorded: number }[];
|
||||||
|
do {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
latestResponse = await getStatsBetweenTimestamps({ start, end, serialNumber, offset });
|
||||||
|
allStats = allStats.concat(latestResponse);
|
||||||
|
offset += 100;
|
||||||
|
if (setProgress && count > 0) setProgress((allStats.length / count) * 100);
|
||||||
|
} while (latestResponse.length === 100);
|
||||||
|
if (setProgress) setProgress(100);
|
||||||
|
|
||||||
|
return allStats.sort((a, b) => a.recorded - b.recorded);
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetDeviceStatsWithTimestamps = ({
|
export const useGetDeviceStatsWithTimestamps = ({
|
||||||
serialNumber,
|
serialNumber,
|
||||||
limit,
|
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
onError,
|
onError,
|
||||||
|
setProgress,
|
||||||
}: {
|
}: {
|
||||||
serialNumber?: string;
|
serialNumber?: string;
|
||||||
limit: number;
|
|
||||||
start?: number;
|
start?: number;
|
||||||
end?: number;
|
end?: number;
|
||||||
onError?: (e: AxiosError) => void;
|
onError?: (e: AxiosError) => void;
|
||||||
|
setProgress?: (pct: number) => void;
|
||||||
}) =>
|
}) =>
|
||||||
useQuery(
|
useQuery(
|
||||||
['deviceStatistics', serialNumber, { limit, start, end }],
|
['deviceStatistics', serialNumber, { start, end }],
|
||||||
getStatsBetweenTimestamps(limit, start, end, serialNumber),
|
getStatsBetweenTimestampsWithProgress({ start, end, serialNumber }, setProgress),
|
||||||
{
|
{
|
||||||
enabled: serialNumber !== undefined && serialNumber !== '' && start !== undefined && end !== undefined,
|
enabled: serialNumber !== undefined && serialNumber !== '' && start !== undefined && end !== undefined,
|
||||||
staleTime: 1000 * 60,
|
staleTime: 1000 * 60,
|
||||||
|
|||||||
@@ -5,25 +5,13 @@ import { v4 as uuid } from 'uuid';
|
|||||||
import StatisticsCardDatePickers from './DatePickers';
|
import StatisticsCardDatePickers from './DatePickers';
|
||||||
import InterfaceChart from './InterfaceChart';
|
import InterfaceChart from './InterfaceChart';
|
||||||
import DeviceMemoryChart from './MemoryChart';
|
import DeviceMemoryChart from './MemoryChart';
|
||||||
|
import { useStatisticsCard } from './useStatisticsCard';
|
||||||
import ViewLastStatsModal from './ViewLastStatsModal';
|
import ViewLastStatsModal from './ViewLastStatsModal';
|
||||||
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';
|
||||||
import { CardHeader } from 'components/Containers/Card/CardHeader';
|
import { CardHeader } from 'components/Containers/Card/CardHeader';
|
||||||
import { LoadingOverlay } from 'components/LoadingOverlay';
|
import { LoadingOverlay } from 'components/LoadingOverlay';
|
||||||
import {
|
|
||||||
DeviceStatistics,
|
|
||||||
useGetDeviceStatsLatestHour,
|
|
||||||
useGetDeviceStatsWithTimestamps,
|
|
||||||
} from 'hooks/Network/Statistics';
|
|
||||||
|
|
||||||
const extractMemory = (stat: DeviceStatistics) => {
|
|
||||||
let used: number | undefined;
|
|
||||||
if (stat.unit && stat.unit.memory) {
|
|
||||||
used = stat.unit.memory.total - stat.unit.memory.free;
|
|
||||||
}
|
|
||||||
return { ...stat.unit?.memory, used };
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
@@ -31,20 +19,10 @@ type Props = {
|
|||||||
|
|
||||||
const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [time, setTime] = React.useState<{ start: Date; end: Date } | undefined>();
|
const { time, setTime, parsedData, isLoading, selected, onSelectInterface, refresh } = useStatisticsCard({
|
||||||
const [selected, setSelected] = React.useState('memory');
|
|
||||||
const getStats = useGetDeviceStatsLatestHour({ serialNumber, limit: 10000 });
|
|
||||||
const getCustomStats = useGetDeviceStatsWithTimestamps({
|
|
||||||
serialNumber,
|
serialNumber,
|
||||||
limit: 10000,
|
|
||||||
start: time ? Math.floor(time.start.getTime() / 1000) : undefined,
|
|
||||||
end: time ? Math.floor(time.end.getTime() / 1000) : undefined,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const onChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
||||||
setSelected(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setNewTime = (start: Date, end: Date) => {
|
const setNewTime = (start: Date, end: Date) => {
|
||||||
setTime({ start, end });
|
setTime({ start, end });
|
||||||
};
|
};
|
||||||
@@ -52,79 +30,6 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
setTime(undefined);
|
setTime(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsedData = React.useMemo(() => {
|
|
||||||
if (!getStats.data && !getCustomStats.data) return undefined;
|
|
||||||
|
|
||||||
const data: Record<string, { tx: number[]; rx: number[]; recorded: number[]; maxRx: number; maxTx: number }> = {};
|
|
||||||
const memoryData = {
|
|
||||||
used: [] as number[],
|
|
||||||
buffered: [] as number[],
|
|
||||||
cached: [] as number[],
|
|
||||||
free: [] as number[],
|
|
||||||
total: [] as number[],
|
|
||||||
recorded: [] as number[],
|
|
||||||
};
|
|
||||||
const previousRx: { [key: string]: number } = {};
|
|
||||||
const previousTx: { [key: string]: number } = {};
|
|
||||||
|
|
||||||
const dataToLoop = getCustomStats.data ?? getStats.data;
|
|
||||||
|
|
||||||
for (const [index, stat] of dataToLoop ? dataToLoop.data.entries() : []) {
|
|
||||||
if (index === 0) {
|
|
||||||
let updated = false;
|
|
||||||
for (const inter of stat.data.interfaces ?? []) {
|
|
||||||
if (!updated && selected === 'memory') {
|
|
||||||
updated = true;
|
|
||||||
setSelected(inter.name);
|
|
||||||
}
|
|
||||||
previousRx[inter.name] = inter.counters?.rx_bytes ?? 0;
|
|
||||||
previousTx[inter.name] = inter.counters?.tx_bytes ?? 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const newMem = extractMemory(stat.data);
|
|
||||||
memoryData.used.push(newMem.used ?? 0);
|
|
||||||
memoryData.buffered.push(newMem.buffered ?? 0);
|
|
||||||
memoryData.cached.push(newMem.cached ?? 0);
|
|
||||||
memoryData.free.push(newMem.free ?? 0);
|
|
||||||
memoryData.total.push(newMem.total ?? 0);
|
|
||||||
memoryData.recorded.push(stat.recorded);
|
|
||||||
|
|
||||||
for (const inter of stat.data.interfaces ?? []) {
|
|
||||||
const rx = inter.counters?.rx_bytes ?? 0;
|
|
||||||
const tx = inter.counters?.tx_bytes ?? 0;
|
|
||||||
let rxDelta = rx - (previousRx[inter.name] ?? 0);
|
|
||||||
if (rxDelta < 0) rxDelta = 0;
|
|
||||||
let txDelta = tx - (previousTx[inter.name] ?? 0);
|
|
||||||
if (txDelta < 0) txDelta = 0;
|
|
||||||
if (data[inter.name] === undefined)
|
|
||||||
data[inter.name] = {
|
|
||||||
rx: [rxDelta],
|
|
||||||
tx: [txDelta],
|
|
||||||
recorded: [stat.recorded],
|
|
||||||
maxTx: txDelta,
|
|
||||||
maxRx: rxDelta,
|
|
||||||
};
|
|
||||||
else {
|
|
||||||
data[inter.name]?.rx.push(rxDelta);
|
|
||||||
data[inter.name]?.tx.push(txDelta);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
previousRx[inter.name] = rx;
|
|
||||||
previousTx[inter.name] = tx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
interfaces: data,
|
|
||||||
memory: memoryData,
|
|
||||||
};
|
|
||||||
}, [getStats.data, getCustomStats.data]);
|
|
||||||
|
|
||||||
const interfaces = React.useMemo(() => {
|
const interfaces = React.useMemo(() => {
|
||||||
if (!parsedData) return undefined;
|
if (!parsedData) return undefined;
|
||||||
|
|
||||||
@@ -140,13 +45,6 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
return <DeviceMemoryChart data={parsedData.memory} />;
|
return <DeviceMemoryChart data={parsedData.memory} />;
|
||||||
}, [parsedData]);
|
}, [parsedData]);
|
||||||
|
|
||||||
const isLoading = React.useMemo(() => {
|
|
||||||
if (!time && getStats?.isFetching) return true;
|
|
||||||
if (time && getCustomStats.isFetching) return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}, [getStats, getCustomStats, time]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card mb={4}>
|
<Card mb={4}>
|
||||||
<CardHeader display="block">
|
<CardHeader display="block">
|
||||||
@@ -156,7 +54,7 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
<HStack>
|
<HStack>
|
||||||
<ViewLastStatsModal serialNumber={serialNumber} />
|
<ViewLastStatsModal serialNumber={serialNumber} />
|
||||||
<StatisticsCardDatePickers defaults={time} setTime={setNewTime} onClear={onClear} />
|
<StatisticsCardDatePickers defaults={time} setTime={setNewTime} onClear={onClear} />
|
||||||
<Select value={selected} onChange={onChange}>
|
<Select value={selected} onChange={onSelectInterface}>
|
||||||
{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()}>
|
||||||
@@ -168,9 +66,9 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
</Select>
|
</Select>
|
||||||
<RefreshButton
|
<RefreshButton
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={getStats.refetch}
|
onClick={refresh}
|
||||||
isCompact
|
isCompact
|
||||||
isFetching={getStats.isFetching}
|
isFetching={isLoading.isLoading}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
/>
|
/>
|
||||||
@@ -188,12 +86,17 @@ const DeviceStatisticsCard = ({ serialNumber }: Props) => {
|
|||||||
</Heading>
|
</Heading>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
{!getStats?.data && isLoading ? (
|
{(!parsedData && isLoading.isLoading) || (isLoading.isLoading && isLoading.progress !== undefined) ? (
|
||||||
<Center my="auto">
|
<Center my="auto" mt="100px">
|
||||||
<Spinner size="xl" mt="100px" />
|
{isLoading.progress !== undefined && (
|
||||||
|
<Heading size="md" mr={2}>
|
||||||
|
{isLoading.progress.toFixed(2)}%
|
||||||
|
</Heading>
|
||||||
|
)}
|
||||||
|
<Spinner size="xl" />
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
<LoadingOverlay isLoading={isLoading}>
|
<LoadingOverlay isLoading={isLoading.isLoading}>
|
||||||
<Box>
|
<Box>
|
||||||
{selected === 'memory' && memory}
|
{selected === 'memory' && memory}
|
||||||
{interfaces}
|
{interfaces}
|
||||||
|
|||||||
137
src/pages/Device/StatisticsCard/useStatisticsCard.ts
Normal file
137
src/pages/Device/StatisticsCard/useStatisticsCard.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
DeviceStatistics,
|
||||||
|
useGetDeviceStatsLatestHour,
|
||||||
|
useGetDeviceStatsWithTimestamps,
|
||||||
|
} from 'hooks/Network/Statistics';
|
||||||
|
|
||||||
|
const extractMemory = (stat: DeviceStatistics) => {
|
||||||
|
let used: number | undefined;
|
||||||
|
if (stat.unit && stat.unit.memory) {
|
||||||
|
used = stat.unit.memory.total - stat.unit.memory.free;
|
||||||
|
}
|
||||||
|
return { ...stat.unit?.memory, used };
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useStatisticsCard = ({ serialNumber }: Props) => {
|
||||||
|
const [selected, setSelected] = React.useState('memory');
|
||||||
|
const [progress, setProgress] = React.useState(0);
|
||||||
|
const [time, setTime] = React.useState<{ start: Date; end: Date } | undefined>();
|
||||||
|
const onProgressChange = React.useCallback((newProgress: number) => {
|
||||||
|
setProgress(newProgress);
|
||||||
|
}, []);
|
||||||
|
const getStats = useGetDeviceStatsLatestHour({ serialNumber, limit: 100 });
|
||||||
|
const getCustomStats = useGetDeviceStatsWithTimestamps({
|
||||||
|
serialNumber,
|
||||||
|
start: time ? Math.floor(time.start.getTime() / 1000) : undefined,
|
||||||
|
end: time ? Math.floor(time.end.getTime() / 1000) : undefined,
|
||||||
|
setProgress: onProgressChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSelectInterface = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
setSelected(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const parsedData = React.useMemo(() => {
|
||||||
|
if (!getStats.data && !getCustomStats.data) return undefined;
|
||||||
|
|
||||||
|
const data: Record<string, { tx: number[]; rx: number[]; recorded: number[]; maxRx: number; maxTx: number }> = {};
|
||||||
|
const memoryData = {
|
||||||
|
used: [] as number[],
|
||||||
|
buffered: [] as number[],
|
||||||
|
cached: [] as number[],
|
||||||
|
free: [] as number[],
|
||||||
|
total: [] as number[],
|
||||||
|
recorded: [] as number[],
|
||||||
|
};
|
||||||
|
const previousRx: { [key: string]: number } = {};
|
||||||
|
const previousTx: { [key: string]: number } = {};
|
||||||
|
|
||||||
|
const dataToLoop = getCustomStats.data ?? getStats.data?.data;
|
||||||
|
|
||||||
|
for (const [index, stat] of dataToLoop ? dataToLoop.entries() : []) {
|
||||||
|
if (index === 0) {
|
||||||
|
let updated = false;
|
||||||
|
for (const inter of stat.data.interfaces ?? []) {
|
||||||
|
if (!updated && selected === 'memory') {
|
||||||
|
updated = true;
|
||||||
|
setSelected(inter.name);
|
||||||
|
}
|
||||||
|
previousRx[inter.name] = inter.counters?.rx_bytes ?? 0;
|
||||||
|
previousTx[inter.name] = inter.counters?.tx_bytes ?? 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const newMem = extractMemory(stat.data);
|
||||||
|
memoryData.used.push(newMem.used ?? 0);
|
||||||
|
memoryData.buffered.push(newMem.buffered ?? 0);
|
||||||
|
memoryData.cached.push(newMem.cached ?? 0);
|
||||||
|
memoryData.free.push(newMem.free ?? 0);
|
||||||
|
memoryData.total.push(newMem.total ?? 0);
|
||||||
|
memoryData.recorded.push(stat.recorded);
|
||||||
|
|
||||||
|
for (const inter of stat.data.interfaces ?? []) {
|
||||||
|
const rx = inter.counters?.rx_bytes ?? 0;
|
||||||
|
const tx = inter.counters?.tx_bytes ?? 0;
|
||||||
|
let rxDelta = rx - (previousRx[inter.name] ?? 0);
|
||||||
|
if (rxDelta < 0) rxDelta = 0;
|
||||||
|
let txDelta = tx - (previousTx[inter.name] ?? 0);
|
||||||
|
if (txDelta < 0) txDelta = 0;
|
||||||
|
if (data[inter.name] === undefined)
|
||||||
|
data[inter.name] = {
|
||||||
|
rx: [rxDelta],
|
||||||
|
tx: [txDelta],
|
||||||
|
recorded: [stat.recorded],
|
||||||
|
maxTx: txDelta,
|
||||||
|
maxRx: rxDelta,
|
||||||
|
};
|
||||||
|
else {
|
||||||
|
data[inter.name]?.rx.push(rxDelta);
|
||||||
|
data[inter.name]?.tx.push(txDelta);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
previousRx[inter.name] = rx;
|
||||||
|
previousTx[inter.name] = tx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
interfaces: data,
|
||||||
|
memory: memoryData,
|
||||||
|
};
|
||||||
|
}, [getStats.data, getCustomStats.data]);
|
||||||
|
|
||||||
|
const refresh = React.useCallback(() => {
|
||||||
|
if (!time) getStats.refetch();
|
||||||
|
else getCustomStats.refetch();
|
||||||
|
}, [time]);
|
||||||
|
|
||||||
|
const isLoading = React.useMemo(() => {
|
||||||
|
if (!time && getStats?.isFetching) return { isLoading: true };
|
||||||
|
if (time && getCustomStats.isFetching) return { isLoading: true, progress };
|
||||||
|
|
||||||
|
return { isLoading: false };
|
||||||
|
}, [getStats, getCustomStats, time, progress]);
|
||||||
|
|
||||||
|
return React.useMemo(
|
||||||
|
() => ({
|
||||||
|
parsedData,
|
||||||
|
isLoading,
|
||||||
|
onSelectInterface,
|
||||||
|
selected,
|
||||||
|
time,
|
||||||
|
setTime,
|
||||||
|
refresh,
|
||||||
|
}),
|
||||||
|
[parsedData, isLoading, selected, time],
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user