From db3cbb0b35e059e88ba10c96602678cdcf6fdf0b Mon Sep 17 00:00:00 2001 From: Sebastian Rubina Date: Mon, 14 Jul 2025 15:38:30 -0400 Subject: [PATCH] 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 --- public/locales/en/translation.json | 2 + .../Buttons/DeviceActionDropdown/index.tsx | 10 ++-- src/components/Modals/ReEnrollModal/index.tsx | 50 +++++++++++++++++++ src/pages/Device/Wrapper.tsx | 9 ++-- 4 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 src/components/Modals/ReEnrollModal/index.tsx diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index ec7faac..5f6432d 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -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!" }, diff --git a/src/components/Buttons/DeviceActionDropdown/index.tsx b/src/components/Buttons/DeviceActionDropdown/index.tsx index 487281c..9e1a579 100644 --- a/src/components/Buttons/DeviceActionDropdown/index.tsx +++ b/src/components/Buttons/DeviceActionDropdown/index.tsx @@ -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 = ({ - {onReEnroll && {t('controller.devices.re_enroll')}} + {onOpenReEnrollModal && ( + onOpenReEnrollModal(device.serialNumber)}> + {t('controller.devices.re_enroll')} + + )} {t('controller.telemetry.title')} {t('script.one')} {t('controller.devices.trace')} diff --git a/src/components/Modals/ReEnrollModal/index.tsx b/src/components/Modals/ReEnrollModal/index.tsx new file mode 100644 index 0000000..10bb773 --- /dev/null +++ b/src/components/Modals/ReEnrollModal/index.tsx @@ -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 ( + + {isLoading ? ( +
+ +
+ ) : ( + <> + + {t('controller.devices.re_enroll_warning', { serialNumber })} + +
+ +
+ + )} +
+ ); +}; + +export default ReEnrollModal; \ No newline at end of file diff --git a/src/pages/Device/Wrapper.tsx b/src/pages/Device/Wrapper.tsx index b5e9f68..29decd8 100644 --- a/src/pages/Device/Wrapper.tsx +++ b/src/pages/Device/Wrapper.tsx @@ -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) => { + {scriptModal.modal}