mirror of
https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
synced 2025-11-01 18:57:46 +00:00
336 lines
12 KiB
TypeScript
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;
|