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}
- )}
);