Start to build relay dialog
This commit is contained in:
parent
73163e6e69
commit
8a65687970
@ -45,6 +45,18 @@ pub struct StaticConstraints {
|
|||||||
pub dev_name_len: SizeConstraint,
|
pub dev_name_len: SizeConstraint,
|
||||||
/// Device description constraint
|
/// Device description constraint
|
||||||
pub dev_description_len: SizeConstraint,
|
pub dev_description_len: SizeConstraint,
|
||||||
|
/// Relay name constraint
|
||||||
|
pub relay_name_len: SizeConstraint,
|
||||||
|
/// Relay priority constraint
|
||||||
|
pub relay_priority: SizeConstraint,
|
||||||
|
/// Relay consumption constraint
|
||||||
|
pub relay_consumption: SizeConstraint,
|
||||||
|
/// Relay minimal uptime
|
||||||
|
pub relay_minimal_uptime: SizeConstraint,
|
||||||
|
/// Relay minimal downtime
|
||||||
|
pub relay_minimal_downtime: SizeConstraint,
|
||||||
|
/// Relay daily minimal uptime
|
||||||
|
pub relay_daily_minimal_runtime: SizeConstraint,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StaticConstraints {
|
impl Default for StaticConstraints {
|
||||||
@ -52,6 +64,12 @@ impl Default for StaticConstraints {
|
|||||||
Self {
|
Self {
|
||||||
dev_name_len: SizeConstraint::new(1, 50),
|
dev_name_len: SizeConstraint::new(1, 50),
|
||||||
dev_description_len: SizeConstraint::new(0, 100),
|
dev_description_len: SizeConstraint::new(0, 100),
|
||||||
|
relay_name_len: SizeConstraint::new(1, 100),
|
||||||
|
relay_priority: SizeConstraint::new(0, 999999),
|
||||||
|
relay_consumption: SizeConstraint::new(0, 999999),
|
||||||
|
relay_minimal_uptime: SizeConstraint::new(0, 9999999),
|
||||||
|
relay_minimal_downtime: SizeConstraint::new(0, 9999999),
|
||||||
|
relay_daily_minimal_runtime: SizeConstraint::new(0, 3600 * 24),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ pub struct Device {
|
|||||||
/// time of a device
|
/// time of a device
|
||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct DailyMinRuntime {
|
pub struct DailyMinRuntime {
|
||||||
/// Minimum time, in seconds, that this relay should run
|
/// Minimum time, in seconds, that this relay should run each day
|
||||||
pub min_runtime: usize,
|
pub min_runtime: usize,
|
||||||
/// The seconds in the days (from 00:00) where the counter is reset
|
/// The seconds in the days (from 00:00) where the counter is reset
|
||||||
pub reset_time: usize,
|
pub reset_time: usize,
|
||||||
@ -87,13 +87,13 @@ pub struct DeviceRelay {
|
|||||||
name: String,
|
name: String,
|
||||||
/// Whether this relay can be turned on or not
|
/// Whether this relay can be turned on or not
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
/// Relay priority when selecting relays to turn of / on. 0 = lowest priority
|
/// Relay priority when selecting relays to turn on. 0 = lowest priority
|
||||||
priority: usize,
|
priority: usize,
|
||||||
/// Estimated consumption of the electrical equipment triggered by the relay
|
/// Estimated consumption of the electrical equipment triggered by the relay
|
||||||
consumption: usize,
|
consumption: usize,
|
||||||
/// Minimal time this relay shall be left on before it can be turned off
|
/// Minimal time this relay shall be left on before it can be turned off (in seconds)
|
||||||
minimal_uptime: usize,
|
minimal_uptime: usize,
|
||||||
/// Minimal time this relay shall be left off before it can be turned on again
|
/// Minimal time this relay shall be left off before it can be turned on again (in seconds)
|
||||||
minimal_downtime: usize,
|
minimal_downtime: usize,
|
||||||
/// Optional minimal runtime requirements for this relay
|
/// Optional minimal runtime requirements for this relay
|
||||||
daily_runtime: Option<DailyMinRuntime>,
|
daily_runtime: Option<DailyMinRuntime>,
|
||||||
|
124
central_frontend/package-lock.json
generated
124
central_frontend/package-lock.json
generated
@ -15,7 +15,9 @@
|
|||||||
"@mdi/react": "^1.6.1",
|
"@mdi/react": "^1.6.1",
|
||||||
"@mui/icons-material": "^5.15.21",
|
"@mui/icons-material": "^5.15.21",
|
||||||
"@mui/material": "^5.15.21",
|
"@mui/material": "^5.15.21",
|
||||||
|
"@mui/x-date-pickers": "^7.11.1",
|
||||||
"date-and-time": "^3.3.0",
|
"date-and-time": "^3.3.0",
|
||||||
|
"dayjs": "^1.11.12",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^6.24.0"
|
"react-router-dom": "^6.24.0"
|
||||||
@ -337,9 +339,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.24.7",
|
"version": "7.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
|
||||||
"integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==",
|
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
@ -1265,12 +1267,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/private-theming": {
|
"node_modules/@mui/private-theming": {
|
||||||
"version": "5.15.20",
|
"version": "5.16.5",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.20.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.5.tgz",
|
||||||
"integrity": "sha512-BK8F94AIqSrnaPYXf2KAOjGZJgWfvqAVQ2gVR3EryvQFtuBnG6RwodxrCvd3B48VuMy6Wsk897+lQMUxJyk+6g==",
|
"integrity": "sha512-CSLg0YkpDqg0aXOxtjo3oTMd3XWMxvNb5d0v4AYVqwOltU8q6GvnZjhWyCLjGSCrcgfwm6/VDjaKLPlR14wxIA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.23.9",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@mui/utils": "^5.15.20",
|
"@mui/utils": "^5.16.5",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1291,9 +1293,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/styled-engine": {
|
"node_modules/@mui/styled-engine": {
|
||||||
"version": "5.15.14",
|
"version": "5.16.4",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.4.tgz",
|
||||||
"integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==",
|
"integrity": "sha512-0+mnkf+UiAmTVB8PZFqOhqf729Yh0Cxq29/5cA3VAyDVTRIUUQ8FXQhiAhUIbijFmM72rY80ahFPXIm4WDbzcA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.23.9",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@emotion/cache": "^11.11.0",
|
"@emotion/cache": "^11.11.0",
|
||||||
@ -1322,15 +1324,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/system": {
|
"node_modules/@mui/system": {
|
||||||
"version": "5.15.20",
|
"version": "5.16.5",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.20.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.5.tgz",
|
||||||
"integrity": "sha512-LoMq4IlAAhxzL2VNUDBTQxAb4chnBe8JvRINVNDiMtHE2PiPOoHlhOPutSxEbaL5mkECPVWSv6p8JEV+uykwIA==",
|
"integrity": "sha512-uzIUGdrWddUx1HPxW4+B2o4vpgKyRxGe/8BxbfXVDPNPHX75c782TseoCnR/VyfnZJfqX87GcxDmnZEE1c031g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.23.9",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@mui/private-theming": "^5.15.20",
|
"@mui/private-theming": "^5.16.5",
|
||||||
"@mui/styled-engine": "^5.15.14",
|
"@mui/styled-engine": "^5.16.4",
|
||||||
"@mui/types": "^7.2.14",
|
"@mui/types": "^7.2.15",
|
||||||
"@mui/utils": "^5.15.20",
|
"@mui/utils": "^5.16.5",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
@ -1361,9 +1363,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/types": {
|
"node_modules/@mui/types": {
|
||||||
"version": "7.2.14",
|
"version": "7.2.15",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.15.tgz",
|
||||||
"integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==",
|
"integrity": "sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^17.0.0 || ^18.0.0"
|
"@types/react": "^17.0.0 || ^18.0.0"
|
||||||
},
|
},
|
||||||
@ -1374,14 +1376,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/utils": {
|
"node_modules/@mui/utils": {
|
||||||
"version": "5.15.20",
|
"version": "5.16.5",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.20.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.5.tgz",
|
||||||
"integrity": "sha512-mAbYx0sovrnpAu1zHc3MDIhPqL8RPVC5W5xcO1b7PiSCJPtckIZmBkp8hefamAvUiAV8gpfMOM6Zb+eSisbI2A==",
|
"integrity": "sha512-CwhcA9y44XwK7k2joL3Y29mRUnoBt+gOZZdGyw7YihbEwEErJYBtDwbZwVgH68zAljGe/b+Kd5bzfl63Gi3R2A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.23.9",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@types/prop-types": "^15.7.11",
|
"@mui/types": "^7.2.15",
|
||||||
|
"@types/prop-types": "^15.7.12",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-is": "^18.2.0"
|
"react-is": "^18.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
@ -1400,6 +1404,71 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/x-date-pickers": {
|
||||||
|
"version": "7.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.11.1.tgz",
|
||||||
|
"integrity": "sha512-CflouzTNSv0YeOA8iiYpJMtqGlwGC8LI9EE9egDGhatR9Mn5geRDTXsm0rRG/4pMOfaRxyJc6Yzr/axBhEXM7w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.24.8",
|
||||||
|
"@mui/base": "^5.0.0-beta.40",
|
||||||
|
"@mui/system": "^5.16.5",
|
||||||
|
"@mui/utils": "^5.16.5",
|
||||||
|
"@types/react-transition-group": "^4.4.10",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-transition-group": "^4.4.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.9.0",
|
||||||
|
"@emotion/styled": "^11.8.1",
|
||||||
|
"@mui/material": "^5.15.14",
|
||||||
|
"date-fns": "^2.25.0 || ^3.2.0",
|
||||||
|
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0",
|
||||||
|
"dayjs": "^1.10.7",
|
||||||
|
"luxon": "^3.0.2",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"moment-hijri": "^2.1.2",
|
||||||
|
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"date-fns": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"date-fns-jalali": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"luxon": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment-hijri": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment-jalaali": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -2211,6 +2280,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.3.0.tgz",
|
||||||
"integrity": "sha512-UguWfh9LkUecVrGSE0B7SpAnGRMPATmpwSoSij24/lDnwET3A641abfDBD/TdL0T+E04f8NWlbMkD9BscVvIZg=="
|
"integrity": "sha512-UguWfh9LkUecVrGSE0B7SpAnGRMPATmpwSoSij24/lDnwET3A641abfDBD/TdL0T+E04f8NWlbMkD9BscVvIZg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz",
|
||||||
|
"integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg=="
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.5",
|
"version": "4.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
||||||
|
@ -17,7 +17,9 @@
|
|||||||
"@mdi/react": "^1.6.1",
|
"@mdi/react": "^1.6.1",
|
||||||
"@mui/icons-material": "^5.15.21",
|
"@mui/icons-material": "^5.15.21",
|
||||||
"@mui/material": "^5.15.21",
|
"@mui/material": "^5.15.21",
|
||||||
|
"@mui/x-date-pickers": "^7.11.1",
|
||||||
"date-and-time": "^3.3.0",
|
"date-and-time": "^3.3.0",
|
||||||
|
"dayjs": "^1.11.12",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^6.24.0"
|
"react-router-dom": "^6.24.0"
|
||||||
|
@ -8,6 +8,12 @@ export interface ServerConfig {
|
|||||||
export interface ServerConstraint {
|
export interface ServerConstraint {
|
||||||
dev_name_len: LenConstraint;
|
dev_name_len: LenConstraint;
|
||||||
dev_description_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 {
|
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 "./index.css";
|
||||||
import { ServerApi } from "./api/ServerApi";
|
import { ServerApi } from "./api/ServerApi";
|
||||||
import { AsyncWidget } from "./widgets/AsyncWidget";
|
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(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<DarkThemeProvider>
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
<AlertDialogProvider>
|
<DarkThemeProvider>
|
||||||
<ConfirmDialogProvider>
|
<AlertDialogProvider>
|
||||||
<SnackbarProvider>
|
<ConfirmDialogProvider>
|
||||||
<LoadingMessageProvider>
|
<SnackbarProvider>
|
||||||
<AsyncWidget
|
<LoadingMessageProvider>
|
||||||
loadKey={1}
|
<AsyncWidget
|
||||||
load={async () => await ServerApi.LoadConfig()}
|
loadKey={1}
|
||||||
errMsg="Failed to connect to backend to retrieve static config!"
|
load={async () => await ServerApi.LoadConfig()}
|
||||||
build={() => <App />}
|
errMsg="Failed to connect to backend to retrieve static config!"
|
||||||
/>
|
build={() => <App />}
|
||||||
</LoadingMessageProvider>
|
/>
|
||||||
</SnackbarProvider>
|
</LoadingMessageProvider>
|
||||||
</ConfirmDialogProvider>
|
</SnackbarProvider>
|
||||||
</AlertDialogProvider>
|
</ConfirmDialogProvider>
|
||||||
</DarkThemeProvider>
|
</AlertDialogProvider>
|
||||||
|
</DarkThemeProvider>
|
||||||
|
</LocalizationProvider>
|
||||||
</React.StrictMode>
|
</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 DeleteIcon from "@mui/icons-material/Delete";
|
||||||
import { IconButton, Tooltip } from "@mui/material";
|
import { Grid, IconButton, Tooltip } from "@mui/material";
|
||||||
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";
|
||||||
@ -10,6 +10,7 @@ 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 { GeneralDeviceInfo } from "./GeneralDeviceInfo";
|
||||||
|
import { DeviceRelays } from "./DeviceRelays";
|
||||||
|
|
||||||
export function DeviceRoute(): React.ReactElement {
|
export function DeviceRoute(): React.ReactElement {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@ -79,7 +80,14 @@ function DeviceRouteInner(p: {
|
|||||||
</Tooltip>
|
</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>
|
</SolarEnergyRouteContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@ export function GeneralDeviceInfo(p: {
|
|||||||
<DeviceInfoProperty
|
<DeviceInfoProperty
|
||||||
label="Enabled"
|
label="Enabled"
|
||||||
value={p.device.enabled ? "YES" : "NO"}
|
value={p.device.enabled ? "YES" : "NO"}
|
||||||
|
color={p.device.enabled ? "green" : "red"}
|
||||||
/>
|
/>
|
||||||
<DeviceInfoProperty
|
<DeviceInfoProperty
|
||||||
label="Maximum number of relays"
|
label="Maximum number of relays"
|
||||||
@ -82,11 +83,12 @@ function DeviceInfoProperty(p: {
|
|||||||
icon?: React.ReactElement;
|
icon?: React.ReactElement;
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
color?: string;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<TableRow hover sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
|
<TableRow hover sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
|
||||||
<TableCell>{p.label}</TableCell>
|
<TableCell>{p.label}</TableCell>
|
||||||
<TableCell>{p.value}</TableCell>
|
<TableCell style={{ color: p.color }}>{p.value}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,29 @@
|
|||||||
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current UNIX time, in seconds
|
* Get current UNIX time, in seconds
|
||||||
*/
|
*/
|
||||||
export function time(): number {
|
export function time(): number {
|
||||||
return Math.floor(new Date().getTime() / 1000);
|
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();
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user