Update general device information

This commit is contained in:
Pierre HUBERT 2024-09-09 21:43:57 +02:00
parent a97614ce44
commit 36ba4efd9f
8 changed files with 109 additions and 30 deletions

View File

@ -301,7 +301,7 @@ impl Handler<SynchronizeDevice> for EnergyActor {
#[derive(serde::Serialize)]
pub struct ResDevState {
id: DeviceId,
pub id: DeviceId,
last_ping: u64,
online: bool,
}

View File

@ -152,6 +152,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
"/web_api/device/{id}",
web::get().to(devices_controller::get_single),
)
.route(
"/web_api/device/{id}/state",
web::get().to(devices_controller::state_single),
)
.route(
"/web_api/device/{id}/validate",
web::post().to(devices_controller::validate_device),

View File

@ -52,6 +52,17 @@ pub async fn get_single(actor: WebEnergyActor, id: web::Path<DeviceInPath>) -> H
Ok(HttpResponse::Ok().json(dev))
}
/// Get a single device state
pub async fn state_single(actor: WebEnergyActor, id: web::Path<DeviceInPath>) -> HttpResult {
let states = actor.send(energy_actor::GetDevicesState).await?;
let Some(state) = states.into_iter().find(|s| s.id == id.id) else {
return Ok(HttpResponse::NotFound().body("Requested device not found!"));
};
Ok(HttpResponse::Ok().json(state))
}
/// Validate a device
pub async fn validate_device(actor: WebEnergyActor, id: web::Path<DeviceInPath>) -> HttpResult {
actor

View File

@ -120,6 +120,18 @@ export class DeviceApi {
).data;
}
/**
* Get the current state of a single device
*/
static async GetSingleState(id: string): Promise<DeviceState> {
return (
await APIClient.exec({
uri: `/device/${encodeURIComponent(id)}/state`,
method: "GET",
})
).data;
}
/**
* Update a device general information
*/

View File

@ -0,0 +1,15 @@
import { TableCell, TableRow } from "@mui/material";
export function DeviceInfoProperty(p: {
icon?: React.ReactElement;
label: string;
value: string;
color?: string;
}): React.ReactElement {
return (
<TableRow hover sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
<TableCell>{p.label}</TableCell>
<TableCell style={{ color: p.color }}>{p.value}</TableCell>
</TableRow>
);
}

View File

@ -1,5 +1,7 @@
import DeleteIcon from "@mui/icons-material/Delete";
import RefreshIcon from "@mui/icons-material/Refresh";
import { IconButton, Tooltip } from "@mui/material";
import Grid from "@mui/material/Grid2";
import React from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Device, DeviceApi } from "../../api/DeviceApi";
@ -9,9 +11,9 @@ import { useLoadingMessage } from "../../hooks/context_providers/LoadingMessageP
import { useSnackbar } from "../../hooks/context_providers/SnackbarProvider";
import { AsyncWidget } from "../../widgets/AsyncWidget";
import { SolarEnergyRouteContainer } from "../../widgets/SolarEnergyRouteContainer";
import { GeneralDeviceInfo } from "./GeneralDeviceInfo";
import { DeviceRelays } from "./DeviceRelays";
import Grid from "@mui/material/Grid2";
import { DeviceStateBlock } from "./DeviceStateBlock";
import { GeneralDeviceInfo } from "./GeneralDeviceInfo";
export function DeviceRoute(): React.ReactElement {
const { id } = useParams();
@ -70,15 +72,23 @@ function DeviceRouteInner(p: {
loadingMessage.hide();
}
};
return (
<SolarEnergyRouteContainer
label={`Device ${p.device.name}`}
actions={
<span>
<Tooltip title="Refresh information">
<IconButton onClick={p.onReload}>
<RefreshIcon />
</IconButton>
</Tooltip>
<Tooltip title="Delete device">
<IconButton onClick={() => deleteDevice(p.device)}>
<DeleteIcon />
</IconButton>
</Tooltip>
</span>
}
>
<Grid container spacing={2}>
@ -88,6 +98,9 @@ function DeviceRouteInner(p: {
<Grid size={{ xs: 12, md: 6 }}>
<DeviceRelays {...p} />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<DeviceStateBlock {...p} />
</Grid>
</Grid>
</SolarEnergyRouteContainer>
);

View File

@ -0,0 +1,44 @@
import React from "react";
import { Device, DeviceApi, DeviceState } from "../../api/DeviceApi";
import { AsyncWidget } from "../../widgets/AsyncWidget";
import { DeviceRouteCard } from "./DeviceRouteCard";
import { Table, TableBody } from "@mui/material";
import { DeviceInfoProperty } from "./DeviceInfoProperty";
import { timeDiff } from "../../widgets/TimeWidget";
export function DeviceStateBlock(p: { device: Device }): React.ReactElement {
const [state, setState] = React.useState<DeviceState>();
const load = async () => {
setState(await DeviceApi.GetSingleState(p.device.id));
};
return (
<DeviceRouteCard title="Device state">
<AsyncWidget
loadKey={p.device.id}
load={load}
ready={!!state}
errMsg="Failed to load device state!"
build={() => <DeviceStateInner state={state!} />}
/>
</DeviceRouteCard>
);
}
function DeviceStateInner(p: { state: DeviceState }): React.ReactElement {
return (
<Table size="small">
<TableBody>
<DeviceInfoProperty
label="Status"
value={p.state.online ? "Online" : "Offline"}
/>
<DeviceInfoProperty
label="Last ping"
value={timeDiff(0, p.state.last_ping)}
/>
</TableBody>
</Table>
);
}

View File

@ -1,16 +1,10 @@
import EditIcon from "@mui/icons-material/Edit";
import {
IconButton,
Table,
TableBody,
TableCell,
TableRow,
Tooltip,
} from "@mui/material";
import { IconButton, Table, TableBody, Tooltip } from "@mui/material";
import React from "react";
import { Device } from "../../api/DeviceApi";
import { EditDeviceMetadataDialog } from "../../dialogs/EditDeviceMetadataDialog";
import { formatDate } from "../../widgets/TimeWidget";
import { DeviceInfoProperty } from "./DeviceInfoProperty";
import { DeviceRouteCard } from "./DeviceRouteCard";
export function GeneralDeviceInfo(p: {
@ -78,17 +72,3 @@ export function GeneralDeviceInfo(p: {
</>
);
}
function DeviceInfoProperty(p: {
icon?: React.ReactElement;
label: string;
value: string;
color?: string;
}): React.ReactElement {
return (
<TableRow hover sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
<TableCell>{p.label}</TableCell>
<TableCell style={{ color: p.color }}>{p.value}</TableCell>
</TableRow>
);
}