Update general device information
This commit is contained in:
parent
a97614ce44
commit
36ba4efd9f
@ -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,
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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={
|
||||
<Tooltip title="Delete device">
|
||||
<IconButton onClick={() => deleteDevice(p.device)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<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>
|
||||
);
|
||||
|
44
central_frontend/src/routes/DeviceRoute/DeviceStateBlock.tsx
Normal file
44
central_frontend/src/routes/DeviceRoute/DeviceStateBlock.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user