diff --git a/CHANGELOG.md b/CHANGELOG.md index 6431e363..f54fc6d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Add Delete device button in Device Info Card ([#397](https://github.com/astarte-platform/astarte-dashboard/issues/397)). ## [1.1.0] - 2023-06-20 diff --git a/src/DeviceStatusPage/DeviceInfoCard.tsx b/src/DeviceStatusPage/DeviceInfoCard.tsx index cec7f0e7..de107a2b 100644 --- a/src/DeviceStatusPage/DeviceInfoCard.tsx +++ b/src/DeviceStatusPage/DeviceInfoCard.tsx @@ -62,6 +62,7 @@ interface DeviceInfoCardProps { onInhibitCredentialsClick: () => void; onEnableCredentialsClick: () => void; onWipeCredentialsClick: () => void; + onDeleteDeviceClick: () => void; } const DeviceInfoCard = ({ @@ -69,6 +70,7 @@ const DeviceInfoCard = ({ onInhibitCredentialsClick, onEnableCredentialsClick, onWipeCredentialsClick, + onDeleteDeviceClick, }: DeviceInfoCardProps): React.ReactElement => ( Device Info @@ -96,6 +98,9 @@ const DeviceInfoCard = ({ + diff --git a/src/DeviceStatusPage/index.tsx b/src/DeviceStatusPage/index.tsx index 39607573..804b1e46 100644 --- a/src/DeviceStatusPage/index.tsx +++ b/src/DeviceStatusPage/index.tsx @@ -17,8 +17,8 @@ */ import React, { useCallback, useMemo, useState } from 'react'; -import { Col, Container, Row, Spinner } from 'react-bootstrap'; -import { Link, useParams } from 'react-router-dom'; +import { Col, Container, Form, Row, Spinner } from 'react-bootstrap'; +import { Link, useNavigate, useParams } from 'react-router-dom'; import type { AstarteDevice } from 'astarte-client'; import BackButton from '../ui/BackButton'; @@ -95,6 +95,11 @@ type ReregisterDeviceModalT = { kind: 'reregister_device_modal'; }; +type DeleteDeviceModalT = { + kind: 'delete_device_modal'; + isDeletingDevice: boolean; +}; + function isWipeCredentialsModal(modal: PageModal): modal is WipeCredentialsModalT { return modal.kind === 'wipe_credentials_modal'; } @@ -131,6 +136,10 @@ function isDeviceReregistrationModal(modal: PageModal): modal is ReregisterDevic return modal.kind === 'reregister_device_modal'; } +function isDeleteDeviceModal(modal: PageModal): modal is DeleteDeviceModalT { + return modal.kind === 'delete_device_modal'; +} + type PageModal = | WipeCredentialsModalT | AddToGroupModalT @@ -140,15 +149,19 @@ type PageModal = | NewAttributeModalT | EditAttributeModalT | DeleteAttributeModalT - | ReregisterDeviceModalT; + | ReregisterDeviceModalT + | DeleteDeviceModalT; export default (): React.ReactElement => { const { deviceId = '' } = useParams(); const astarte = useAstarte(); + const navigate = useNavigate(); const deviceFetcher = useFetch(() => astarte.client.getDeviceInfo(deviceId)); const groupsFetcher = useFetch(() => astarte.client.getGroupList()); const [devicePageAlers, devicePageAlersController] = useAlerts(); const [activeModal, setActiveModal] = useState(null); + const [confirmString, setConfirmString] = useState(''); + const canDelete = confirmString === deviceId; const unjoinedGroups = useMemo(() => { if (deviceFetcher.status === 'ok' && groupsFetcher.status === 'ok') { @@ -190,6 +203,19 @@ export default (): React.ReactElement => { }); }, [astarte.client, deviceId, dismissModal, devicePageAlersController]); + const handleDeleteDevice = useCallback(() => { + astarte.client + .deleteDevice(deviceId) + .then(() => { + setActiveModal({ kind: 'delete_device_modal', isDeletingDevice: true }); + navigate('/devices'); + }) + .catch(() => { + devicePageAlersController.showError(`Couldn't delete the device`); + dismissModal(); + }); + }, [astarte.client, deviceId, dismissModal, devicePageAlersController, navigate]); + const addDeviceToGroup = useCallback( (groupName) => { astarte.client @@ -313,6 +339,12 @@ export default (): React.ReactElement => { isWipingCredentials: false, }) } + onDeleteDeviceClick={() => + setActiveModal({ + kind: 'delete_device_modal', + isDeletingDevice: false, + }) + } /> {

)} + {activeModal && isDeleteDeviceModal(activeModal) && ( + { + setActiveModal({ ...activeModal, isDeletingDevice: true }); + handleDeleteDevice(); + }} + isConfirming={activeModal.isDeletingDevice} + disabled={!canDelete} + > +

+ You are going to permanently delete  + {deviceId} and the corresponding data. Deleted devices cannot be restored but a + new one can be registered instead. +

+

+ Please type {deviceId} to proceed. +

+ + ) => + setConfirmString(e.target.value) + } + /> + +
+ )} {activeModal && isAddToGroupModal(activeModal) && ( { + await this.$delete(this.apiConfig.device({ ...this.config, deviceId })); + } + async getFlowInstances(): Promise> { const response = await this.$get(this.apiConfig.flows(this.config)); return response.data;