Files
OptimCloud-gw-ui/src/pages/Devices/ListCard/index.tsx
Charles 21452d091f [WIFI-11542] AP Scripts
Signed-off-by: Charles <charles.bourque96@gmail.com>
2022-12-01 16:12:30 +00:00

426 lines
14 KiB
TypeScript

import * as React from 'react';
import { Box, Button, Heading, Image, Spacer, Tooltip, useDisclosure } from '@chakra-ui/react';
import { LockSimple } from 'phosphor-react';
import ReactCountryFlag from 'react-country-flag';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import Actions from './Actions';
import DeviceSearchBar from './DeviceSearchBar';
import DeviceListFirmwareButton from './FirmwareButton';
import AP from './icons/AP.png';
import IOT from './icons/IOT.png';
import MESH from './icons/MESH.png';
import SWITCH from './icons/SWITCH.png';
import { RefreshButton } from 'components/Buttons/RefreshButton';
import { CardBody } from 'components/Containers/Card/CardBody';
import { CardHeader } from 'components/Containers/Card/CardHeader';
import { ColumnPicker } from 'components/DataTables/ColumnPicker';
import { DataTable } from 'components/DataTables/DataTable';
import FormattedDate from 'components/InformationDisplays/FormattedDate';
import { ConfigureModal } from 'components/Modals/ConfigureModal';
import { EventQueueModal } from 'components/Modals/EventQueueModal';
import FactoryResetModal from 'components/Modals/FactoryResetModal';
import { FirmwareUpgradeModal } from 'components/Modals/FirmwareUpgradeModal';
import { useScriptModal } from 'components/Modals/ScriptModal/useScriptModal';
import { TelemetryModal } from 'components/Modals/TelemetryModal';
import { TraceModal } from 'components/Modals/TraceModal';
import { WifiScanModal } from 'components/Modals/WifiScanModal';
import DataCell from 'components/TableCells/DataCell';
import NumberCell from 'components/TableCells/NumberCell';
import { DeviceWithStatus, useGetDeviceCount, useGetDevices } from 'hooks/Network/Devices';
import { FirmwareAgeResponse, useGetFirmwareAges } from 'hooks/Network/Firmware';
import { Column, PageInfo } from 'models/Table';
const ICON_STYLE = { width: '24px', height: '24px', borderRadius: '20px' };
const ICONS = {
AP: <Image borderRadius="full" boxSize="25px" src={AP} left="auto" right="auto" />,
SWITCH: <Image borderRadius="full" boxSize="25px" src={SWITCH} left="auto" right="auto" />,
IOT: <Image borderRadius="full" boxSize="25px" src={IOT} left="auto" right="auto" />,
MESH: <Image borderRadius="full" boxSize="25px" src={MESH} left="auto" right="auto" />,
};
const BADGE_COLORS: Record<string, string> = {
VALID_CERTIFICATE: 'red',
NO_CERTIFICATE: 'red',
MISMATCH_SERIAL: 'yellow',
VERIFIED: 'green',
};
const DeviceListCard = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const [serialNumber, setSerialNumber] = React.useState<string>('');
const [hiddenColumns, setHiddenColumns] = React.useState<string[]>([]);
const [pageInfo, setPageInfo] = React.useState<PageInfo | undefined>(undefined);
const scanModalProps = useDisclosure();
const resetModalProps = useDisclosure();
const upgradeModalProps = useDisclosure();
const traceModalProps = useDisclosure();
const eventQueueProps = useDisclosure();
const telemetryModalProps = useDisclosure();
const configureModalProps = useDisclosure();
const scriptModal = useScriptModal();
const getCount = useGetDeviceCount({ enabled: true });
const getDevices = useGetDevices({
pageInfo,
enabled: true,
});
const getAges = useGetFirmwareAges({
serialNumbers: getDevices.data?.devicesWithStatus.map((device) => device.serialNumber),
});
const onOpenScan = (serial: string) => {
setSerialNumber(serial);
scanModalProps.onOpen();
};
const onOpenFactoryReset = (serial: string) => {
setSerialNumber(serial);
resetModalProps.onOpen();
};
const onOpenUpgradeModal = (serial: string) => {
setSerialNumber(serial);
upgradeModalProps.onOpen();
};
const onOpenTrace = (serial: string) => {
setSerialNumber(serial);
traceModalProps.onOpen();
};
const onOpenEventQueue = (serial: string) => {
setSerialNumber(serial);
eventQueueProps.onOpen();
};
const onOpenTelemetry = (serial: string) => {
setSerialNumber(serial);
telemetryModalProps.onOpen();
};
const onOpenConfigure = (serial: string) => {
setSerialNumber(serial);
configureModalProps.onOpen();
};
const goToSerial = (serial: string) => () => {
navigate(`/devices/${serial}`);
};
const badgeCell = React.useCallback(
(device: DeviceWithStatus) => (
<Box
h="35px"
w="35px"
borderRadius="50em"
bgColor={BADGE_COLORS[device.verifiedCertificate] ?? 'red'}
alignItems="center"
display="inline-flex"
justifyContent="center"
position="relative"
>
<Tooltip
label={`${device.verifiedCertificate} - ${
device.connected ? t('common.connected') : t('common.disconnected')
} ${device.restrictedDevice ? `- ${t('devices.restricted')}` : ''}`}
>
{ICONS[device.deviceType] ?? ICONS.AP}
</Tooltip>
<Box
w="0.65em"
h="0.65em"
borderRadius="full"
position="absolute"
bg={device.connected ? 'green' : 'red'}
right={0.5}
bottom={0}
borderColor="gray.200"
borderWidth={1}
/>
{device.restrictedDevice && (
<Box
w="0.95em"
h="0.95em"
borderRadius="full"
position="absolute"
bg="blue.100"
left={-1}
top={0}
borderColor="gray.200"
borderWidth={1}
>
<LockSimple
size={12}
style={{
color: 'black',
}}
/>
</Box>
)}
</Box>
),
[],
);
const serialCell = React.useCallback(
(device: DeviceWithStatus) => (
<Button variant="link" onClick={goToSerial(device.serialNumber)} fontSize="sm">
<pre>{device.serialNumber}</pre>
</Button>
),
[],
);
const dataCell = React.useCallback(
(v: number) => (
<Box textAlign="right">
<DataCell bytes={v} />
</Box>
),
[],
);
const dateCell = React.useCallback(
(v?: number | string, hidePrefix?: boolean) =>
v !== undefined && typeof v === 'number' && v !== 0 ? (
<FormattedDate date={v as number} hidePrefix={hidePrefix} />
) : (
'-'
),
[],
);
const firmwareCell = React.useCallback(
(device: DeviceWithStatus & { age?: FirmwareAgeResponse }) => (
<DeviceListFirmwareButton device={device} age={device.age} onOpenUpgrade={onOpenUpgradeModal} />
),
[getAges],
);
const localeCell = React.useCallback(
(device: DeviceWithStatus) => (
<Tooltip label={`${device.locale !== '' ? `${device.locale} - ` : ''}${device.ipAddress}`} placement="top">
<Box w="100%" textOverflow="ellipsis" overflow="hidden" whiteSpace="nowrap">
{device.locale !== '' && device.ipAddress !== '' && (
<ReactCountryFlag style={ICON_STYLE} countryCode={device.locale} svg />
)}
{` ${device.ipAddress}`}
</Box>
</Tooltip>
),
[],
);
const numberCell = React.useCallback((v?: number) => <NumberCell value={v !== undefined ? v : 0} />, []);
const actionCell = React.useCallback(
(device: DeviceWithStatus) => (
<Actions
device={device}
refreshTable={getDevices.refetch}
onOpenScan={onOpenScan}
onOpenFactoryReset={onOpenFactoryReset}
onOpenUpgradeModal={onOpenUpgradeModal}
onOpenTrace={onOpenTrace}
onOpenEventQueue={onOpenEventQueue}
onOpenConfigureModal={onOpenConfigure}
onOpenTelemetryModal={onOpenTelemetry}
onOpenScriptModal={scriptModal.openModal}
/>
),
[],
);
const columns: Column<DeviceWithStatus>[] = React.useMemo(
(): Column<DeviceWithStatus>[] => [
{
id: 'badge',
Header: '',
Footer: '',
accessor: 'badge',
Cell: (v) => badgeCell(v.cell.row.original),
customWidth: '35px',
alwaysShow: true,
disableSortBy: true,
},
{
id: 'serialNumber',
Header: t('inventory.serial_number'),
Footer: '',
accessor: 'serialNumber',
Cell: (v) => serialCell(v.cell.row.original),
alwaysShow: true,
customMaxWidth: '200px',
customWidth: '130px',
customMinWidth: '130px',
disableSortBy: true,
},
{
id: 'firmware',
Header: t('commands.revision'),
Footer: '',
accessor: 'firmware',
Cell: (v) => firmwareCell(v.cell.row.original),
customWidth: '50px',
disableSortBy: true,
},
{
id: 'compatible',
Header: t('common.type'),
Footer: '',
accessor: 'compatible',
customWidth: '50px',
disableSortBy: true,
},
{
id: 'IP',
Header: 'IP',
Footer: '',
accessor: 'IP',
Cell: (v) => localeCell(v.cell.row.original),
disableSortBy: true,
},
{
id: 'lastContact',
Header: t('analytics.last_contact'),
Footer: '',
accessor: 'lastContact',
Cell: (v) => dateCell(v.cell.row.original.lastContact),
disableSortBy: true,
},
{
id: 'lastFWUpdate',
Header: t('controller.devices.last_upgrade'),
Footer: '',
accessor: 'lastFWUpdate',
Cell: (v) => dateCell(v.cell.row.original.lastFWUpdate),
disableSortBy: true,
},
{
id: 'rxBytes',
Header: 'Rx',
Footer: '',
accessor: 'rxBytes',
Cell: (v) => dataCell(v.cell.row.original.rxBytes),
customWidth: '50px',
disableSortBy: true,
},
{
id: 'txBytes',
Header: 'Tx',
Footer: '',
accessor: 'txBytes',
Cell: (v) => dataCell(v.cell.row.original.txBytes),
customWidth: '50px',
disableSortBy: true,
},
{
id: '2G',
Header: '2G',
Footer: '',
accessor: 'associations_2G',
Cell: (v) => numberCell(v.cell.row.original.associations_2G),
customWidth: '50px',
disableSortBy: true,
},
{
id: '5G',
Header: '5G',
Footer: '',
accessor: 'associations_5G',
Cell: (v) => numberCell(v.cell.row.original.associations_5G),
customWidth: '50px',
disableSortBy: true,
},
{
id: '6G',
Header: '6G',
Footer: '',
accessor: 'associations_6G',
Cell: (v) => numberCell(v.cell.row.original.associations_6G),
customWidth: '50px',
disableSortBy: true,
},
{
id: 'certificateExpiryDate',
Header: t('devices.certificate_expiry'),
Footer: '',
accessor: 'certificateExpiryDate',
Cell: (v) => dateCell(v.cell.row.original.certificateExpiryDate, true),
customWidth: '50px',
disableSortBy: true,
},
{
id: 'actions',
Header: t('common.actions'),
Footer: '',
accessor: 'actions',
Cell: (v) => actionCell(v.cell.row.original),
customWidth: '50px',
alwaysShow: true,
disableSortBy: true,
},
],
[t, firmwareCell],
);
const data = React.useMemo(() => {
if (!getDevices.data) return [];
return getDevices.data.devicesWithStatus.map((device) => ({
...device,
age: getAges?.data?.ages.find(({ serialNumber: devSerial }) => devSerial === device.serialNumber),
}));
}, [getAges, getDevices]);
return (
<>
<CardHeader px={4} pt={4}>
<Heading size="md" my="auto" mr={2}>
{getCount.data?.count} {t('devices.title')}
</Heading>
<DeviceSearchBar />
<Spacer />
<ColumnPicker
columns={columns as Column<unknown>[]}
hiddenColumns={hiddenColumns}
setHiddenColumns={setHiddenColumns}
preference="gateway.devices.table.hiddenColumns"
/>
<RefreshButton
onClick={() => {
getDevices.refetch();
getCount.refetch();
}}
isCompact
ml={2}
isFetching={getCount.isFetching || getDevices.isFetching}
/>
</CardHeader>
<CardBody p={4}>
<Box overflowX="auto" w="100%">
<DataTable
columns={
columns.filter(({ id }) => !hiddenColumns.find((hidden) => hidden === id)) as {
id: string;
Header: string;
Footer: string;
accessor: string;
}[]
}
data={data ?? []}
isLoading={getCount.isFetching || getDevices.isFetching}
isManual
hiddenColumns={hiddenColumns}
obj={t('devices.title')}
count={getCount.data?.count || 0}
// @ts-ignore
setPageInfo={setPageInfo}
saveSettingsId="gateway.devices.table"
minHeight="600px"
/>
</Box>
</CardBody>
<WifiScanModal modalProps={scanModalProps} serialNumber={serialNumber} />
<FirmwareUpgradeModal modalProps={upgradeModalProps} serialNumber={serialNumber} />
<FactoryResetModal modalProps={resetModalProps} serialNumber={serialNumber} />
<TraceModal modalProps={traceModalProps} serialNumber={serialNumber} />
<EventQueueModal modalProps={eventQueueProps} serialNumber={serialNumber} />
<ConfigureModal modalProps={configureModalProps} serialNumber={serialNumber} />
<TelemetryModal modalProps={telemetryModalProps} serialNumber={serialNumber} />
{scriptModal.modal}
</>
);
};
export default React.memo(DeviceListCard);