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 = ({
Wipe credential secret
+
+ Delete device
+
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;