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)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct ResDevState {
|
pub struct ResDevState {
|
||||||
id: DeviceId,
|
pub id: DeviceId,
|
||||||
last_ping: u64,
|
last_ping: u64,
|
||||||
online: bool,
|
online: bool,
|
||||||
}
|
}
|
||||||
|
@ -152,6 +152,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
|
|||||||
"/web_api/device/{id}",
|
"/web_api/device/{id}",
|
||||||
web::get().to(devices_controller::get_single),
|
web::get().to(devices_controller::get_single),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/web_api/device/{id}/state",
|
||||||
|
web::get().to(devices_controller::state_single),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/web_api/device/{id}/validate",
|
"/web_api/device/{id}/validate",
|
||||||
web::post().to(devices_controller::validate_device),
|
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))
|
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
|
/// Validate a device
|
||||||
pub async fn validate_device(actor: WebEnergyActor, id: web::Path<DeviceInPath>) -> HttpResult {
|
pub async fn validate_device(actor: WebEnergyActor, id: web::Path<DeviceInPath>) -> HttpResult {
|
||||||
actor
|
actor
|
||||||
|
@ -120,6 +120,18 @@ export class DeviceApi {
|
|||||||
).data;
|
).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
|
* 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 DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||||
import { IconButton, Tooltip } from "@mui/material";
|
import { IconButton, Tooltip } from "@mui/material";
|
||||||
|
import Grid from "@mui/material/Grid2";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { Device, DeviceApi } from "../../api/DeviceApi";
|
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 { useSnackbar } from "../../hooks/context_providers/SnackbarProvider";
|
||||||
import { AsyncWidget } from "../../widgets/AsyncWidget";
|
import { AsyncWidget } from "../../widgets/AsyncWidget";
|
||||||
import { SolarEnergyRouteContainer } from "../../widgets/SolarEnergyRouteContainer";
|
import { SolarEnergyRouteContainer } from "../../widgets/SolarEnergyRouteContainer";
|
||||||
import { GeneralDeviceInfo } from "./GeneralDeviceInfo";
|
|
||||||
import { DeviceRelays } from "./DeviceRelays";
|
import { DeviceRelays } from "./DeviceRelays";
|
||||||
import Grid from "@mui/material/Grid2";
|
import { DeviceStateBlock } from "./DeviceStateBlock";
|
||||||
|
import { GeneralDeviceInfo } from "./GeneralDeviceInfo";
|
||||||
|
|
||||||
export function DeviceRoute(): React.ReactElement {
|
export function DeviceRoute(): React.ReactElement {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@ -70,15 +72,23 @@ function DeviceRouteInner(p: {
|
|||||||
loadingMessage.hide();
|
loadingMessage.hide();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SolarEnergyRouteContainer
|
<SolarEnergyRouteContainer
|
||||||
label={`Device ${p.device.name}`}
|
label={`Device ${p.device.name}`}
|
||||||
actions={
|
actions={
|
||||||
<Tooltip title="Delete device">
|
<span>
|
||||||
<IconButton onClick={() => deleteDevice(p.device)}>
|
<Tooltip title="Refresh information">
|
||||||
<DeleteIcon />
|
<IconButton onClick={p.onReload}>
|
||||||
</IconButton>
|
<RefreshIcon />
|
||||||
</Tooltip>
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Delete device">
|
||||||
|
<IconButton onClick={() => deleteDevice(p.device)}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
@ -88,6 +98,9 @@ function DeviceRouteInner(p: {
|
|||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<DeviceRelays {...p} />
|
<DeviceRelays {...p} />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<DeviceStateBlock {...p} />
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</SolarEnergyRouteContainer>
|
</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 EditIcon from "@mui/icons-material/Edit";
|
||||||
import {
|
import { IconButton, Table, TableBody, Tooltip } from "@mui/material";
|
||||||
IconButton,
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableRow,
|
|
||||||
Tooltip,
|
|
||||||
} from "@mui/material";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Device } from "../../api/DeviceApi";
|
import { Device } from "../../api/DeviceApi";
|
||||||
import { EditDeviceMetadataDialog } from "../../dialogs/EditDeviceMetadataDialog";
|
import { EditDeviceMetadataDialog } from "../../dialogs/EditDeviceMetadataDialog";
|
||||||
import { formatDate } from "../../widgets/TimeWidget";
|
import { formatDate } from "../../widgets/TimeWidget";
|
||||||
|
import { DeviceInfoProperty } from "./DeviceInfoProperty";
|
||||||
import { DeviceRouteCard } from "./DeviceRouteCard";
|
import { DeviceRouteCard } from "./DeviceRouteCard";
|
||||||
|
|
||||||
export function GeneralDeviceInfo(p: {
|
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