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: , SWITCH: , IOT: , MESH: , }; const BADGE_COLORS: Record = { VALID_CERTIFICATE: 'red', NO_CERTIFICATE: 'red', MISMATCH_SERIAL: 'yellow', VERIFIED: 'green', }; const DeviceListCard = () => { const { t } = useTranslation(); const navigate = useNavigate(); const [serialNumber, setSerialNumber] = React.useState(''); const [hiddenColumns, setHiddenColumns] = React.useState([]); const [pageInfo, setPageInfo] = React.useState(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) => ( {ICONS[device.deviceType] ?? ICONS.AP} {device.restrictedDevice && ( )} ), [], ); const serialCell = React.useCallback( (device: DeviceWithStatus) => ( ), [], ); const dataCell = React.useCallback( (v: number) => ( ), [], ); const dateCell = React.useCallback( (v?: number | string, hidePrefix?: boolean) => v !== undefined && typeof v === 'number' && v !== 0 ? ( ) : ( '-' ), [], ); const firmwareCell = React.useCallback( (device: DeviceWithStatus & { age?: FirmwareAgeResponse }) => ( ), [getAges], ); const localeCell = React.useCallback( (device: DeviceWithStatus) => ( {device.locale !== '' && device.ipAddress !== '' && ( )} {` ${device.ipAddress}`} ), [], ); const numberCell = React.useCallback((v?: number) => , []); const actionCell = React.useCallback( (device: DeviceWithStatus) => ( ), [], ); const columns: Column[] = React.useMemo( (): Column[] => [ { 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 ( <> {getCount.data?.count} {t('devices.title')} []} hiddenColumns={hiddenColumns} setHiddenColumns={setHiddenColumns} preference="gateway.devices.table.hiddenColumns" /> { getDevices.refetch(); getCount.refetch(); }} isCompact ml={2} isFetching={getCount.isFetching || getDevices.isFetching} /> !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" /> {scriptModal.modal} ); }; export default React.memo(DeviceListCard);