Check for dependencies conflict before deleting a device
This commit is contained in:
		@@ -23,6 +23,8 @@ pub enum DevicesListError {
 | 
				
			|||||||
    DeviceNotFound,
 | 
					    DeviceNotFound,
 | 
				
			||||||
    #[error("Requested device is not validated")]
 | 
					    #[error("Requested device is not validated")]
 | 
				
			||||||
    DeviceNotValidated,
 | 
					    DeviceNotValidated,
 | 
				
			||||||
 | 
					    #[error("Failed to delete device: {0}")]
 | 
				
			||||||
 | 
					    DeleteDeviceFailed(&'static str),
 | 
				
			||||||
    #[error("Failed to update relay configuration: {0}")]
 | 
					    #[error("Failed to update relay configuration: {0}")]
 | 
				
			||||||
    UpdateRelayFailed(&'static str),
 | 
					    UpdateRelayFailed(&'static str),
 | 
				
			||||||
    #[error("Failed to delete relay: {0}")]
 | 
					    #[error("Failed to delete relay: {0}")]
 | 
				
			||||||
@@ -178,6 +180,19 @@ impl DevicesList {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Delete a device
 | 
					    /// Delete a device
 | 
				
			||||||
    pub fn delete(&mut self, id: &DeviceId) -> anyhow::Result<()> {
 | 
					    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);
 | 
					        let crt_path = AppConfig::get().device_cert_path(id);
 | 
				
			||||||
        if crt_path.is_file() {
 | 
					        if crt_path.is_file() {
 | 
				
			||||||
            let cert = self.get_cert(id)?;
 | 
					            let cert = self.get_cert(id)?;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,13 +6,14 @@ import {
 | 
				
			|||||||
} from "react-router-dom";
 | 
					} from "react-router-dom";
 | 
				
			||||||
import { AuthApi } from "./api/AuthApi";
 | 
					import { AuthApi } from "./api/AuthApi";
 | 
				
			||||||
import { ServerApi } from "./api/ServerApi";
 | 
					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 { LoginRoute } from "./routes/LoginRoute";
 | 
				
			||||||
import { NotFoundRoute } from "./routes/NotFoundRoute";
 | 
					import { NotFoundRoute } from "./routes/NotFoundRoute";
 | 
				
			||||||
import { HomeRoute } from "./routes/HomeRoute";
 | 
					 | 
				
			||||||
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
					 | 
				
			||||||
import { PendingDevicesRoute } from "./routes/PendingDevicesRoute";
 | 
					import { PendingDevicesRoute } from "./routes/PendingDevicesRoute";
 | 
				
			||||||
import { DevicesRoute } from "./routes/DevicesRoute";
 | 
					import { RelaysListRoute } from "./routes/RelaysListRoute";
 | 
				
			||||||
import { DeviceRoute } from "./routes/DeviceRoute/DeviceRoute";
 | 
					import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function App() {
 | 
					export function App() {
 | 
				
			||||||
  if (!AuthApi.SignedIn && !ServerApi.Config.auth_disabled)
 | 
					  if (!AuthApi.SignedIn && !ServerApi.Config.auth_disabled)
 | 
				
			||||||
@@ -25,6 +26,7 @@ export function App() {
 | 
				
			|||||||
        <Route path="pending_devices" element={<PendingDevicesRoute />} />
 | 
					        <Route path="pending_devices" element={<PendingDevicesRoute />} />
 | 
				
			||||||
        <Route path="devices" element={<DevicesRoute />} />
 | 
					        <Route path="devices" element={<DevicesRoute />} />
 | 
				
			||||||
        <Route path="dev/:id" element={<DeviceRoute />} />
 | 
					        <Route path="dev/:id" element={<DeviceRoute />} />
 | 
				
			||||||
 | 
					        <Route path="relays" element={<RelaysListRoute />} />
 | 
				
			||||||
        <Route path="*" element={<NotFoundRoute />} />
 | 
					        <Route path="*" element={<NotFoundRoute />} />
 | 
				
			||||||
      </Route>
 | 
					      </Route>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										67
									
								
								central_frontend/src/routes/RelaysListRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								central_frontend/src/routes/RelaysListRoute.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -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<DeviceRelay[] | undefined>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const load = async () => {
 | 
				
			||||||
 | 
					    setList(await RelayApi.GetList());
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const reload = () => {
 | 
				
			||||||
 | 
					    loadKey.current += 1;
 | 
				
			||||||
 | 
					    setList(undefined);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <SolarEnergyRouteContainer
 | 
				
			||||||
 | 
					      label="Relays list"
 | 
				
			||||||
 | 
					      actions={
 | 
				
			||||||
 | 
					        <Tooltip title="Refresh list">
 | 
				
			||||||
 | 
					          <IconButton onClick={reload}>
 | 
				
			||||||
 | 
					            <RefreshIcon />
 | 
				
			||||||
 | 
					          </IconButton>
 | 
				
			||||||
 | 
					        </Tooltip>
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <AsyncWidget
 | 
				
			||||||
 | 
					        loadKey={loadKey.current}
 | 
				
			||||||
 | 
					        ready={!!list}
 | 
				
			||||||
 | 
					        errMsg="Failed to load the list of relays!"
 | 
				
			||||||
 | 
					        load={load}
 | 
				
			||||||
 | 
					        build={() => <RelaysList onReload={reload} list={list!} />}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </SolarEnergyRouteContainer>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function RelaysList(p: {
 | 
				
			||||||
 | 
					  list: DeviceRelay[];
 | 
				
			||||||
 | 
					  onReload: () => void;
 | 
				
			||||||
 | 
					}): React.ReactElement {
 | 
				
			||||||
 | 
					  return <>todo</>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 Icon from "@mdi/react";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  List,
 | 
					  List,
 | 
				
			||||||
  ListItemButton,
 | 
					  ListItemButton,
 | 
				
			||||||
  ListItemIcon,
 | 
					  ListItemIcon,
 | 
				
			||||||
  ListItemSecondaryAction,
 | 
					 | 
				
			||||||
  ListItemText,
 | 
					  ListItemText,
 | 
				
			||||||
} from "@mui/material";
 | 
					} from "@mui/material";
 | 
				
			||||||
import { useLocation } from "react-router-dom";
 | 
					import { useLocation } from "react-router-dom";
 | 
				
			||||||
@@ -31,25 +30,29 @@ export function SolarEnergyNavList(): React.ReactElement {
 | 
				
			|||||||
        uri="/pending_devices"
 | 
					        uri="/pending_devices"
 | 
				
			||||||
        icon={<Icon path={mdiNewBox} size={1} />}
 | 
					        icon={<Icon path={mdiNewBox} size={1} />}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					      <NavLink
 | 
				
			||||||
 | 
					        label="Relays"
 | 
				
			||||||
 | 
					        uri="/relays"
 | 
				
			||||||
 | 
					        icon={<Icon path={mdiElectricSwitch} size={1} />}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
    </List>
 | 
					    </List>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function NavLink(p: {
 | 
					function NavLink(
 | 
				
			||||||
  icon: React.ReactElement;
 | 
					  p: Readonly<{
 | 
				
			||||||
  uri: string;
 | 
					    icon: React.ReactElement;
 | 
				
			||||||
  label: string;
 | 
					    uri: string;
 | 
				
			||||||
  secondaryAction?: React.ReactElement;
 | 
					    label: string;
 | 
				
			||||||
}): React.ReactElement {
 | 
					    secondaryAction?: React.ReactElement;
 | 
				
			||||||
 | 
					  }>
 | 
				
			||||||
 | 
					): React.ReactElement {
 | 
				
			||||||
  const location = useLocation();
 | 
					  const location = useLocation();
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <RouterLink to={p.uri}>
 | 
					    <RouterLink to={p.uri}>
 | 
				
			||||||
      <ListItemButton selected={p.uri === location.pathname}>
 | 
					      <ListItemButton selected={p.uri === location.pathname}>
 | 
				
			||||||
        <ListItemIcon>{p.icon}</ListItemIcon>
 | 
					        <ListItemIcon>{p.icon}</ListItemIcon>
 | 
				
			||||||
        <ListItemText primary={p.label} />
 | 
					        <ListItemText primary={p.label} />
 | 
				
			||||||
        {p.secondaryAction && (
 | 
					 | 
				
			||||||
          <ListItemSecondaryAction>{p.secondaryAction}</ListItemSecondaryAction>
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </ListItemButton>
 | 
					      </ListItemButton>
 | 
				
			||||||
    </RouterLink>
 | 
					    </RouterLink>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user