mirror of
https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
synced 2025-10-29 17:32:20 +00:00
[WIFI-13136] Display WiFi scan new station count and channel utilization values
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.11.0(13)",
|
"version": "2.11.0(15)",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.11.0(13)",
|
"version": "2.11.0(15)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/anatomy": "^2.1.1",
|
"@chakra-ui/anatomy": "^2.1.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.11.0(13)",
|
"version": "2.11.0(15)",
|
||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
|
|||||||
8
src/@tanstack.react-table.d.ts
vendored
8
src/@tanstack.react-table.d.ts
vendored
@@ -4,18 +4,22 @@ import '@tanstack/react-table';
|
|||||||
|
|
||||||
declare module '@tanstack/table-core' {
|
declare module '@tanstack/table-core' {
|
||||||
interface ColumnMeta<TData extends RowData, TValue> {
|
interface ColumnMeta<TData extends RowData, TValue> {
|
||||||
|
ref?: React.MutableRefObject<HTMLTableCellElement | null>;
|
||||||
|
customMinWidth?: string;
|
||||||
|
anchored?: boolean;
|
||||||
stopPropagation?: boolean;
|
stopPropagation?: boolean;
|
||||||
alwaysShow?: boolean;
|
alwaysShow?: boolean;
|
||||||
anchored?: boolean;
|
|
||||||
hasPopover?: boolean;
|
hasPopover?: boolean;
|
||||||
customMaxWidth?: string;
|
customMaxWidth?: string;
|
||||||
customMinWidth?: string;
|
|
||||||
customWidth?: string;
|
customWidth?: string;
|
||||||
isMonospace?: boolean;
|
isMonospace?: boolean;
|
||||||
isCentered?: boolean;
|
isCentered?: boolean;
|
||||||
columnSelectorOptions?: {
|
columnSelectorOptions?: {
|
||||||
label?: string;
|
label?: string;
|
||||||
};
|
};
|
||||||
|
rowContentOptions?: {
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
};
|
||||||
headerOptions?: {
|
headerOptions?: {
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ export const DataGridCellRow = <TValue extends object>({
|
|||||||
backgroundColor: hoveredRowBg,
|
backgroundColor: hoveredRowBg,
|
||||||
}}
|
}}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
borderRight="1px solid gray"
|
|
||||||
>
|
>
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<Td
|
<Td
|
||||||
@@ -55,6 +54,7 @@ export const DataGridCellRow = <TValue extends object>({
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
border="0.5px solid gray"
|
border="0.5px solid gray"
|
||||||
|
style={cell.column.columnDef.meta?.rowContentOptions?.style}
|
||||||
>
|
>
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
</Td>
|
</Td>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export type DataGridHeaderRowProps<TValue extends object> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DataGridHeaderRow = <TValue extends object>({ headerGroup }: DataGridHeaderRowProps<TValue>) => (
|
export const DataGridHeaderRow = <TValue extends object>({ headerGroup }: DataGridHeaderRowProps<TValue>) => (
|
||||||
<Tr p={0} borderRight="1px solid gray">
|
<Tr p={0}>
|
||||||
{headerGroup.headers.map((header) => (
|
{headerGroup.headers.map((header) => (
|
||||||
<Th
|
<Th
|
||||||
color="gray.400"
|
color="gray.400"
|
||||||
|
|||||||
@@ -40,13 +40,16 @@ export type DataGridOptions<TValue extends object> = {
|
|||||||
onRowClick?: (row: TValue) => (() => void) | undefined;
|
onRowClick?: (row: TValue) => (() => void) | undefined;
|
||||||
refetch?: () => void;
|
refetch?: () => void;
|
||||||
showAsCard?: boolean;
|
showAsCard?: boolean;
|
||||||
|
hideTablePreferences?: boolean;
|
||||||
|
hideTableTitleRow?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DataGridProps<TValue extends object> = {
|
export type DataGridProps<TValue extends object> = {
|
||||||
|
innerTableKey?: string | number;
|
||||||
controller: UseDataGridReturn;
|
controller: UseDataGridReturn;
|
||||||
columns: DataGridColumn<TValue>[];
|
columns: DataGridColumn<TValue>[];
|
||||||
header: {
|
header: {
|
||||||
title: string;
|
title: string | React.ReactNode;
|
||||||
objectListed: string;
|
objectListed: string;
|
||||||
leftContent?: React.ReactNode;
|
leftContent?: React.ReactNode;
|
||||||
addButton?: React.ReactNode;
|
addButton?: React.ReactNode;
|
||||||
@@ -58,6 +61,7 @@ export type DataGridProps<TValue extends object> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DataGrid = <TValue extends object>({
|
export const DataGrid = <TValue extends object>({
|
||||||
|
innerTableKey,
|
||||||
controller,
|
controller,
|
||||||
columns,
|
columns,
|
||||||
header,
|
header,
|
||||||
@@ -149,6 +153,20 @@ export const DataGrid = <TValue extends object>({
|
|||||||
...tableOptions,
|
...tableOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If this is a manual DataTable, with a page index that is higher than 0 and higher than the max possible page, we send to index 0
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (
|
||||||
|
options.isManual &&
|
||||||
|
!isLoading &&
|
||||||
|
data &&
|
||||||
|
pagination.pageIndex > 0 &&
|
||||||
|
options.count !== undefined &&
|
||||||
|
Math.ceil(options.count / pagination.pageSize) - 1 < pagination.pageIndex
|
||||||
|
) {
|
||||||
|
controller.onPaginationChange({ pageIndex: 0, pageSize: pagination.pageSize });
|
||||||
|
}
|
||||||
|
}, [options.count, isLoading, pagination, data]);
|
||||||
|
|
||||||
if (isLoading && !options.showAsCard && data.length === 0) {
|
if (isLoading && !options.showAsCard && data.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center>
|
||||||
@@ -160,25 +178,29 @@ export const DataGrid = <TValue extends object>({
|
|||||||
return options.showAsCard ? (
|
return options.showAsCard ? (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<Heading size="md" my="auto" mr={2}>
|
{typeof header.title === 'string' ? (
|
||||||
{header.title}
|
<Heading size="md" my="auto" mr={2}>
|
||||||
</Heading>
|
{header.title}
|
||||||
|
</Heading>
|
||||||
|
) : (
|
||||||
|
header.title
|
||||||
|
)}
|
||||||
{header.leftContent}
|
{header.leftContent}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<HStack spacing={2}>
|
<HStack spacing={2}>
|
||||||
{header.otherButtons}
|
{header.otherButtons}
|
||||||
{header.addButton}
|
{header.addButton}
|
||||||
{
|
{options.hideTablePreferences ? null : (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<TableSettingsModal<TValue> controller={controller} columns={columns} />
|
<TableSettingsModal<TValue> controller={controller} columns={columns} />
|
||||||
}
|
)}
|
||||||
{options.refetch ? <RefreshButton onClick={options.refetch} isCompact isFetching={isLoading} /> : null}
|
{options.refetch ? <RefreshButton onClick={options.refetch} isCompact isFetching={isLoading} /> : null}
|
||||||
</HStack>
|
</HStack>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody display="flex" flexDirection="column">
|
<CardBody display="flex" flexDirection="column">
|
||||||
<LoadingOverlay isLoading={isLoading}>
|
<LoadingOverlay isLoading={isLoading}>
|
||||||
<TableContainer minH={minimumHeight}>
|
<TableContainer minH={minimumHeight}>
|
||||||
<Table size="small" variant="simple" textColor={textColor} w="100%" fontSize="14px">
|
<Table size="small" variant="simple" textColor={textColor} w="100%" fontSize="14px" key={innerTableKey}>
|
||||||
<Thead>
|
<Thead>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<DataGridHeaderRow<TValue> key={headerGroup.id} headerGroup={headerGroup} />
|
<DataGridHeaderRow<TValue> key={headerGroup.id} headerGroup={headerGroup} />
|
||||||
@@ -206,7 +228,7 @@ export const DataGrid = <TValue extends object>({
|
|||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<Box w="100%">
|
<Box w="100%">
|
||||||
<Flex mb={2}>
|
<Flex mb={2} hidden={options.hideTableTitleRow}>
|
||||||
<Heading size="md" my="auto" mr={2}>
|
<Heading size="md" my="auto" mr={2}>
|
||||||
{header.title}
|
{header.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
@@ -215,16 +237,16 @@ export const DataGrid = <TValue extends object>({
|
|||||||
<HStack spacing={2}>
|
<HStack spacing={2}>
|
||||||
{header.otherButtons}
|
{header.otherButtons}
|
||||||
{header.addButton}
|
{header.addButton}
|
||||||
{
|
{options.hideTablePreferences ? null : (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<TableSettingsModal<TValue> controller={controller} columns={columns} />
|
<TableSettingsModal<TValue> controller={controller} columns={columns} />
|
||||||
}
|
)}
|
||||||
{options.refetch ? <RefreshButton onClick={options.refetch} isCompact isFetching={isLoading} /> : null}
|
{options.refetch ? <RefreshButton onClick={options.refetch} isCompact isFetching={isLoading} /> : null}
|
||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
<LoadingOverlay isLoading={isLoading}>
|
<LoadingOverlay isLoading={isLoading}>
|
||||||
<TableContainer minH={minimumHeight}>
|
<TableContainer minH={minimumHeight}>
|
||||||
<Table size="small" variant="simple" textColor={textColor} w="100%" fontSize="14px">
|
<Table size="small" variant="simple" textColor={textColor} w="100%" fontSize="14px" key={innerTableKey}>
|
||||||
<Thead>
|
<Thead>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<DataGridHeaderRow<TValue> key={headerGroup.id} headerGroup={headerGroup} />
|
<DataGridHeaderRow<TValue> key={headerGroup.id} headerGroup={headerGroup} />
|
||||||
|
|||||||
@@ -104,18 +104,43 @@ const GlobalSearchBar = () => {
|
|||||||
.then(() => callback([]));
|
.then(() => callback([]));
|
||||||
}
|
}
|
||||||
if (v.match('^[a-fA-F0-9-*]+$')) {
|
if (v.match('^[a-fA-F0-9-*]+$')) {
|
||||||
|
let result: { label: string; value: string; type: 'serial' }[] = [];
|
||||||
|
let tryAgain = true;
|
||||||
|
|
||||||
await store
|
await store
|
||||||
.searchSerialNumber(v)
|
.searchSerialNumber(v)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
callback(
|
result = res.map((r) => ({
|
||||||
res.map((r) => ({
|
label: r,
|
||||||
|
value: r,
|
||||||
|
type: 'serial',
|
||||||
|
}));
|
||||||
|
tryAgain = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
result = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tryAgain) {
|
||||||
|
// Wait 1 second and try again
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
await store
|
||||||
|
.searchSerialNumber(v)
|
||||||
|
.then((res) => {
|
||||||
|
result = res.map((r) => ({
|
||||||
label: r,
|
label: r,
|
||||||
value: r,
|
value: r,
|
||||||
type: 'serial',
|
type: 'serial',
|
||||||
})),
|
}));
|
||||||
);
|
tryAgain = false;
|
||||||
})
|
})
|
||||||
.catch(() => []);
|
.catch(() => {
|
||||||
|
result = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(result);
|
||||||
}
|
}
|
||||||
return callback([]);
|
return callback([]);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,18 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import { Box, Button, Center, Heading, IconButton, Spacer, useColorMode } from '@chakra-ui/react';
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Heading,
|
|
||||||
IconButton,
|
|
||||||
Spacer,
|
|
||||||
Table,
|
|
||||||
Tbody,
|
|
||||||
Td,
|
|
||||||
Th,
|
|
||||||
Thead,
|
|
||||||
Tr,
|
|
||||||
useColorMode,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { JsonViewer } from '@textea/json-viewer';
|
import { JsonViewer } from '@textea/json-viewer';
|
||||||
import { ArrowLeft } from '@phosphor-icons/react';
|
import { ArrowLeft } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -20,21 +7,124 @@ import { v4 as uuid } from 'uuid';
|
|||||||
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 { ScanChannel } from 'models/Device';
|
import { DeviceScanResult, ScanChannel } from 'models/Device';
|
||||||
|
import { DataGrid } from 'components/DataTables/DataGrid';
|
||||||
|
import { DataGridColumn, useDataGrid } from 'components/DataTables/DataGrid/useDataGrid';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
channelInfo: ScanChannel;
|
channelInfo: ScanChannel;
|
||||||
}
|
}
|
||||||
const ResultCard: React.FC<Props> = ({ channelInfo: { channel, devices } }) => {
|
|
||||||
|
const ueCell = (ies: DeviceScanResult['ies'], setIes: (ies: DeviceScanResult['ies']) => void) => (
|
||||||
|
<Button size="sm" colorScheme="blue" onClick={() => setIes(ies)} w="100%">
|
||||||
|
{ies.length}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const centerIfUndefinedCell = (v?: string | number, suffix?: string) =>
|
||||||
|
v !== undefined ? `${v}${suffix ? `${suffix}` : ''}` : <Center>-</Center>;
|
||||||
|
|
||||||
|
const ResultCard = ({ channelInfo: { channel, devices } }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
const [ies, setIes] = React.useState<{ content: unknown; name: string; type: number }[] | undefined>();
|
const [ies, setIes] = React.useState<{ content: unknown; name: string; type: number }[] | undefined>();
|
||||||
|
const tableController = useDataGrid({
|
||||||
|
tableSettingsId: 'wifiscan.devices.table',
|
||||||
|
defaultOrder: ['ssid', 'signal', 'actions'],
|
||||||
|
defaultSortBy: [
|
||||||
|
{
|
||||||
|
desc: false,
|
||||||
|
id: 'ssid',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns: DataGridColumn<DeviceScanResult>[] = React.useMemo(
|
||||||
|
(): DataGridColumn<DeviceScanResult>[] => [
|
||||||
|
{
|
||||||
|
id: 'ssid',
|
||||||
|
header: 'SSID',
|
||||||
|
footer: '',
|
||||||
|
accessorKey: 'ssid',
|
||||||
|
meta: {
|
||||||
|
anchored: true,
|
||||||
|
alwaysShow: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'signal',
|
||||||
|
header: 'Signal',
|
||||||
|
footer: '',
|
||||||
|
accessorKey: 'signal',
|
||||||
|
cell: (v) => `${v.cell.row.original.signal} db`,
|
||||||
|
meta: {
|
||||||
|
anchored: true,
|
||||||
|
customWidth: '80px',
|
||||||
|
alwaysShow: true,
|
||||||
|
rowContentOptions: {
|
||||||
|
style: {
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'station',
|
||||||
|
header: 'UEs',
|
||||||
|
accessorKey: 'sta_count',
|
||||||
|
cell: (v) => centerIfUndefinedCell(v.cell.row.original.sta_count),
|
||||||
|
meta: {
|
||||||
|
anchored: true,
|
||||||
|
customWidth: '40px',
|
||||||
|
alwaysShow: true,
|
||||||
|
rowContentOptions: {
|
||||||
|
style: {
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'utilization',
|
||||||
|
header: 'Ch. Util.',
|
||||||
|
accessorKey: 'ch_util',
|
||||||
|
cell: (v) => centerIfUndefinedCell(v.cell.row.original.ch_util, '%'),
|
||||||
|
meta: {
|
||||||
|
anchored: true,
|
||||||
|
customWidth: '60px',
|
||||||
|
alwaysShow: true,
|
||||||
|
headerOptions: {
|
||||||
|
tooltip: 'Channel Utilization (%)',
|
||||||
|
},
|
||||||
|
rowContentOptions: {
|
||||||
|
style: {
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ies',
|
||||||
|
header: 'Ies',
|
||||||
|
footer: '',
|
||||||
|
accessorKey: 'actions',
|
||||||
|
cell: (v) => ueCell(v.cell.row.original.ies ?? [], setIes),
|
||||||
|
meta: {
|
||||||
|
customWidth: '50px',
|
||||||
|
isCentered: true,
|
||||||
|
alwaysShow: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card variant="widget">
|
<Card>
|
||||||
<CardHeader display="flex">
|
<CardHeader display="flex">
|
||||||
<Heading size="md" my="auto">
|
<Heading size="md" my="auto">
|
||||||
{t('commands.channel')} #{channel} ({devices.length} {t('devices.title')})
|
{t('commands.channel')} #{channel} ({devices.length}{' '}
|
||||||
|
{devices.length === 1 ? t('devices.one') : t('devices.title')})
|
||||||
</Heading>
|
</Heading>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{ies && (
|
{ies && (
|
||||||
@@ -49,52 +139,43 @@ const ResultCard: React.FC<Props> = ({ channelInfo: { channel, devices } }) => {
|
|||||||
)}
|
)}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Box h="400px" w="100%" overflowY="auto" overflowX="auto" px={0}>
|
{ies ? (
|
||||||
{ies ? (
|
<Box w="800px">
|
||||||
<Box w="800px">
|
{ies.map(({ content, name, type }) => (
|
||||||
{ies.map(({ content, name, type }) => (
|
<Box key={uuid()} my={2}>
|
||||||
<Box key={uuid()} my={2}>
|
<Heading size="sm" mb={2} textDecor="underline">
|
||||||
<Heading size="sm" mb={2} textDecor="underline">
|
{name} ({type})
|
||||||
{name} ({type})
|
</Heading>
|
||||||
</Heading>
|
<JsonViewer
|
||||||
<JsonViewer
|
rootName={false}
|
||||||
rootName={false}
|
displayDataTypes={false}
|
||||||
displayDataTypes={false}
|
enableClipboard
|
||||||
enableClipboard
|
theme={colorMode === 'light' ? undefined : 'dark'}
|
||||||
theme={colorMode === 'light' ? undefined : 'dark'}
|
value={content as object}
|
||||||
value={content as object}
|
style={{ background: 'unset', display: 'unset' }}
|
||||||
style={{ background: 'unset', display: 'unset' }}
|
/>
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
))}
|
||||||
))}
|
</Box>
|
||||||
</Box>
|
) : (
|
||||||
) : (
|
<DataGrid<DeviceScanResult>
|
||||||
<Table variant="simple" px={0}>
|
controller={tableController}
|
||||||
<Thead>
|
header={{
|
||||||
<Tr>
|
title: '',
|
||||||
<Th>SSID</Th>
|
objectListed: t('devices.title'),
|
||||||
<Th width="110px" isNumeric>
|
}}
|
||||||
{t('commands.signal')}
|
columns={columns}
|
||||||
</Th>
|
data={devices}
|
||||||
<Th w="10px">IEs</Th>
|
options={{
|
||||||
</Tr>
|
count: devices.length,
|
||||||
</Thead>
|
onRowClick: (device) => () => setIes(device.ies ?? []),
|
||||||
<Tbody>
|
hideTablePreferences: true,
|
||||||
{devices.map((dev) => (
|
isHidingControls: true,
|
||||||
<Tr key={uuid()}>
|
minimumHeight: '0px',
|
||||||
<Td>{dev.ssid}</Td>
|
hideTableTitleRow: true,
|
||||||
<Td width="110px">{dev.signal} db</Td>
|
}}
|
||||||
<Td w="10px">
|
/>
|
||||||
<Button size="sm" colorScheme="blue" onClick={() => setIes(dev.ies ?? [])}>
|
)}
|
||||||
{dev.ies?.length ?? 0}
|
|
||||||
</Button>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { Alert, Heading, SimpleGrid } from '@chakra-ui/react';
|
import { Alert, Heading, VStack } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import ResultCard from './ResultCard';
|
import ResultCard from './ResultCard';
|
||||||
@@ -11,7 +11,7 @@ interface Props {
|
|||||||
setCsvData: (data: DeviceScanResult[]) => void;
|
setCsvData: (data: DeviceScanResult[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WifiScanResultDisplay: React.FC<Props> = ({ results, setCsvData }) => {
|
const WifiScanResultDisplay = ({ results, setCsvData }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const scanResults = useMemo(() => {
|
const scanResults = useMemo(() => {
|
||||||
@@ -54,18 +54,18 @@ const WifiScanResultDisplay: React.FC<Props> = ({ results, setCsvData }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{results.errorCode === 1 && (
|
{results.errorCode === 1 && (
|
||||||
<Heading size="sm">
|
<Heading size="md">
|
||||||
<Alert colorScheme="red">{t('commands.wifiscan_error_1')}</Alert>
|
<Alert colorScheme="red">{t('commands.wifiscan_error_1')}</Alert>
|
||||||
</Heading>
|
</Heading>
|
||||||
)}
|
)}
|
||||||
<Heading size="sm">
|
<Heading size="md" mb={2}>
|
||||||
{t('commands.execution_time')}: {Math.floor(results.executionTime / 1000)}s
|
{t('commands.execution_time')}: {Math.floor(results.executionTime / 1000)}s
|
||||||
</Heading>
|
</Heading>
|
||||||
<SimpleGrid minChildWidth="360px" spacing={2}>
|
<VStack spacing={4} align="stretch">
|
||||||
{scanResults?.scanList.map((channel) => (
|
{scanResults?.scanList.map((channel) => (
|
||||||
<ResultCard key={uuid()} channelInfo={channel} />
|
<ResultCard key={uuid()} channelInfo={channel} />
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</VStack>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ export const Navbar = ({
|
|||||||
top="15px"
|
top="15px"
|
||||||
border={scrolled ? '0.5px solid' : undefined}
|
border={scrolled ? '0.5px solid' : undefined}
|
||||||
w={isCompact ? '100%' : 'calc(100% - 254px)'}
|
w={isCompact ? '100%' : 'calc(100% - 254px)'}
|
||||||
|
zIndex={1}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
w="100%"
|
w="100%"
|
||||||
|
|||||||
@@ -114,12 +114,16 @@ interface BssidResult {
|
|||||||
bssid: string;
|
bssid: string;
|
||||||
capability: number;
|
capability: number;
|
||||||
channel: number;
|
channel: number;
|
||||||
|
/** Channel Utilization percentage (ex.: 28 -> 28% channel utilization) */
|
||||||
|
ch_util?: number;
|
||||||
frequency: number;
|
frequency: number;
|
||||||
ht_oper: string;
|
ht_oper: string;
|
||||||
ies: { content: unknown; name: string; type: number }[];
|
ies: { content: unknown; name: string; type: number }[];
|
||||||
last_seen: number;
|
last_seen: number;
|
||||||
ssid: string;
|
ssid: string;
|
||||||
signal: number;
|
signal: number;
|
||||||
|
/** Station count */
|
||||||
|
sta_count?: number;
|
||||||
tsf: number;
|
tsf: number;
|
||||||
meshid?: string;
|
meshid?: string;
|
||||||
vht_oper: string;
|
vht_oper: string;
|
||||||
@@ -144,20 +148,8 @@ export interface WifiScanResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceScanResult {
|
export type DeviceScanResult = BssidResult;
|
||||||
bssid: string;
|
|
||||||
capability: number;
|
|
||||||
channel: number;
|
|
||||||
frequency: number;
|
|
||||||
ht_oper: string;
|
|
||||||
ies: { content: unknown; name: string; type: number }[];
|
|
||||||
last_seen: number;
|
|
||||||
ssid: string;
|
|
||||||
signal: number | string;
|
|
||||||
tsf: number;
|
|
||||||
meshid?: string;
|
|
||||||
vht_oper: string;
|
|
||||||
}
|
|
||||||
export interface ScanChannel {
|
export interface ScanChannel {
|
||||||
channel: number;
|
channel: number;
|
||||||
devices: DeviceScanResult[];
|
devices: DeviceScanResult[];
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ const WifiAnalysisCard = ({ serialNumber }: Props) => {
|
|||||||
<SliderTrack>
|
<SliderTrack>
|
||||||
<SliderFilledTrack />
|
<SliderFilledTrack />
|
||||||
</SliderTrack>
|
</SliderTrack>
|
||||||
<SliderThumb />
|
<SliderThumb zIndex={0} />
|
||||||
</Slider>
|
</Slider>
|
||||||
)}
|
)}
|
||||||
<Box />
|
<Box />
|
||||||
|
|||||||
Reference in New Issue
Block a user