From 78663854cc44b95e552952fe686be376911cce9f Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Sat, 31 Aug 2024 20:46:02 +0200 Subject: [PATCH] Check for dependencies conflict before deleting a device --- central_backend/src/devices/devices_list.rs | 15 +++++ central_frontend/src/App.tsx | 10 +-- .../src/routes/RelaysListRoute.tsx | 67 +++++++++++++++++++ .../src/widgets/SolarEnergyNavList.tsx | 25 ++++--- 4 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 central_frontend/src/routes/RelaysListRoute.tsx diff --git a/central_backend/src/devices/devices_list.rs b/central_backend/src/devices/devices_list.rs index bc8b23e..4c418a0 100644 --- a/central_backend/src/devices/devices_list.rs +++ b/central_backend/src/devices/devices_list.rs @@ -23,6 +23,8 @@ pub enum DevicesListError { DeviceNotFound, #[error("Requested device is not validated")] DeviceNotValidated, + #[error("Failed to delete device: {0}")] + DeleteDeviceFailed(&'static str), #[error("Failed to update relay configuration: {0}")] UpdateRelayFailed(&'static str), #[error("Failed to delete relay: {0}")] @@ -178,6 +180,19 @@ impl DevicesList { /// Delete a device pub fn delete(&mut self, id: &DeviceId) -> anyhow::Result<()> { + // Check for conflicts + let device = self + .get_single(id) + .ok_or(DevicesListError::DeleteDeviceFailed("Device not found!"))?; + for r in &device.relays { + if !self.relay_get_direct_dependencies(r.id).is_empty() { + return Err(DevicesListError::DeleteDeviceFailed( + "A relay of this device is required by another relay!", + ) + .into()); + } + } + let crt_path = AppConfig::get().device_cert_path(id); if crt_path.is_file() { let cert = self.get_cert(id)?; diff --git a/central_frontend/src/App.tsx b/central_frontend/src/App.tsx index 18e6d1c..c724bb4 100644 --- a/central_frontend/src/App.tsx +++ b/central_frontend/src/App.tsx @@ -6,13 +6,14 @@ import { } from "react-router-dom"; import { AuthApi } from "./api/AuthApi"; import { ServerApi } from "./api/ServerApi"; +import { DeviceRoute } from "./routes/DeviceRoute/DeviceRoute"; +import { DevicesRoute } from "./routes/DevicesRoute"; +import { HomeRoute } from "./routes/HomeRoute"; import { LoginRoute } from "./routes/LoginRoute"; import { NotFoundRoute } from "./routes/NotFoundRoute"; -import { HomeRoute } from "./routes/HomeRoute"; -import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage"; import { PendingDevicesRoute } from "./routes/PendingDevicesRoute"; -import { DevicesRoute } from "./routes/DevicesRoute"; -import { DeviceRoute } from "./routes/DeviceRoute/DeviceRoute"; +import { RelaysListRoute } from "./routes/RelaysListRoute"; +import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage"; export function App() { if (!AuthApi.SignedIn && !ServerApi.Config.auth_disabled) @@ -25,6 +26,7 @@ export function App() { } /> } /> } /> + } /> } /> ) diff --git a/central_frontend/src/routes/RelaysListRoute.tsx b/central_frontend/src/routes/RelaysListRoute.tsx new file mode 100644 index 0000000..0044181 --- /dev/null +++ b/central_frontend/src/routes/RelaysListRoute.tsx @@ -0,0 +1,67 @@ +import CheckIcon from "@mui/icons-material/Check"; +import DeleteIcon from "@mui/icons-material/Delete"; +import RefreshIcon from "@mui/icons-material/Refresh"; +import { + IconButton, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Tooltip, +} from "@mui/material"; +import React from "react"; +import { Device, DeviceApi, DeviceRelay } 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"; +import { RelayApi } from "../api/RelayApi"; + +export function RelaysListRoute(): React.ReactElement { + const loadKey = React.useRef(1); + + const [list, setList] = React.useState(); + + const load = async () => { + setList(await RelayApi.GetList()); + }; + + const reload = () => { + loadKey.current += 1; + setList(undefined); + }; + + return ( + + + + + + } + > + } + /> + + ); +} + +function RelaysList(p: { + list: DeviceRelay[]; + onReload: () => void; +}): React.ReactElement { + return <>todo; +} diff --git a/central_frontend/src/widgets/SolarEnergyNavList.tsx b/central_frontend/src/widgets/SolarEnergyNavList.tsx index 784d4d9..bf180c6 100644 --- a/central_frontend/src/widgets/SolarEnergyNavList.tsx +++ b/central_frontend/src/widgets/SolarEnergyNavList.tsx @@ -1,10 +1,9 @@ -import { mdiChip, mdiHome, mdiNewBox } from "@mdi/js"; +import { mdiChip, mdiElectricSwitch, mdiHome, mdiNewBox } from "@mdi/js"; import Icon from "@mdi/react"; import { List, ListItemButton, ListItemIcon, - ListItemSecondaryAction, ListItemText, } from "@mui/material"; import { useLocation } from "react-router-dom"; @@ -31,25 +30,29 @@ export function SolarEnergyNavList(): React.ReactElement { uri="/pending_devices" icon={} /> + } + /> ); } -function NavLink(p: { - icon: React.ReactElement; - uri: string; - label: string; - secondaryAction?: React.ReactElement; -}): React.ReactElement { +function NavLink( + p: Readonly<{ + icon: React.ReactElement; + uri: string; + label: string; + secondaryAction?: React.ReactElement; + }> +): React.ReactElement { const location = useLocation(); return ( {p.icon} - {p.secondaryAction && ( - {p.secondaryAction} - )} );