Update general device information
This commit is contained in:
		@@ -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>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user