Display the list of devices

This commit is contained in:
Pierre HUBERT 2024-07-04 19:52:09 +02:00
parent b59e807de1
commit 751e33cb72
4 changed files with 158 additions and 1 deletions

View File

@ -11,6 +11,7 @@ import { NotFoundRoute } from "./routes/NotFoundRoute";
import { HomeRoute } from "./routes/HomeRoute"; import { HomeRoute } from "./routes/HomeRoute";
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage"; import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
import { PendingDevicesRoute } from "./routes/PendingDevicesRoute"; import { PendingDevicesRoute } from "./routes/PendingDevicesRoute";
import { DevicesRoute } from "./routes/DevicesRoute";
export function App() { export function App() {
if (!AuthApi.SignedIn && !ServerApi.Config.auth_disabled) if (!AuthApi.SignedIn && !ServerApi.Config.auth_disabled)
@ -20,6 +21,7 @@ export function App() {
createRoutesFromElements( createRoutesFromElements(
<Route path="*" element={<BaseAuthenticatedPage />}> <Route path="*" element={<BaseAuthenticatedPage />}>
<Route path="" element={<HomeRoute />} /> <Route path="" element={<HomeRoute />} />
<Route path="devices" element={<DevicesRoute />} />
<Route path="pending_devices" element={<PendingDevicesRoute />} /> <Route path="pending_devices" element={<PendingDevicesRoute />} />
<Route path="*" element={<NotFoundRoute />} /> <Route path="*" element={<NotFoundRoute />} />
</Route> </Route>

View File

@ -49,6 +49,19 @@ export class DeviceApi {
}) })
).data; ).data;
} }
/**
* Get the list of validated devices
*/
static async ValidatedList(): Promise<Device[]> {
return (
await APIClient.exec({
uri: "/devices/list_validated",
method: "GET",
})
).data;
}
/** /**
* Validate a device * Validate a device
*/ */

View File

@ -0,0 +1,137 @@
import {
Tooltip,
IconButton,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from "@mui/material";
import React from "react";
import { Device, DeviceApi } from "../api/DeviceApi";
import { AsyncWidget } from "../widgets/AsyncWidget";
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
import RefreshIcon from "@mui/icons-material/Refresh";
import { TimeWidget } from "../widgets/TimeWidget";
import DeleteIcon from "@mui/icons-material/Delete";
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";
export function DevicesRoute(): React.ReactElement {
const loadKey = React.useRef(1);
const [list, setList] = React.useState<Device[] | undefined>();
const load = async () => {
setList(await DeviceApi.ValidatedList());
};
const reload = () => {
loadKey.current += 1;
setList(undefined);
};
return (
<SolarEnergyRouteContainer
label="Devices"
actions={
<Tooltip title="Refresh table">
<IconButton onClick={reload}>
<RefreshIcon />
</IconButton>
</Tooltip>
}
>
<AsyncWidget
loadKey={loadKey.current}
ready={!!list}
errMsg="Failed to load the list of validated devices!"
load={load}
build={() => <ValidatedDevicesList onReload={reload} list={list!} />}
/>
</SolarEnergyRouteContainer>
);
}
function ValidatedDevicesList(p: {
list: 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.list.length === 0) {
return <p>There is no device validated yet.</p>;
}
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>#</TableCell>
<TableCell>Model</TableCell>
<TableCell>Version</TableCell>
<TableCell>Max number of relays</TableCell>
<TableCell>Created</TableCell>
<TableCell>Updated</TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{p.list.map((dev) => (
<TableRow key={dev.id}>
<TableCell component="th" scope="row">
{dev.id}
</TableCell>
<TableCell>{dev.info.reference}</TableCell>
<TableCell>{dev.info.version}</TableCell>
<TableCell>{dev.info.max_relays}</TableCell>
<TableCell>
<TimeWidget time={dev.time_create} />
</TableCell>
<TableCell>
<TimeWidget time={dev.time_update} />
</TableCell>
<TableCell>
<Tooltip title="Delete device">
<IconButton onClick={() => deleteDevice(dev)}>
<DeleteIcon />
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}

View File

@ -1,4 +1,4 @@
import { mdiHome, mdiNewBox } from "@mdi/js"; import { mdiChip, mdiHome, mdiNewBox } from "@mdi/js";
import Icon from "@mdi/react"; import Icon from "@mdi/react";
import { import {
List, List,
@ -21,6 +21,11 @@ export function SolarEnergyNavList(): React.ReactElement {
}} }}
> >
<NavLink label="Home" uri="/" icon={<Icon path={mdiHome} size={1} />} /> <NavLink label="Home" uri="/" icon={<Icon path={mdiHome} size={1} />} />
<NavLink
label="Devices"
uri="/devices"
icon={<Icon path={mdiChip} size={1} />}
/>
<NavLink <NavLink
label="Pending devices" label="Pending devices"
uri="/pending_devices" uri="/pending_devices"