Start to build relay dialog
This commit is contained in:
@ -8,6 +8,12 @@ export interface ServerConfig {
|
||||
export interface ServerConstraint {
|
||||
dev_name_len: LenConstraint;
|
||||
dev_description_len: LenConstraint;
|
||||
relay_name_len: LenConstraint;
|
||||
relay_priority: LenConstraint;
|
||||
relay_consumption: LenConstraint;
|
||||
relay_minimal_uptime: LenConstraint;
|
||||
relay_minimal_downtime: LenConstraint;
|
||||
relay_daily_minimal_runtime: LenConstraint;
|
||||
}
|
||||
|
||||
export interface LenConstraint {
|
||||
|
283
central_frontend/src/dialogs/EditDeviceRelaysDialog.tsx
Normal file
283
central_frontend/src/dialogs/EditDeviceRelaysDialog.tsx
Normal file
@ -0,0 +1,283 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { TimePicker } from "@mui/x-date-pickers";
|
||||
import React from "react";
|
||||
import { Device, DeviceRelay } from "../api/DeviceApi";
|
||||
import { ServerApi } from "../api/ServerApi";
|
||||
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
|
||||
import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider";
|
||||
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
|
||||
import { dayjsToTimeOfDay, timeOfDay } from "../utils/DateUtils";
|
||||
import { lenValid } from "../utils/StringsUtils";
|
||||
import { CheckboxInput } from "../widgets/forms/CheckboxInput";
|
||||
import { TextInput } from "../widgets/forms/TextInput";
|
||||
|
||||
export function EditDeviceRelaysDialog(p: {
|
||||
onClose: () => void;
|
||||
relay?: DeviceRelay;
|
||||
device: Device;
|
||||
onUpdated: () => void;
|
||||
}): React.ReactElement {
|
||||
const loadingMessage = useLoadingMessage();
|
||||
const alert = useAlert();
|
||||
const snackbar = useSnackbar();
|
||||
|
||||
const [relay, setRelay] = React.useState<DeviceRelay>(
|
||||
p.relay ?? {
|
||||
id: "",
|
||||
name: "relay",
|
||||
enabled: false,
|
||||
priority: 1,
|
||||
consumption: 500,
|
||||
minimal_downtime: 60 * 5,
|
||||
minimal_uptime: 60 * 5,
|
||||
depends_on: [],
|
||||
conflicts_with: [],
|
||||
}
|
||||
);
|
||||
|
||||
const creating = !p.relay;
|
||||
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
loadingMessage.show(
|
||||
`${creating ? "Creating" : "Updating"} relay information`
|
||||
);
|
||||
|
||||
// TODO
|
||||
|
||||
snackbar(
|
||||
`The relay have been successfully ${creating ? "created" : "updated"}!`
|
||||
);
|
||||
p.onUpdated();
|
||||
} catch (e) {
|
||||
console.error("Failed to update device relay information!" + e);
|
||||
alert(`Failed to ${creating ? "create" : "update"} relay! ${e}`);
|
||||
} finally {
|
||||
loadingMessage.hide();
|
||||
}
|
||||
};
|
||||
|
||||
const canSubmit =
|
||||
lenValid(relay.name, ServerApi.Config.constraints.relay_name_len) &&
|
||||
relay.priority >= 0;
|
||||
|
||||
return (
|
||||
<Dialog open>
|
||||
<DialogTitle>Edit relay information</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogFormTitle>General info</DialogFormTitle>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<TextInput
|
||||
editable
|
||||
label="Relay name"
|
||||
value={relay.name}
|
||||
onValueChange={(v) =>
|
||||
setRelay((r) => {
|
||||
return {
|
||||
...r,
|
||||
name: v ?? "",
|
||||
};
|
||||
})
|
||||
}
|
||||
size={ServerApi.Config.constraints.dev_name_len}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<CheckboxInput
|
||||
editable
|
||||
label="Enable relay"
|
||||
checked={relay.enabled}
|
||||
onValueChange={(v) =>
|
||||
setRelay((r) => {
|
||||
return {
|
||||
...r,
|
||||
enabled: v,
|
||||
};
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextInput
|
||||
editable
|
||||
label="Priority"
|
||||
value={relay.priority.toString()}
|
||||
type="number"
|
||||
onValueChange={(v) =>
|
||||
setRelay((r) => {
|
||||
return {
|
||||
...r,
|
||||
priority: Number(v) ?? 0,
|
||||
};
|
||||
})
|
||||
}
|
||||
size={ServerApi.Config.constraints.relay_priority}
|
||||
helperText="Relay priority when selecting relays to turn on. 0 = lowest priority"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextInput
|
||||
editable
|
||||
label="Consumption"
|
||||
value={relay.consumption.toString()}
|
||||
type="number"
|
||||
onValueChange={(v) =>
|
||||
setRelay((r) => {
|
||||
return {
|
||||
...r,
|
||||
consumption: Number(v) ?? 0,
|
||||
};
|
||||
})
|
||||
}
|
||||
size={ServerApi.Config.constraints.relay_consumption}
|
||||
helperText="Estimated consumption of device powered by relay"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextInput
|
||||
editable
|
||||
label="Minimal uptime"
|
||||
value={relay.minimal_uptime.toString()}
|
||||
type="number"
|
||||
onValueChange={(v) =>
|
||||
setRelay((r) => {
|
||||
return {
|
||||
...r,
|
||||
minimal_uptime: Number(v) ?? 0,
|
||||
};
|
||||
})
|
||||
}
|
||||
size={ServerApi.Config.constraints.relay_minimal_uptime}
|
||||
helperText="Minimal time this relay shall be left on before it can be turned off (in seconds)"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextInput
|
||||
editable
|
||||
label="Minimal downtime"
|
||||
value={relay.minimal_downtime.toString()}
|
||||
type="number"
|
||||
onValueChange={(v) =>
|
||||
setRelay((r) => {
|
||||
return {
|
||||
...r,
|
||||
minimal_downtime: Number(v) ?? 0,
|
||||
};
|
||||
})
|
||||
}
|
||||
size={ServerApi.Config.constraints.relay_minimal_downtime}
|
||||
helperText="Minimal time this relay shall be left on before it can be turned off (in seconds)"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<DialogFormTitle>Daily runtime</DialogFormTitle>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<CheckboxInput
|
||||
editable
|
||||
label="Enable minimal runtime"
|
||||
checked={!!relay.daily_runtime}
|
||||
onValueChange={(v) =>
|
||||
setRelay((r) => {
|
||||
return {
|
||||
...r,
|
||||
daily_runtime: v
|
||||
? { reset_time: 0, min_runtime: 3600, catch_up_hours: [] }
|
||||
: undefined,
|
||||
};
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{!!relay.daily_runtime && (
|
||||
<>
|
||||
<Grid item xs={6}>
|
||||
<TextInput
|
||||
editable
|
||||
label="Minimal daily runtime"
|
||||
value={relay.daily_runtime!.min_runtime.toString()}
|
||||
type="number"
|
||||
onValueChange={(v) =>
|
||||
setRelay((r) => {
|
||||
return {
|
||||
...r,
|
||||
daily_runtime: {
|
||||
...r.daily_runtime!,
|
||||
min_runtime: Number(v),
|
||||
},
|
||||
};
|
||||
})
|
||||
}
|
||||
size={
|
||||
ServerApi.Config.constraints.relay_daily_minimal_runtime
|
||||
}
|
||||
helperText="Minimum time, in seconds, that this relay should run each day"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TimePicker
|
||||
label="Reset time"
|
||||
value={timeOfDay(relay.daily_runtime!.reset_time)}
|
||||
onChange={(d) =>
|
||||
setRelay((r) => {
|
||||
return {
|
||||
...r,
|
||||
daily_runtime: {
|
||||
...r.daily_runtime!,
|
||||
reset_time: d ? dayjsToTimeOfDay(d) : 0,
|
||||
},
|
||||
};
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TimePicker
|
||||
label="Catch up hours"
|
||||
value={timeOfDay(relay.daily_runtime!.reset_time)}
|
||||
onChange={(d) =>
|
||||
setRelay((r) => {
|
||||
return {
|
||||
...r,
|
||||
daily_runtime: {
|
||||
...r.daily_runtime!,
|
||||
reset_time: d ? dayjsToTimeOfDay(d) : 0,
|
||||
},
|
||||
};
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={p.onClose}>Cancel</Button>
|
||||
<Button onClick={onSubmit} autoFocus disabled={!canSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogFormTitle(p: React.PropsWithChildren): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<span style={{ height: "2px" }}></span>
|
||||
<Typography variant="h6">{p.children}</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
@ -13,24 +13,28 @@ import { SnackbarProvider } from "./hooks/context_providers/SnackbarProvider";
|
||||
import "./index.css";
|
||||
import { ServerApi } from "./api/ServerApi";
|
||||
import { AsyncWidget } from "./widgets/AsyncWidget";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<DarkThemeProvider>
|
||||
<AlertDialogProvider>
|
||||
<ConfirmDialogProvider>
|
||||
<SnackbarProvider>
|
||||
<LoadingMessageProvider>
|
||||
<AsyncWidget
|
||||
loadKey={1}
|
||||
load={async () => await ServerApi.LoadConfig()}
|
||||
errMsg="Failed to connect to backend to retrieve static config!"
|
||||
build={() => <App />}
|
||||
/>
|
||||
</LoadingMessageProvider>
|
||||
</SnackbarProvider>
|
||||
</ConfirmDialogProvider>
|
||||
</AlertDialogProvider>
|
||||
</DarkThemeProvider>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<DarkThemeProvider>
|
||||
<AlertDialogProvider>
|
||||
<ConfirmDialogProvider>
|
||||
<SnackbarProvider>
|
||||
<LoadingMessageProvider>
|
||||
<AsyncWidget
|
||||
loadKey={1}
|
||||
load={async () => await ServerApi.LoadConfig()}
|
||||
errMsg="Failed to connect to backend to retrieve static config!"
|
||||
build={() => <App />}
|
||||
/>
|
||||
</LoadingMessageProvider>
|
||||
</SnackbarProvider>
|
||||
</ConfirmDialogProvider>
|
||||
</AlertDialogProvider>
|
||||
</DarkThemeProvider>
|
||||
</LocalizationProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
50
central_frontend/src/routes/DeviceRoute/DeviceRelays.tsx
Normal file
50
central_frontend/src/routes/DeviceRoute/DeviceRelays.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
import React from "react";
|
||||
import { Device, DeviceRelay } from "../../api/DeviceApi";
|
||||
import { DeviceRouteCard } from "./DeviceRouteCard";
|
||||
import { EditDeviceRelaysDialog } from "../../dialogs/EditDeviceRelaysDialog";
|
||||
|
||||
export function DeviceRelays(p: {
|
||||
device: Device;
|
||||
onReload: () => void;
|
||||
}): React.ReactElement {
|
||||
const [dialogOpen, setDialogOpen] = React.useState(false);
|
||||
const [currRelay, setCurrRelay] = React.useState<DeviceRelay | undefined>();
|
||||
|
||||
const createNewRelay = () => {
|
||||
setDialogOpen(true);
|
||||
setCurrRelay(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{dialogOpen && (
|
||||
<EditDeviceRelaysDialog
|
||||
device={p.device}
|
||||
onClose={() => setDialogOpen(false)}
|
||||
relay={currRelay}
|
||||
onUpdated={() => {
|
||||
setDialogOpen(false);
|
||||
p.onReload();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<DeviceRouteCard
|
||||
title="Device relays"
|
||||
actions={
|
||||
<Tooltip title="Create new relay">
|
||||
<IconButton
|
||||
onClick={createNewRelay}
|
||||
disabled={p.device.relays.length >= p.device.info.max_relays}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
TODO : relays list ({p.device.relays.length}) relays now)
|
||||
</DeviceRouteCard>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
import { Grid, IconButton, Tooltip } from "@mui/material";
|
||||
import React from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Device, DeviceApi } from "../../api/DeviceApi";
|
||||
@ -10,6 +10,7 @@ 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";
|
||||
|
||||
export function DeviceRoute(): React.ReactElement {
|
||||
const { id } = useParams();
|
||||
@ -79,7 +80,14 @@ function DeviceRouteInner(p: {
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<GeneralDeviceInfo {...p} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<GeneralDeviceInfo {...p} />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<DeviceRelays {...p} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</SolarEnergyRouteContainer>
|
||||
);
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ export function GeneralDeviceInfo(p: {
|
||||
<DeviceInfoProperty
|
||||
label="Enabled"
|
||||
value={p.device.enabled ? "YES" : "NO"}
|
||||
color={p.device.enabled ? "green" : "red"}
|
||||
/>
|
||||
<DeviceInfoProperty
|
||||
label="Maximum number of relays"
|
||||
@ -82,11 +83,12 @@ 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>{p.value}</TableCell>
|
||||
<TableCell style={{ color: p.color }}>{p.value}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,29 @@
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
|
||||
/**
|
||||
* Get current UNIX time, in seconds
|
||||
*/
|
||||
export function time(): number {
|
||||
return Math.floor(new Date().getTime() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dayjs representation of given time of day
|
||||
*/
|
||||
export function timeOfDay(time: number): Dayjs {
|
||||
const hours = Math.floor(time / 3600);
|
||||
const minutes = Math.floor(time / 60) - hours * 60;
|
||||
|
||||
return dayjs(
|
||||
`2022-04-17T${hours.toString().padStart(2, "0")}:${minutes
|
||||
.toString()
|
||||
.padStart(2, "0")}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get time of day (in secs) from a given dayjs representation
|
||||
*/
|
||||
export function dayjsToTimeOfDay(d: Dayjs): number {
|
||||
return d.hour() * 3600 + d.minute() * 60 + d.second();
|
||||
}
|
||||
|
Reference in New Issue
Block a user