Files
OptimCloud-gw-ui/src/pages/Device/Wrapper.tsx
2023-04-12 10:43:35 +02:00

336 lines
12 KiB
TypeScript

import * as React from 'react';
import {
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
Box,
Button,
Heading,
HStack,
Portal,
Spacer,
Tag,
TagLabel,
TagLeftIcon,
Tooltip,
useBreakpoint,
useColorModeValue,
useDisclosure,
useToast,
} from '@chakra-ui/react';
import axios from 'axios';
import { Heart, HeartBreak, LockSimple, LockSimpleOpen, WifiHigh, WifiSlash } from 'phosphor-react';
import { useTranslation } from 'react-i18next';
import Masonry from 'react-masonry-css';
import { useNavigate } from 'react-router-dom';
import DeviceDetails from './Details';
import DeviceLogsCard from './LogsCard';
import DeviceNotes from './Notes';
import RadiusClientsCard from './RadiusClients';
import RestrictionsCard from './RestrictionsCard';
import DeviceStatisticsCard from './StatisticsCard';
import DeviceSummary from './Summary';
import WifiAnalysisCard from './WifiAnalysis';
import { DeleteButton } from 'components/Buttons/DeleteButton';
import DeviceActionDropdown from 'components/Buttons/DeviceActionDropdown';
import { RefreshButton } from 'components/Buttons/RefreshButton';
import { Card } from 'components/Containers/Card';
import { CardHeader } from 'components/Containers/Card/CardHeader';
import GlobalSearchBar from 'components/GlobalSearchBar';
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 { RebootModal } from 'components/Modals/RebootModal';
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 { useDeleteDevice, useGetDevice, useGetDeviceHealthChecks, useGetDeviceStatus } from 'hooks/Network/Devices';
type Props = {
serialNumber: string;
};
const DevicePageWrapper = ({ serialNumber }: Props) => {
const { t } = useTranslation();
const toast = useToast();
const breakpoint = useBreakpoint();
const cancelRef = React.useRef(null);
const navigate = useNavigate();
const { mutateAsync: deleteDevice, isLoading: isDeleting } = useDeleteDevice({
serialNumber,
});
const getDevice = useGetDevice({ serialNumber });
const getStatus = useGetDeviceStatus({ serialNumber });
const getHealth = useGetDeviceHealthChecks({ serialNumber, limit: 1 });
const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure();
const scanModalProps = useDisclosure();
const resetModalProps = useDisclosure();
const eventQueueProps = useDisclosure();
const configureModalProps = useDisclosure();
const upgradeModalProps = useDisclosure();
const telemetryModalProps = useDisclosure();
const traceModalProps = useDisclosure();
const rebootModalProps = useDisclosure();
const scriptModal = useScriptModal();
// Sticky-top styles
const isCompact = breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md';
const boxShadow = useColorModeValue('0px 7px 23px rgba(0, 0, 0, 0.05)', 'none');
const handleDeleteClick = () =>
deleteDevice(serialNumber, {
onSuccess: () => {
toast({
id: `delete-device-success-${serialNumber}`,
title: t('common.success'),
status: 'success',
duration: 5000,
isClosable: true,
position: 'top-right',
});
navigate('/devices');
},
onError: (e) => {
if (axios.isAxiosError(e)) {
toast({
id: `delete-device-error-${serialNumber}`,
title: t('common.error'),
description: e.response?.data?.ErrorDescription,
status: 'error',
duration: 5000,
isClosable: true,
position: 'top-right',
});
}
},
});
const connectedTag = React.useMemo(() => {
if (!getStatus.data) return null;
return (
<Tag size="lg" colorScheme={getStatus?.data?.connected ? 'green' : 'red'}>
<TagLeftIcon boxSize="18px" as={getStatus.data.connected ? WifiHigh : WifiSlash} />
<TagLabel>{getStatus?.data?.connected ? t('common.connected') : t('common.disconnected')}</TagLabel>
</Tag>
);
}, [getStatus.data]);
const healthTag = React.useMemo(() => {
if (!getStatus.data || !getStatus.data.connected || !getHealth.data || getHealth.data?.values?.length === 0)
return null;
let color = 'red';
let sanity: number | undefined;
if (getHealth.data?.values?.[0]) {
const { sanity: sanityValue } = getHealth.data.values[0];
sanity = sanityValue;
if (sanityValue === 100) color = 'green';
else if (sanityValue > 80) color = 'yellow';
}
return (
<Tooltip
hasArrow
label={
getHealth.data?.values?.[0]?.recorded !== undefined ? (
<FormattedDate date={getHealth.data?.values?.[0]?.recorded} />
) : (
''
)
}
>
<Tag size="lg" colorScheme={color}>
<TagLeftIcon boxSize="18px" as={color === 'green' ? Heart : HeartBreak} />
<TagLabel>{sanity ? `${sanity}%` : t('common.unknown')}</TagLabel>
</Tag>
</Tooltip>
);
}, [getStatus.data, getHealth.data]);
const restrictedTag = React.useMemo(() => {
if (!getDevice.data || !getDevice.data.restrictedDevice) return null;
if (getDevice.data.restrictionDetails?.developer)
return (
<Tooltip label={t('devices.restricted_overriden')} hasArrow>
<Tag size="lg" colorScheme="green">
<TagLeftIcon boxSize="18px" as={LockSimpleOpen} />
<TagLabel>
{t('devices.restricted')} {isCompact ? '' : '(Dev Mode)'}
</TagLabel>
</Tag>
</Tooltip>
);
return (
<Tag size="lg" colorScheme="red">
<TagLeftIcon boxSize="18px" as={LockSimple} />
<TagLabel>{t('devices.restricted')}</TagLabel>
</Tag>
);
}, [getDevice.data, isCompact]);
const refresh = () => {
getDevice.refetch();
getStatus.refetch();
getHealth.refetch();
};
return (
<>
{isCompact ? (
<Card p={2} mb={4}>
<CardHeader>
<HStack spacing={2}>
<Heading size="md">{serialNumber}</Heading>
{connectedTag}
{healthTag}
{restrictedTag}
</HStack>
<Spacer />
<HStack spacing={2}>
<GlobalSearchBar />
<DeleteButton isCompact onClick={onDeleteOpen} />
{getDevice?.data && (
<DeviceActionDropdown
// @ts-ignore
device={getDevice?.data}
refresh={refresh}
onOpenScan={scanModalProps.onOpen}
onOpenFactoryReset={resetModalProps.onOpen}
onOpenUpgradeModal={upgradeModalProps.onOpen}
onOpenTrace={traceModalProps.onOpen}
onOpenEventQueue={eventQueueProps.onOpen}
onOpenConfigureModal={configureModalProps.onOpen}
onOpenTelemetryModal={telemetryModalProps.onOpen}
onOpenScriptModal={scriptModal.openModal}
onOpenRebootModal={rebootModalProps.onOpen}
size="md"
isCompact
/>
)}
<RefreshButton
onClick={refresh}
isFetching={getDevice.isFetching || getHealth.isFetching || getStatus.isFetching}
isCompact
// @ts-ignore
colorScheme="blue"
/>
</HStack>
</CardHeader>
</Card>
) : (
<Portal>
<Card
p={2}
mb={4}
top="100px"
position="fixed"
w="calc(100vw - 271px)"
right={{ base: '0px', sm: '0px', lg: '20px' }}
boxShadow={boxShadow}
>
<CardHeader>
<HStack spacing={2}>
<Heading size="md">{serialNumber}</Heading>
{connectedTag}
{healthTag}
{restrictedTag}
</HStack>
<Spacer />
<HStack spacing={2}>
<GlobalSearchBar />
<DeleteButton isCompact onClick={onDeleteOpen} />
{getDevice?.data && (
<DeviceActionDropdown
// @ts-ignore
device={getDevice?.data}
refresh={refresh}
onOpenScan={scanModalProps.onOpen}
onOpenFactoryReset={resetModalProps.onOpen}
onOpenUpgradeModal={upgradeModalProps.onOpen}
onOpenTrace={traceModalProps.onOpen}
onOpenEventQueue={eventQueueProps.onOpen}
onOpenConfigureModal={configureModalProps.onOpen}
onOpenTelemetryModal={telemetryModalProps.onOpen}
onOpenRebootModal={rebootModalProps.onOpen}
onOpenScriptModal={scriptModal.openModal}
size="md"
/>
)}
<RefreshButton
onClick={refresh}
isFetching={getDevice.isFetching || getHealth.isFetching || getStatus.isFetching}
isCompact
// @ts-ignore
colorScheme="blue"
/>
</HStack>
</CardHeader>
</Card>
</Portal>
)}
<AlertDialog isOpen={isDeleteOpen} leastDestructiveRef={cancelRef} onClose={onDeleteClose}>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
{t('crud.delete')} {serialNumber}
</AlertDialogHeader>
<AlertDialogBody>{t('crud.delete_confirm', { obj: t('devices.one') })}</AlertDialogBody>
<AlertDialogFooter>
<Button colorScheme="gray" mr="1" onClick={onDeleteClose} ref={cancelRef}>
{t('common.cancel')}
</Button>
<Button colorScheme="red" ml="1" onClick={handleDeleteClick} isLoading={isDeleting}>
{t('common.yes')}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
<WifiScanModal modalProps={scanModalProps} serialNumber={serialNumber} />
<FirmwareUpgradeModal modalProps={upgradeModalProps} serialNumber={serialNumber} />
<FactoryResetModal modalProps={resetModalProps} serialNumber={serialNumber} />
<TraceModal serialNumber={serialNumber} modalProps={traceModalProps} />
<EventQueueModal serialNumber={serialNumber} modalProps={eventQueueProps} />
<ConfigureModal serialNumber={serialNumber} modalProps={configureModalProps} />
<TelemetryModal serialNumber={serialNumber} modalProps={telemetryModalProps} />
<RebootModal serialNumber={serialNumber} modalProps={rebootModalProps} />
{scriptModal.modal}
<Box mt={isCompact ? '0px' : '68px'}>
<Masonry
breakpointCols={{
default: 2,
2200: 2,
1100: 1,
}}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
<DeviceSummary serialNumber={serialNumber} />
<DeviceDetails serialNumber={serialNumber} />
<DeviceStatisticsCard serialNumber={serialNumber} />
<WifiAnalysisCard serialNumber={serialNumber} />
<DeviceLogsCard serialNumber={serialNumber} />
{getDevice.data && getDevice.data?.hasRADIUSSessions > 0 ? (
<RadiusClientsCard serialNumber={serialNumber} />
) : null}
<RestrictionsCard serialNumber={serialNumber} />
<Box />
<DeviceNotes serialNumber={serialNumber} />
</Masonry>
</Box>
</>
);
};
export default DevicePageWrapper;