Add device re-enrollment with confirmation modal

- Add ReEnrollModal component for user confirmation before re-enrollment
  - Update DeviceActionDropdown to open modal instead of direct action
  - Add modal state management in Device Wrapper component
  - Add translation keys for re-enrollment UI with certificate renewal
  messaging
  - Remove direct useReEnroll hook usage in favor of modal pattern

Signed-off-by: Sebastian Rubina <sebastian.rubina@icloud.com>
This commit is contained in:
Sebastian Rubina
2025-07-14 15:38:30 -04:00
parent c895274ebf
commit db3cbb0b35
4 changed files with 64 additions and 7 deletions

View File

@@ -516,6 +516,8 @@
"trace_description": "Launch a remote trace of this device for either a specific duration or a number of packets",
"re_enroll": "Re-enroll",
"re_enroll_initiated": "Re-enrollment initiated for device {{serialNumber}}",
"re_enroll_warning": "This will renew the operational certificate for device {{serialNumber}}. The device will obtain a new certificate from the controller.",
"confirm_re_enroll": "Renew Certificate for {{serialNumber}}",
"update_success": "Device updated!",
"updated_blacklist": "Updated Blacklist!"
},

View File

@@ -32,7 +32,7 @@ interface Props {
onOpenTelemetryModal: (serialNumber: string) => void;
onOpenScriptModal: (device: GatewayDevice) => void;
onOpenRebootModal: (serialNumber: string) => void;
onReEnroll?: () => void;
onOpenReEnrollModal?: (serialNumber: string) => void;
size?: 'sm' | 'md' | 'lg';
isCompact?: boolean;
}
@@ -50,7 +50,7 @@ const DeviceActionDropdown = ({
onOpenConfigureModal,
onOpenScriptModal,
onOpenRebootModal,
onReEnroll,
onOpenReEnrollModal,
size,
isCompact,
}: Props) => {
@@ -236,7 +236,11 @@ const DeviceActionDropdown = ({
<MenuItem onClick={handleRebootClick} hidden={!isCompact}>
{t('commands.reboot')}
</MenuItem>
{onReEnroll && <MenuItem onClick={onReEnroll}>{t('controller.devices.re_enroll')}</MenuItem>}
{onOpenReEnrollModal && (
<MenuItem onClick={() => onOpenReEnrollModal(device.serialNumber)}>
{t('controller.devices.re_enroll')}
</MenuItem>
)}
<MenuItem onClick={handleOpenTelemetry}>{t('controller.telemetry.title')}</MenuItem>
<MenuItem onClick={handleOpenScript}>{t('script.one')}</MenuItem>
<MenuItem onClick={handleOpenTrace}>{t('controller.devices.trace')}</MenuItem>

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { Center, Spinner, Alert, Button } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { Modal } from '../Modal';
import { useReEnroll } from 'hooks/Network/ReEnroll';
import { ModalProps } from 'models/Modal';
interface Props {
modalProps: ModalProps;
serialNumber: string;
}
const ReEnrollModal = ({ modalProps: { isOpen, onClose }, serialNumber }: Props) => {
const { t } = useTranslation();
const { mutate: reEnroll, isLoading } = useReEnroll({ serialNumber });
const submit = () => {
reEnroll(
{ serialNumber, when: 0 },
{
onSuccess: () => {
onClose();
},
}
);
};
return (
<Modal isOpen={isOpen} onClose={onClose} title={t('controller.devices.re_enroll')}>
{isLoading ? (
<Center>
<Spinner size="lg" />
</Center>
) : (
<>
<Alert colorScheme="blue" mb={6}>
{t('controller.devices.re_enroll_warning', { serialNumber })}
</Alert>
<Center mb={6}>
<Button size="lg" colorScheme="blue" onClick={submit} fontWeight="bold">
{t('controller.devices.confirm_re_enroll', { serialNumber })}
</Button>
</Center>
</>
)}
</Modal>
);
};
export default ReEnrollModal;

View File

@@ -41,6 +41,7 @@ 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 ReEnrollModal from 'components/Modals/ReEnrollModal';
import { useScriptModal } from 'components/Modals/ScriptModal/useScriptModal';
import ethernetConnected from './ethernetIconConnected.svg?react';
import ethernetDisconnected from './ethernetIconDisconnected.svg?react';
@@ -48,7 +49,6 @@ 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';
import { useReEnroll } from 'hooks/Network/ReEnroll';
import SwitchPortExamination from './SwitchPortExamination';
type Props = {
@@ -77,8 +77,8 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
const telemetryModalProps = useDisclosure();
const traceModalProps = useDisclosure();
const rebootModalProps = useDisclosure();
const reEnrollModalProps = useDisclosure();
const scriptModal = useScriptModal();
const reEnroll = useReEnroll({ serialNumber });
// Sticky-top styles
const isCompact = breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md';
const boxShadow = useColorModeValue('0px 7px 23px rgba(0, 0, 0, 0.05)', 'none');
@@ -218,7 +218,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
onOpenTelemetryModal={telemetryModalProps.onOpen}
onOpenScriptModal={scriptModal.openModal}
onOpenRebootModal={rebootModalProps.onOpen}
onReEnroll={() => reEnroll.mutate({ serialNumber, when: 0 })}
onOpenReEnrollModal={reEnrollModalProps.onOpen}
size="md"
isCompact
/>
@@ -271,7 +271,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
onOpenTelemetryModal={telemetryModalProps.onOpen}
onOpenRebootModal={rebootModalProps.onOpen}
onOpenScriptModal={scriptModal.openModal}
onReEnroll={() => reEnroll.mutate({ serialNumber, when: 0 })}
onOpenReEnrollModal={reEnrollModalProps.onOpen}
size="md"
/>
)}
@@ -315,6 +315,7 @@ const DevicePageWrapper = ({ serialNumber }: Props) => {
<ConfigureModal serialNumber={serialNumber} modalProps={configureModalProps} />
<TelemetryModal serialNumber={serialNumber} modalProps={telemetryModalProps} />
<RebootModal serialNumber={serialNumber} modalProps={rebootModalProps} />
<ReEnrollModal serialNumber={serialNumber} modalProps={reEnrollModalProps} />
{scriptModal.modal}
<Box mt={isCompact ? '0px' : '68px'}>
<Masonry