diff --git a/central_backend/src/devices/devices_list.rs b/central_backend/src/devices/devices_list.rs index b52b4b0..d0e2fe0 100644 --- a/central_backend/src/devices/devices_list.rs +++ b/central_backend/src/devices/devices_list.rs @@ -95,4 +95,27 @@ impl DevicesList { pub fn full_list(&self) -> Vec { self.0.clone().into_values().collect() } + + /// Delete a device + pub fn delete(&mut self, id: &DeviceId) -> anyhow::Result<()> { + let crt_path = AppConfig::get().device_cert_path(id); + if crt_path.is_file() { + // TODO : implement + unimplemented!("Certificate revocation not implemented yet!"); + } + + let csr_path = AppConfig::get().device_csr_path(id); + if csr_path.is_file() { + std::fs::remove_file(&csr_path)?; + } + + let conf_path = AppConfig::get().device_config_path(id); + if conf_path.is_file() { + std::fs::remove_file(&conf_path)?; + } + + self.0.remove(id); + + Ok(()) + } } diff --git a/central_backend/src/energy/energy_actor.rs b/central_backend/src/energy/energy_actor.rs index 822af25..eb39584 100644 --- a/central_backend/src/energy/energy_actor.rs +++ b/central_backend/src/energy/energy_actor.rs @@ -94,6 +94,22 @@ impl Handler for EnergyActor { } } +/// Delete a device +#[derive(Message)] +#[rtype(result = "anyhow::Result<()>")] +pub struct DeleteDevice(pub DeviceId); + +impl Handler for EnergyActor { + type Result = anyhow::Result<()>; + + fn handle(&mut self, msg: DeleteDevice, _ctx: &mut Context) -> Self::Result { + log::info!("Requested to delete device {:?}...", &msg.0); + self.devices.delete(&msg.0)?; + // TODO : delete energy related information + Ok(()) + } +} + /// Get the list of devices #[derive(Message)] #[rtype(result = "Vec")] diff --git a/central_backend/src/server/servers.rs b/central_backend/src/server/servers.rs index 7a484bb..977d824 100644 --- a/central_backend/src/server/servers.rs +++ b/central_backend/src/server/servers.rs @@ -139,6 +139,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> "/web_api/devices/list_validated", web::get().to(devices_controller::list_validated), ) + .route( + "/web_api/device/{id}", + web::delete().to(devices_controller::delete_device), + ) // Devices API .route( "/devices_api/utils/time", diff --git a/central_backend/src/server/web_api/devices_controller.rs b/central_backend/src/server/web_api/devices_controller.rs index 7c91340..482b344 100644 --- a/central_backend/src/server/web_api/devices_controller.rs +++ b/central_backend/src/server/web_api/devices_controller.rs @@ -1,7 +1,8 @@ +use crate::devices::device::DeviceId; use crate::energy::energy_actor; use crate::server::custom_error::HttpResult; use crate::server::WebEnergyActor; -use actix_web::HttpResponse; +use actix_web::{web, HttpResponse}; /// Get the list of pending (not accepted yet) devices pub async fn list_pending(actor: WebEnergyActor) -> HttpResult { @@ -26,3 +27,17 @@ pub async fn list_validated(actor: WebEnergyActor) -> HttpResult { Ok(HttpResponse::Ok().json(list)) } + +#[derive(serde::Deserialize)] +pub struct DeviceInPath { + id: DeviceId, +} + +/// Delete a device +pub async fn delete_device(actor: WebEnergyActor, id: web::Path) -> HttpResult { + actor + .send(energy_actor::DeleteDevice(id.id.clone())) + .await??; + + Ok(HttpResponse::Accepted().finish()) +} diff --git a/central_frontend/src/api/DeviceApi.ts b/central_frontend/src/api/DeviceApi.ts index 772b0a4..a9f5a57 100644 --- a/central_frontend/src/api/DeviceApi.ts +++ b/central_frontend/src/api/DeviceApi.ts @@ -49,4 +49,14 @@ export class DeviceApi { }) ).data; } + + /** + * Delete a device + */ + static async Delete(d: Device): Promise { + await APIClient.exec({ + uri: `/device/${encodeURIComponent(d.id)}`, + method: "DELETE", + }); + } } diff --git a/central_frontend/src/routes/PendingDevicesRoute.tsx b/central_frontend/src/routes/PendingDevicesRoute.tsx index 44ed869..992e0ee 100644 --- a/central_frontend/src/routes/PendingDevicesRoute.tsx +++ b/central_frontend/src/routes/PendingDevicesRoute.tsx @@ -1,16 +1,22 @@ -import React from "react"; -import { AsyncWidget } from "../widgets/AsyncWidget"; -import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer"; -import { Device, DeviceApi } from "../api/DeviceApi"; +import DeleteIcon from "@mui/icons-material/Delete"; import { - TableContainer, + IconButton, Paper, Table, + TableBody, + TableCell, + TableContainer, TableHead, TableRow, - TableCell, - TableBody, } from "@mui/material"; +import React from "react"; +import { Device, DeviceApi } from "../api/DeviceApi"; +import { useAlert } from "../hooks/context_providers/AlertDialogProvider"; +import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider"; +import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider"; +import { useSnackbar } from "../hooks/context_providers/SnackbarProvider"; +import { AsyncWidget } from "../widgets/AsyncWidget"; +import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer"; import { TimeWidget } from "../widgets/TimeWidget"; export function PendingDevicesRoute(): React.ReactElement { @@ -46,6 +52,37 @@ function PendingDevicesList(p: { pending: Device[]; onReload: () => void; }): React.ReactElement { + const alert = useAlert(); + const confirm = useConfirm(); + const snackbar = useSnackbar(); + const loadingMessage = useLoadingMessage(); + + const deleteDevice = async (d: Device) => { + try { + if ( + !(await confirm( + `Do you really want to delete the device ${d.id}? The operation cannot be reverted!` + )) + ) + return; + + loadingMessage.show("Deleting device..."); + await DeviceApi.Delete(d); + + snackbar("The device has been successfully deleted!"); + p.onReload(); + } catch (e) { + console.error(`Failed to delete device! ${e})`); + alert("Failed to delete device!"); + } finally { + loadingMessage.hide(); + } + }; + + if (p.pending.length === 0) { + return

There is no device awaiting confirmation right now.

; + } + return ( @@ -54,8 +91,9 @@ function PendingDevicesList(p: { #ModelVersion - Maximum number of relays + Max number of relaysCreated + @@ -70,6 +108,11 @@ function PendingDevicesList(p: { + + deleteDevice(dev)}> + + + ))}