556 Commits

Author SHA1 Message Date
Proddy
cd2afd02be Merge pull request #1675 from MichaelDvP/dev2
update sk-language
2024-03-27 18:59:41 +05:30
MichaelDvP
07f8f9e704 update sk-language 2024-03-27 12:49:04 +01:00
Proddy
b7b09a8c93 Merge pull request #1674 from MichaelDvP/dev2
fix sk-language #1673, update pkg
2024-03-27 17:11:03 +05:30
MichaelDvP
8628bfa983 fix sk-language, update pkg 2024-03-27 11:34:49 +01:00
Proddy
8ef8eeb9ec Merge pull request #1672 from MichaelDvP/dev2
dev2 3.7.0-test, merge all changes from dev
2024-03-24 17:35:09 +01:00
MichaelDvP
765ddb6702 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2024-03-24 10:03:20 +01:00
Proddy
6cab020241 Merge pull request #1670 from proddy/dev
fix minor lint warnings
2024-03-24 09:45:53 +01:00
proddy
cf489f7632 fix minor lint warnings 2024-03-24 09:45:12 +01:00
MichaelDvP
6943913d30 make factory partition default on 16M systems 2024-03-24 09:06:28 +01:00
MichaelDvP
c5eaebc4b4 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2024-03-24 08:38:07 +01:00
Proddy
6905abf9f4 Merge pull request #1669 from proddy/dev
sync issues
2024-03-23 22:11:15 +01:00
proddy
ef3b8a308f update node 2024-03-23 22:07:04 +01:00
Proddy
8a66c056d8 Merge branch 'emsesp:dev' into dev 2024-03-23 19:24:29 +01:00
proddy
7967754024 3.7.0 2024-03-23 19:24:06 +01:00
Proddy
800528f843 Merge pull request #1667 from proddy/dev
changes to web layout (status and settings)
2024-03-23 19:18:59 +01:00
Proddy
a6a60215d4 Merge branch 'dev' into dev 2024-03-23 19:18:54 +01:00
proddy
793021573a update packages for 3.6.5 release 2024-03-23 17:28:38 +01:00
proddy
0deaafb9ce fix NTP icons, add AP 2024-03-23 17:16:45 +01:00
proddy
2ab50bd0a2 fix incorrect link when clicking on version 2024-03-22 17:06:42 +01:00
proddy
ecb82bd48b tidy up 2024-03-22 17:03:10 +01:00
proddy
5592d18e1f button border is consistent across screens 2024-03-22 17:02:02 +01:00
proddy
bcfcc7690f fix uptime in seconds 2024-03-22 17:01:47 +01:00
proddy
a2fa2515b3 updates to backend to match new frontend - #1665 2024-03-22 16:25:18 +01:00
proddy
c8e7eb3657 always show bus status even if offline - #1666 2024-03-22 16:23:08 +01:00
proddy
24ea975575 added status and renamed components 2024-03-20 23:57:19 +01:00
proddy
863bc04c21 status screen updates 2024-03-19 23:25:31 +01:00
proddy
217b424320 updates to settng page 2024-03-18 21:59:11 +01:00
proddy
e022c34fe7 fix read-only access 2024-03-18 13:25:17 +01:00
proddy
1af103d5ee updates to web pages 2024-03-17 23:23:09 +01:00
proddy
20ddbeb709 rename User defined entities to 'Custom entities' 2024-03-17 19:08:50 +01:00
proddy
e1ad7d3c01 add vscode settings.json back in 2024-03-17 19:08:31 +01:00
proddy
8f7c65c9b5 update packages 2024-03-17 19:08:11 +01:00
proddy
9bf7fbfb2e #1665 2024-03-17 19:08:03 +01:00
Proddy
2739712c5b Merge pull request #1661 from MichaelDvP/dev
add telnet command to reboot to previous version #1657
2024-03-16 08:41:23 +01:00
MichaelDvP
fbfaea6b56 Merge branch 'dev' into dev2 2024-03-15 17:43:08 +01:00
MichaelDvP
21207f88f3 update packages 2024-03-15 17:06:36 +01:00
MichaelDvP
9945b8d09f changelog, 3.6.5-dev.18 2024-03-15 14:50:01 +01:00
MichaelDvP
ee3fafa066 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2024-03-15 14:49:33 +01:00
MichaelDvP
eec3b3be7a restart option partitionname, console-command set service 2024-03-15 14:42:47 +01:00
MichaelDvP
77ad209ce1 stop fetching empty messages after 5 min 2024-03-15 13:59:20 +01:00
MichaelDvP
8a0152ebe6 log running partition on startup 2024-03-15 13:57:52 +01:00
MichaelDvP
ce8d8699c3 add 16M partitions with factory and two ota 2024-03-15 13:57:03 +01:00
Proddy
2efb9d18c9 Merge pull request #1662 from proddy/dev
small regression fixes from the last messy build I created (sorry!)
2024-03-13 18:51:50 +01:00
proddy
9af782c485 fix web log 2024-03-13 18:49:38 +01:00
proddy
bc232fcff2 added some debug log messages 2024-03-13 17:50:33 +01:00
proddy
287232be5c fix bug where changing wifi ssid/password was not persisted from console 2024-03-13 17:39:39 +01:00
proddy
c1058ba06c package update 2024-03-13 17:37:35 +01:00
proddy
9fe6d1092a add back monitor filters for colors in VSC 2024-03-13 17:37:27 +01:00
proddy
ada55ffaba package cleanup 2024-03-13 17:37:07 +01:00
proddy
c70b1c3bbd don't need to start mDNS for IPv6 as its already running 2024-03-13 17:36:58 +01:00
MichaelDvP
a5708e11ba update packages 2024-03-13 16:21:41 +01:00
MichaelDvP
8dfc84eac2 add restart other command to change partition #1657 2024-03-13 16:03:28 +01:00
Proddy
e00c30cd4f Merge pull request #1658 from proddy/dev
fix broken web - #1656
2024-03-12 23:15:14 +01:00
Proddy
be6bb1de6a fixes #1656 2024-03-12 23:08:57 +01:00
Proddy
0bd57973c5 remove duplicate wifi log message for ipv6 2024-03-12 23:08:24 +01:00
Proddy
39cfa3ab79 uri doesnt need to be a string 2024-03-12 23:08:06 +01:00
Proddy
d36fe1c0bf update yarn 2024-03-12 23:07:55 +01:00
Proddy
27aa57da3c Merge pull request #1655 from proddy/dev
fix compiling local test
2024-03-10 21:36:55 +01:00
Proddy
b7bd2be0a5 fix compiling local test 2024-03-10 21:36:22 +01:00
Proddy
9ad80fc74d Merge pull request #1653 from MichaelDvP/dev
CW100/RC100 controlmode, fix emsesp#1650
2024-03-10 21:17:34 +01:00
Proddy
7fc7c24a20 Merge pull request #1654 from proddy/dev
cleanup and making asyncWS cleaner
2024-03-10 21:17:21 +01:00
Proddy
c1ae0e76c8 autoformatting 2024-03-10 21:15:52 +01:00
Proddy
f1f9bacf76 ignore vscode 2024-03-10 21:15:42 +01:00
Proddy
4e3eb3aeaa asyncwebserver optimizations 2024-03-10 21:15:34 +01:00
Proddy
18c5aaf598 ignore local vsc files 2024-03-10 21:14:20 +01:00
Proddy
cff60f4ed8 update API tests 2024-03-10 21:14:09 +01:00
MichaelDvP
9fe54825f8 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2024-03-09 16:09:17 +01:00
Proddy
c5f2dba1ef Merge pull request #1651 from proddy/dev
minor text changes
2024-03-09 14:52:16 +01:00
proddy
b94b3e7e2e added comment to show test add example 2024-03-09 14:51:03 +01:00
proddy
c75f7b6c7d simulate hc's 2024-03-09 14:50:52 +01:00
proddy
d25ead5208 update version in standalone 2024-03-09 14:50:37 +01:00
proddy
68f09f03f8 formatting console 2024-03-09 14:50:23 +01:00
MichaelDvP
fc1e009f09 CW100/RC100 controlmode, fix #1650 2024-03-09 14:38:07 +01:00
Proddy
a5ef1d16d5 Merge pull request #1649 from proddy/dev
package updates
2024-03-09 00:58:53 -08:00
Proddy
e80c2b0814 package updates 2024-03-09 09:58:12 +01:00
MichaelDvP
7ba330176a update packages 2024-03-07 11:38:00 +01:00
MichaelDvP
ab9caeba9c roomctrl: disable rf_sensor, set type with controlmode 2024-03-07 11:37:31 +01:00
MichaelDvP
7a5eeaa88a weblog refresh_sync to 80 ms 2024-03-07 11:12:26 +01:00
Proddy
bb3550810d Merge pull request #1647 from MichaelDvP/dev2
update testbuild to latest dev change
2024-03-02 10:54:20 +01:00
Proddy
5bdf7978bf Merge pull request #1646 from MichaelDvP/dev
enable IPv6 for tasmota-arduino
2024-03-02 10:53:45 +01:00
MichaelDvP
f77fb12c80 revert uart change, event.size not set for break. 2024-03-02 10:41:15 +01:00
MichaelDvP
9fc109eec1 update changelog, 3.6.5-dev.16 2024-02-29 18:54:49 +01:00
MichaelDvP
f1342e4d62 Merge branch 'dev' into dev2 2024-02-29 15:06:05 +01:00
MichaelDvP
854f764b3c add esp32_s3_32M profile, 16M partitions with larger data space, packages 2024-02-29 14:28:58 +01:00
MichaelDvP
463c68d08c enable IPv6 for tasmota, add env:ci_16M for E32V2 2024-02-29 10:12:42 +01:00
MichaelDvP
2ddd2401eb add limits to entities #1642 2024-02-28 12:41:21 +01:00
MichaelDvP
ff045b1a01 back to platform 2024.01.01, show dns ipv6 in right format 2024-02-28 12:40:27 +01:00
MichaelDvP
7c73e70986 more weather compensation entities #1642 2024-02-28 07:46:22 +01:00
MichaelDvP
8699bd4eb0 update packages 2024-02-28 07:45:17 +01:00
MichaelDvP
626c32763f add weather compensation, #1642 2024-02-27 17:48:54 +01:00
MichaelDvP
56c958a141 back to tasmota 2024.01.00, fix dns issue 2024-02-27 16:58:24 +01:00
MichaelDvP
d09abc1b49 back to platform 24.01.00 2024-02-27 16:17:50 +01:00
Proddy
8c4fc495a3 Merge pull request #1639 from MichaelDvP/dev2
merge dev changes/fixes, new entities, env for N32R8 chip, fix custom board profile on boot
2024-02-27 08:26:58 +01:00
Proddy
fd1d4b97a0 Merge pull request #1638 from MichaelDvP/dev
fix command attribute #1637
2024-02-27 08:25:16 +01:00
MichaelDvP
d8b77fc056 Merge branch 'dev' into dev2 2024-02-27 07:58:21 +01:00
MichaelDvP
82579869a4 fix command attribute #1637 2024-02-27 07:53:22 +01:00
MichaelDvP
12690eeaf4 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2024-02-27 07:27:44 +01:00
MichaelDvP
a359618cca v.test.16, update changelog 2024-02-26 14:59:36 +01:00
MichaelDvP
20165a528d heatpump dhw stop temperatures #1624 2024-02-26 14:59:05 +01:00
MichaelDvP
3a23dae178 show in info and use for mqtt: heap_caps_get_free_size, #1622 2024-02-26 14:58:23 +01:00
MichaelDvP
e50d4fafb5 Slovak language fix #1636 2024-02-26 14:56:49 +01:00
MichaelDvP
673b4c2881 add env and partition for devKitC-1-N32R8, #1635 2024-02-26 14:56:18 +01:00
MichaelDvP
b676c4d164 fix thermostat wwc2 commands 2024-02-25 13:54:06 +01:00
MichaelDvP
40716f9c55 add RC300 wwc2 entities 2024-02-25 10:51:08 +01:00
MichaelDvP
df0210bfac update packages 2024-02-25 10:50:31 +01:00
MichaelDvP
41ac8120d0 16M partitions, second nvs 2024-02-24 18:32:20 +01:00
MichaelDvP
6a66c7def7 fix custom board profile on boot 2024-02-24 18:31:39 +01:00
MichaelDvP
3b0b6d75a7 uart check break first 2024-02-24 18:31:09 +01:00
MichaelDvP
292ed242c4 AsyncTCP queue 32 2024-02-24 18:30:38 +01:00
MichaelDvP
bb670e97ff add platform to system_info 2024-02-24 18:30:08 +01:00
MichaelDvP
768bdcaaaa add platform to system info 2024-02-24 14:38:08 +01:00
Proddy
1db4a33a1d Merge pull request #1631 from MichaelDvP/dev
small fixes
2024-02-24 13:28:50 +01:00
MichaelDvP
61d11ce440 update packages 2024-02-24 10:04:09 +01:00
MichaelDvP
08918a7349 AsyncTCP stack and queue settings 2024-02-24 10:03:58 +01:00
MichaelDvP
87542fb9df update packages 2024-02-24 09:47:34 +01:00
Proddy
fb09386c22 Merge pull request #1633 from proddy/dev
fix issue with HA, removing availability check for online status as it may not have been published in time
2024-02-23 19:43:17 +01:00
Proddy
d42ae52aff fix issue with HA, removing availability check for online status as it may not have been published in time 2024-02-23 19:42:15 +01:00
Proddy
271d1fda92 package update 2024-02-23 19:41:44 +01:00
Proddy
df982e3ea9 Merge pull request #1632 from MichaelDvP/dev2
update to all dev changes
2024-02-23 19:13:32 +01:00
MichaelDvP
222aaca218 store relais states in nvs 2024-02-23 10:00:15 +01:00
MichaelDvP
8a56c599e6 uart-isr to iram 2024-02-23 09:57:53 +01:00
MichaelDvP
003d3740af partitions: nvs: 20k, fs: 1M 2024-02-23 09:57:34 +01:00
MichaelDvP
74342ba654 Merge branch 'dev' into dev2 2024-02-23 08:56:43 +01:00
MichaelDvP
392015f3af Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2024-02-23 08:48:27 +01:00
Proddy
c769fd6d2c Merge pull request #1626 from proddy/dev
Expose EMS-ESP commands (like reset) via MQTT Discovery #1605
2024-02-22 21:29:21 +01:00
Proddy
0f06bfa91c 3.6.5-dev15 2024-02-22 21:29:04 +01:00
Proddy
dffc4a7c02 package update 2024-02-22 21:27:43 +01:00
Proddy
34201025c3 update with #1628 2024-02-21 23:10:56 +01:00
Proddy
189ea6b23d Send MQTT heartbeat immediately after connection 2024-02-21 23:04:32 +01:00
Proddy
24f2d86059 update packages 2024-02-21 23:04:18 +01:00
Proddy
1a08ab6a2b remove RSSI from info payload as its in the heartbeat 2024-02-21 23:04:10 +01:00
MichaelDvP
5e5e6ff053 fixed ip for ETH, setting order 2024-02-21 16:02:04 +01:00
MichaelDvP
08204a94d8 fix typo #1622 2024-02-21 16:01:35 +01:00
MichaelDvP
0eb3df704e Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2024-02-21 15:59:56 +01:00
Proddy
2eb77b5f97 package update 2024-02-20 23:28:06 +01:00
Proddy
33b6ece55b Expose EMS-ESP commands (like reset) via MQTT Discovery #1605 2024-02-20 23:27:56 +01:00
MichaelDvP
0010f71a3c uart in iram 2024-02-19 09:56:54 +01:00
MichaelDvP
38a546d6f7 remotetemp with RC200 v32.02, version as 10 byte telegram., fix #1622 2024-02-17 18:32:29 +01:00
MichaelDvP
4346de27b6 remote thermostat 30 sec interval, update packages 2024-02-16 13:41:30 +01:00
MichaelDvP
d797c3371b Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2024-02-15 09:08:01 +01:00
Proddy
5d3f8e5b69 updates 2024-02-14 22:19:11 +01:00
Proddy
8ebc552cac Merge pull request #1615 from proddy/dev
fix WiFi TxPower, plus a ton of other refactoring
2024-02-14 15:08:04 +01:00
proddy
944d86b644 removed unused _retained's 2024-02-14 15:07:35 +01:00
proddy
8bd2a39d4e update for 3.6.5-dev14 2024-02-14 15:07:17 +01:00
proddy
ed9cad6e39 hostname is multiline 2024-02-14 15:07:02 +01:00
proddy
e31330e931 200 char limit for hostnames 2024-02-14 15:06:51 +01:00
proddy
49d749e89f move generateClientId() 2024-02-14 14:47:29 +01:00
proddy
d3fadd7081 remove comment 2024-02-14 14:47:17 +01:00
proddy
31ff0f5aba add override 2024-02-14 14:47:09 +01:00
proddy
b24a63b992 add missing overrides 2024-02-14 14:46:59 +01:00
proddy
f5ec9e9602 remove unneeded file 2024-02-14 14:46:20 +01:00
proddy
b6accb8d02 add extra {} to SubscribeItem list[1] 2024-02-14 14:46:10 +01:00
Proddy
a35486ec24 remove bogus flags 2024-02-14 11:09:08 +01:00
Proddy
fdaa9a6188 tidy up standalone 2024-02-14 11:00:16 +01:00
Proddy
daf08e7bd9 support standalone 2024-02-14 11:00:03 +01:00
Proddy
994e1fc26b remove bogus include 2024-02-14 10:54:16 +01:00
Proddy
34b7dd61cf clean up standalone 2024-02-14 10:53:53 +01:00
Proddy
ce3c3e0b3e tidy up lambda's 2024-02-13 20:19:58 +01:00
Proddy
92a80c3aaf factoryReset only used with button in test mode 2024-02-13 20:19:41 +01:00
Proddy
df21c15972 code cleaning 2024-02-13 20:19:21 +01:00
Proddy
b683d1dd21 default mqtt will base off ESP32's chip code 2024-02-13 20:19:03 +01:00
Proddy
a7d0259b30 replace lambda [&] with [this] 2024-02-13 15:19:06 +01:00
Proddy
16779064f4 update 2024-02-13 15:17:47 +01:00
Proddy
732ad4bc6a remove retainStr 2024-02-13 15:17:34 +01:00
Proddy
f40a6f20c6 change log text 2024-02-13 15:16:58 +01:00
proddy
4bf22dd6a5 refresh sync to 30ms for faster display in web 2024-02-13 10:57:37 +01:00
proddy
62ae5332de use consistent log message format 2024-02-13 10:57:11 +01:00
proddy
12e65279ef package update 2024-02-13 10:56:56 +01:00
proddy
295b90f49c NTP log text 2024-02-13 10:56:50 +01:00
Proddy
644907e58b fix error in event log 2024-02-12 14:51:10 +01:00
Proddy
a8a875f9d5 add missing tx_power 2024-02-12 14:40:08 +01:00
Proddy
6cd9dfc685 fixes for #1615 2024-02-12 14:25:21 +01:00
Proddy
80a3007f8b refactoring 2024-02-12 14:21:30 +01:00
Proddy
ed5f0bc6d5 start logging service sooner so weblog captures setup like network, mqtt etc 2024-02-12 11:22:56 +01:00
Proddy
df1109ea39 remove wifi_tweak 2024-02-12 11:22:21 +01:00
Proddy
1f7c968d0d remove OriginID from state service 2024-02-12 11:22:07 +01:00
Proddy
65cf8005a4 fixes for txPower 2024-02-11 23:01:32 +01:00
proddy
7c97aaf735 try out etags instead of immutable cache 2024-02-11 21:54:31 +01:00
proddy
23cfdd9b34 remove cmd 2024-02-11 21:54:08 +01:00
Proddy
b454e87405 Merge branch 'emsesp:dev' into dev 2024-02-11 14:23:24 +01:00
Proddy
3d715c45e0 Wifi Tx Power not adjusted #1614 2024-02-11 14:21:51 +01:00
proddy
fea63b0d52 #1614 - WiFi Tx Power 2024-02-11 10:32:33 +01:00
MichaelDvP
ce33fa6535 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2024-02-10 17:25:50 +01:00
Proddy
57296e55f2 Merge pull request #1610 from proddy/dev
memory optimizations
2024-02-10 16:43:34 +01:00
Proddy
12f0120afd fix standalone compiling 2024-02-10 16:17:40 +01:00
Proddy
303e86a5eb remove unused comments 2024-02-10 15:43:59 +01:00
Proddy
52479c408f added comment for refactor reminder 2024-02-10 15:43:49 +01:00
Proddy
fc8eea91eb scope optimization 2024-02-10 15:43:35 +01:00
Proddy
fe5a6fb568 added missing data points 2024-02-10 15:43:19 +01:00
Proddy
55672cc9de don't show bus scanning, so orange warning doesn't render on refresh 2024-02-10 15:43:03 +01:00
Proddy
6d6291e659 remove feature as its not used, and speed up web loading slightly 2024-02-10 15:42:14 +01:00
MichaelDvP
c2be9b210e GPIO check, typo and missing platform 2024-02-10 14:35:53 +01:00
proddy
257b40c2e4 fix MQTT discovery of custom entity doesn't consider type of data #1587 2024-02-10 09:51:29 +01:00
proddy
e6b61b7a51 bump 3.6.5-dev.13 2024-02-10 09:50:58 +01:00
proddy
f167be37a1 ArduinoJson 7.0.3 2024-02-10 09:50:32 +01:00
proddy
4ac2d1a9a7 remove extra v 2024-02-10 09:50:13 +01:00
proddy
8c602cd058 default -O2 2024-02-10 09:50:04 +01:00
proddy
b8f6664176 rename msgpack 2024-02-10 09:49:53 +01:00
proddy
1024dbb61f refactoring 2024-02-09 18:23:55 +01:00
MichaelDvP
464341c2cb DHW meter for heatpump #1609, test remote RF sensor 2024-02-09 09:13:55 +01:00
MichaelDvP
f765d7c31b update packages 2024-02-09 09:02:26 +01:00
MichaelDvP
72b64a0c30 arduino 3.0 adapt for tasmota <=2024.01.10, not compatble with new IP6Address 2024-02-08 21:42:13 +01:00
MichaelDvP
2b88fec2ee check valid pins for board_profile and analog 2024-02-08 18:52:27 +01:00
MichaelDvP
119b2b9514 RC100H emulation version and telegrams 2024-02-08 18:51:45 +01:00
MichaelDvP
4f406e8d33 update packages 2024-02-08 18:47:46 +01:00
MichaelDvP
0d80f58ea6 AsyncWebServer arduino3.0 compatible 2024-02-08 18:39:15 +01:00
MichaelDvP
6c7a3ad68c update esp32 platform 2024-02-08 17:42:35 +01:00
MichaelDvP
52a8b20c54 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2024-02-06 19:30:24 +01:00
MichaelDvP
5213382246 add #1609 dhw energy meter and some thermostat junkers settings 2024-02-06 19:25:14 +01:00
MichaelDvP
0b452ddd39 use FB10 telegram 0x123, ack writes 2024-02-05 13:24:35 +01:00
Proddy
253adfeb45 memory optimizations 2024-02-05 09:38:11 +01:00
MichaelDvP
355c7cbd92 fix ht3-reply 2024-02-05 08:52:29 +01:00
MichaelDvP
99d7ff0dc7 async tcp formatting 2024-02-05 08:42:38 +01:00
MichaelDvP
6564e444ab thermostat emulation check ht3 adddress 2024-02-05 07:33:52 +01:00
MichaelDvP
51b0a0ae5b Junkers FB10 telegram-id 2024-02-04 20:14:21 +01:00
Proddy
738d9b1d94 Merge pull request #1608 from xobed/dev
Fix Slovenian -> Slovak
2024-02-04 18:20:49 +01:00
xobed
4319d648aa Fix Slovenian -> Slovak 2024-02-04 17:52:05 +01:00
Proddy
2595b906a5 Merge pull request #1606 from proddy/dev
small updates
2024-02-04 15:10:06 +01:00
Proddy
c98e1a629b add type to device info, so we know if its a Ram variable or not 2024-02-04 14:52:35 +01:00
Proddy
ae7f0445a3 added DEBUG around logger().debug to prevent 'undefined' showing in log 2024-02-04 14:52:12 +01:00
Proddy
8406657906 text change 2024-02-04 14:51:44 +01:00
Proddy
9135635af2 auto-formatting 2024-02-04 14:51:35 +01:00
Proddy
25098409df text update 2024-02-04 14:51:22 +01:00
Proddy
8176120bf9 text update 2024-02-04 14:51:10 +01:00
Proddy
5cfb7b4548 default C++17 and added CONFIG_ASYNC_TCP_STACK_SIZE 2024-02-04 14:51:03 +01:00
Proddy
3f17d74bc6 update packages 2024-02-04 14:50:43 +01:00
Proddy
1694a0b41d use default test general 2024-02-04 14:50:28 +01:00
Proddy
ec8a182aa3 bump version 2024-02-04 14:50:14 +01:00
Proddy
22b70ac378 replace list with vector (makes no diff) 2024-02-04 14:49:59 +01:00
Proddy
40a685aeb2 remove cache 2024-02-04 14:49:23 +01:00
Proddy
a580998f25 update libraries 2024-02-04 14:49:11 +01:00
MichaelDvP
faa888ff36 remote emulation FB10, #1602 2024-02-04 14:38:34 +01:00
Proddy
afc34fc387 Merge branch 'emsesp:dev' into dev 2024-02-04 10:24:39 +01:00
Proddy
45335be4ed Merge pull request #1603 from MichaelDvP/dev
add diff for #1597, fix #1599
2024-02-04 10:23:20 +01:00
MichaelDvP
b834c8fd89 Merge branch 'dev' into dev2 2024-02-03 18:38:34 +01:00
MichaelDvP
24162b7350 update packages 2024-02-03 18:37:26 +01:00
MichaelDvP
3bb7e3514f add #1597 temp diff 2024-02-03 18:14:31 +01:00
MichaelDvP
58b75ee203 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2024-02-03 15:00:01 +01:00
MichaelDvP
d32178480d fix #1599 2024-02-03 14:59:14 +01:00
MichaelDvP
38e08be752 button should always be input 2024-02-03 14:58:49 +01:00
MichaelDvP
9573c4ed94 pretty_telegram 2nd fallback for name 2024-02-03 14:58:07 +01:00
Proddy
56e5e87238 Merge branch 'emsesp:dev' into dev 2024-01-30 20:54:58 +01:00
Proddy
ef47ee62a3 Merge pull request #1592 from MichaelDvP/dev2
update dev2 to latest dev changes
2024-01-30 20:38:49 +01:00
Proddy
a9a2380287 Merge pull request #1591 from MichaelDvP/dev
fix #1590 and more checks for device-telegram mapping
2024-01-30 19:57:08 +01:00
MichaelDvP
5bd9ec963f update translations 2024-01-30 08:09:18 +01:00
MichaelDvP
427e8cf11c update packages 2024-01-30 08:09:03 +01:00
MichaelDvP
de9c224a23 show sensor command commands 2024-01-30 08:05:50 +01:00
MichaelDvP
5532d20657 update packages 2024-01-30 07:47:42 +01:00
MichaelDvP
b7ce69ee2d map RFM200/T1RF to connect/extension 2024-01-30 07:42:57 +01:00
MichaelDvP
00b3525503 show sensor command commands 2024-01-30 07:41:49 +01:00
MichaelDvP
1065c9eec9 translations 2024-01-30 07:41:17 +01:00
MichaelDvP
8d3dd9d8e9 Merge branch 'dev' into dev2 2024-01-30 07:31:00 +01:00
MichaelDvP
0f799d5922 fix broadcast sending 2024-01-30 07:15:11 +01:00
MichaelDvP
3a8bed6976 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2024-01-29 10:20:15 +01:00
proddy
aadf4b7a53 package update 2024-01-28 21:58:25 +01:00
Proddy
ed7ba17bc7 Merge pull request #1575 from proddy/dev
small changes, optimizations and fixes
2024-01-28 18:30:19 +01:00
Proddy
526c36728f Merge branch 'emsesp:dev' into dev 2024-01-28 18:29:52 +01:00
Proddy
c7a35ebc58 updated with changes to sensor commands 2024-01-28 18:29:22 +01:00
Proddy
5735ffd222 tidied up commands 2024-01-28 18:28:26 +01:00
Proddy
d11508282f tidied up commands info, values etc 2024-01-28 18:28:12 +01:00
Proddy
30e11ad593 added comment 2024-01-28 18:27:51 +01:00
proddy
040954bb70 tidy up commands for temperature sensor 2024-01-28 16:36:18 +01:00
proddy
eec0051997 hide IP addresses 2024-01-28 14:09:42 +01:00
proddy
fab74a9061 package update 2024-01-28 13:21:52 +01:00
proddy
8e9edcb673 fixes #1583 2024-01-28 13:21:45 +01:00
MichaelDvP
2bd66bf4b6 reset wait_validate, typo 2024-01-28 12:16:35 +01:00
MichaelDvP
ce6e89338f reset wait_validate 2024-01-28 12:15:16 +01:00
MichaelDvP
9e423d9769 Merge branch 'dev' of https://github.com/MichaelDvP/EMS-ESP32 into dev 2024-01-28 11:37:41 +01:00
MichaelDvP
52bb6b8218 Merge branch 'dev' into dev2 2024-01-28 11:35:43 +01:00
MichaelDvP
5748bd4074 Merge branch 'dev' of https://github.com/MichaelDvP/EMS-ESP32 into dev 2024-01-28 11:31:42 +01:00
MichaelDvP
6155645436 process telegram selection of device 2024-01-28 11:31:17 +01:00
MichaelDvP
85a839e86b Merge branch 'emsesp:dev' into dev 2024-01-28 11:21:47 +01:00
MichaelDvP
0760e6e021 hpPressure telegram 2024-01-28 09:07:49 +01:00
MichaelDvP
fbd3ebbd4e fetch mixer 0x2CC 2024-01-28 09:07:28 +01:00
MichaelDvP
24b8e004ec fix jsonvariant in command 2024-01-28 09:07:05 +01:00
MichaelDvP
542246c142 hpPressure telegram 2024-01-28 09:03:22 +01:00
MichaelDvP
31bea94d9c fetch mixer 0x2CC 2024-01-28 08:41:02 +01:00
MichaelDvP
5669deeb80 fix jsonvariant in command 2024-01-28 08:40:38 +01:00
proddy
34cafe0d4d minor refactor moving scheduler item list loading once 2024-01-26 23:01:44 +01:00
proddy
e319f5e270 package update 2024-01-26 23:01:17 +01:00
Proddy
133decf453 Merge pull request #1579 from myxor/myxor-patch-2
Add Buderus MX300 Internet Gateway
2024-01-24 23:41:48 +01:00
Marco H
541615d405 Add Buderus MX300 Internet Gateway 2024-01-24 20:02:02 +01:00
MichaelDvP
1042298541 add brackets to make logic clear 2024-01-24 11:10:16 +01:00
MichaelDvP
6aca61deee typo, telegram name for pretty telegram 2024-01-24 07:44:20 +01:00
MichaelDvP
9266454f82 rework process telegram 2024-01-23 13:47:28 +01:00
MichaelDvP
21de630f8e check telegrams for dest==thermostat 2024-01-22 16:13:23 +01:00
Proddy
8a0e037c60 change default version for testing 2024-01-22 10:03:02 +01:00
Proddy
326e7bcc2a add TODO comments so reminding me what to do next 2024-01-21 21:51:52 +01:00
Proddy
cc8839ab31 add tests in a consistent way 2024-01-21 21:49:39 +01:00
Proddy
fcffa3df5c add comment 2024-01-21 21:49:06 +01:00
Proddy
27d0ba0526 store custom files for testing 2024-01-21 21:48:36 +01:00
Proddy
18d329f3ee add analog test 2024-01-21 21:48:24 +01:00
Proddy
9e064eb564 remove comments 2024-01-21 21:48:04 +01:00
Proddy
2764185132 minor text changes 2024-01-21 18:28:07 +01:00
Proddy
79d7142e5f add test data 2024-01-21 18:27:58 +01:00
Proddy
2a3838771a cleanup tests 2024-01-21 18:27:22 +01:00
Proddy
8d712c47f0 package update 2024-01-21 18:26:47 +01:00
Proddy
a3ccc83cf3 change text for custom entities 2024-01-21 18:26:36 +01:00
proddy
fe30b8de8d work on muter/upload testing 2024-01-21 11:46:13 +01:00
proddy
d8671dd114 formatting 2024-01-21 11:45:40 +01:00
proddy
603036a5e9 formatting 2024-01-21 11:45:29 +01:00
proddy
9eb617bcb0 package update 2024-01-21 11:45:22 +01:00
proddy
82e1b069eb rename callback function 2024-01-21 11:45:00 +01:00
Proddy
89da6d5851 Merge pull request #1574 from MichaelDvP/dev2
Update dev2 to latest changes of dev
2024-01-21 10:18:10 +01:00
MichaelDvP
1491f283a8 update packages 2024-01-21 09:35:26 +01:00
MichaelDvP
a8c3b21ee6 Merge branch 'dev' into dev2 2024-01-21 09:35:16 +01:00
Proddy
1b27e1fd09 Merge pull request #1572 from proddy/dev
update web standalone with variable custom entity
2024-01-20 21:00:20 +01:00
Proddy
d99222450c minor code optimizations 2024-01-20 20:58:46 +01:00
Proddy
ef075787dc add ram to test data 2024-01-20 20:58:38 +01:00
Proddy
ec411a7dff Merge pull request #1570 from MichaelDvP/dev
custom variables #1423
2024-01-20 20:20:34 +01:00
Proddy
fd4f649db3 Merge pull request #1571 from proddy/dev
minor tweaks
2024-01-20 20:18:50 +01:00
Proddy
0c93f1daa5 update esptool files 2024-01-20 20:17:46 +01:00
Proddy
7da2806ab4 add missing security endpoint 2024-01-20 20:17:30 +01:00
Proddy
f88685833d fix endless loop with token 2024-01-20 20:17:17 +01:00
MichaelDvP
c0e77698aa Add custom variables #1423 2024-01-20 17:57:01 +01:00
MichaelDvP
9fd7b2553c set min/max for hpDiffPress 2024-01-20 17:24:47 +01:00
Proddy
bb5ca8a804 Merge pull request #1569 from MichaelDvP/dev
telegram mapping to device, add 0x2CC telegrams
2024-01-20 15:13:39 +01:00
MichaelDvP
cd8921e78e add hpSetDiffPressure #1563 2024-01-20 15:01:41 +01:00
MichaelDvP
9260db330e add mixer values #1554 2024-01-20 15:01:11 +01:00
MichaelDvP
3b32dcb407 Map telegrams as mentioned in #1563 2024-01-20 14:59:59 +01:00
Proddy
549a0302b7 Merge pull request #1568 from MichaelDvP/dev
fix #1565 and workaround for #1564
2024-01-20 12:22:31 +01:00
MichaelDvP
4d1a428acf fix #1565, use String for output a single api_data 2024-01-20 10:48:16 +01:00
MichaelDvP
cc83dab97b remove unused type 2024-01-20 10:46:10 +01:00
MichaelDvP
65ff765219 workaround for #1564 2024-01-20 10:45:52 +01:00
MichaelDvP
d5cb5c1c51 update packages 2024-01-20 10:45:01 +01:00
MichaelDvP
4974208a65 update arduinoJson 7.02 2024-01-20 08:36:06 +01:00
MichaelDvP
e88ede2d8b typo 2024-01-20 08:29:17 +01:00
Proddy
2b6fd41d5a Merge pull request #1559 from proddy/dev
add tag after fullname instead of before
2024-01-17 11:58:55 +01:00
Proddy
e26208a5e9 package update 2024-01-17 11:58:33 +01:00
Proddy
12a545ddbf rollback #1338 2024-01-17 11:58:25 +01:00
Proddy
4ad5c7299e fixes #1338 2024-01-14 21:32:00 +01:00
Proddy
c04371dfae rename Discovery to lowercase so it looks consistent 2024-01-14 21:31:43 +01:00
Proddy
d810494211 update react-toastify 2024-01-14 21:31:20 +01:00
Proddy
18dd207d3c fixes #1338 2024-01-14 21:12:50 +01:00
Proddy
a34c8661bd optimize so easier to port to IDF later 2024-01-14 21:12:36 +01:00
Proddy
f9516860e3 remove comment 2024-01-14 21:12:15 +01:00
Proddy
ded7b547e1 Merge pull request #1558 from proddy/dev
renamed Web custom entity TSX class, remove bogus URI handler from Web customization, added icon to Custom table
2024-01-14 15:26:14 +01:00
Proddy
77607263a9 3.6.5-dev.9 2024-01-14 15:22:07 +01:00
Proddy
c55e05e7b2 remove redundant rest call to /rest/customization 2024-01-14 15:16:50 +01:00
Proddy
d529cbf269 package update 2024-01-14 15:15:48 +01:00
Proddy
c578154b5e tidy up custom entities, add writeable icon (inspired by #1557) 2024-01-14 15:15:40 +01:00
MichaelDvP
6c398109f4 Mixer set message to 0x2CD, .. 2024-01-14 10:20:54 +01:00
Proddy
bbfdb0ff0e Merge pull request #1555 from proddy/dev
routine updates
2024-01-13 22:24:02 +01:00
Proddy
48de155201 update example 2024-01-13 22:21:50 +01:00
Proddy
a2cfe00113 update tasmota lib 2024-01-13 22:21:43 +01:00
Proddy
e0c8557d5c package update 2024-01-13 22:21:12 +01:00
MichaelDvP
74691ce34a roomctrl RC200 version with 2.id 2024-01-13 15:43:31 +01:00
MichaelDvP
ef6ac3848f mixer telegram 0x2CC, #1554 2024-01-13 15:36:01 +01:00
MichaelDvP
5c490834cf fix telegram length check of remote 2024-01-13 13:25:25 +01:00
Proddy
d6aa1fb48b ArduinoJson 7.0.1 2024-01-13 11:37:37 +01:00
Proddy
2190db77ad package update 2024-01-13 11:37:19 +01:00
MichaelDvP
7b4f76d51d remote type depends on control setting 2024-01-12 10:42:16 +01:00
MichaelDvP
16010b2223 remote use RC200 for hc3/4 2024-01-12 09:50:21 +01:00
Proddy
8c2aba8eb1 Merge pull request #1552 from proddy/dev
new 'add' test
2024-01-12 08:50:55 +01:00
proddy
c834c5e43e debug formatting 2024-01-11 21:37:48 +01:00
proddy
94f268a62d add new 'add' test 2024-01-11 21:37:40 +01:00
proddy
1f81ccb686 remove comment 2024-01-11 21:36:58 +01:00
proddy
356180dbf9 package update 2024-01-11 21:36:52 +01:00
MichaelDvP
ea2d5b77c0 use RC100H again for remote 2024-01-11 18:24:46 +01:00
MichaelDvP
81b0b77e2b type-ids RemoteCorrection/Batterie device dependend 2024-01-11 17:34:24 +01:00
MichaelDvP
af1209cb04 fix roomctrl for hc>1 2024-01-11 07:55:07 +01:00
MichaelDvP
b6ec8e14ec remote emulation RC200 for hc3/4 instead of RC100H 2024-01-10 18:32:23 +01:00
MichaelDvP
63cf4bdc21 remote thermostat for hc3 at 0x1A 2024-01-09 10:51:59 +01:00
Proddy
1d025d5b97 Merge pull request #1548 from proddy/dev
also remove references from JsonObjectConst and JsonArray
2024-01-08 11:45:30 +01:00
Proddy
cf8c5430d1 also remove references from JsonObjectConst and JsonArray 2024-01-08 11:44:33 +01:00
Proddy
1dde495f61 Merge pull request #1546 from MichaelDvP/dev
fix typo #1521
2024-01-08 11:33:50 +01:00
Proddy
c3650817ef Merge pull request #1547 from proddy/dev
remove & reference to JsonVariant (prevent dangling references)
2024-01-08 11:33:37 +01:00
Proddy
3f10523e66 remove & reference to JsonVariant (prevent dangling references) 2024-01-08 11:32:58 +01:00
MichaelDvP
8ddc167f93 Merge branch 'dev' into dev2 2024-01-08 11:26:17 +01:00
MichaelDvP
54d2f38841 Merge branch 'dev2' of https://github.com/emsesp/EMS-ESP32 into dev2 2024-01-08 11:26:07 +01:00
Proddy
ad9e463923 update package 2024-01-08 11:25:35 +01:00
MichaelDvP
5f2859fae2 fix typo #1521 2024-01-08 11:23:17 +01:00
Proddy
2b03d01a15 Merge pull request #1544 from proddy/dev
small changes, but many
2024-01-06 18:00:23 +01:00
Proddy
2366f6ba50 update licensing year to 2024 2024-01-06 17:58:11 +01:00
Proddy
10d6728c82 remove obsolete reference to JsonObject 2024-01-06 17:42:42 +01:00
Proddy
778cdaabb6 Merge branch 'emsesp:dev' into dev 2024-01-06 17:37:49 +01:00
Proddy
efa4f80b99 Merge pull request #1542 from MichaelDvP/dev
fix #1495 and #1536
2024-01-06 17:16:35 +01:00
Proddy
f25ab5f293 Merge pull request #1543 from MichaelDvP/dev2
Update dev2 to latest changes from dev
2024-01-06 17:13:03 +01:00
MichaelDvP
ae15c7ccd0 add boiler C1200W, #1536 2024-01-06 16:50:37 +01:00
MichaelDvP
025f43953a Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2024-01-06 16:36:01 +01:00
MichaelDvP
bf95205af6 fix #1495 wwComfort mode 2024-01-06 16:10:12 +01:00
Proddy
cb11305416 upper case Digital In 2024-01-06 10:48:49 +01:00
Proddy
5e0c6a527a fix test for device list 2024-01-06 10:48:27 +01:00
Proddy
8540e71145 add additional cache header 2024-01-06 10:48:15 +01:00
Proddy
cb2746b741 show total size in a comment in file 2024-01-06 10:48:03 +01:00
Proddy
209644d500 unused 2024-01-06 10:47:51 +01:00
Proddy
273dc4b83c fix case of Value (introduced by Polish translations) 2024-01-06 10:47:43 +01:00
Proddy
75574f663b uppercase Digital In/Out 2024-01-06 10:47:15 +01:00
Proddy
462870fe6b uppercase Value 2024-01-06 10:47:02 +01:00
Proddy
f365fe5317 Merge pull request #1539 from proddy/dev
ArduinoJson version 7.0.0 - #1538
2024-01-05 22:42:20 +01:00
Proddy
19eb755157 fix Arduino 7 (using to<> and not as<> 2024-01-05 22:39:35 +01:00
Proddy
26e4badc1b add sections for MQTT Discovery 2024-01-05 22:39:08 +01:00
Proddy
fb00f4eef9 update 3.6.5.dev-8 2024-01-05 22:37:24 +01:00
Proddy
2ef85bb9fa package update 2024-01-05 22:37:07 +01:00
Proddy
2dfba3f7d0 default GPIO is 21 (40 is invalid) 2024-01-05 22:36:53 +01:00
Proddy
5259d55f23 show Platform (taken from build) 2024-01-05 22:36:31 +01:00
Proddy
af237c4fc0 Arduino v7 2024-01-04 23:43:30 +01:00
Proddy
13a915e1f4 Merge pull request #1537 from proddy/dev
fixes #1360 (domoticz) and #1528 (HA dev name)
2024-01-04 18:12:43 +01:00
Proddy
df33a24951 fix add_ha_sections_to_doc calls 2024-01-04 18:11:08 +01:00
Proddy
e62fc14b6d support domoticz - MQTT autodiscovery in Domoticz not working #1360 2024-01-04 18:10:53 +01:00
Proddy
591b8afcb0 restructuring and added additional domoticz support 2024-01-04 18:10:16 +01:00
Proddy
35ee8c33b3 add comment 2024-01-04 18:09:42 +01:00
Proddy
9ca47627d2 updated example pio 2024-01-04 18:09:29 +01:00
Proddy
4c27cb831e update tests for temp sensors 2024-01-04 18:09:18 +01:00
Proddy
a3b6656be7 add additional Domoticz to discovery_type 2024-01-04 18:09:00 +01:00
Proddy
b0076cd5da change comments 2024-01-04 18:08:41 +01:00
Proddy
b6dbf93de2 add missing setContentType 2024-01-04 18:08:31 +01:00
MichaelDvP
e8217b68a5 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2024-01-04 11:53:38 +01:00
MichaelDvP
ebfe487a3a add Bosch C1200 boiler id 12 2024-01-04 11:43:49 +01:00
MichaelDvP
ce34567939 another test wwComfort 2024-01-04 11:43:29 +01:00
Proddy
3f42c4d56b fix typo 2024-01-03 23:57:00 +01:00
Proddy
55ce551868 added hostname to network status 2024-01-03 23:56:52 +01:00
Proddy
d16e77bba3 updated example 2024-01-03 23:56:31 +01:00
Proddy
c9efd095e7 show network hostname in webui 2024-01-03 23:56:23 +01:00
Proddy
8384344c5a no need to pretty json output 2024-01-03 23:03:41 +01:00
Proddy
cb5f707b2d use add_ha_sections_to_doc(), add via_device, add check for domoticz 2024-01-03 23:00:07 +01:00
Proddy
e525552e10 wifi console help text 2024-01-03 22:58:21 +01:00
Proddy
78bd0a1b76 espressif 6.5.0 2024-01-03 22:57:59 +01:00
Proddy
1316fe9509 3.6.5-dev.8 2024-01-03 22:57:13 +01:00
Proddy
c45fd23227 package update 2024-01-03 22:56:26 +01:00
Proddy
d6856e8a23 optimized chunking 2024-01-03 22:56:16 +01:00
Proddy
5fcad37fb9 ok to show devices if no ems connection 2024-01-03 22:56:03 +01:00
Proddy
ab58a3dfd3 add spacing 2024-01-03 22:55:44 +01:00
MichaelDvP
a81695e973 Merge branch 'dev2' of https://github.com/emsesp/EMS-ESP32 into dev2 2024-01-03 08:04:42 +01:00
MichaelDvP
8d1a36c669 wwcomfort on ems+ 2024-01-03 08:04:20 +01:00
Proddy
0fce450418 Merge pull request #1530 from proddy/dev
minor changes
2023-12-28 14:21:35 +01:00
Proddy
3a92d69c77 minor changes from https branch 2023-12-28 14:20:06 +01:00
Proddy
fa1a372468 https://github.com/emsesp/EMS-ESP32/pull/1517 2023-12-28 14:19:54 +01:00
Proddy
62b341a614 Merge pull request #1517 from arturzx/dev
fix: Fixed dev section in analog sensor HA discovery
2023-12-28 13:56:34 +01:00
Proddy
0058632d71 Merge pull request #1523 from pswid/dev
Polish translation update
2023-12-28 13:53:45 +01:00
Proddy
339408e12a Merge pull request #1529 from proddy/dev
fix HA modes from dev2 for #1525
2023-12-28 13:41:38 +01:00
Proddy
8bffa6b304 bump version 2023-12-28 13:40:45 +01:00
Proddy
7c80364bcf package update 2023-12-28 13:39:28 +01:00
Proddy
ac950e0e92 fix from dev2 for https://github.com/emsesp/EMS-ESP32/pull/1525 2023-12-28 13:39:14 +01:00
Proddy
77700c4873 Merge pull request #1525 from Bingo2023/dev2
Dev2 - corrected mode control for HA (including translations).
2023-12-28 07:48:01 +01:00
Bingo2023
d035a29f24 // corrected mode control for HA (including translations).
modified:   src/mqtt.cpp
2023-12-23 14:14:20 +01:00
Bingo2023
4c51b90663 modified: src/mqtt.cpp 2023-12-22 20:02:12 +01:00
Proddy
8d33a89e39 Merge pull request #1524 from MichaelDvP/dev
add ES79 and fix german translation, fixes #1521, #1522
2023-12-22 16:18:04 +01:00
MichaelDvP
41260a4370 fix nompower warning 2023-12-22 14:46:23 +01:00
MichaelDvP
7d15a8010d update pkg 2023-12-22 12:32:10 +01:00
MichaelDvP
606a7e6eec add Sieger ES79, #1521 2023-12-22 12:27:39 +01:00
MichaelDvP
83e400fbe9 fix german translation from #1522 2023-12-22 12:25:07 +01:00
pswid
d3ae73e6b2 Restored Polish characters in the font file
Polish characters disappeared from the re.woff2 file sometime between April and August this year. I assume it was unintentional.
2023-12-22 11:15:17 +01:00
pswid
b4b2531e33 Polish translation update 2023-12-22 10:45:37 +01:00
Proddy
225e482814 Merge pull request #1519 from MichaelDvP/dev_1
compile with tasmota arduino 2.14 and 3.0
2023-12-20 22:06:39 +01:00
MichaelDvP
ddd1f5de5b Merge branch 'dev' into dev2 2023-12-19 18:35:10 +01:00
MichaelDvP
446601c6e0 update mqttClient, works with tasmota-arduino 2.14 / 3.0 without WiFiSecure 2023-12-19 13:47:10 +01:00
MichaelDvP
9f3e2dbcd3 merge PR#1497 from pswid, add partiton, resort system status 2023-12-19 13:44:15 +01:00
Artur Zabroński
3163a142a9 fix: Fixed dev section in analog sensor HA discovery 2023-12-19 08:51:56 +01:00
MichaelDvP
dcb0d5087f Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2023-12-18 18:18:10 +01:00
MichaelDvP
e2544947f7 sk-translation 2023-12-18 10:47:58 +01:00
MichaelDvP
19743da558 translation cold water 2023-12-18 09:48:25 +01:00
Proddy
08c5fd8f64 Merge pull request #1514 from MichaelDvP/dev
ethernet with arduino_3.0, sort translations alphabetical
2023-12-18 09:45:38 +01:00
Proddy
a28cdaa930 Merge pull request #1515 from proddy/dev
add SK entity translations (from Michael)
2023-12-18 09:44:25 +01:00
proddy
3f31962a9a add SK translations (from Michael) 2023-12-18 09:43:38 +01:00
proddy
651b1b1c50 package update 2023-12-18 09:43:21 +01:00
MichaelDvP
ca2ca972b9 add sk entity translations 2023-12-17 13:58:04 +01:00
MichaelDvP
215c4e49ca sort translation alphabetical 2023-12-17 12:48:36 +01:00
MichaelDvP
d1dbce84c7 ethernet working with arduino_3.0 2023-12-17 12:39:36 +01:00
MichaelDvP
fb4a0b0ae8 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2023-12-17 12:35:46 +01:00
MichaelDvP
854a39fe6b ethernet working with arduino_3.0 2023-12-17 12:35:07 +01:00
Proddy
a01ee4a48d Merge pull request #1513 from proddy/dev
fix SK
2023-12-17 09:47:19 +01:00
proddy
8ed867314d fix SK 2023-12-17 09:46:53 +01:00
Proddy
abf4eeb39a Merge pull request #1508 from misa1515/patch-8
Update index.ts
2023-12-17 09:33:00 +01:00
Proddy
40a85634ef Merge pull request #1509 from misa1515/patch-9
Update LayoutAuthMenu.tsx
2023-12-17 09:32:31 +01:00
Proddy
a5c41a1cd8 Merge pull request #1510 from misa1515/patch-10
Update SignIn.tsx
2023-12-17 09:32:07 +01:00
Proddy
c6668e1d6a Merge pull request #1511 from misa1515/patch-11
Update system.cpp
2023-12-17 09:31:38 +01:00
misa1515
5384abc780 Update system.cpp 2023-12-16 22:54:14 +01:00
misa1515
444d5fb7c3 Update SignIn.tsx 2023-12-16 22:50:51 +01:00
misa1515
3b76a020c7 Update LayoutAuthMenu.tsx 2023-12-16 22:49:23 +01:00
misa1515
804187afb9 Update index.ts 2023-12-16 22:44:54 +01:00
Proddy
ef2ed0f4d1 Merge pull request #1507 from proddy/dev
add SK
2023-12-16 22:40:08 +01:00
proddy
68084fc9b5 add SK 2023-12-16 22:36:25 +01:00
MichaelDvP
a684a46404 Merge branch 'dev' into dev2 2023-12-14 07:59:01 +01:00
MichaelDvP
1556bf02ba fix setting dhw comfort #1495 2023-12-14 07:57:48 +01:00
Proddy
b29c36d01d Merge pull request #1500 from MichaelDvP/dev2
Testbild keep up to date with dev
2023-12-13 11:52:18 +01:00
MichaelDvP
a0e1894262 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2023-12-13 07:24:06 +01:00
MichaelDvP
29a3e79804 Merge branch 'dev' into dev2 2023-12-12 14:41:31 +01:00
MichaelDvP
72f4d00cb3 Merge branch 'dev' into dev2 2023-12-09 15:41:03 +01:00
MichaelDvP
8453422c9c Merge branch 'dev' into dev2 2023-12-08 19:15:41 +01:00
Proddy
d81b833951 Merge pull request #1490 from MichaelDvP/dev2
fix RC300 mode, #1488
2023-12-08 11:53:36 +01:00
MichaelDvP
510602e117 fix RC300 mode, #1488 2023-12-08 11:34:20 +01:00
Proddy
4008883627 Merge pull request #1482 from MichaelDvP/dev2
Testbuild
2023-12-04 20:28:49 +01:00
MichaelDvP
4081a55207 Merge branch 'dev' into dev2 2023-12-04 17:39:29 +01:00
MichaelDvP
1845d5060a add hpMaxPower 2023-12-04 17:11:40 +01:00
MichaelDvP
ad577eaa2a Merge branch 'dev' into dev2 2023-12-03 18:24:06 +01:00
MichaelDvP
0d4607a922 send "step" as string 2023-11-29 11:55:27 +01:00
MichaelDvP
067100d375 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2023-11-29 07:12:41 +01:00
MichaelDvP
9118cd7c5b init for second exhaustTemp value (test.0c) 2023-11-28 17:54:47 +01:00
MichaelDvP
8b0cf599f4 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2023-11-27 07:34:24 +01:00
MichaelDvP
7d6bb6b9c8 add meter heating 0x4AF, offset 24 2023-11-26 11:06:28 +01:00
MichaelDvP
2a6fedc6b3 hetpump energy meters, sync with HP id 0x08 2023-11-26 09:11:46 +01:00
MichaelDvP
e3a7e9fe33 Merge branch 'dev' into dev2 2023-11-26 09:03:58 +01:00
MichaelDvP
548fdd823b version 2023-11-25 15:50:42 +01:00
MichaelDvP
2b486ffa36 revert package update 2023-11-24 13:23:33 +01:00
MichaelDvP
c3f9d9ddd6 Merge branch 'dev2' of https://github.com/MichaelDvP/EMS-ESP32 into dev2 2023-11-24 10:44:44 +01:00
MichaelDvP
740f3b4ef7 Merge branch 'dev' into dev2 2023-11-24 10:28:38 +01:00
MichaelDvP
932a496f47 revert to react-router-dom 6.19.0 to fix tab-routing-issue 2023-11-24 10:15:34 +01:00
MichaelDvP
60beeddb66 HIU heating/tapwater-active, always use EMSdevice:: for flags 2023-11-23 17:30:38 +01:00
MichaelDvP
c61c34f10e HIU heating/tapwater-active, always use EMSdevice:: for flags 2023-11-23 17:24:40 +01:00
MichaelDvP
96a04da1ff add settings for #1389 2023-11-23 15:27:38 +01:00
MichaelDvP
bd8472b34e fix settings for EMS boilers 2023-11-23 15:26:00 +01:00
MichaelDvP
09228e4637 update MqttClient 2023-11-23 09:23:04 +01:00
MichaelDvP
40a79c51ce add EMS_DEVICE_FLAG_BC400, sort wwmodes #1452 2023-11-22 19:45:06 +01:00
MichaelDvP
2edfe0f42c add hpPumpMode #1449 2023-11-22 07:37:40 +01:00
MichaelDvP
a2eb8dfe83 update packages 2023-11-22 07:37:32 +01:00
MichaelDvP
4c60545057 Merge branch 'dev' into dev2 2023-11-22 07:10:31 +01:00
MichaelDvP
d06b3285bd Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2023-11-21 21:58:59 +01:00
MichaelDvP
4dcfe8e0f6 get mode for seltemp, fix #1450 2023-11-21 21:58:10 +01:00
MichaelDvP
42e679d5ba Merge branch 'dev2' of https://github.com/emsesp/EMS-ESP32 into dev 2023-11-21 11:32:31 +01:00
MichaelDvP
4db1c7dfca add boostmode/time #1446 2023-11-21 11:25:25 +01:00
MichaelDvP
64fb84dd54 Merge branch 'dev' into dev2 2023-11-21 11:24:04 +01:00
MichaelDvP
a17a9b71a2 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2023-11-18 13:49:53 +01:00
MichaelDvP
0a10e78bfd Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2023-11-16 09:14:25 +01:00
MichaelDvP
50777bd681 temporary fix for values/info command 2023-11-15 19:11:59 +01:00
Proddy
2f5558c311 Merge pull request #1429 from proddy/dev2
in sync with dev
2023-11-15 18:23:19 +01:00
Proddy
21c3fe5d8e in sync with dev 2023-11-15 18:22:12 +01:00
MichaelDvP
acb453bd4b fix water: retValve and circ time control 2023-11-15 18:13:43 +01:00
MichaelDvP
9c6b9a5359 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2023-11-15 11:27:19 +01:00
MichaelDvP
0d07a9e50c add some water entities 2023-11-15 11:26:45 +01:00
MichaelDvP
f9e1940c7b fix crash on entering wrong day for switchtime 2023-11-14 18:11:32 +01:00
MichaelDvP
72c0625823 Merge branch 'dev2x' into dev2 2023-11-14 10:57:43 +01:00
MichaelDvP
6926f6fd0b Merge branch 'dev2' of https://github.com/emsesp/EMS-ESP32 into dev2 2023-11-14 10:07:19 +01:00
MichaelDvP
e30c476e5c Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2023-11-13 19:52:50 +01:00
MichaelDvP
509122bf4b fix RC300 wwdisinfectTime 2023-11-13 19:29:03 +01:00
MichaelDvP
84fab951ba fix RC300 summertemp, mode 2023-11-13 18:01:14 +01:00
MichaelDvP
f34be2a884 test for fixing #1420 2023-11-13 13:59:30 +01:00
MichaelDvP
bed1650350 Merge branch 'dev2x' into dev2 2023-11-13 13:57:05 +01:00
MichaelDvP
1f8a477939 RC300/BC400 mode setting 2023-11-13 13:54:13 +01:00
MichaelDvP
6699d9ad80 Merge branch 'dev2x' into dev2 2023-11-12 14:32:03 +01:00
MichaelDvP
253eb72dbf Merge branch 'dev2x' into dev2 2023-11-12 13:55:14 +01:00
MichaelDvP
fe772f85bf Merge branch 'dev2x' into dev2 2023-11-12 11:42:18 +01:00
MichaelDvP
0e55caf721 Merge branch 'dev2x' into dev2 2023-11-11 14:36:04 +01:00
MichaelDvP
434ef2b333 Merge branch 'dev2x' into dev2 2023-11-11 14:33:19 +01:00
MichaelDvP
e0ab208c52 fix retTemp, #1334 2023-11-11 14:10:49 +01:00
MichaelDvP
5b1f3d266e Merge branch 'dev2' of https://github.com/emsesp/EMS-ESP32 into dev2 2023-11-11 13:52:22 +01:00
MichaelDvP
4c83f5fe60 sort water entities 2023-11-11 13:47:32 +01:00
MichaelDvP
2f658a9a14 add boiler wwSelTempEcoplus 2023-11-08 14:18:28 +01:00
MichaelDvP
c3f487eced update packages 2023-11-07 12:49:46 +01:00
MichaelDvP
a8a12dd1f8 check second servicecode-char for nonascii 0xF0. 2023-11-07 11:44:37 +01:00
MichaelDvP
7f7e3c47ec Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev2 2023-11-07 07:20:08 +01:00
MichaelDvP
eafeb5c5d2 Merge branch 'dev2' of https://github.com/proddy/EMS-ESP32 into dev2 2023-11-07 07:15:43 +01:00
MichaelDvP
caca8bf802 fix 4-way-valve enum 2023-11-07 07:10:49 +01:00
MichaelDvP
338091578b wwEcoPlus, rename some water entities 2023-11-07 07:06:05 +01:00
MichaelDvP
188bfa4525 add hp 4-way valve 2023-11-05 15:57:02 +01:00
MichaelDvP
037a9848bc version 3.6.3-dev.6c 2023-11-05 14:24:39 +01:00
MichaelDvP
b6543169de boiler add input states, remove redundant activity states 2023-11-05 14:23:40 +01:00
MichaelDvP
14b3b058fe remove unused water values temp_2, temp_6 2023-11-05 14:22:37 +01:00
MichaelDvP
fa4763309d merge pl translations 2023-11-05 14:21:56 +01:00
MichaelDvP
adcc59642c cleanup publishing 2023-11-05 14:20:13 +01:00
MichaelDvP
d18fd4948c update packages 2023-11-05 14:19:46 +01:00
MichaelDvP
4e4258f9dc enlarge tx queue to 100 2023-11-04 18:56:49 +01:00
MichaelDvP
ab6cf78822 warning in log on tx-queue overflow 2023-11-04 18:17:33 +01:00
MichaelDvP
d105c18bf7 fix typos, double entities, publish time water 2023-11-04 17:00:33 +01:00
MichaelDvP
6bbf4e4778 Merge branch 'dev2' of https://github.com/emsesp/EMS-ESP32 into dev2 2023-11-04 15:25:50 +01:00
MichaelDvP
3101f5e6ae move dhw entities from mixer/solar to new water device, add pool device 2023-11-04 15:24:43 +01:00
493 changed files with 22697 additions and 22409 deletions

View File

@@ -13,12 +13,12 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: '3.11' python-version: '3.11'
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '20'
- name: Get EMS-ESP source code and version - name: Get EMS-ESP source code and version
id: build_info id: build_info
@@ -48,6 +48,10 @@ jobs:
run: | run: |
platformio run -e ci_s3 platformio run -e ci_s3
- name: Build E32V2 firmware
run: |
platformio run -e ci_16M
- name: Create a GH Release - name: Create a GH Release
id: 'automatic_releases' id: 'automatic_releases'
uses: 'marvinpinto/action-automatic-releases@latest' uses: 'marvinpinto/action-automatic-releases@latest'

View File

@@ -12,12 +12,12 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: '3.11' python-version: '3.11'
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '20'
- name: Install PlatformIO - name: Install PlatformIO
run: | run: |

View File

@@ -13,12 +13,12 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: '3.11' python-version: '3.11'
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '20'
- name: Get EMS-ESP source code and version - name: Get EMS-ESP source code and version
id: build_info id: build_info

12
.gitignore vendored
View File

@@ -12,11 +12,11 @@ cppcheck.out.xml
# platformio # platformio
.pio .pio
pio_local.ini pio_local.ini
*_old
# OS specific # OS specific
.DS_Store .DS_Store
*Thumbs.db *Thumbs.db
emsesp
# web specfic # web specfic
build/ build/
@@ -37,13 +37,14 @@ stats.html
!.yarn/sdks !.yarn/sdks
!.yarn/versions !.yarn/versions
yarn.lock yarn.lock
interface/analyse.html analyse.html
interface/vite.config.ts.timestamp*
# scripts # scripts
test.sh test.sh
scripts/run.sh scripts/run.sh
scripts/__pycache__ scripts/__pycache__
/scripts/stackdmp.txt scripts/stackdmp.txt
# i18n generated files # i18n generated files
interface/src/i18n/i18n-react.tsx interface/src/i18n/i18n-react.tsx
@@ -57,7 +58,6 @@ interface/src/i18n/i18n-util.async.ts
sonar/ sonar/
bw-output/ bw-output/
# entity dump results # testing
# dump_entities.csv emsesp
# dump_entities.xls*

View File

@@ -2,8 +2,6 @@
// See http://go.microsoft.com/fwlink/?LinkId=827846 // See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format // for the documentation about the extensions.json format
"recommendations": [ "recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"platformio.platformio-ide" "platformio.platformio-ide"
], ],
"unwantedRecommendations": [ "unwantedRecommendations": [

View File

@@ -84,5 +84,8 @@
"**/*.map", "**/*.map",
"**/ArduinoJson/**" "**/ArduinoJson/**"
], ],
"cSpell.enableFiletypes": ["!cpp"] "cSpell.enableFiletypes": [
"!cpp",
"!typescript"
]
} }

18
.vscode/tasks.json vendored
View File

@@ -1,18 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "build standalone emsesp",
"command": "make",
"args": [],
"problemMatcher": ["$gcc"],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@@ -5,6 +5,56 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.6.5] March 23 2024
## **IMPORTANT! BREAKING CHANGES**
- The Wifi Tx Power setting in Network Settings will be reset to Auto
## Added
- thermostat boost mode and boost time [#1446](https://github.com/emsesp/EMS-ESP32/issues/1446)
- heatpump energy meters [#1463](https://github.com/emsesp/EMS-ESP32/issues/1463)
- heatpump max power [#1475](https://github.com/emsesp/EMS-ESP32/issues/1475)
- checkbox for MQTT-TLS enable [#1474](https://github.com/emsesp/EMS-ESP32/issues/1474)
- added SK (Slovak) language. Thanks @misa1515
- CPU info [#1497](https://github.com/emsesp/EMS-ESP32/pull/1497)
- Show network hostname in Web UI under Network Status
- Improved HA Discovery so each section (EMS device, Scheduler, Analog, Temperature, Custom, Shower) have their own section
- boiler Bosch C1200W, id 12, [#1536](https://github.com/emsesp/EMS-ESP32/issues/1536)
- mixer MM100 telegram 0x2CC [#1554](https://github.com/emsesp/EMS-ESP32/issues/1554)
- boiler hpSetDiffPressure [#1563](https://github.com/emsesp/EMS-ESP32/issues/1563)
- custom variables [#1423](https://github.com/emsesp/EMS-ESP32/issues/1423)
- weather compensation [#1642](https://github.com/emsesp/EMS-ESP32/issues/1642)
- env and partitions for DevKitC-1-N32R8 [#1635](https://github.com/emsesp/EMS-ESP32/discussions/1635)
- command `restart partitionname` and button long press to start with other partition [#1657](https://github.com/emsesp/EMS-ESP32/issues/1657)
- command `set service <mqtt|ota|ntp|ap> <enable|disable>` [#1663](https://github.com/emsesp/EMS-ESP32/issues/1663)
## Fixed
- exhaust temperature for some boilers
- add back boil2hyst [#1477](https://github.com/emsesp/EMS-ESP32/issues/1477)
- subscribed MQTT topics not detecting changes by EMS-ESP [#1494](https://github.com/emsesp/EMS-ESP32/issues/1494)
- changed HA name and grouping to be consistent [#1528](https://github.com/emsesp/EMS-ESP32/issues/1528)
- MQTT autodiscovery in Domoticz not working [#1360](https://github.com/emsesp/EMS-ESP32/issues/1528)
- dhw comfort for new ems+, [#1495](https://github.com/emsesp/EMS-ESP32/issues/1495)
- added writeable icon to Web's Custom Entity page for each entity shown in the table
- Wifi Tx Power not adjusted [#1614](https://github.com/emsesp/EMS-ESP32/issues/1614)
- MQTT discovery of custom entity doesn't consider type of data [#1587](https://github.com/emsesp/EMS-ESP32/issues/1587)
- WiFi TxPower wasn't correctly used. Added an 'Auto' setting, which is the default.
- dns w/wo IPv6 [#1644](https://github.com/emsesp/EMS-ESP32/issues/1644)
## Changed
- HA don't set entity_category to Diagnostic/Configuration for EMS entities [#1459](https://github.com/emsesp/EMS-ESP32/discussions/1459)
- upgraded ArduinoJson to 7.0.0 #1538 and then 7.0.2
- small changes to the API for analog and temperature sensors
- Length of mqtt Broker adress [#1619](https://github.com/emsesp/EMS-ESP32/issues/1619)
- C++ optimizations - see <https://github.com/emsesp/EMS-ESP32/pull/1615>
- Send MQTT heartbeat immediately after connection [#1628](https://github.com/emsesp/EMS-ESP32/issues/1628)
- 16MB partitions with second nvs, larger FS, Coredump, optional factory partition
- stop fetching empty telegrams after 5 min
## [3.6.4] November 24 2023 ## [3.6.4] November 24 2023
## **IMPORTANT! BREAKING CHANGES** ## **IMPORTANT! BREAKING CHANGES**
@@ -236,7 +286,7 @@ There are breaking changes between 3.5.x and earlier versions of 3.6.0. Please r
- fix Table resizing in WebUI [#519](https://github.com/emsesp/EMS-ESP32/issues/519) - fix Table resizing in WebUI [#519](https://github.com/emsesp/EMS-ESP32/issues/519)
- allow larger customization files [#570](https://github.com/emsesp/EMS-ESP32/issues/570) - allow larger customization files [#570](https://github.com/emsesp/EMS-ESP32/issues/570)
- losing entitiy wwcomfort [#581](https://github.com/emsesp/EMS-ESP32/issues/581) - losing entity wwcomfort [#581](https://github.com/emsesp/EMS-ESP32/issues/581)
## Changed ## Changed

View File

@@ -1,22 +1,23 @@
# Changelog # Changelog
## [3.6.5] ## [3.7.0]
## **IMPORTANT! BREAKING CHANGES** ## **IMPORTANT! BREAKING CHANGES**
- new device WATER shows dhw entities from MM100 and SM100 in dhw setting
## Added ## Added
- thermostat boost mode and boost time [#1446](https://github.com/emsesp/EMS-ESP32/issues/1446) - some more entities for dhw with SM100 module
- heatpump energy meters [#1463](https://github.com/emsesp/EMS-ESP32/issues/1463) - thermostat second dhw circuit [#1634](https://github.com/emsesp/EMS-ESP32/issues/1634)
- heatpump max power [#1475](https://github.com/emsesp/EMS-ESP32/issues/1475) - remote thermostat emulation for RC100H, RC200 and FB10 [#1287](https://github.com/emsesp/EMS-ESP32/discussions/1287), [#1602](https://github.com/emsesp/EMS-ESP32/discussions/1602), [#1551](https://github.com/emsesp/EMS-ESP32/discussions/1551)
- checkbox for MQTT-TLS enable [#1474](https://github.com/emsesp/EMS-ESP32/issues/1474) - heatpump dhw stop temperatures [#1624](https://github.com/emsesp/EMS-ESP32/issues/1624)
## Fixed ## Fixed
- exhaust temperature for some boilers
- add back boil2hyst [#1477](https://github.com/emsesp/EMS-ESP32/issues/1477)
- subscribed MQTT topics not detecting changes by EMS-ESP [#1494](https://github.com/emsesp/EMS-ESP32/issues/1494)
## Changed ## Changed
- HA don't set entity_category to Diagnostic/Configuration for EMS entities [#1459](https://github.com/emsesp/EMS-ESP32/discussions/1459) - use flag for BC400 compatible thermostats, manage different mode settings
- use factory partition for 16M flash
- store digital out states to nvs
- Refresh UI - moving settings to one location [#1665](https://github.com/emsesp/EMS-ESP32/issues/1665)

View File

@@ -42,7 +42,7 @@ DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DAR
DEFINES += -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -D__linux__ -DEMC_RX_BUFFER_SIZE=1500 DEFINES += -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -D__linux__ -DEMC_RX_BUFFER_SIZE=1500
DEFINES += $(ARGS) DEFINES += $(ARGS)
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.4-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.0-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# Sources & Files # Sources & Files

View File

@@ -1,6 +1,9 @@
# Name, Type, SubType, Offset, Size, Flags # Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000, nvs, data, nvs, 0x9000, 0x005000,
otadata, data, ota, , 0x2000, otadata, data, ota, , 0x002000,
app0, app, ota_0, , 0x7F0000, boot, app, factory, , 0x280000,
app1, app, ota_1, , 0x7F0000, app0, app, ota_0, , 0x590000,
spiffs, data, spiffs, , 64K, app1, app, ota_1, , 0x590000,
nvs1, data, nvs, , 0x040000,
spiffs, data, spiffs, , 0x200000,
coredump, data, coredump,, 0x010000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000 0x005000
3 otadata data ota 0x2000 0x002000
4 app0 boot app ota_0 factory 0x7F0000 0x280000
5 app1 app0 app ota_1 ota_0 0x7F0000 0x590000
6 spiffs app1 data app spiffs ota_1 64K 0x590000
7 nvs1 data nvs 0x040000
8 spiffs data spiffs 0x200000
9 coredump data coredump 0x010000

8
esp32_partition_32M.csv Normal file
View File

@@ -0,0 +1,8 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x005000,
otadata, data, ota, , 0x002000,
app0, app, ota_0, , 0xDD0000,
app1, app, ota_1, , 0xDD0000,
nvs1, data, nvs, , 0x040000,
spiffs, data, spiffs, , 0x400000,
coredump, data, coredump,, 0x010000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x005000
3 otadata data ota 0x002000
4 app0 app ota_0 0xDD0000
5 app1 app ota_1 0xDD0000
6 nvs1 data nvs 0x040000
7 spiffs data spiffs 0x400000
8 coredump data coredump 0x010000

View File

@@ -36,7 +36,6 @@ build_flags =
-D FACTORY_MQTT_PORT=1883 -D FACTORY_MQTT_PORT=1883
-D FACTORY_MQTT_USERNAME=\"\" -D FACTORY_MQTT_USERNAME=\"\"
-D FACTORY_MQTT_PASSWORD=\"\" -D FACTORY_MQTT_PASSWORD=\"\"
-D FACTORY_MQTT_CLIENT_ID=\"ems-esp\"
-D FACTORY_MQTT_KEEP_ALIVE=60 -D FACTORY_MQTT_KEEP_ALIVE=60
-D FACTORY_MQTT_CLEAN_SESSION=false -D FACTORY_MQTT_CLEAN_SESSION=false
-D FACTORY_MQTT_MAX_TOPIC_LENGTH=128 -D FACTORY_MQTT_MAX_TOPIC_LENGTH=128

View File

@@ -5,8 +5,8 @@
}, },
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"airbnb/hooks", // "airbnb/hooks",
"airbnb-typescript", // "airbnb-typescript",
"plugin:react/recommended", "plugin:react/recommended",
"plugin:react/jsx-runtime", "plugin:react/jsx-runtime",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",

File diff suppressed because one or more lines are too long

View File

@@ -4,4 +4,4 @@ enableGlobalCache: false
nodeLinker: node-modules nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.0.2.cjs yarnPath: .yarn/releases/yarn-4.1.1.cjs

View File

@@ -1,6 +1,6 @@
{ {
"name": "EMS-ESP", "name": "EMS-ESP",
"version": "3.6.5", "version": "3.7",
"description": "build EMS-ESP WebUI", "description": "build EMS-ESP WebUI",
"homepage": "https://emsesp.github.io/docs", "homepage": "https://emsesp.github.io/docs",
"author": "proddy", "author": "proddy",
@@ -13,27 +13,29 @@
"build-hosted": "typesafe-i18n --no-watch && vite build --mode hosted", "build-hosted": "typesafe-i18n --no-watch && vite build --mode hosted",
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"npm:mock-api\" \"vite preview\"", "preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"npm:mock-api\" \"vite preview\"",
"mock-api": "bun --watch ../mock-api/server.ts", "mock-api": "bun --watch ../mock-api/server.ts",
"old_mock-api": "bun --watch ../mock-api/server.js",
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"npm:mock-api\" \"vite\"", "standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"npm:mock-api\" \"vite\"",
"old_standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"npm:old_mock-api\" \"vite\"",
"typesafe-i18n": "typesafe-i18n --no-watch", "typesafe-i18n": "typesafe-i18n --no-watch",
"webUI": "node progmem-generator.js", "webUI": "node progmem-generator.js",
"format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'", "format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'",
"lint": "eslint . --cache --fix" "lint": "eslint . --cache --fix"
}, },
"dependencies": { "dependencies": {
"@alova/adapter-xhr": "^1.0.1", "@alova/adapter-xhr": "^1.0.3",
"@babel/core": "^7.23.6", "@babel/core": "^7.24.3",
"@emotion/react": "^11.11.1", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.0", "@mui/icons-material": "^5.15.14",
"@mui/material": "^5.15.0", "@mui/material": "^5.15.14",
"@table-library/react-table-library": "4.1.7", "@table-library/react-table-library": "4.1.7",
"@types/imagemin": "^8.0.5", "@types/imagemin": "^8.0.5",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.10.4", "@types/node": "^20.11.30",
"@types/react": "^18.2.43", "@types/react": "^18.2.72",
"@types/react-dom": "^18.2.17", "@types/react-dom": "^18.2.22",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"alova": "^2.16.0", "alova": "^2.18.0",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"history": "^5.3.0", "history": "^5.3.0",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
@@ -42,37 +44,35 @@
"react": "latest", "react": "latest",
"react-dom": "latest", "react-dom": "latest",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-icons": "^4.12.0", "react-icons": "^5.0.1",
"react-router-dom": "^6.20.1", "react-router-dom": "^6.22.3",
"react-toastify": "^9.1.3", "react-toastify": "^10.0.5",
"sockette": "^2.0.6", "sockette": "^2.0.6",
"typesafe-i18n": "^5.26.2", "typesafe-i18n": "^5.26.2",
"typescript": "^5.3.3" "typescript": "^5.4.3"
}, },
"devDependencies": { "devDependencies": {
"@preact/compat": "^17.1.2", "@preact/compat": "^17.1.2",
"@preact/preset-vite": "^2.7.0", "@preact/preset-vite": "^2.8.2",
"@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^6.14.0", "@typescript-eslint/parser": "^7.4.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"eslint": "^8.55.0", "eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1", "eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-autofix": "^1.1.0", "eslint-plugin-autofix": "^1.1.0",
"eslint-plugin-import": "^2.29.0", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prettier": "alpha", "eslint-plugin-prettier": "alpha",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"preact": "^10.19.3", "preact": "^10.20.1",
"prettier": "^3.1.1", "prettier": "^3.2.5",
"rollup-plugin-visualizer": "^5.11.0", "rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.26.0", "terser": "^5.29.2",
"vite": "^5.0.8", "vite": "^5.2.6",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^4.2.2" "vite-tsconfig-paths": "^4.3.2"
}, },
"packageManager": "yarn@4.0.2" "packageManager": "yarn@4.1.1"
} }

View File

@@ -2,6 +2,7 @@ import { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream }
import { resolve, relative, sep } from 'path'; import { resolve, relative, sep } from 'path';
import zlib from 'zlib'; import zlib from 'zlib';
import mime from 'mime-types'; import mime from 'mime-types';
import crypto from 'crypto';
const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n'; const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n';
const INDENT = ' '; const INDENT = ' ';
@@ -11,13 +12,17 @@ const bytesPerLine = 20;
var totalSize = 0; var totalSize = 0;
const generateWWWClass = () => const generateWWWClass = () =>
`typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler; `typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
// Total size is ${totalSize} bytes
class WWWData { class WWWData {
${indent}public: ${indent}public:
${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) { ${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
${fileInfo ${fileInfo
.map((file) => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size});`) .map(
(file) =>
`${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size}, "${file.hash}");`
)
.join('\n')} .join('\n')}
${indent.repeat(2)}} ${indent.repeat(2)}}
}; };
@@ -49,6 +54,12 @@ const writeFile = (relativeFilePath, buffer) => {
writeStream.write('const uint8_t ' + variable + '[] = {'); writeStream.write('const uint8_t ' + variable + '[] = {');
// const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 }); // const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 });
const zipBuffer = zlib.gzipSync(buffer, { level: 9 }); const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
// create sha
const hashSum = crypto.createHash('sha256');
hashSum.update(zipBuffer);
const hash = hashSum.digest('hex');
zipBuffer.forEach((b) => { zipBuffer.forEach((b) => {
if (!(size % bytesPerLine)) { if (!(size % bytesPerLine)) {
writeStream.write('\n'); writeStream.write('\n');
@@ -57,15 +68,19 @@ const writeFile = (relativeFilePath, buffer) => {
writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).slice(-2) + ','); writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).slice(-2) + ',');
size++; size++;
}); });
if (size % bytesPerLine) { if (size % bytesPerLine) {
writeStream.write('\n'); writeStream.write('\n');
} }
writeStream.write('};\n\n'); writeStream.write('};\n\n');
fileInfo.push({ fileInfo.push({
uri: '/' + relativeFilePath.replace(sep, '/'), uri: '/' + relativeFilePath.replace(sep, '/'),
mimeType, mimeType,
variable, variable,
size size,
hash
}); });
// console.log(relativeFilePath + ' (size ' + size + ' bytes)'); // console.log(relativeFilePath + ' (size ' + size + ' bytes)');

Binary file not shown.

View File

@@ -4,7 +4,6 @@ import { ToastContainer, Slide } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.min.css'; import 'react-toastify/dist/ReactToastify.min.css';
import { localStorageDetector } from 'typesafe-i18n/detectors'; import { localStorageDetector } from 'typesafe-i18n/detectors';
import { FeaturesLoader } from './contexts/features';
import type { FC } from 'react'; import type { FC } from 'react';
import AppRouting from 'AppRouting'; import AppRouting from 'AppRouting';
import CustomTheme from 'CustomTheme'; import CustomTheme from 'CustomTheme';
@@ -27,11 +26,9 @@ const App: FC = () => {
return ( return (
<TypesafeI18n locale={detectedLocale}> <TypesafeI18n locale={detectedLocale}>
<CustomTheme> <CustomTheme>
<FeaturesLoader>
<AppRouting /> <AppRouting />
</FeaturesLoader>
<ToastContainer <ToastContainer
position="bottom-left" position="bottom-right"
autoClose={3000} autoClose={3000}
hideProgressBar={false} hideProgressBar={false}
newestOnTop={false} newestOnTop={false}

View File

@@ -1,64 +1,55 @@
import { useContext, type FC } from 'react';
import { Navigate, Routes, Route } from 'react-router-dom'; import { Navigate, Routes, Route } from 'react-router-dom';
import Dashboard from './project/Dashboard';
import Help from './project/Help'; import Help from './project/Help';
import Settings from './project/Settings'; import { Layout } from 'components';
import type { FC } from 'react'; import { AuthenticatedContext } from 'contexts/authentication';
import Settings from 'framework/Settings';
import { Layout, RequireAdmin } from 'components';
import AccessPoint from 'framework/ap/AccessPoint'; import AccessPoint from 'framework/ap/AccessPoint';
import Mqtt from 'framework/mqtt/Mqtt'; import Mqtt from 'framework/mqtt/Mqtt';
import NetworkConnection from 'framework/network/NetworkConnection'; import Network from 'framework/network/Network';
import NetworkTime from 'framework/ntp/NetworkTime'; import NetworkTime from 'framework/ntp/NetworkTime';
import OTASettings from 'framework/ota/OTASettings';
import Security from 'framework/security/Security'; import Security from 'framework/security/Security';
import ESPSystemStatus from 'framework/system/ESPSystemStatus';
import System from 'framework/system/System'; import System from 'framework/system/System';
import UploadDownload from 'framework/system/UploadDownload';
import ApplicationSettings from 'project/ApplicationSettings';
import CustomEntities from 'project/CustomEntities';
import Customization from 'project/Customization';
import Devices from 'project/Devices';
import Scheduler from 'project/Scheduler';
import Sensors from 'project/Sensors';
const AuthenticatedRouting: FC = () => ( const AuthenticatedRouting: FC = () => {
// const location = useLocation(); const { me } = useContext(AuthenticatedContext);
// const navigate = useNavigate(); return (
// const handleApiResponseError = useCallback(
// (error: AxiosError) => {
// if (error.response && error.response.status === 401) {
// AuthenticationApi.storeLoginRedirect(location);
// navigate('/unauthorized');
// }
// return Promise.reject(error);
// },
// [location, navigate]
// );
// useEffect(() => {
// const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError);
// return () => AXIOS.interceptors.response.eject(axiosHandlerId);
// }, [handleApiResponseError]);
<Layout> <Layout>
<Routes> <Routes>
<Route path="/dashboard/*" element={<Dashboard />} /> <Route path="/devices/*" element={<Devices />} />
<Route <Route path="/sensors/*" element={<Sensors />} />
path="/settings/*"
element={
<RequireAdmin>
<Settings />
</RequireAdmin>
}
/>
<Route path="/help/*" element={<Help />} />
<Route path="/network/*" element={<NetworkConnection />} />
<Route path="/ap/*" element={<AccessPoint />} />
<Route path="/ntp/*" element={<NetworkTime />} />
<Route path="/mqtt/*" element={<Mqtt />} />
<Route
path="/security/*"
element={
<RequireAdmin>
<Security />
</RequireAdmin>
}
/>
<Route path="/system/*" element={<System />} /> <Route path="/system/*" element={<System />} />
<Route path="/help/*" element={<Help />} />
<Route path="/*" element={<Navigate to="/" />} /> <Route path="/*" element={<Navigate to="/" />} />
{me.admin && (
<>
<Route path="/customizations/*" element={<Customization />} />
<Route path="/scheduler/*" element={<Scheduler />} />
<Route path="/customentities/*" element={<CustomEntities />} />
<Route path="/settings/*" element={<Settings />} />
<Route path="/settings/network/*" element={<Network />} />
<Route path="/settings/ems-esp/*" element={<ApplicationSettings />} />
<Route path="/settings/ap/*" element={<AccessPoint />} />
<Route path="/settings/ntp/*" element={<NetworkTime />} />
<Route path="/settings/mqtt/*" element={<Mqtt />} />
<Route path="/settings/ota/*" element={<OTASettings />} />
<Route path="/settings/security/*" element={<Security />} />
<Route path="/settings/espsystemstatus/*" element={<ESPSystemStatus />} />
<Route path="/settings/upload/*" element={<UploadDownload />} />
</>
)}
</Routes> </Routes>
</Layout> </Layout>
); );
};
export default AuthenticatedRouting; export default AuthenticatedRouting;

View File

@@ -3,7 +3,6 @@ import { Box, Paper, Typography, MenuItem, TextField, Button } from '@mui/materi
import { useRequest } from 'alova'; import { useRequest } from 'alova';
import { useContext, useState } from 'react'; import { useContext, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { FeaturesContext } from './contexts/features';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import type { Locales } from 'i18n/i18n-types'; import type { Locales } from 'i18n/i18n-types';
@@ -22,6 +21,7 @@ import ITflag from 'i18n/IT.svg';
import NLflag from 'i18n/NL.svg'; import NLflag from 'i18n/NL.svg';
import NOflag from 'i18n/NO.svg'; import NOflag from 'i18n/NO.svg';
import PLflag from 'i18n/PL.svg'; import PLflag from 'i18n/PL.svg';
import SKflag from 'i18n/SK.svg';
import SVflag from 'i18n/SV.svg'; import SVflag from 'i18n/SV.svg';
import TRflag from 'i18n/TR.svg'; import TRflag from 'i18n/TR.svg';
import { I18nContext } from 'i18n/i18n-react'; import { I18nContext } from 'i18n/i18n-react';
@@ -34,8 +34,6 @@ const SignIn: FC = () => {
const { LL, setLocale, locale } = useContext(I18nContext); const { LL, setLocale, locale } = useContext(I18nContext);
const { features } = useContext(FeaturesContext);
const [signInRequest, setSignInRequest] = useState<SignInRequest>({ const [signInRequest, setSignInRequest] = useState<SignInRequest>({
username: '', username: '',
password: '' password: ''
@@ -111,7 +109,6 @@ const SignIn: FC = () => {
})} })}
> >
<Typography variant="h4">{PROJECT_NAME}</Typography> <Typography variant="h4">{PROJECT_NAME}</Typography>
<Typography variant="subtitle2">{features.version}</Typography>
<TextField name="locale" variant="outlined" value={locale} onChange={onLocaleSelected} size="small" select> <TextField name="locale" variant="outlined" value={locale} onChange={onLocaleSelected} size="small" select>
<MenuItem key="de" value="de"> <MenuItem key="de" value="de">
@@ -142,6 +139,10 @@ const SignIn: FC = () => {
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} /> <img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;PL &nbsp;PL
</MenuItem> </MenuItem>
<MenuItem key="sk" value="sk">
<img src={SKflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;SK
</MenuItem>
<MenuItem key="sv" value="sv"> <MenuItem key="sv" value="sv">
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} /> <img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;SV &nbsp;SV

View File

@@ -32,7 +32,7 @@ export function fetchLoginRedirect(): Partial<Path> {
const signInSearch = getStorage().getItem(SIGN_IN_SEARCH); const signInSearch = getStorage().getItem(SIGN_IN_SEARCH);
clearLoginRedirect(); clearLoginRedirect();
return { return {
pathname: signInPathname || `/dashboard`, pathname: signInPathname || `/devices`,
search: (signInPathname && signInSearch) || undefined search: (signInPathname && signInSearch) || undefined
}; };
} }

View File

@@ -1,5 +0,0 @@
import { alovaInstance } from './endpoints';
import type { Features } from 'types';
export const readFeatures = () => alovaInstance.Get<Features>('/rest/features');

View File

@@ -1,6 +1,7 @@
import { alovaInstance } from './endpoints'; import { alovaInstance } from './endpoints';
import type { MqttSettings, MqttStatus } from 'types'; import type { MqttSettingsType, MqttStatusType } from 'types';
export const readMqttStatus = () => alovaInstance.Get<MqttStatus>('/rest/mqttStatus'); export const readMqttStatus = () => alovaInstance.Get<MqttStatusType>('/rest/mqttStatus');
export const readMqttSettings = () => alovaInstance.Get<MqttSettings>('/rest/mqttSettings'); export const readMqttSettings = () => alovaInstance.Get<MqttSettingsType>('/rest/mqttSettings');
export const updateMqttSettings = (data: MqttSettings) => alovaInstance.Post<MqttSettings>('/rest/mqttSettings', data); export const updateMqttSettings = (data: MqttSettingsType) =>
alovaInstance.Post<MqttSettingsType>('/rest/mqttSettings', data);

View File

@@ -1,7 +1,10 @@
import { alovaInstance, alovaInstanceGH } from './endpoints'; import { alovaInstance, alovaInstanceGH } from './endpoints';
import type { OTASettings, SystemStatus, LogSettings } from 'types'; import type { OTASettings, SystemStatus, LogSettings, ESPSystemStatus } from 'types';
// SystemStatus - also used to ping in Restart monitor for pinging // ESPSystemStatus - also used to ping in Restart monitor for pinging
export const readESPSystemStatus = () => alovaInstance.Get<ESPSystemStatus>('/rest/ESPSystemStatus');
// SystemStatus
export const readSystemStatus = () => alovaInstance.Get<SystemStatus>('/rest/systemStatus'); export const readSystemStatus = () => alovaInstance.Get<SystemStatus>('/rest/systemStatus');
// commands // commands

View File

@@ -4,8 +4,7 @@ import type { FC } from 'react';
import type { RequiredChildrenProps } from 'utils'; import type { RequiredChildrenProps } from 'utils';
interface SectionContentProps extends RequiredChildrenProps { interface SectionContentProps extends RequiredChildrenProps {
title: string; title?: string;
titleGutter?: boolean;
id?: string; id?: string;
} }
@@ -13,7 +12,9 @@ const SectionContent: FC<SectionContentProps> = (props) => {
const { children, title, id } = props; const { children, title, id } = props;
return ( return (
<Paper id={id} sx={{ p: 2, m: 2 }}> <Paper id={id} sx={{ p: 2, m: 2 }}>
{title && (
<Divider sx={{ pb: 2, borderColor: 'primary.main', fontSize: 20, color: 'primary.main' }}>{title}</Divider> <Divider sx={{ pb: 2, borderColor: 'primary.main', fontSize: 20, color: 'primary.main' }}>{title}</Divider>
)}
{children} {children}
</Paper> </Paper>
); );

View File

@@ -1,6 +1,5 @@
import MenuIcon from '@mui/icons-material/Menu'; import MenuIcon from '@mui/icons-material/Menu';
import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material'; import { AppBar, IconButton, Toolbar, Typography } from '@mui/material';
import LayoutAuthMenu from './LayoutAuthMenu';
import type { FC } from 'react'; import type { FC } from 'react';
export const DRAWER_WIDTH = 210; export const DRAWER_WIDTH = 210;
@@ -27,8 +26,6 @@ const LayoutAppBar: FC<LayoutAppBarProps> = ({ title, onToggleDrawer }) => (
<Typography variant="h6" noWrap component="div"> <Typography variant="h6" noWrap component="div">
{title} {title}
</Typography> </Typography>
<Box flexGrow={1} />
<LayoutAuthMenu />
</Toolbar> </Toolbar>
</AppBar> </AppBar>
); );

View File

@@ -1,161 +0,0 @@
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import PersonIcon from '@mui/icons-material/Person';
import {
Box,
Button,
Divider,
IconButton,
Popover,
Typography,
Avatar,
styled,
MenuItem,
TextField
} from '@mui/material';
import { useState, useContext } from 'react';
import type { TypographyProps } from '@mui/material';
import type { Locales } from 'i18n/i18n-types';
import type { FC, ChangeEventHandler } from 'react';
import { AuthenticatedContext } from 'contexts/authentication';
import DEflag from 'i18n/DE.svg';
import FRflag from 'i18n/FR.svg';
import GBflag from 'i18n/GB.svg';
import ITflag from 'i18n/IT.svg';
import NLflag from 'i18n/NL.svg';
import NOflag from 'i18n/NO.svg';
import PLflag from 'i18n/PL.svg';
import SVflag from 'i18n/SV.svg';
import TRflag from 'i18n/TR.svg';
import { I18nContext } from 'i18n/i18n-react';
import { loadLocaleAsync } from 'i18n/i18n-util.async';
const ItemTypography = styled(Typography)<TypographyProps>({
maxWidth: '250px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
});
const LayoutAuthMenu: FC = () => {
const { me, signOut } = useContext(AuthenticatedContext);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const { locale, LL, setLocale } = useContext(I18nContext);
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({ target }) => {
const loc = target.value as Locales;
localStorage.setItem('lang', loc);
await loadLocaleAsync(loc);
setLocale(loc);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = anchorEl ? 'app-menu-popover' : undefined;
return (
<>
<TextField
name="locale"
InputProps={{ style: { fontSize: 10 } }}
variant="outlined"
value={locale}
onChange={onLocaleSelected}
size="small"
select
>
<MenuItem key="de" value="de">
<img src={DEflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;DE
</MenuItem>
<MenuItem key="en" value="en">
<img src={GBflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;EN
</MenuItem>
<MenuItem key="fr" value="fr">
<img src={FRflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;FR
</MenuItem>
<MenuItem key="it" value="it">
<img src={ITflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;IT
</MenuItem>
<MenuItem key="nl" value="nl">
<img src={NLflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;NL
</MenuItem>
<MenuItem key="no" value="no">
<img src={NOflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;NO
</MenuItem>
<MenuItem key="pl" value="pl">
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;PL
</MenuItem>
<MenuItem key="sv" value="sv">
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;SV
</MenuItem>
<MenuItem key="tr" value="tr">
<img src={TRflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;TR
</MenuItem>
</TextField>
<IconButton
id="open-auth-menu"
sx={{ ml: 1, padding: 0 }}
aria-describedby={id}
color="inherit"
onClick={handleClick}
>
<AccountCircleIcon />
</IconButton>
<Popover
id="app-menu-popover"
sx={{ mt: 1 }}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center'
}}
>
<Box display="flex" flexDirection="row" alignItems="center" p={2}>
<Avatar sx={{ width: 80, height: 80 }}>
<PersonIcon fontSize="large" />
</Avatar>
<Box pl={2}>
<ItemTypography variant="h6">{me.username}</ItemTypography>
<ItemTypography variant="body1">
{me.admin ? LL.ADMIN() : LL.GUEST()}&nbsp;{LL.USER(2)}
</ItemTypography>
</Box>
</Box>
<Divider />
<Box p={1.5}>
<Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}>
{LL.SIGN_OUT()}
</Button>
</Box>
</Popover>
</>
);
};
export default LayoutAuthMenu;

View File

@@ -1,6 +1,8 @@
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material'; import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
import { DRAWER_WIDTH } from './Layout'; import { DRAWER_WIDTH } from './Layout';
import LayoutMenu from './LayoutMenu'; import LayoutMenu from './LayoutMenu';
import type { FC } from 'react'; import type { FC } from 'react';
import { PROJECT_NAME } from 'api/env'; import { PROJECT_NAME } from 'api/env';

View File

@@ -1,53 +1,254 @@
import AccessTimeIcon from '@mui/icons-material/AccessTime'; import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import AssessmentIcon from '@mui/icons-material/Assessment';
import DashboardIcon from '@mui/icons-material/Dashboard'; import CategoryIcon from '@mui/icons-material/Category';
import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import ConstructionIcon from '@mui/icons-material/Construction';
import InfoIcon from '@mui/icons-material/Info'; import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import LockIcon from '@mui/icons-material/Lock'; import LiveHelpIcon from '@mui/icons-material/LiveHelp';
import MoreTimeIcon from '@mui/icons-material/MoreTime';
import PersonIcon from '@mui/icons-material/Person';
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import SensorsIcon from '@mui/icons-material/Sensors';
import SettingsIcon from '@mui/icons-material/Settings'; import SettingsIcon from '@mui/icons-material/Settings';
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TuneIcon from '@mui/icons-material/Tune';
import { Divider, List } from '@mui/material';
import { useContext } from 'react';
import type { FC } from 'react';
import {
Divider,
List,
Box,
Button,
Popover,
Avatar,
MenuItem,
TextField,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText
} from '@mui/material';
import { useContext, useState } from 'react';
import type { Locales } from 'i18n/i18n-types';
import type { FC, ChangeEventHandler } from 'react';
import LayoutMenuItem from 'components/layout/LayoutMenuItem'; import LayoutMenuItem from 'components/layout/LayoutMenuItem';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import DEflag from 'i18n/DE.svg';
import FRflag from 'i18n/FR.svg';
import GBflag from 'i18n/GB.svg';
import ITflag from 'i18n/IT.svg';
import NLflag from 'i18n/NL.svg';
import NOflag from 'i18n/NO.svg';
import PLflag from 'i18n/PL.svg';
import SKflag from 'i18n/SK.svg';
import SVflag from 'i18n/SV.svg';
import TRflag from 'i18n/TR.svg';
import { I18nContext } from 'i18n/i18n-react';
import { loadLocaleAsync } from 'i18n/i18n-util.async';
const LayoutMenu: FC = () => { const LayoutMenu: FC = () => {
const authenticatedContext = useContext(AuthenticatedContext); const { me, signOut } = useContext(AuthenticatedContext);
const { LL } = useI18nContext(); const { locale, LL, setLocale } = useContext(I18nContext);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const open = Boolean(anchorEl);
const id = anchorEl ? 'app-menu-popover' : undefined;
const [menuOpen, setMenuOpen] = useState(true);
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({ target }) => {
const loc = target.value as Locales;
localStorage.setItem('lang', loc);
await loadLocaleAsync(loc);
setLocale(loc);
};
const handleClick = (event: any) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return ( return (
<> <>
<List disablePadding component="nav"> <List component="nav">
<LayoutMenuItem icon={DashboardIcon} label={LL.DASHBOARD()} to={`/dashboard`} /> <LayoutMenuItem icon={CategoryIcon} label={LL.DEVICES()} to={`/devices`} />
<LayoutMenuItem <LayoutMenuItem icon={SensorsIcon} label={LL.SENSORS()} to={`/sensors`} />
icon={TuneIcon}
label={LL.SETTINGS_OF('')}
to={`/settings`}
disabled={!authenticatedContext.me.admin}
/>
<LayoutMenuItem icon={InfoIcon} label={LL.HELP_OF('')} to={`/help`} />
<Divider /> <Divider />
</List>
<List disablePadding component="nav"> <Box
<LayoutMenuItem icon={SettingsEthernetIcon} label={LL.NETWORK(0)} to="/network" /> sx={{
<LayoutMenuItem icon={SettingsInputAntennaIcon} label={LL.ACCESS_POINT(0)} to="/ap" /> bgcolor: menuOpen ? 'rgba(71, 98, 130, 0.2)' : null,
<LayoutMenuItem icon={AccessTimeIcon} label="NTP" to="/ntp" /> pb: menuOpen ? 2 : 0
<LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" /> }}
<LayoutMenuItem >
icon={LockIcon} <ListItemButton
label={LL.SECURITY(0)} alignItems="flex-start"
to="/security" onClick={() => setMenuOpen(!menuOpen)}
disabled={!authenticatedContext.me.admin} sx={{
pt: 2.5,
pb: menuOpen ? 0 : 2.5,
'&:hover, &:focus': { '& svg': { opacity: 1 } }
}}
>
<ListItemText
// TODO: translate
primary="Customize"
primaryTypographyProps={{
fontWeight: '600',
mb: '2px',
color: 'lightblue'
}}
// TODO: translate
secondary="Customizations, Scheduler and Custom Entities"
secondaryTypographyProps={{
noWrap: true,
fontSize: 12,
color: menuOpen ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)'
}}
sx={{ my: 0 }}
/> />
<LayoutMenuItem icon={SettingsIcon} label={LL.SYSTEM(0)} to="/system" /> <KeyboardArrowDown
sx={{
mr: -1,
opacity: 0,
transform: menuOpen ? 'rotate(-180deg)' : 'rotate(0)',
transition: '0.2s'
}}
/>
</ListItemButton>
{menuOpen && (
<>
<LayoutMenuItem
icon={ConstructionIcon}
label={LL.CUSTOMIZATIONS()}
disabled={!me.admin}
to={`/customizations`}
/>
<LayoutMenuItem icon={MoreTimeIcon} label={LL.SCHEDULER()} disabled={!me.admin} to={`/scheduler`} />
<LayoutMenuItem
icon={PlaylistAddIcon}
label={LL.CUSTOM_ENTITIES(0)}
disabled={!me.admin}
to={`/customentities`}
/>
</>
)}
</Box>
</List> </List>
<List style={{ marginTop: `auto` }}>
<LayoutMenuItem icon={AssessmentIcon} label={LL.SYSTEM(0)} to="/system" />
<LayoutMenuItem icon={SettingsIcon} label={LL.SETTINGS(0)} disabled={!me.admin} to="/settings" />
<LayoutMenuItem icon={LiveHelpIcon} label={LL.HELP_OF('')} to={`/help`} />
</List>
<Divider />
<List>
<ListItem disablePadding onClick={handleClick}>
<ListItemButton>
<ListItemIcon>
<AccountCircleIcon />
</ListItemIcon>
<ListItemText>{me.username}</ListItemText>
</ListItemButton>
</ListItem>
</List>
<Popover
id={id}
sx={{ mt: 1 }}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center'
}}
>
<Box
p={2}
sx={{
borderRadius: 2,
border: '2px solid grey'
}}
>
<List>
<ListItem disablePadding>
<Avatar sx={{ bgcolor: '#b1395f', color: 'white' }}>
<PersonIcon />
</Avatar>
<ListItemText
sx={{ pl: 2 }}
primary={me.username}
secondary={(me.admin ? LL.ADMIN() : LL.GUEST()) + ' Account'}
/>
</ListItem>
</List>
<Box p={2}>
<TextField
name="locale"
InputProps={{ style: { fontSize: 10 } }}
variant="outlined"
value={locale}
onChange={onLocaleSelected}
size="small"
select
>
<MenuItem key="de" value="de">
<img src={DEflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;DE
</MenuItem>
<MenuItem key="en" value="en">
<img src={GBflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;EN
</MenuItem>
<MenuItem key="fr" value="fr">
<img src={FRflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;FR
</MenuItem>
<MenuItem key="it" value="it">
<img src={ITflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;IT
</MenuItem>
<MenuItem key="nl" value="nl">
<img src={NLflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;NL
</MenuItem>
<MenuItem key="no" value="no">
<img src={NOflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;NO
</MenuItem>
<MenuItem key="pl" value="pl">
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;PL
</MenuItem>
<MenuItem key="sk" value="sk">
<img src={SKflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;SK
</MenuItem>
<MenuItem key="sv" value="sv">
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;SV
</MenuItem>
<MenuItem key="tr" value="tr">
<img src={TRflag} style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;TR
</MenuItem>
</TextField>
</Box>
<Box>
<Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}>
{LL.SIGN_OUT()}
</Button>
</Box>
</Box>
</Popover>
</> </>
); );
}; };

View File

@@ -1,4 +1,4 @@
import { ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import type { SvgIconProps } from '@mui/material'; import type { SvgIconProps } from '@mui/material';
import type { FC } from 'react'; import type { FC } from 'react';
@@ -18,14 +18,12 @@ const LayoutMenuItem: FC<LayoutMenuItemProps> = ({ icon: Icon, label, to, disabl
const selected = routeMatches(to, pathname); const selected = routeMatches(to, pathname);
return ( return (
<ListItem disablePadding>
<ListItemButton component={Link} to={to} disabled={disabled} selected={selected}> <ListItemButton component={Link} to={to} disabled={disabled} selected={selected}>
<ListItemIcon sx={{ color: selected ? '#90caf9' : '#9e9e9e' }}> <ListItemIcon sx={{ color: selected ? '#90caf9' : '#9e9e9e' }}>
<Icon /> <Icon />
</ListItemIcon> </ListItemIcon>
<ListItemText sx={{ color: selected ? '#90caf9' : '#f5f5f5' }}>{label}</ListItemText> <ListItemText sx={{ color: selected ? '#90caf9' : '#f5f5f5' }}>{label}</ListItemText>
</ListItemButton> </ListItemButton>
</ListItem>
); );
}; };

View File

@@ -0,0 +1,52 @@
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
import { Avatar, ListItem, ListItemAvatar, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import { Link } from 'react-router-dom';
import type { SvgIconProps } from '@mui/material';
import type { FC } from 'react';
interface ListMenuItemProps {
icon: React.ComponentType<SvgIconProps>;
bgcolor?: string;
label: string;
text: string;
to?: string;
disabled?: boolean;
}
function RenderIcon({ icon: Icon, bgcolor, label, text }: ListMenuItemProps) {
return (
<>
<ListItemAvatar>
<Avatar sx={{ bgcolor, color: 'white' }}>
<Icon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={label} secondary={text} />
</>
);
}
const LayoutMenuItem: FC<ListMenuItemProps> = ({ icon, bgcolor, label, text, to, disabled }) => (
<>
{to && !disabled ? (
<ListItem
disablePadding
secondaryAction={
<ListItemIcon style={{ justifyContent: 'right', color: 'lightblue', verticalAlign: 'middle' }}>
<NavigateNextIcon />
</ListItemIcon>
}
>
<ListItemButton component={Link} to={to}>
<RenderIcon icon={icon} bgcolor={bgcolor} label={label} text={text} to="" />
</ListItemButton>
</ListItem>
) : (
<ListItem>
<RenderIcon icon={icon} bgcolor={bgcolor} label={label} text={text} to="" />
</ListItem>
)}
</>
);
export default LayoutMenuItem;

View File

@@ -1,12 +1,8 @@
import { useLocation } from 'react-router-dom'; import { useMatch, useResolvedPath } from 'react-router-dom';
export const useRouterTab = () => { export const useRouterTab = () => {
const loc = useLocation().pathname; const routerTabPathMatch = useMatch(useResolvedPath(':tab').pathname);
const routerTab = loc.substring(0, loc.lastIndexOf('/')) ? loc : false; const routerTab = routerTabPathMatch?.params?.tab || false;
// const routerTabPath = useResolvedPath(':tab');
// const routerTabPathMatch = useMatch(routerTabPath.pathname);
// const routerTab = routerTabPathMatch?.params?.tab || false;
return { routerTab } as const; return { routerTab } as const;
}; };

View File

@@ -50,8 +50,10 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, isUploading, pr
const progressText = () => { const progressText = () => {
if (uploading) { if (uploading) {
if (progress.total) { if (progress.total && progress.loaded) {
return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%'; return progress.loaded <= progress.total
? LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%'
: LL.UPLOADING() + ': ' + Math.round((progress.total * 100) / progress.loaded) + '%';
} }
} }
return LL.UPLOAD_DROP_TEXT(); return LL.UPLOAD_DROP_TEXT();
@@ -61,7 +63,7 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, isUploading, pr
<Box <Box
{...getRootProps({ {...getRootProps({
sx: { sx: {
py: 8, py: 4,
px: 2, px: 2,
borderWidth: 2, borderWidth: 2,
borderRadius: 2, borderRadius: 2,
@@ -83,7 +85,13 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, isUploading, pr
<Box width="100%" p={2}> <Box width="100%" p={2}>
<LinearProgress <LinearProgress
variant="determinate" variant="determinate"
value={progress.total === 0 ? 0 : Math.round((progress.loaded * 100) / progress.total)} value={
progress.total === 0 || progress.loaded === 0
? 0
: progress.loaded <= progress.total
? Math.round((progress.loaded * 100) / progress.total)
: Math.round((progress.total * 100) / progress.loaded)
}
/> />
</Box> </Box>
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}> <Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>

View File

@@ -1,25 +0,0 @@
import { useRequest } from 'alova';
import { FeaturesContext } from '.';
import type { FC } from 'react';
import type { RequiredChildrenProps } from 'utils';
import * as FeaturesApi from 'api/features';
const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
const { data: features } = useRequest(FeaturesApi.readFeatures);
if (features) {
return (
<FeaturesContext.Provider
value={{
features
}}
>
{props.children}
</FeaturesContext.Provider>
);
}
};
export default FeaturesLoader;

View File

@@ -1,10 +0,0 @@
import { createContext } from 'react';
import type { Features } from 'types';
export interface FeaturesContextValue {
features: Features;
}
const FeaturesContextDefaultValue = {} as FeaturesContextValue;
export const FeaturesContext = createContext(FeaturesContextDefaultValue);

View File

@@ -1,2 +0,0 @@
export * from './context';
export { default as FeaturesLoader } from './FeaturesLoader';

View File

@@ -0,0 +1,250 @@
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import CancelIcon from '@mui/icons-material/Cancel';
import CastIcon from '@mui/icons-material/Cast';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import ImportExportIcon from '@mui/icons-material/ImportExport';
import LockIcon from '@mui/icons-material/Lock';
import MemoryIcon from '@mui/icons-material/Memory';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TuneIcon from '@mui/icons-material/Tune';
import { List, Button, Dialog, DialogActions, DialogContent, DialogTitle, Box } from '@mui/material';
import { useRequest } from 'alova';
import { useState, type FC } from 'react';
import { toast } from 'react-toastify';
import RestartMonitor from './system/RestartMonitor';
import { dialogStyle } from 'CustomTheme';
import * as SystemApi from 'api/system';
import { ButtonRow, SectionContent, useLayoutTitle } from 'components';
import ListMenuItem from 'components/layout/ListMenuItem';
import { useI18nContext } from 'i18n/i18n-react';
const Settings: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.SETTINGS(0));
const [confirmRestart, setConfirmRestart] = useState<boolean>(false);
const [confirmFactoryReset, setConfirmFactoryReset] = useState<boolean>(false);
const [processing, setProcessing] = useState<boolean>(false);
const [restarting, setRestarting] = useState<boolean>();
const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false
});
const { send: factoryResetCommand } = useRequest(SystemApi.factoryReset(), {
immediate: false
});
const { send: partitionCommand } = useRequest(SystemApi.partition(), {
immediate: false
});
const restart = async () => {
setProcessing(true);
await restartCommand()
.then(() => {
setRestarting(true);
})
.catch((err) => {
toast.error(err.message);
})
.finally(() => {
setConfirmRestart(false);
setProcessing(false);
});
};
const factoryReset = async () => {
setProcessing(true);
await factoryResetCommand()
.then(() => {
setRestarting(true);
})
.catch((err) => {
toast.error(err.message);
})
.finally(() => {
setConfirmFactoryReset(false);
setProcessing(false);
});
};
const partition = async () => {
setProcessing(true);
await partitionCommand()
.then(() => {
setRestarting(true);
})
.catch((err) => {
toast.error(err.message);
})
.finally(() => {
setConfirmRestart(false);
setProcessing(false);
});
};
const renderRestartDialog = () => (
<Dialog sx={dialogStyle} open={confirmRestart} onClose={() => setConfirmRestart(false)}>
<DialogTitle>{LL.RESTART()}</DialogTitle>
<DialogContent dividers>{LL.RESTART_CONFIRM()}</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => setConfirmRestart(false)}
disabled={processing}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<PowerSettingsNewIcon />}
variant="outlined"
onClick={restart}
disabled={processing}
color="primary"
>
{LL.RESTART()}
</Button>
<Button
startIcon={<PowerSettingsNewIcon />}
variant="outlined"
onClick={partition}
disabled={processing}
color="primary"
>
EMS-ESP Loader
</Button>
</DialogActions>
</Dialog>
);
const renderFactoryResetDialog = () => (
<Dialog sx={dialogStyle} open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}>
<DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
<DialogContent dividers>{LL.SYSTEM_FACTORY_TEXT_DIALOG()}</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => setConfirmFactoryReset(false)}
disabled={processing}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<SettingsBackupRestoreIcon />}
variant="outlined"
onClick={factoryReset}
disabled={processing}
color="error"
>
{LL.FACTORY_RESET()}
</Button>
</DialogActions>
</Dialog>
);
const content = () => (
<>
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
{/* TODO: translate */}
<ListMenuItem
icon={TuneIcon}
bgcolor="#134ba2"
label={LL.APPLICATION_SETTINGS()}
text="Modify EMS-ESP Application Settings"
to="ems-esp"
/>
<ListMenuItem
icon={SettingsEthernetIcon}
bgcolor="#40828f"
label={LL.NETWORK(0)}
text={LL.CONFIGURE(LL.SETTINGS_OF(LL.NETWORK(0)))}
to="network"
/>
<ListMenuItem
icon={SettingsInputAntennaIcon}
bgcolor="#5f9a5f"
label={LL.ACCESS_POINT(0)}
text={LL.CONFIGURE(LL.ACCESS_POINT(0))}
to="ap"
/>
<ListMenuItem
icon={AccessTimeIcon}
bgcolor="#c5572c"
label="NTP"
text={LL.CONFIGURE(LL.LOCAL_TIME())}
to="ntp"
/>
<ListMenuItem icon={DeviceHubIcon} bgcolor="#68374d" label="MQTT" text={LL.CONFIGURE('MQTT')} to="mqtt" />
<ListMenuItem icon={CastIcon} bgcolor="#efc34b" label="OTA" text={LL.CONFIGURE('OTA')} to="ota" />
{/* TODO: translate */}
<ListMenuItem icon={LockIcon} label={LL.SECURITY(0)} text="Add/Remove Users" to="security" />
<ListMenuItem
icon={MemoryIcon}
bgcolor="#b1395f"
label={LL.STATUS_OF('ESP32')}
text="ESP32 Information"
to="espsystemstatus"
/>
{/* TODO: translate */}
<ListMenuItem
icon={ImportExportIcon}
bgcolor="#5d89f7"
label={LL.UPLOAD_DOWNLOAD()}
text="Upload/Download Settings and Firmware"
to="upload"
/>
</List>
{renderRestartDialog()}
{renderFactoryResetDialog()}
<Box mt={1} display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<ButtonRow>
<Button
startIcon={<PowerSettingsNewIcon />}
variant="outlined"
color="primary"
onClick={() => setConfirmRestart(true)}
>
{LL.RESTART()}
</Button>
</ButtonRow>
</Box>
<Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow>
<Button
startIcon={<SettingsBackupRestoreIcon />}
variant="outlined"
onClick={() => setConfirmFactoryReset(true)}
color="error"
>
{LL.FACTORY_RESET()}
</Button>
</ButtonRow>
</Box>
</Box>
</>
);
return <SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>;
};
export default Settings;

View File

@@ -6,7 +6,7 @@ import { useState } from 'react';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react'; import type { FC } from 'react';
import type { APSettings } from 'types'; import type { APSettingsType } from 'types';
import * as APApi from 'api/ap'; import * as APApi from 'api/ap';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
@@ -24,10 +24,10 @@ import { numberValue, updateValueDirty, useRest } from 'utils';
import { createAPSettingsValidator, validate } from 'validators'; import { createAPSettingsValidator, validate } from 'validators';
export const isAPEnabled = ({ provision_mode }: APSettings) => export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED; provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
const APSettingsForm: FC = () => { const APSettings: FC = () => {
const { const {
loadData, loadData,
saving, saving,
@@ -39,7 +39,7 @@ const APSettingsForm: FC = () => {
blocker, blocker,
saveData, saveData,
errorMessage errorMessage
} = useRest<APSettings>({ } = useRest<APSettingsType>({
read: APApi.readAPSettings, read: APApi.readAPSettings,
update: APApi.updateAPSettings update: APApi.updateAPSettings
}); });
@@ -205,11 +205,11 @@ const APSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF(LL.ACCESS_POINT(1))} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>
); );
}; };
export default APSettingsForm; export default APSettings;

View File

@@ -7,14 +7,14 @@ import { useRequest } from 'alova';
import type { Theme } from '@mui/material'; import type { Theme } from '@mui/material';
import type { FC } from 'react'; import type { FC } from 'react';
import type { APStatus } from 'types'; import type { APStatusType } from 'types';
import * as APApi from 'api/ap'; import * as APApi from 'api/ap';
import { ButtonRow, FormLoader, SectionContent } from 'components'; import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { APNetworkStatus } from 'types'; import { APNetworkStatus } from 'types';
export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {
switch (status) { switch (status) {
case APNetworkStatus.ACTIVE: case APNetworkStatus.ACTIVE:
return theme.palette.success.main; return theme.palette.success.main;
@@ -27,14 +27,14 @@ export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
} }
}; };
const APStatusForm: FC = () => { const APStatus: FC = () => {
const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus);
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const theme = useTheme(); const theme = useTheme();
const apStatus = ({ status }: APStatus) => { const apStatus = ({ status }: APStatusType) => {
switch (status) { switch (status) {
case APNetworkStatus.ACTIVE: case APNetworkStatus.ACTIVE:
return LL.ACTIVE(); return LL.ACTIVE();
@@ -99,11 +99,7 @@ const APStatusForm: FC = () => {
); );
}; };
return ( return <SectionContent>{content()}</SectionContent>;
<SectionContent title={LL.STATUS_OF(LL.ACCESS_POINT(1))} titleGutter>
{content()}
</SectionContent>
);
}; };
export default APStatusForm; export default APStatus;

View File

@@ -1,12 +1,10 @@
import { Tab } from '@mui/material'; import { Tab } from '@mui/material';
import { useContext } from 'react';
import { Navigate, Routes, Route } from 'react-router-dom'; import { Navigate, Routes, Route } from 'react-router-dom';
import APSettingsForm from './APSettingsForm'; import APSettings from './APSettings';
import APStatusForm from './APStatusForm'; import APStatus from './APStatus';
import type { FC } from 'react'; import type { FC } from 'react';
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components'; import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
@@ -15,32 +13,18 @@ const AccessPoint: FC = () => {
useLayoutTitle(LL.ACCESS_POINT(0)); useLayoutTitle(LL.ACCESS_POINT(0));
const authenticatedContext = useContext(AuthenticatedContext);
const { routerTab } = useRouterTab(); const { routerTab } = useRouterTab();
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="/ap/status" label={LL.STATUS_OF(LL.ACCESS_POINT(1))} /> <Tab value="settings" label={LL.SETTINGS_OF(LL.ACCESS_POINT(1))} />
<Tab <Tab value="status" label={LL.STATUS_OF(LL.ACCESS_POINT(1))} />
value="/ap/settings"
label={LL.SETTINGS_OF(LL.ACCESS_POINT(1))}
disabled={!authenticatedContext.me.admin}
/>
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<APStatusForm />} /> <Route path="status" element={<APStatus />} />
<Route index element={<Navigate to="status" />} /> <Route path="settings" element={<APSettings />} />
<Route <Route path="*" element={<Navigate replace to="settings" />} />
path="settings"
element={
<RequireAdmin>
<APSettingsForm />
</RequireAdmin>
}
/>
<Route path="*" element={<Navigate replace to="/ap/status" />} />
</Routes> </Routes>
</> </>
); );

View File

@@ -1,12 +1,10 @@
import { Tab } from '@mui/material'; import { Tab } from '@mui/material';
import { useContext } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import MqttSettingsForm from './MqttSettingsForm'; import MqttSettings from './MqttSettings';
import MqttStatusForm from './MqttStatusForm'; import MqttStatus from './MqttStatus';
import type { FC } from 'react'; import type { FC } from 'react';
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components'; import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
@@ -15,26 +13,18 @@ const Mqtt: FC = () => {
useLayoutTitle('MQTT'); useLayoutTitle('MQTT');
const authenticatedContext = useContext(AuthenticatedContext);
const { routerTab } = useRouterTab(); const { routerTab } = useRouterTab();
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="/mqtt/status" label={LL.STATUS_OF('MQTT')} /> <Tab value="settings" label={LL.SETTINGS_OF('MQTT')} />
<Tab value="/mqtt/settings" label={LL.SETTINGS_OF('MQTT')} disabled={!authenticatedContext.me.admin} /> <Tab value="status" label={LL.STATUS_OF('MQTT')} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<MqttStatusForm />} /> <Route path="status" element={<MqttStatus />} />
<Route <Route path="settings" element={<MqttSettings />} />
path="settings" <Route path="*" element={<Navigate replace to="settings" />} />
element={
<RequireAdmin>
<MqttSettingsForm />
</RequireAdmin>
}
/>
<Route path="*" element={<Navigate replace to="/mqtt/status" />} />
</Routes> </Routes>
</> </>
); );

View File

@@ -5,7 +5,7 @@ import { useState } from 'react';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react'; import type { FC } from 'react';
import type { MqttSettings } from 'types'; import type { MqttSettingsType } from 'types';
import * as MqttApi from 'api/mqtt'; import * as MqttApi from 'api/mqtt';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
@@ -21,7 +21,7 @@ import { numberValue, updateValueDirty, useRest } from 'utils';
import { createMqttSettingsValidator, validate } from 'validators'; import { createMqttSettingsValidator, validate } from 'validators';
const MqttSettingsForm: FC = () => { const MqttSettings: FC = () => {
const { const {
loadData, loadData,
saving, saving,
@@ -33,7 +33,7 @@ const MqttSettingsForm: FC = () => {
blocker, blocker,
saveData, saveData,
errorMessage errorMessage
} = useRest<MqttSettings>({ } = useRest<MqttSettingsType>({
read: MqttApi.readMqttSettings, read: MqttApi.readMqttSettings,
update: MqttApi.updateMqttSettings update: MqttApi.updateMqttSettings
}); });
@@ -72,6 +72,7 @@ const MqttSettingsForm: FC = () => {
name="host" name="host"
label={LL.ADDRESS_OF(LL.BROKER())} label={LL.ADDRESS_OF(LL.BROKER())}
fullWidth fullWidth
multiline
variant="outlined" variant="outlined"
value={data.host} value={data.host}
onChange={updateFormValue} onChange={updateFormValue}
@@ -273,6 +274,7 @@ const MqttSettingsForm: FC = () => {
> >
<MenuItem value={0}>Home Assistant</MenuItem> <MenuItem value={0}>Home Assistant</MenuItem>
<MenuItem value={1}>Domoticz</MenuItem> <MenuItem value={1}>Domoticz</MenuItem>
<MenuItem value={2}>Domoticz (latest)</MenuItem>
</TextField> </TextField>
</Grid> </Grid>
<Grid item xs={12} sm={6} md={4}> <Grid item xs={12} sm={6} md={4}>
@@ -386,6 +388,21 @@ const MqttSettingsForm: FC = () => {
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={12} sm={6} md={4}>
<TextField
name="publish_time_water"
label={LL.MQTT_INT_WATER()}
InputProps={{
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}}
fullWidth
variant="outlined"
value={numberValue(data.publish_time_water)}
type="number"
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid item xs={12} sm={6} md={4}> <Grid item xs={12} sm={6} md={4}>
<TextField <TextField
name="publish_time_sensor" name="publish_time_sensor"
@@ -447,11 +464,11 @@ const MqttSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF('MQTT')} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>
); );
}; };
export default MqttSettingsForm; export default MqttSettings;

View File

@@ -8,13 +8,13 @@ import { useRequest } from 'alova';
import type { Theme } from '@mui/material'; import type { Theme } from '@mui/material';
import type { FC } from 'react'; import type { FC } from 'react';
import type { MqttStatus } from 'types'; import type { MqttStatusType } from 'types';
import * as MqttApi from 'api/mqtt'; import * as MqttApi from 'api/mqtt';
import { ButtonRow, FormLoader, SectionContent } from 'components'; import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { MqttDisconnectReason } from 'types'; import { MqttDisconnectReason } from 'types';
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => { export const mqttStatusHighlight = ({ enabled, connected }: MqttStatusType, theme: Theme) => {
if (!enabled) { if (!enabled) {
return theme.palette.info.main; return theme.palette.info.main;
} }
@@ -24,27 +24,27 @@ export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: T
return theme.palette.error.main; return theme.palette.error.main;
}; };
export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatus, theme: Theme) => { export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatusType, theme: Theme) => {
if (mqtt_fails === 0) return theme.palette.success.main; if (mqtt_fails === 0) return theme.palette.success.main;
if (mqtt_fails < 10) return theme.palette.warning.main; if (mqtt_fails < 10) return theme.palette.warning.main;
return theme.palette.error.main; return theme.palette.error.main;
}; };
export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatus, theme: Theme) => { export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatusType, theme: Theme) => {
if (mqtt_queued <= 1) return theme.palette.success.main; if (mqtt_queued <= 1) return theme.palette.success.main;
return theme.palette.warning.main; return theme.palette.warning.main;
}; };
const MqttStatusForm: FC = () => { const MqttStatus: FC = () => {
const { data: data, send: loadData, error } = useRequest(MqttApi.readMqttStatus); const { data: data, send: loadData, error } = useRequest(MqttApi.readMqttStatus);
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const theme = useTheme(); const theme = useTheme();
const mqttStatus = ({ enabled, connected, connect_count }: MqttStatus) => { const mqttStatus = ({ enabled, connected, connect_count }: MqttStatusType) => {
if (!enabled) { if (!enabled) {
return LL.NOT_ENABLED(); return LL.NOT_ENABLED();
} }
@@ -54,7 +54,7 @@ const MqttStatusForm: FC = () => {
return LL.DISCONNECTED() + (connect_count > 1 ? ' (' + connect_count + ')' : ''); return LL.DISCONNECTED() + (connect_count > 1 ? ' (' + connect_count + ')' : '');
}; };
const disconnectReason = ({ disconnect_reason }: MqttStatus) => { const disconnectReason = ({ disconnect_reason }: MqttStatusType) => {
switch (disconnect_reason) { switch (disconnect_reason) {
case MqttDisconnectReason.TCP_DISCONNECTED: case MqttDisconnectReason.TCP_DISCONNECTED:
return 'TCP disconnected'; return 'TCP disconnected';
@@ -146,11 +146,7 @@ const MqttStatusForm: FC = () => {
); );
}; };
return ( return <SectionContent>{content()}</SectionContent>;
<SectionContent title={LL.STATUS_OF('MQTT')} titleGutter>
{content()}
</SectionContent>
);
}; };
export default MqttStatusForm; export default MqttStatus;

View File

@@ -0,0 +1,59 @@
import { Tab } from '@mui/material';
import { useCallback, useState } from 'react';
import { Navigate, Routes, Route, useNavigate } from 'react-router-dom';
import NetworkSettings from './NetworkSettings';
import NetworkStatus from './NetworkStatus';
import { WiFiConnectionContext } from './WiFiConnectionContext';
import WiFiNetworkScanner from './WiFiNetworkScanner';
import type { FC } from 'react';
import type { WiFiNetwork } from 'types';
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const Network: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.NETWORK(0));
const { routerTab } = useRouterTab();
const navigate = useNavigate();
const [selectedNetwork, setSelectedNetwork] = useState<WiFiNetwork>();
const selectNetwork = useCallback(
(network: WiFiNetwork) => {
setSelectedNetwork(network);
navigate('settings');
},
[navigate]
);
const deselectNetwork = useCallback(() => {
setSelectedNetwork(undefined);
}, []);
return (
<WiFiConnectionContext.Provider
value={{
selectedNetwork,
selectNetwork,
deselectNetwork
}}
>
<RouterTabs value={routerTab}>
<Tab value="settings" label={LL.SETTINGS_OF(LL.NETWORK(1))} />
<Tab value="status" label={LL.STATUS_OF(LL.NETWORK(1))} />
<Tab value="scan" label={LL.NETWORK_SCAN()} />
</RouterTabs>
<Routes>
<Route path="status" element={<NetworkStatus />} />
<Route path="scan" element={<WiFiNetworkScanner />} />
<Route path="settings" element={<NetworkSettings />} />
<Route path="*" element={<Navigate replace to="settings" />} />
</Routes>
</WiFiConnectionContext.Provider>
);
};
export default Network;

View File

@@ -1,79 +0,0 @@
import { Tab } from '@mui/material';
import { useCallback, useContext, useState } from 'react';
import { Navigate, Routes, Route, useNavigate } from 'react-router-dom';
import NetworkSettingsForm from './NetworkSettingsForm';
import NetworkStatusForm from './NetworkStatusForm';
import { WiFiConnectionContext } from './WiFiConnectionContext';
import WiFiNetworkScanner from './WiFiNetworkScanner';
import type { FC } from 'react';
import type { WiFiNetwork } from 'types';
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
const NetworkConnection: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.NETWORK(0));
const { routerTab } = useRouterTab();
const authenticatedContext = useContext(AuthenticatedContext);
const navigate = useNavigate();
const [selectedNetwork, setSelectedNetwork] = useState<WiFiNetwork>();
const selectNetwork = useCallback(
(network: WiFiNetwork) => {
setSelectedNetwork(network);
navigate('settings');
},
[navigate]
);
const deselectNetwork = useCallback(() => {
setSelectedNetwork(undefined);
}, []);
return (
<WiFiConnectionContext.Provider
value={{
selectedNetwork,
selectNetwork,
deselectNetwork
}}
>
<RouterTabs value={routerTab}>
<Tab value="/network/status" label={LL.STATUS_OF(LL.NETWORK(1))} />
<Tab value="/network/scan" label={LL.NETWORK_SCAN()} disabled={!authenticatedContext.me.admin} />
<Tab
value="/network/settings"
label={LL.SETTINGS_OF(LL.NETWORK(1))}
disabled={!authenticatedContext.me.admin}
/>
</RouterTabs>
<Routes>
<Route path="status" element={<NetworkStatusForm />} />
<Route
path="scan"
element={
<RequireAdmin>
<WiFiNetworkScanner />
</RequireAdmin>
}
/>
<Route
path="settings"
element={
<RequireAdmin>
<NetworkSettingsForm />
</RequireAdmin>
}
/>
<Route path="*" element={<Navigate replace to="/network/status" />} />
</Routes>
</WiFiConnectionContext.Provider>
);
};
export default NetworkConnection;

View File

@@ -15,8 +15,8 @@ import {
ListItemSecondaryAction, ListItemSecondaryAction,
ListItemText, ListItemText,
Typography, Typography,
InputAdornment, TextField,
TextField MenuItem
} from '@mui/material'; } from '@mui/material';
// eslint-disable-next-line import/named // eslint-disable-next-line import/named
import { updateState, useRequest } from 'alova'; import { updateState, useRequest } from 'alova';
@@ -28,7 +28,7 @@ import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react'; import type { FC } from 'react';
import type { NetworkSettings } from 'types'; import type { NetworkSettingsType } from 'types';
import * as NetworkApi from 'api/network'; import * as NetworkApi from 'api/network';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { import {
@@ -43,12 +43,12 @@ import {
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValueDirty, useRest } from 'utils'; import { updateValueDirty, useRest } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
import { createNetworkSettingsValidator } from 'validators/network'; import { createNetworkSettingsValidator } from 'validators/network';
const WiFiSettingsForm: FC = () => { const NetworkSettings: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext); const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext);
@@ -68,7 +68,7 @@ const WiFiSettingsForm: FC = () => {
saveData, saveData,
errorMessage, errorMessage,
restartNeeded restartNeeded
} = useRest<NetworkSettings>({ } = useRest<NetworkSettingsType>({
read: NetworkApi.readNetworkSettings, read: NetworkApi.readNetworkSettings,
update: NetworkApi.updateNetworkSettings update: NetworkApi.updateNetworkSettings
}); });
@@ -88,7 +88,7 @@ const WiFiSettingsForm: FC = () => {
static_ip_config: false, static_ip_config: false,
enableIPv6: false, enableIPv6: false,
bandwidth20: false, bandwidth20: false,
tx_power: 20, tx_power: 0,
nosleep: false, nosleep: false,
enableMDNS: true, enableMDNS: true,
enableCORS: false, enableCORS: false,
@@ -135,7 +135,7 @@ const WiFiSettingsForm: FC = () => {
return ( return (
<> <>
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography variant="h6" color="primary">
WiFi WiFi
</Typography> </Typography>
{selectedNetwork ? ( {selectedNetwork ? (
@@ -196,20 +196,29 @@ const WiFiSettingsForm: FC = () => {
margin="normal" margin="normal"
/> />
)} )}
<ValidatedTextField <TextField
fieldErrors={fieldErrors}
name="tx_power" name="tx_power"
label={LL.TX_POWER()} label={LL.TX_POWER()}
InputProps={{
endAdornment: <InputAdornment position="end">dBm</InputAdornment>
}}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={numberValue(data.tx_power)} value={data.tx_power}
onChange={updateFormValue} onChange={updateFormValue}
type="number"
margin="normal" margin="normal"
/> select
>
<MenuItem value={0}>Auto</MenuItem>
<MenuItem value={78}>19.5 dBm</MenuItem>
<MenuItem value={76}>19 dBm</MenuItem>
<MenuItem value={74}>18.5 dBm</MenuItem>
<MenuItem value={68}>17 dBm</MenuItem>
<MenuItem value={60}>15 dBm</MenuItem>
<MenuItem value={52}>13 dBm</MenuItem>
<MenuItem value={44}>11 dBm</MenuItem>
<MenuItem value={34}>8.5 dBm</MenuItem>
<MenuItem value={28}>7 dBm</MenuItem>
<MenuItem value={20}>5 dBm</MenuItem>
<MenuItem value={8}>2 dBm</MenuItem>
</TextField>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />} control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />}
label={LL.NETWORK_DISABLE_SLEEP()} label={LL.NETWORK_DISABLE_SLEEP()}
@@ -250,10 +259,12 @@ const WiFiSettingsForm: FC = () => {
margin="normal" margin="normal"
/> />
)} )}
{data.enableIPv6 !== undefined && (
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="enableIPv6" checked={data.enableIPv6} onChange={updateFormValue} />} control={<Checkbox name="enableIPv6" checked={data.enableIPv6} onChange={updateFormValue} />}
label={LL.NETWORK_ENABLE_IPV6()} label={LL.NETWORK_ENABLE_IPV6()}
/> />
)}
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="static_ip_config" checked={data.static_ip_config} onChange={updateFormValue} />} control={<Checkbox name="static_ip_config" checked={data.static_ip_config} onChange={updateFormValue} />}
label={LL.NETWORK_FIXED_IP()} label={LL.NETWORK_FIXED_IP()}
@@ -313,7 +324,7 @@ const WiFiSettingsForm: FC = () => {
</> </>
)} )}
{restartNeeded && ( {restartNeeded && (
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}> <MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}> <Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
{LL.RESTART()} {LL.RESTART()}
</Button> </Button>
@@ -349,11 +360,11 @@ const WiFiSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF(LL.NETWORK(1))} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : content()} {restarting ? <RestartMonitor /> : content()}
</SectionContent> </SectionContent>
); );
}; };
export default WiFiSettingsForm; export default NetworkSettings;

View File

@@ -1,5 +1,6 @@
import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import DnsIcon from '@mui/icons-material/Dns'; import DnsIcon from '@mui/icons-material/Dns';
import GiteIcon from '@mui/icons-material/Gite';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import RouterIcon from '@mui/icons-material/Router'; import RouterIcon from '@mui/icons-material/Router';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
@@ -10,18 +11,18 @@ import { useRequest } from 'alova';
import type { Theme } from '@mui/material'; import type { Theme } from '@mui/material';
import type { FC } from 'react'; import type { FC } from 'react';
import type { NetworkStatus } from 'types'; import type { NetworkStatusType } from 'types';
import * as NetworkApi from 'api/network'; import * as NetworkApi from 'api/network';
import { ButtonRow, FormLoader, SectionContent } from 'components'; import { ButtonRow, FormLoader, SectionContent } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { NetworkConnectionStatus } from 'types'; import { NetworkConnectionStatus } from 'types';
const isConnected = ({ status }: NetworkStatus) => const isConnected = ({ status }: NetworkStatusType) =>
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED || status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED; status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED;
const networkStatusHighlight = ({ status }: NetworkStatus, theme: Theme) => { const networkStatusHighlight = ({ status }: NetworkStatusType, theme: Theme) => {
switch (status) { switch (status) {
case NetworkConnectionStatus.WIFI_STATUS_IDLE: case NetworkConnectionStatus.WIFI_STATUS_IDLE:
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED: case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
@@ -38,7 +39,7 @@ const networkStatusHighlight = ({ status }: NetworkStatus, theme: Theme) => {
} }
}; };
const networkQualityHighlight = ({ rssi }: NetworkStatus, theme: Theme) => { const networkQualityHighlight = ({ rssi }: NetworkStatusType, theme: Theme) => {
if (rssi <= -85) { if (rssi <= -85) {
return theme.palette.error.main; return theme.palette.error.main;
} else if (rssi <= -75) { } else if (rssi <= -75) {
@@ -47,17 +48,18 @@ const networkQualityHighlight = ({ rssi }: NetworkStatus, theme: Theme) => {
return theme.palette.success.main; return theme.palette.success.main;
}; };
export const isWiFi = ({ status }: NetworkStatus) => status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED; export const isWiFi = ({ status }: NetworkStatusType) => status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED;
export const isEthernet = ({ status }: NetworkStatus) => status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED; export const isEthernet = ({ status }: NetworkStatusType) =>
status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED;
const dnsServers = ({ dns_ip_1, dns_ip_2 }: NetworkStatus) => { const dnsServers = ({ dns_ip_1, dns_ip_2 }: NetworkStatusType) => {
if (!dns_ip_1) { if (!dns_ip_1) {
return 'none'; return 'none';
} }
return dns_ip_1 + (!dns_ip_2 || dns_ip_2 === '0.0.0.0' ? '' : ',' + dns_ip_2); return dns_ip_1 + (!dns_ip_2 || dns_ip_2 === '0.0.0.0' ? '' : ',' + dns_ip_2);
}; };
const IPs = (status: NetworkStatus) => { const IPs = (status: NetworkStatusType) => {
if (!status.local_ipv6 || status.local_ipv6 === '0000:0000:0000:0000:0000:0000:0000:0000') { if (!status.local_ipv6 || status.local_ipv6 === '0000:0000:0000:0000:0000:0000:0000:0000') {
return status.local_ip; return status.local_ip;
} }
@@ -67,14 +69,14 @@ const IPs = (status: NetworkStatus) => {
return status.local_ip + ', ' + status.local_ipv6; return status.local_ip + ', ' + status.local_ipv6;
}; };
const NetworkStatusForm: FC = () => { const NetworkStatus: FC = () => {
const { data: data, send: loadData, error } = useRequest(NetworkApi.readNetworkStatus); const { data: data, send: loadData, error } = useRequest(NetworkApi.readNetworkStatus);
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const theme = useTheme(); const theme = useTheme();
const networkStatus = ({ status }: NetworkStatus) => { const networkStatus = ({ status }: NetworkStatusType) => {
switch (status) { switch (status) {
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD: case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
return LL.INACTIVE(1); return LL.INACTIVE(1);
@@ -115,6 +117,15 @@ const NetworkStatusForm: FC = () => {
<ListItemText primary="Status" secondary={networkStatus(data)} /> <ListItemText primary="Status" secondary={networkStatus(data)} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: networkStatusHighlight(data, theme) }}>
<GiteIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Hostname" secondary={data.hostname} />
</ListItem>
<Divider variant="inset" component="li" />
{isWiFi(data) && ( {isWiFi(data) && (
<> <>
<ListItem> <ListItem>
@@ -183,11 +194,7 @@ const NetworkStatusForm: FC = () => {
); );
}; };
return ( return <SectionContent>{content()}</SectionContent>;
<SectionContent title={LL.STATUS_OF(LL.NETWORK(1))} titleGutter>
{content()}
</SectionContent>
);
}; };
export default NetworkStatusForm; export default NetworkStatus;

View File

@@ -56,7 +56,7 @@ const WiFiNetworkScanner: FC = () => {
}; };
return ( return (
<SectionContent title={LL.NETWORK_SCANNER()}> <SectionContent>
{renderNetworkScanner()} {renderNetworkScanner()}
<ButtonRow> <ButtonRow>
<Button <Button

View File

@@ -8,7 +8,7 @@ import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react'; import type { FC } from 'react';
import type { NTPSettings } from 'types'; import type { NTPSettingsType } from 'types';
import * as NTPApi from 'api/ntp'; import * as NTPApi from 'api/ntp';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
@@ -23,7 +23,7 @@ import { updateValueDirty, useRest } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp'; import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
const NTPSettingsForm: FC = () => { const NTPSettings: FC = () => {
const { const {
loadData, loadData,
saving, saving,
@@ -35,7 +35,7 @@ const NTPSettingsForm: FC = () => {
blocker, blocker,
saveData, saveData,
errorMessage errorMessage
} = useRest<NTPSettings>({ } = useRest<NTPSettingsType>({
read: NTPApi.readNTPSettings, read: NTPApi.readNTPSettings,
update: NTPApi.updateNTPSettings update: NTPApi.updateNTPSettings
}); });
@@ -130,11 +130,11 @@ const NTPSettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF('NTP')} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>
); );
}; };
export default NTPSettingsForm; export default NTPSettings;

View File

@@ -22,25 +22,39 @@ import {
Typography Typography
} from '@mui/material'; } from '@mui/material';
import { useRequest } from 'alova'; import { useRequest } from 'alova';
import { useContext, useState } from 'react'; import { useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import type { Theme } from '@mui/material'; import type { Theme } from '@mui/material';
import type { FC } from 'react'; import type { FC } from 'react';
import type { NTPStatus } from 'types'; import type { NTPStatusType } from 'types';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import * as NTPApi from 'api/ntp'; import * as NTPApi from 'api/ntp';
import { ButtonRow, FormLoader, SectionContent } from 'components'; import { ButtonRow, FormLoader, SectionContent } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { NTPSyncStatus } from 'types'; import { NTPSyncStatus } from 'types';
import { formatDateTime, formatLocalDateTime } from 'utils'; import { formatDateTime, formatLocalDateTime } from 'utils';
export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE; const NTPStatus: FC = () => {
export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED; const { data: data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => { const [localTime, setLocalTime] = useState<string>('');
const [settingTime, setSettingTime] = useState<boolean>(false);
const [processing, setProcessing] = useState<boolean>(false);
const { LL } = useI18nContext();
const { send: updateTime } = useRequest((local_time) => NTPApi.updateTime(local_time), {
immediate: false
});
NTPApi.updateTime;
const isNtpActive = ({ status }: NTPStatusType) => status === NTPSyncStatus.NTP_ACTIVE;
const isNtpEnabled = ({ status }: NTPStatusType) => status !== NTPSyncStatus.NTP_DISABLED;
const ntpStatusHighlight = ({ status }: NTPStatusType, theme: Theme) => {
switch (status) { switch (status) {
case NTPSyncStatus.NTP_DISABLED: case NTPSyncStatus.NTP_DISABLED:
return theme.palette.info.main; return theme.palette.info.main;
@@ -53,22 +67,6 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => {
} }
}; };
const NTPStatusForm: FC = () => {
const { data: data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
const [localTime, setLocalTime] = useState<string>('');
const [settingTime, setSettingTime] = useState<boolean>(false);
const [processing, setProcessing] = useState<boolean>(false);
const { me } = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
const { send: updateTime } = useRequest((local_time) => NTPApi.updateTime(local_time), {
immediate: false
});
NTPApi.updateTime;
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value); const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value);
const openSetTime = () => { const openSetTime = () => {
@@ -78,7 +76,7 @@ const NTPStatusForm: FC = () => {
const theme = useTheme(); const theme = useTheme();
const ntpStatus = ({ status }: NTPStatus) => { const ntpStatus = ({ status }: NTPStatusType) => {
switch (status) { switch (status) {
case NTPSyncStatus.NTP_DISABLED: case NTPSyncStatus.NTP_DISABLED:
return LL.NOT_ENABLED(); return LL.NOT_ENABLED();
@@ -201,7 +199,7 @@ const NTPStatusForm: FC = () => {
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
{me.admin && data && !isNtpActive(data) && ( {data && !isNtpActive(data) && (
<Box flexWrap="nowrap" whiteSpace="nowrap"> <Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow> <ButtonRow>
<Button onClick={openSetTime} variant="outlined" color="primary" startIcon={<AccessTimeIcon />}> <Button onClick={openSetTime} variant="outlined" color="primary" startIcon={<AccessTimeIcon />}>
@@ -216,11 +214,7 @@ const NTPStatusForm: FC = () => {
); );
}; };
return ( return <SectionContent>{content()}</SectionContent>;
<SectionContent title={LL.STATUS_OF('NTP')} titleGutter>
{content()}
</SectionContent>
);
}; };
export default NTPStatusForm; export default NTPStatus;

View File

@@ -1,12 +1,10 @@
import { Tab } from '@mui/material'; import { Tab } from '@mui/material';
import { useContext } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import NTPSettingsForm from './NTPSettingsForm'; import NTPSettings from './NTPSettings';
import NTPStatusForm from './NTPStatusForm'; import NTPStatus from './NTPStatus';
import type { FC } from 'react'; import type { FC } from 'react';
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from 'components'; import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
@@ -14,26 +12,18 @@ const NetworkTime: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle('NTP'); useLayoutTitle('NTP');
const authenticatedContext = useContext(AuthenticatedContext);
const { routerTab } = useRouterTab(); const { routerTab } = useRouterTab();
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="/ntp/status" label={LL.STATUS_OF('NTP')} /> <Tab value="settings" label={LL.SETTINGS_OF('NTP')} />
<Tab value="/ntp/settings" label={LL.SETTINGS_OF('NTP')} disabled={!authenticatedContext.me.admin} /> <Tab value="status" label={LL.STATUS_OF('NTP')} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<NTPStatusForm />} /> <Route path="status" element={<NTPStatus />} />
<Route <Route path="settings" element={<NTPSettings />} />
path="settings" <Route path="*" element={<Navigate replace to="settings" />} />
element={
<RequireAdmin>
<NTPSettingsForm />
</RequireAdmin>
}
/>
<Route path="*" element={<Navigate replace to="/ntp/status" />} />
</Routes> </Routes>
</> </>
); );

View File

@@ -5,7 +5,7 @@ import { useState } from 'react';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react'; import type { FC } from 'react';
import type { OTASettings } from 'types'; import type { OTASettingsType } from 'types';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
@@ -14,7 +14,8 @@ import {
SectionContent, SectionContent,
ValidatedPasswordField, ValidatedPasswordField,
ValidatedTextField, ValidatedTextField,
BlockNavigation BlockNavigation,
useLayoutTitle
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
@@ -23,7 +24,7 @@ import { numberValue, updateValueDirty, useRest } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
import { OTA_SETTINGS_VALIDATOR } from 'validators/system'; import { OTA_SETTINGS_VALIDATOR } from 'validators/system';
const OTASettingsForm: FC = () => { const OTASettings: FC = () => {
const { const {
loadData, loadData,
saveData, saveData,
@@ -35,7 +36,7 @@ const OTASettingsForm: FC = () => {
setDirtyFlags, setDirtyFlags,
blocker, blocker,
errorMessage errorMessage
} = useRest<OTASettings>({ } = useRest<OTASettingsType>({
read: SystemApi.readOTASettings, read: SystemApi.readOTASettings,
update: SystemApi.updateOTASettings update: SystemApi.updateOTASettings
}); });
@@ -61,6 +62,8 @@ const OTASettingsForm: FC = () => {
} }
}; };
useLayoutTitle('OTA');
return ( return (
<> <>
<BlockFormControlLabel <BlockFormControlLabel
@@ -117,11 +120,11 @@ const OTASettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF('OTA')} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>
); );
}; };
export default OTASettingsForm; export default OTASettings;

View File

@@ -37,7 +37,8 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
if (open) { if (open) {
void generateToken(); void generateToken();
} }
}, [open, generateToken]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
return ( return (
<Dialog sx={dialogStyle} onClose={onClose} open={!!username} fullWidth maxWidth="sm"> <Dialog sx={dialogStyle} onClose={onClose} open={!!username} fullWidth maxWidth="sm">

View File

@@ -14,9 +14,9 @@ import { useContext, useState } from 'react';
import { useBlocker } from 'react-router-dom'; import { useBlocker } from 'react-router-dom';
import GenerateToken from './GenerateToken'; import GenerateToken from './GenerateToken';
import UserForm from './UserForm'; import User from './User';
import type { FC } from 'react'; import type { FC } from 'react';
import type { SecuritySettings, User } from 'types'; import type { SecuritySettingsType, UserType } from 'types';
import * as SecurityApi from 'api/security'; import * as SecurityApi from 'api/security';
import { ButtonRow, FormLoader, MessageBox, SectionContent, BlockNavigation } from 'components'; import { ButtonRow, FormLoader, MessageBox, SectionContent, BlockNavigation } from 'components';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
@@ -24,13 +24,13 @@ import { useI18nContext } from 'i18n/i18n-react';
import { useRest } from 'utils'; import { useRest } from 'utils';
import { createUserValidator } from 'validators'; import { createUserValidator } from 'validators';
const ManageUsersForm: FC = () => { const ManageUsers: FC = () => {
const { loadData, saveData, saving, data, updateDataValue, errorMessage } = useRest<SecuritySettings>({ const { loadData, saveData, saving, data, updateDataValue, errorMessage } = useRest<SecuritySettingsType>({
read: SecurityApi.readSecuritySettings, read: SecurityApi.readSecuritySettings,
update: SecurityApi.updateSecuritySettings update: SecurityApi.updateSecuritySettings
}); });
const [user, setUser] = useState<User>(); const [user, setUser] = useState<UserType>();
const [creating, setCreating] = useState<boolean>(false); const [creating, setCreating] = useState<boolean>(false);
const [changed, setChanged] = useState<number>(0); const [changed, setChanged] = useState<number>(0);
const [generatingToken, setGeneratingToken] = useState<string>(); const [generatingToken, setGeneratingToken] = useState<string>();
@@ -86,7 +86,7 @@ const ManageUsersForm: FC = () => {
const noAdminConfigured = () => !data.users.find((u) => u.admin); const noAdminConfigured = () => !data.users.find((u) => u.admin);
const removeUser = (toRemove: User) => { const removeUser = (toRemove: UserType) => {
const users = data.users.filter((u) => u.username !== toRemove.username); const users = data.users.filter((u) => u.username !== toRemove.username);
updateDataValue({ ...data, users }); updateDataValue({ ...data, users });
setChanged(changed + 1); setChanged(changed + 1);
@@ -101,7 +101,7 @@ const ManageUsersForm: FC = () => {
}); });
}; };
const editUser = (toEdit: User) => { const editUser = (toEdit: UserType) => {
setCreating(false); setCreating(false);
setUser({ ...toEdit }); setUser({ ...toEdit });
}; };
@@ -219,7 +219,7 @@ const ManageUsersForm: FC = () => {
</Box> </Box>
<GenerateToken username={generatingToken} onClose={closeGenerateToken} /> <GenerateToken username={generatingToken} onClose={closeGenerateToken} />
<UserForm <User
user={user} user={user}
setUser={setUser} setUser={setUser}
creating={creating} creating={creating}
@@ -232,11 +232,11 @@ const ManageUsersForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.MANAGE_USERS()} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>
); );
}; };
export default ManageUsersForm; export default ManageUsers;

View File

@@ -1,7 +1,7 @@
import { Tab } from '@mui/material'; import { Tab } from '@mui/material';
import { Navigate, Routes, Route } from 'react-router-dom'; import { Navigate, Routes, Route } from 'react-router-dom';
import ManageUsersForm from './ManageUsersForm'; import ManageUsers from './ManageUsers';
import SecuritySettingsForm from './SecuritySettingsForm'; import SecuritySettings from './SecuritySettings';
import type { FC } from 'react'; import type { FC } from 'react';
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components'; import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
@@ -17,13 +17,13 @@ const Security: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="/security/users" label={LL.MANAGE_USERS()} /> <Tab value="settings" label={LL.SETTINGS_OF(LL.SECURITY(1))} />
<Tab value="/security/settings" label={LL.SETTINGS_OF(LL.SECURITY(1))} /> <Tab value="users" label={LL.MANAGE_USERS()} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="users" element={<ManageUsersForm />} /> <Route path="users" element={<ManageUsers />} />
<Route path="settings" element={<SecuritySettingsForm />} /> <Route path="settings" element={<SecuritySettings />} />
<Route path="*" element={<Navigate replace to="/security/users" />} /> <Route path="*" element={<Navigate replace to="settings" />} />
</Routes> </Routes>
</> </>
); );

View File

@@ -5,16 +5,16 @@ import { useContext, useState } from 'react';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react'; import type { FC } from 'react';
import type { SecuritySettings } from 'types'; import type { SecuritySettingsType } from 'types';
import * as SecurityApi from 'api/security'; import * as SecurityApi from 'api/security';
import { ButtonRow, FormLoader, MessageBox, SectionContent, ValidatedPasswordField, BlockNavigation } from 'components'; import { ButtonRow, FormLoader, MessageBox, SectionContent, ValidatedPasswordField, BlockNavigation } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { updateValueDirty, useRest } from 'utils'; import { updateValueDirty, useRest } from 'utils';
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators'; import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
const SecuritySettingsForm: FC = () => { const SecuritySettings: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -29,7 +29,7 @@ const SecuritySettingsForm: FC = () => {
blocker, blocker,
saveData, saveData,
errorMessage errorMessage
} = useRest<SecuritySettings>({ } = useRest<SecuritySettingsType>({
read: SecurityApi.readSecuritySettings, read: SecurityApi.readSecuritySettings,
update: SecurityApi.updateSecuritySettings update: SecurityApi.updateSecuritySettings
}); });
@@ -96,11 +96,11 @@ const SecuritySettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SETTINGS_OF(LL.SECURITY(1))} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>
); );
}; };
export default SecuritySettingsForm; export default SecuritySettings;

View File

@@ -8,7 +8,7 @@ import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator'; import type { ValidateFieldsError } from 'async-validator';
import type { FC } from 'react'; import type { FC } from 'react';
import type { User } from 'types'; import type { UserType } from 'types';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { BlockFormControlLabel, ValidatedPasswordField, ValidatedTextField } from 'components'; import { BlockFormControlLabel, ValidatedPasswordField, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
@@ -19,14 +19,14 @@ interface UserFormProps {
creating: boolean; creating: boolean;
validator: Schema; validator: Schema;
user?: User; user?: UserType;
setUser: React.Dispatch<React.SetStateAction<User | undefined>>; setUser: React.Dispatch<React.SetStateAction<UserType | undefined>>;
onDoneEditing: () => void; onDoneEditing: () => void;
onCancelEditing: () => void; onCancelEditing: () => void;
} }
const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDoneEditing, onCancelEditing }) => { const User: FC<UserFormProps> = ({ creating, validator, user, setUser, onDoneEditing, onCancelEditing }) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const updateFormValue = updateValue(setUser); const updateFormValue = updateValue(setUser);
@@ -104,4 +104,4 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
); );
}; };
export default UserForm; export default User;

View File

@@ -0,0 +1,154 @@
import AppsIcon from '@mui/icons-material/Apps';
import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
import DevicesIcon from '@mui/icons-material/Devices';
import FolderIcon from '@mui/icons-material/Folder';
import MemoryIcon from '@mui/icons-material/Memory';
import RefreshIcon from '@mui/icons-material/Refresh';
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
import SdStorageIcon from '@mui/icons-material/SdStorage';
import { Avatar, Box, Button, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@mui/material';
import { useRequest } from 'alova';
import type { FC } from 'react';
import * as SystemApi from 'api/system';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
function formatNumber(num: number) {
return new Intl.NumberFormat().format(num);
}
const ESPSystemStatus: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.STATUS_OF('ESP32'));
const { data: data, send: loadData, error } = useRequest(SystemApi.readESPSystemStatus, { force: true });
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (
<>
<List>
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
<DevicesIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="SDK" secondary={data.arduino_version + ' / ESP-IDF ' + data.sdk_version} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
<DeveloperBoardIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary="CPU"
secondary={
data.esp_platform +
'/' +
data.cpu_type +
' (rev.' +
data.cpu_rev +
', ' +
(data.cpu_cores == 1 ? 'single-core)' : 'dual-core)') +
' @ ' +
data.cpu_freq_mhz +
' Mhz'
}
/>
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
<MemoryIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.HEAP()}
secondary={formatNumber(data.free_heap) + ' KB / ' + formatNumber(data.max_alloc_heap) + ' KB '}
/>
</ListItem>
{data.psram_size !== undefined && data.free_psram !== undefined && (
<>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
<AppsIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.PSRAM()}
secondary={formatNumber(data.psram_size) + ' KB / ' + formatNumber(data.free_psram) + ' KB'}
/>
</ListItem>
</>
)}
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
<SdStorageIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.FLASH()}
secondary={
formatNumber(data.flash_chip_size) + ' KB / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz'
}
/>
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
<SdCardAlertIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.APPSIZE()}
secondary={
data.partition + ': ' + formatNumber(data.app_used) + ' KB / ' + formatNumber(data.app_free) + ' KB'
}
/>
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5f9a5f', color: 'white' }}>
<FolderIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.FILESYSTEM()}
secondary={formatNumber(data.fs_used) + ' KB / ' + formatNumber(data.fs_free) + ' KB'}
/>
</ListItem>
<Divider variant="inset" component="li" />
</List>
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
{LL.REFRESH()}
</Button>
</ButtonRow>
</Box>
</Box>
</>
);
};
return <SectionContent>{content()}</SectionContent>;
};
export default ESPSystemStatus;

View File

@@ -1,53 +1,42 @@
import { Tab } from '@mui/material'; import { Tab } from '@mui/material';
import { useContext } from 'react'; import { useContext, type FC } from 'react';
import { Navigate, Routes, Route } from 'react-router-dom'; import { Navigate, Routes, Route } from 'react-router-dom';
import OTASettingsForm from './OTASettingsForm';
import SystemLog from './SystemLog'; import SystemLog from './SystemLog';
import SystemStatusForm from './SystemStatusForm'; import SystemStatus from './SystemStatus';
import UploadFileForm from './UploadFileForm';
import type { FC } from 'react';
import { useRouterTab, RouterTabs, useLayoutTitle, RequireAdmin } from 'components'; import { useRouterTab, RouterTabs, useLayoutTitle, RequireAdmin } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import SystemActivity from 'project/SystemActivity';
const System: FC = () => { const System: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.SYSTEM(0)); useLayoutTitle(LL.SYSTEM(0));
const { me } = useContext(AuthenticatedContext);
const { routerTab } = useRouterTab(); const { routerTab } = useRouterTab();
const { me } = useContext(AuthenticatedContext);
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="/system/status" label={LL.STATUS_OF(LL.SYSTEM(1))} /> <Tab value="status" label={LL.STATUS_OF('')} />
<Tab value="/system/log" label={LL.LOG_OF(LL.SYSTEM(2))} /> <Tab value="activity" label={LL.ACTIVITY()} />
<Tab value="/system/ota" label={LL.SETTINGS_OF('OTA')} disabled={!me.admin} /> <Tab disabled={!me.admin} value="log" label={me.admin ? LL.LOG_OF('') : ''} />
<Tab value="/system/upload" label={LL.UPLOAD_DOWNLOAD()} disabled={!me.admin} />
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<SystemStatusForm />} /> <Route path="status" element={<SystemStatus />} />
<Route path="log" element={<SystemLog />} /> <Route path="activity" element={<SystemActivity />} />
<Route <Route
path="ota" path="log"
element={ element={
<RequireAdmin> <RequireAdmin>
<OTASettingsForm /> <SystemLog />
</RequireAdmin> </RequireAdmin>
} }
/> />
<Route <Route path="*" element={<Navigate replace to="status" />} />
path="upload"
element={
<RequireAdmin>
<UploadFileForm />
</RequireAdmin>
}
/>
<Route path="*" element={<Navigate replace to="/system/status" />} />
</Routes> </Routes>
</> </>
); );

View File

@@ -11,7 +11,7 @@ import { addAccessTokenParameter } from 'api/authentication';
import { EVENT_SOURCE_ROOT } from 'api/endpoints'; import { EVENT_SOURCE_ROOT } from 'api/endpoints';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { SectionContent, FormLoader, BlockFormControlLabel, BlockNavigation } from 'components'; import { SectionContent, FormLoader, BlockFormControlLabel, BlockNavigation, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { LogLevel } from 'types'; import { LogLevel } from 'types';
@@ -50,6 +50,8 @@ const levelLabel = (level: LogLevel) => {
const SystemLog: FC = () => { const SystemLog: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
useLayoutTitle(LL.LOG_OF(''));
const { loadData, data, updateDataValue, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } = const { loadData, data, updateDataValue, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
useRest<LogSettings>({ useRest<LogSettings>({
read: SystemApi.readLogSettings, read: SystemApi.readLogSettings,
@@ -232,7 +234,7 @@ const SystemLog: FC = () => {
}; };
return ( return (
<SectionContent title={LL.LOG_OF(LL.SYSTEM(2))} titleGutter id="log-window"> <SectionContent id="log-window">
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{content()} {content()}
</SectionContent> </SectionContent>

View File

@@ -0,0 +1,297 @@
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import BuildIcon from '@mui/icons-material/Build';
import CancelIcon from '@mui/icons-material/Cancel';
import CastIcon from '@mui/icons-material/Cast';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';
import MemoryIcon from '@mui/icons-material/Memory';
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
import RefreshIcon from '@mui/icons-material/Refresh';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TimerIcon from '@mui/icons-material/Timer';
import {
Avatar,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Divider,
List,
ListItem,
ListItemAvatar,
ListItemText,
useTheme
} from '@mui/material';
import { useRequest } from 'alova';
import { useContext, type FC, useState } from 'react';
import { toast } from 'react-toastify';
import { dialogStyle } from 'CustomTheme';
import * as SystemApi from 'api/system';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import ListMenuItem from 'components/layout/ListMenuItem';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
import { busConnectionStatus } from 'project/types';
import { NTPSyncStatus } from 'types';
const SystemStatus: FC = () => {
const { LL } = useI18nContext();
useLayoutTitle(LL.STATUS_OF(''));
const { me } = useContext(AuthenticatedContext);
const [confirmScan, setConfirmScan] = useState<boolean>(false);
const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus, { force: true });
const { send: scanDevices } = useRequest(EMSESP.scanDevices, {
immediate: false
});
const theme = useTheme();
const formatDurationSec = (duration_sec: number) => {
const days = Math.trunc((duration_sec * 1000) / 86400000);
const hours = Math.trunc((duration_sec * 1000) / 3600000) % 24;
const minutes = Math.trunc((duration_sec * 1000) / 60000) % 60;
const seconds = Math.trunc((duration_sec * 1000) / 1000) % 60;
let formatted = '';
if (days) {
formatted += LL.NUM_DAYS({ num: days }) + ' ';
}
if (hours) {
formatted += LL.NUM_HOURS({ num: hours }) + ' ';
}
if (minutes) {
formatted += LL.NUM_MINUTES({ num: minutes }) + ' ';
}
formatted += LL.NUM_SECONDS({ num: seconds });
return formatted;
};
function formatNumber(num: number) {
return new Intl.NumberFormat().format(num);
}
const busStatus = () => {
if (data) {
switch (data.status) {
case busConnectionStatus.BUS_STATUS_CONNECTED:
return LL.CONNECTED(0) + ' (' + formatDurationSec(data.bus_uptime) + ')';
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
return LL.TX_ISSUES();
case busConnectionStatus.BUS_STATUS_OFFLINE:
return LL.DISCONNECTED();
}
}
return 'Unknown';
};
const busStatusHighlight = () => {
switch (data.status) {
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
return theme.palette.warning.main;
case busConnectionStatus.BUS_STATUS_CONNECTED:
return theme.palette.success.main;
case busConnectionStatus.BUS_STATUS_OFFLINE:
return theme.palette.error.main;
default:
return theme.palette.warning.main;
}
};
const ntpStatus = () => {
switch (data.ntp_status) {
case NTPSyncStatus.NTP_DISABLED:
return LL.NOT_ENABLED();
case NTPSyncStatus.NTP_INACTIVE:
return LL.INACTIVE(0);
case NTPSyncStatus.NTP_ACTIVE:
return LL.ACTIVE();
default:
return LL.UNKNOWN();
}
};
const ntpStatusHighlight = () => {
switch (data.ntp_status) {
case NTPSyncStatus.NTP_DISABLED:
return theme.palette.info.main;
case NTPSyncStatus.NTP_INACTIVE:
return theme.palette.error.main;
case NTPSyncStatus.NTP_ACTIVE:
return theme.palette.success.main;
default:
return theme.palette.error.main;
}
};
const activeHighlight = (value: boolean) => (value ? theme.palette.success.main : theme.palette.info.main);
const scan = async () => {
await scanDevices()
.then(() => {
toast.info(LL.SCANNING() + '...');
})
.catch((err) => {
toast.error(err.message);
});
setConfirmScan(false);
};
const renderScanDialog = () => (
<Dialog sx={dialogStyle} open={confirmScan} onClose={() => setConfirmScan(false)}>
<DialogTitle>{LL.SCAN_DEVICES()}</DialogTitle>
<DialogContent dividers>{LL.EMS_SCAN()}</DialogContent>
<DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmScan(false)} color="secondary">
{LL.CANCEL()}
</Button>
<Button startIcon={<PermScanWifiIcon />} variant="outlined" onClick={scan} color="primary">
{LL.SCAN()}
</Button>
</DialogActions>
</Dialog>
);
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (
<>
<List>
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: busStatusHighlight(), color: 'white' }}>
<DirectionsBusIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.EMS_BUS_STATUS()} secondary={busStatus()} />
</ListItem>
<Divider variant="inset" component="li" />
<ListMenuItem
disabled={!me.admin}
icon={TimerIcon}
bgcolor="#c5572c"
label={LL.UPTIME()}
text={formatDurationSec(data.uptime)}
/>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#5d89f7', color: 'white' }}>
<DeviceHubIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.ACTIVE_DEVICES()}
secondary={
LL.NUM_DEVICES({ num: data.num_devices }) +
', ' +
LL.NUM_TEMP_SENSORS({ num: data.num_sensors }) +
', ' +
LL.NUM_ANALOG_SENSORS({ num: data.num_analogs })
}
/>
{me.admin && (
<Button
startIcon={<PermScanWifiIcon />}
variant="outlined"
color="primary"
onClick={() => setConfirmScan(true)}
>
{LL.SCAN_DEVICES()}
</Button>
)}
</ListItem>
<Divider variant="inset" component="li" />
<ListMenuItem
disabled={!me.admin}
icon={BuildIcon}
bgcolor="#134ba2"
label={LL.EMS_ESP_VER()}
text={data.emsesp_version}
to="/settings/upload"
/>
<Divider variant="inset" component="li" />
{/* TODO: translate */}
<ListMenuItem
disabled={!me.admin}
icon={MemoryIcon}
bgcolor="#68374d"
label="System Memory"
text={formatNumber(data.free_heap) + ' KB'}
to="/settings/espsystemstatus"
/>
<Divider variant="inset" component="li" />
<ListMenuItem
disabled={!me.admin}
icon={DeviceHubIcon}
bgcolor={activeHighlight(data.mqtt_status)}
label={LL.STATUS_OF('MQTT')}
text={data.mqtt_status ? LL.ACTIVE() : LL.INACTIVE(0)}
to="/settings/mqtt/status"
/>
<Divider variant="inset" component="li" />
<ListMenuItem
disabled={!me.admin}
icon={AccessTimeIcon}
bgcolor={ntpStatusHighlight()}
label={LL.STATUS_OF('NTP')}
text={ntpStatus()}
to="/settings/ntp/status"
/>
<Divider variant="inset" component="li" />
<ListMenuItem
disabled={!me.admin}
icon={CastIcon}
bgcolor={activeHighlight(data.ota_status)}
label={LL.STATUS_OF('OTA')}
text={data.ota_status ? LL.ACTIVE() : LL.INACTIVE(0)}
to="/settings/ota"
/>
<Divider variant="inset" component="li" />
<ListMenuItem
disabled={!me.admin}
icon={SettingsInputAntennaIcon}
bgcolor={activeHighlight(data.ota_status)}
label={LL.ACCESS_POINT(0)}
text={data.ap_status ? LL.ACTIVE() : LL.INACTIVE(0)}
to="/settings/ap/status"
/>
<Divider variant="inset" component="li" />
</List>
{renderScanDialog()}
<Box mt={2} display="flex" flexWrap="wrap">
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
{LL.REFRESH()}
</Button>
</Box>
</>
);
};
return <SectionContent>{content()}</SectionContent>;
};
export default SystemStatus;

View File

@@ -1,349 +0,0 @@
import AppsIcon from '@mui/icons-material/Apps';
import BuildIcon from '@mui/icons-material/Build';
import CancelIcon from '@mui/icons-material/Cancel';
import DevicesIcon from '@mui/icons-material/Devices';
import FolderIcon from '@mui/icons-material/Folder';
import MemoryIcon from '@mui/icons-material/Memory';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import RefreshIcon from '@mui/icons-material/Refresh';
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
import SdStorageIcon from '@mui/icons-material/SdStorage';
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
import ShowChartIcon from '@mui/icons-material/ShowChart';
import TimerIcon from '@mui/icons-material/Timer';
import {
Avatar,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Divider,
List,
ListItem,
ListItemAvatar,
ListItemText
} from '@mui/material';
import { useRequest } from 'alova';
import { useContext, useState } from 'react';
import { toast } from 'react-toastify';
import { FeaturesContext } from '../../contexts/features';
import RestartMonitor from './RestartMonitor';
import SystemStatusVersionDialog from './SystemStatusVersionDialog';
import type { FC } from 'react';
import { dialogStyle } from 'CustomTheme';
import * as SystemApi from 'api/system';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
function formatNumber(num: number) {
return new Intl.NumberFormat().format(num);
}
const SystemStatusForm: FC = () => {
const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext);
const [confirmRestart, setConfirmRestart] = useState<boolean>(false);
const [confirmFactoryReset, setConfirmFactoryReset] = useState<boolean>(false);
const [processing, setProcessing] = useState<boolean>(false);
const [restarting, setRestarting] = useState<boolean>();
const [versionDialogOpen, setVersionDialogOpen] = useState<boolean>(false);
const { features } = useContext(FeaturesContext);
const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false
});
const { send: factoryResetCommand } = useRequest(SystemApi.factoryReset(), {
immediate: false
});
const { send: partitionCommand } = useRequest(SystemApi.partition(), {
immediate: false
});
const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus, { force: true });
const restart = async () => {
setProcessing(true);
await restartCommand()
.then(() => {
setRestarting(true);
})
.catch((err) => {
toast.error(err.message);
})
.finally(() => {
setConfirmRestart(false);
setProcessing(false);
});
};
const factoryReset = async () => {
setProcessing(true);
await factoryResetCommand()
.then(() => {
setRestarting(true);
})
.catch((err) => {
toast.error(err.message);
})
.finally(() => {
setConfirmFactoryReset(false);
setProcessing(false);
});
};
const partition = async () => {
setProcessing(true);
await partitionCommand()
.then(() => {
setRestarting(true);
})
.catch((err) => {
toast.error(err.message);
})
.finally(() => {
setConfirmRestart(false);
setProcessing(false);
});
};
const renderRestartDialog = () => (
<Dialog sx={dialogStyle} open={confirmRestart} onClose={() => setConfirmRestart(false)}>
<DialogTitle>{LL.RESTART()}</DialogTitle>
<DialogContent dividers>{LL.RESTART_CONFIRM()}</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => setConfirmRestart(false)}
disabled={processing}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<PowerSettingsNewIcon />}
variant="outlined"
onClick={restart}
disabled={processing}
color="primary"
>
{LL.RESTART()}
</Button>
{data?.has_loader && (
<Button
startIcon={<PowerSettingsNewIcon />}
variant="outlined"
onClick={partition}
disabled={processing}
color="primary"
>
EMS-ESP-Loader
</Button>
)}
</DialogActions>
</Dialog>
);
const renderFactoryResetDialog = () => (
<Dialog sx={dialogStyle} open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}>
<DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
<DialogContent dividers>{LL.SYSTEM_FACTORY_TEXT_DIALOG()}</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => setConfirmFactoryReset(false)}
disabled={processing}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<SettingsBackupRestoreIcon />}
variant="outlined"
onClick={factoryReset}
disabled={processing}
color="error"
>
{LL.FACTORY_RESET()}
</Button>
</DialogActions>
</Dialog>
);
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (
<>
<List>
<ListItem>
<ListItemAvatar>
<Avatar>
<BuildIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.EMS_ESP_VER()} secondary={data.emsesp_version} />
<Button color="primary" onClick={() => setVersionDialogOpen(true)}>
{LL.VERSION_CHECK(0)}
</Button>
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<DevicesIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.PLATFORM()} secondary={data.esp_platform + ' / ' + data.sdk_version} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<TimerIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.UPTIME()} secondary={data.uptime} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<ShowChartIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.CPU_FREQ()} secondary={data.cpu_freq_mhz + ' MHz'} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<MemoryIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.HEAP()}
secondary={formatNumber(data.free_heap) + ' KB / ' + formatNumber(data.max_alloc_heap) + ' KB '}
/>
</ListItem>
{data.psram_size !== undefined && data.free_psram !== undefined && (
<>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<AppsIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.PSRAM()}
secondary={formatNumber(data.psram_size) + ' KB / ' + formatNumber(data.free_psram) + ' KB'}
/>
</ListItem>
</>
)}
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<SdStorageIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.FLASH()}
secondary={
formatNumber(data.flash_chip_size) + ' KB / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz'
}
/>
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<SdCardAlertIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.APPSIZE()}
secondary={formatNumber(data.app_used) + ' KB / ' + formatNumber(data.app_free) + ' KB'}
/>
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<FolderIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.FILESYSTEM()}
secondary={formatNumber(data.fs_used) + ' KB / ' + formatNumber(data.fs_free) + ' KB'}
/>
</ListItem>
<Divider variant="inset" component="li" />
</List>
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
{LL.REFRESH()}
</Button>
</ButtonRow>
</Box>
{me.admin && (
<Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow>
<Button
startIcon={<PowerSettingsNewIcon />}
variant="outlined"
color="primary"
onClick={() => setConfirmRestart(true)}
>
{LL.RESTART()}
</Button>
<Button
startIcon={<SettingsBackupRestoreIcon />}
variant="outlined"
onClick={() => setConfirmFactoryReset(true)}
color="error"
>
{LL.FACTORY_RESET()}
</Button>
</ButtonRow>
</Box>
)}
</Box>
{renderRestartDialog()}
{renderFactoryResetDialog()}
</>
);
};
return (
<SectionContent title={LL.STATUS_OF(LL.SYSTEM(1))} titleGutter>
{restarting ? <RestartMonitor /> : content()}
{data && (
<SystemStatusVersionDialog
open={versionDialogOpen}
onClose={() => setVersionDialogOpen(false)}
version={data.emsesp_version}
platform={features.platform}
/>
)}
</SectionContent>
);
};
export default SystemStatusForm;

View File

@@ -1,112 +0,0 @@
import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Link, Typography } from '@mui/material';
import { useRequest } from 'alova';
import { useCallback, useEffect } from 'react';
import { dialogStyle } from 'CustomTheme';
import * as SystemApi from 'api/system';
import MessageBox from 'components/MessageBox';
import { useI18nContext } from 'i18n/i18n-react';
type SystemStatusVersionDialogProps = {
open: boolean;
onClose: () => void;
version: string;
platform: string;
};
const SystemStatusVersionDialog = ({ open, onClose, version, platform }: SystemStatusVersionDialogProps) => {
const { LL } = useI18nContext();
const { send: getLatestVersion, data: latestVersion } = useRequest(SystemApi.getStableVersion, {
immediate: false,
force: true
});
const { send: getLatestDevVersion, data: latestDevVersion } = useRequest(SystemApi.getDevVersion, {
immediate: false,
force: true
});
const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/';
const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/';
const STABLE_RELNOTES_URL = 'https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md';
const DEV_RELNOTES_URL = 'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md';
const uploadURL = window.location.origin + '/system/upload';
const connected = latestVersion && latestDevVersion;
const getVersions = useCallback(async () => {
await getLatestVersion();
await getLatestDevVersion();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (open) {
void getVersions();
}
}, [getVersions, open]);
const getBinURL = (v: string) => 'EMS-ESP-' + v.replaceAll('.', '_') + '-' + platform.replaceAll('-', '_') + '.bin';
return (
<Dialog sx={dialogStyle} open={open} onClose={onClose}>
<DialogTitle>{LL.VERSION_CHECK(1)}</DialogTitle>
<DialogContent dividers>
<MessageBox my={0} level="info" message={LL.VERSION_ON() + ' ' + version + ' (' + platform + ')'} />
{latestVersion && (
<Box mt={2} mb={2}>
{LL.THE_LATEST()}&nbsp;<b>{LL.OFFICIAL()}</b>&nbsp;{LL.RELEASE_IS()}&nbsp;<b>{latestVersion}</b>
&nbsp;(
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
{LL.RELEASE_NOTES()}
</Link>
)&nbsp;(
<Link
target="_blank"
href={STABLE_URL + 'v' + latestVersion + '/' + getBinURL(latestVersion)}
color="primary"
>
{LL.DOWNLOAD(1)}
</Link>
)
</Box>
)}
{latestDevVersion && (
<Box mt={2} mb={2}>
{LL.THE_LATEST()}&nbsp;<b>{LL.DEVELOPMENT()}</b>&nbsp;{LL.RELEASE_IS()}&nbsp;
<b>{latestDevVersion}</b>
&nbsp;(
<Link target="_blank" href={DEV_RELNOTES_URL} color="primary">
{LL.RELEASE_NOTES()}
</Link>
)&nbsp;(
<Link target="_blank" href={DEV_URL + getBinURL(latestDevVersion)} color="primary">
{LL.DOWNLOAD(1)}
</Link>
)
</Box>
)}
{connected && (
<Box color="warning.main" mt={2}>
<Typography variant="body2">
{LL.USE()}&nbsp;
<Link href={uploadURL} color="primary">
{LL.UPLOAD()}
</Link>
&nbsp;{LL.SYSTEM_APPLY_FIRMWARE()}
</Typography>
</Box>
)}
{!connected && <MessageBox my={2} level="warning" message="No internet connection" />}
</DialogContent>
<DialogActions>
<Button variant="outlined" onClick={onClose} color="secondary">
{LL.CLOSE()}
</Button>
</DialogActions>
</Dialog>
);
};
export default SystemStatusVersionDialog;

View File

@@ -0,0 +1,308 @@
import DownloadIcon from '@mui/icons-material/GetApp';
import { Typography, Button, Box, Link } from '@mui/material';
import { useRequest } from 'alova';
import { useState, type FC } from 'react';
import { toast } from 'react-toastify';
import RestartMonitor from './RestartMonitor';
import * as SystemApi from 'api/system';
import { FormLoader, SectionContent, SingleUpload, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
const UploadDownload: FC = () => {
const { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>();
const [md5, setMd5] = useState<string>();
const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(EMSESP.getSettings(), {
immediate: false
});
const { send: getCustomizations, onSuccess: onSuccessGetCustomizations } = useRequest(EMSESP.getCustomizations(), {
immediate: false
});
const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(EMSESP.getEntities(), {
immediate: false
});
const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), {
immediate: false
});
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.API(data), {
immediate: false
});
const { data: data, send: loadData, error } = useRequest(SystemApi.readESPSystemStatus, { force: true });
const { data: latestVersion } = useRequest(SystemApi.getStableVersion, {
immediate: true,
force: true
});
const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion, {
immediate: true,
force: true
});
const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/';
const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/';
const STABLE_RELNOTES_URL = 'https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md';
const DEV_RELNOTES_URL = 'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md';
const getBinURL = (v: string) =>
'EMS-ESP-' + v.replaceAll('.', '_') + '-' + data.esp_platform.replaceAll('-', '_') + '.bin';
const {
loading: isUploading,
uploading: progress,
send: sendUpload,
onSuccess: onSuccessUpload,
abort: cancelUpload
} = useRequest(SystemApi.uploadFile, {
immediate: false,
force: true
});
onSuccessUpload(({ data }: any) => {
if (data) {
setMd5(data.md5);
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
} else {
setRestarting(true);
}
});
const startUpload = async (files: File[]) => {
await sendUpload(files[0]).catch((err) => {
if (err.message === 'The user aborted a request') {
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
} else if (err.message === 'Network Error') {
toast.warning('Invalid file extension or incompatible bin file');
} else {
toast.error(err.message);
}
});
};
const saveFile = (json: any, endpoint: string) => {
const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain'
})
);
anchor.download = 'emsesp_' + endpoint;
anchor.click();
URL.revokeObjectURL(anchor.href);
toast.info(LL.DOWNLOAD_SUCCESSFUL());
};
onSuccessGetSettings((event) => {
saveFile(event.data, 'settings.json');
});
onSuccessGetCustomizations((event) => {
saveFile(event.data, 'customizations.json');
});
onSuccessGetEntities((event) => {
saveFile(event.data, 'entities.json');
});
onSuccessGetSchedule((event) => {
saveFile(event.data, 'schedule.json');
});
onGetAPI((event) => {
saveFile(event.data, event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt');
});
const downloadSettings = async () => {
await getSettings().catch((error) => {
toast.error(error.message);
});
};
const downloadCustomizations = async () => {
await getCustomizations().catch((error) => {
toast.error(error.message);
});
};
const downloadEntities = async () => {
await getEntities().catch((error) => {
toast.error(error.message);
});
};
const downloadSchedule = async () => {
await getSchedule()
.catch((error) => {
toast.error(error.message);
})
.finally(() => {
toast.info(LL.DOWNLOAD_SUCCESSFUL());
});
};
const callAPI = async (device: string, entity: string) => {
await getAPI({ device, entity, id: 0 }).catch((error) => {
toast.error(error.message);
});
};
useLayoutTitle(LL.UPLOAD_DOWNLOAD());
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (
<>
<Typography sx={{ pb: 2 }} variant="h6" color="primary">
{LL.EMS_ESP_VER()}
</Typography>
<Box p={2} border="2px solid grey" borderRadius={2}>
{LL.VERSION_ON() + ' '}
<b>{data.emsesp_version}</b>&nbsp;({data.esp_platform})
{latestVersion && (
<Box mt={2}>
{LL.THE_LATEST()}&nbsp;{LL.OFFICIAL()}&nbsp;{LL.RELEASE_IS()}&nbsp;<b>{latestVersion}</b>
&nbsp;(
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
{LL.RELEASE_NOTES()}
</Link>
)&nbsp;(
<Link
target="_blank"
href={STABLE_URL + 'v' + latestVersion + '/' + getBinURL(latestVersion)}
color="primary"
>
{LL.DOWNLOAD(1)}
</Link>
)
</Box>
)}
{latestDevVersion && (
<Box mt={2}>
{LL.THE_LATEST()}&nbsp;{LL.DEVELOPMENT()}&nbsp;{LL.RELEASE_IS()}&nbsp;
<b>{latestDevVersion}</b>
&nbsp;(
<Link target="_blank" href={DEV_RELNOTES_URL} color="primary">
{LL.RELEASE_NOTES()}
</Link>
)&nbsp;(
<Link target="_blank" href={DEV_URL + getBinURL(latestDevVersion)} color="primary">
{LL.DOWNLOAD(1)}
</Link>
)
</Box>
)}
</Box>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.UPLOAD()}
</Typography>
<Box mb={2} color="warning.main">
<Typography variant="body2">
{LL.UPLOAD_TEXT()}
<br />
<br />
{LL.RESTART_TEXT(1)}.
</Typography>
</Box>
{md5 && (
<Box mb={2}>
<Typography variant="body2">{'MD5: ' + md5}</Typography>
</Box>
)}
<SingleUpload onDrop={startUpload} onCancel={cancelUpload} isUploading={isUploading} progress={progress} />
{!isUploading && (
<>
<Typography sx={{ pt: 4, pb: 2 }} variant="h6" color="primary">
{LL.DOWNLOAD(0)}
</Typography>
<Box color="warning.main">
<Typography mb={1} variant="body2">
{LL.HELP_INFORMATION_4()}
</Typography>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => callAPI('system', 'info')}
>
{LL.SUPPORT_INFORMATION(0)}
</Button>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => callAPI('system', 'allvalues')}
>
All Values
</Button>
</Box>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_SETTINGS_TEXT()}
</Typography>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadSettings}
>
{LL.SETTINGS_OF('')}
</Button>
</Box>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}
</Typography>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadCustomizations}
>
{LL.CUSTOMIZATIONS()}
</Button>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadEntities}
>
{LL.CUSTOM_ENTITIES(0)}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_SCHEDULE_TEXT()}
</Typography>
</Box>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadSchedule}
>
{LL.SCHEDULE(0)}
</Button>
</Box>
</>
)}
</>
);
};
return <SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>;
};
export default UploadDownload;

View File

@@ -1,223 +0,0 @@
import DownloadIcon from '@mui/icons-material/GetApp';
import { Typography, Button, Box } from '@mui/material';
import { useRequest } from 'alova';
import { useState, type FC } from 'react';
import { toast } from 'react-toastify';
import RestartMonitor from './RestartMonitor';
import * as SystemApi from 'api/system';
import { SectionContent, SingleUpload } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from 'project/api';
const UploadFileForm: FC = () => {
const { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>();
const [md5, setMd5] = useState<string>();
const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(EMSESP.getSettings(), {
immediate: false
});
const { send: getCustomizations, onSuccess: onSuccessgetCustomizations } = useRequest(EMSESP.getCustomizations(), {
immediate: false
});
const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(EMSESP.getEntities(), {
immediate: false
});
const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), {
immediate: false
});
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.API(data), {
immediate: false
});
const {
loading: isUploading,
uploading: progress,
send: sendUpload,
onSuccess: onSuccessUpload,
abort: cancelUpload
} = useRequest(SystemApi.uploadFile, {
immediate: false,
force: true
});
onSuccessUpload(({ data }: any) => {
if (data) {
setMd5(data.md5);
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
} else {
setRestarting(true);
}
});
const startUpload = async (files: File[]) => {
await sendUpload(files[0]).catch((err) => {
if (err.message === 'The user aborted a request') {
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
} else if (err.message === 'Network Error') {
toast.warning('Invalid file extension or incompatible bin file');
} else {
toast.error(err.message);
}
});
};
const saveFile = (json: any, endpoint: string) => {
const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain'
})
);
anchor.download = 'emsesp_' + endpoint;
anchor.click();
URL.revokeObjectURL(anchor.href);
toast.info(LL.DOWNLOAD_SUCCESSFUL());
};
onSuccessGetSettings((event) => {
saveFile(event.data, 'settings.json');
});
onSuccessgetCustomizations((event) => {
saveFile(event.data, 'customizations.json');
});
onSuccessGetEntities((event) => {
saveFile(event.data, 'entities.json');
});
onSuccessGetSchedule((event) => {
saveFile(event.data, 'schedule.json');
});
onGetAPI((event) => {
saveFile(event.data, event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt');
});
const downloadSettings = async () => {
await getSettings().catch((error) => {
toast.error(error.message);
});
};
const downloadCustomizations = async () => {
await getCustomizations().catch((error) => {
toast.error(error.message);
});
};
const downloadEntities = async () => {
await getEntities().catch((error) => {
toast.error(error.message);
});
};
const downloadSchedule = async () => {
await getSchedule()
.catch((error) => {
toast.error(error.message);
})
.finally(() => {
toast.info(LL.DOWNLOAD_SUCCESSFUL());
});
};
const callAPI = async (device: string, entity: string) => {
await getAPI({ device, entity, id: 0 }).catch((error) => {
toast.error(error.message);
});
};
const content = () => (
<>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.UPLOAD()}
</Typography>
<Box mb={2} color="warning.main">
<Typography variant="body2">
{LL.UPLOAD_TEXT()}
<br />
{LL.RESTART_TEXT()}.
</Typography>
</Box>
{md5 && (
<Box mb={2}>
<Typography variant="body2">{'MD5: ' + md5}</Typography>
</Box>
)}
<SingleUpload onDrop={startUpload} onCancel={cancelUpload} isUploading={isUploading} progress={progress} />
{!isUploading && (
<>
<Typography sx={{ pt: 4, pb: 2 }} variant="h6" color="primary">
{LL.DOWNLOAD(0)}&nbsp;{LL.SUPPORT_INFORMATION()}
</Typography>
<Box color="warning.main">
<Typography mb={1} variant="body2">
{LL.HELP_INFORMATION_4()}
</Typography>
</Box>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => callAPI('system', 'info')}
>
{LL.SUPPORT_INFORMATION()}
</Button>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => callAPI('system', 'allvalues')}
>
All Values
</Button>
<Typography sx={{ pt: 4, pb: 2 }} variant="h6" color="primary">
{LL.DOWNLOAD(0)}&nbsp;{LL.SETTINGS()}
</Typography>
<Box color="warning.main">
<Typography mb={1} variant="body2">
{LL.DOWNLOAD_SETTINGS_TEXT()}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSettings}>
{LL.SETTINGS_OF('')}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadCustomizations}>
{LL.CUSTOMIZATIONS()}
</Button>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={downloadEntities}
>
{LL.CUSTOM_ENTITIES(0)}
</Button>
<Box color="warning.main">
<Typography mt={2} mb={1} variant="body2">
{LL.DOWNLOAD_SCHEDULE_TEXT()}
</Typography>
</Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSchedule}>
{LL.SCHEDULE(0)}
</Button>
</>
)}
</>
);
return (
<SectionContent title={LL.UPLOAD_DOWNLOAD()} titleGutter>
{restarting ? <RestartMonitor /> : content()}
</SectionContent>
);
};
export default UploadFileForm;

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#FFF" d="M0 85.337h512v341.326H0z"/><path fill="#0052B4" d="M0 196.641h512v118.717H0z"/><path fill="#D80027" d="M0 315.359h512v111.304H0z"/><path fill="#FFF" d="M129.468 181.799v85.136c0 48.429 63.267 63.267 63.267 63.267S256 315.362 256 266.935v-85.136H129.468z"/><path fill="#D80027" d="M146.126 184.294v81.941c0 5.472 1.215 10.64 3.623 15.485h85.97c2.408-4.844 3.623-10.012 3.623-15.485v-81.941h-93.216z"/><path fill="#FFF" d="M221.301 241.427h-21.425v-14.283h14.284v-14.283h-14.284v-14.284h-14.283v14.284h-14.282v14.283h14.282v14.283h-21.426v14.284h21.426v14.283h14.283v-14.283h21.425z"/><path fill="#0052B4" d="M169.232 301.658c9.204 5.783 18.66 9.143 23.502 10.636 4.842-1.494 14.298-4.852 23.502-10.636 9.282-5.833 15.79-12.506 19.484-19.939a24.878 24.878 0 0 0-14.418-4.583c-1.956 0-3.856.232-5.682.657-3.871-8.796-12.658-14.94-22.884-14.94-10.227 0-19.013 6.144-22.884 14.94a25.048 25.048 0 0 0-5.682-.657 24.88 24.88 0 0 0-14.418 4.583c3.691 7.433 10.198 14.106 19.48 19.939z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -12,7 +12,6 @@ const de: Translation = {
USERNAME: 'Nutzername', USERNAME: 'Nutzername',
PASSWORD: 'Passwort', PASSWORD: 'Passwort',
SU_PASSWORD: 'su Passwort', SU_PASSWORD: 'su Passwort',
DASHBOARD: 'Kontrollzentrum',
SETTINGS_OF: '{0} Einstellungen', SETTINGS_OF: '{0} Einstellungen',
HELP_OF: '{0} Hilfe', HELP_OF: '{0} Hilfe',
LOGGED_IN: 'Eingeloggt als {name}', LOGGED_IN: 'Eingeloggt als {name}',
@@ -37,8 +36,6 @@ const de: Translation = {
BRAND: 'Marke', BRAND: 'Marke',
ENTITY_NAME: 'Entitätsname', ENTITY_NAME: 'Entitätsname',
VALUE: '{{Wert|wert}}', VALUE: '{{Wert|wert}}',
DEVICE_DATA: 'Gerätedaten',
SENSOR_DATA: 'Sensordaten',
DEVICES: 'Geräte', DEVICES: 'Geräte',
SENSORS: 'Sensoren', SENSORS: 'Sensoren',
RUN_COMMAND: 'Befehl ausführen', RUN_COMMAND: 'Befehl ausführen',
@@ -83,7 +80,6 @@ const de: Translation = {
FAIL: 'FEHLER', FAIL: 'FEHLER',
QUALITY: 'QUALITÄT', QUALITY: 'QUALITÄT',
SCAN_DEVICES: 'Nach neuen Geräten suchen', SCAN_DEVICES: 'Nach neuen Geräten suchen',
EMS_BUS_STATUS_TITLE: 'EMS-Bus- und Aktivitätsstatus',
SCAN: 'Suche', SCAN: 'Suche',
STATUS_NAMES: [ STATUS_NAMES: [
'EMS-Telegramme empfangen (Rx)', 'EMS-Telegramme empfangen (Rx)',
@@ -163,9 +159,7 @@ const de: Translation = {
OPTIONS: 'Optionen', OPTIONS: 'Optionen',
NAME: 'Name', NAME: 'Name',
CUSTOMIZATIONS_RESET: 'Möchten Sie wirklich alle Anpassungen entfernen, einschließlich der benutzerdefinierten Einstellungen der Temperatur- und Analogsensoren?', CUSTOMIZATIONS_RESET: 'Möchten Sie wirklich alle Anpassungen entfernen, einschließlich der benutzerdefinierten Einstellungen der Temperatur- und Analogsensoren?',
DEVICE_ENTITIES: 'Geräteentitäten',
SUPPORT_INFORMATION: 'Unterstützende Informationen', SUPPORT_INFORMATION: 'Unterstützende Informationen',
CLICK_HERE: 'Hier klicken',
HELP_INFORMATION_1: 'EMS-ESP Konfigurationsanweisungen und mehr finden Sie im Online-Wiki', HELP_INFORMATION_1: 'EMS-ESP Konfigurationsanweisungen und mehr finden Sie im Online-Wiki',
HELP_INFORMATION_2: 'Für einen Live-Community-Chat besuchen Sie unseren Discord-Server', HELP_INFORMATION_2: 'Für einen Live-Community-Chat besuchen Sie unseren Discord-Server',
HELP_INFORMATION_3: 'Um neue Funktionen anzufragen oder Fehler zu melden, eröffnen Sie ein Issue auf Github', HELP_INFORMATION_3: 'Um neue Funktionen anzufragen oder Fehler zu melden, eröffnen Sie ein Issue auf Github',
@@ -181,26 +175,22 @@ const de: Translation = {
STATUS_OF: '{0} Status', STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen', UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen',
VERSION_ON: 'Sie verwenden derzeit', VERSION_ON: 'Sie verwenden derzeit',
SYSTEM_APPLY_FIRMWARE: 'um die neue Firmware anzuwenden',
CLOSE: 'Schließen', CLOSE: 'Schließen',
USE: 'Verwenden Sie', USE: 'Verwenden Sie',
FACTORY_RESET: 'Werkseinstellung', FACTORY_RESET: 'Werkseinstellung',
SYSTEM_FACTORY_TEXT: 'EMS-ESP wurde auf Werkseinstellung gesetzt und startet als Zugangspunkt neu', SYSTEM_FACTORY_TEXT: 'EMS-ESP wurde auf Werkseinstellung gesetzt und startet als Zugangspunkt neu',
SYSTEM_FACTORY_TEXT_DIALOG: 'Sind Sie sicher alle Einstellungen auf Werkseinstellung zu setzen?', SYSTEM_FACTORY_TEXT_DIALOG: 'Sind Sie sicher alle Einstellungen auf Werkseinstellung zu setzen?',
VERSION_CHECK: 'Versionsprüfung',
THE_LATEST: 'Die neueste', THE_LATEST: 'Die neueste',
OFFICIAL: 'offizielle', OFFICIAL: 'offizielle',
DEVELOPMENT: 'Entwicklungs', DEVELOPMENT: 'Entwicklungs',
RELEASE_IS: 'release ist', // TODO translate RELEASE_IS: 'Release ist',
RELEASE_NOTES: 'Versionshinweise', RELEASE_NOTES: 'Versionshinweise',
EMS_ESP_VER: 'EMS-ESP Version', EMS_ESP_VER: 'EMS-ESP Version',
PLATFORM: 'Platform (Platform / SDK)',
UPTIME: 'System Betriebszeit', UPTIME: 'System Betriebszeit',
CPU_FREQ: 'CPU Frequenz',
HEAP: 'freier RAM Speicher (Gesamt / max. Block)', HEAP: 'freier RAM Speicher (Gesamt / max. Block)',
PSRAM: 'PSRAM (Größe / Frei)', PSRAM: 'PSRAM (Größe / Frei)',
FLASH: 'Flash Speicher (Größe / Geschwindigkeit)', FLASH: 'Flash Speicher (Größe / Geschwindigkeit)',
APPSIZE: 'Programm (Genutzt / Frei)', APPSIZE: 'Programm (Partition: Genutzt / Frei)',
FILESYSTEM: 'Dateisystem (Genutzt / Frei)', FILESYSTEM: 'Dateisystem (Genutzt / Frei)',
BUFFER_SIZE: 'max. Puffergröße', BUFFER_SIZE: 'max. Puffergröße',
COMPACT: 'Kompakte Darstellung', COMPACT: 'Kompakte Darstellung',
@@ -230,7 +220,7 @@ const de: Translation = {
BROKER: 'Broker', BROKER: 'Broker',
CLIENT: 'Client', CLIENT: 'Client',
BASE_TOPIC: 'Base', BASE_TOPIC: 'Base',
OPTIONAL: 'Optional', // TODO translate OPTIONAL: 'Optional',
FORMATTING: 'Formattierung', FORMATTING: 'Formattierung',
MQTT_FORMAT: 'Topic/Payload Format', MQTT_FORMAT: 'Topic/Payload Format',
MQTT_NEST_1: 'Eingebettet in einem Gesamttopic', MQTT_NEST_1: 'Eingebettet in einem Gesamttopic',
@@ -246,6 +236,7 @@ const de: Translation = {
MQTT_INT_THERMOSTATS: 'Thermostate', MQTT_INT_THERMOSTATS: 'Thermostate',
MQTT_INT_SOLAR: 'Solarmodule', MQTT_INT_SOLAR: 'Solarmodule',
MQTT_INT_MIXER: 'Mischermodule', MQTT_INT_MIXER: 'Mischermodule',
MQTT_INT_WATER: 'Warmwassermodule',
MQTT_QUEUE: 'MQTT Queue', MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Standard', DEFAULT: 'Standard',
MQTT_ENTITY_FORMAT: 'Entitäts-ID Format', MQTT_ENTITY_FORMAT: 'Entitäts-ID Format',
@@ -309,7 +300,7 @@ const de: Translation = {
LEAVE: 'Verlassen', LEAVE: 'Verlassen',
SCHEDULER: 'Planer', SCHEDULER: 'Planer',
SCHEDULER_HELP_1: 'Fügen Sie eigene, geplante Befehle zur Automatisierung hinzu. Vergeben Sie einen Entitätsnamen um die Aktivierung über API/Mqtt zu steuern', SCHEDULER_HELP_1: 'Fügen Sie eigene, geplante Befehle zur Automatisierung hinzu. Vergeben Sie einen Entitätsnamen um die Aktivierung über API/Mqtt zu steuern',
SCHEDULER_HELP_2: 'Use 00:00 to trigger once on start-up', // TODO translate SCHEDULER_HELP_2: '00:00 aktiviert einmalige Ausführung am Start',
SCHEDULE: 'Zeitplan', SCHEDULE: 'Zeitplan',
TIME: 'Zeit', TIME: 'Zeit',
TIMER: 'Timer', TIMER: 'Timer',
@@ -318,7 +309,7 @@ const de: Translation = {
SCHEDULE_TIMER_2: 'jede Minute', SCHEDULE_TIMER_2: 'jede Minute',
SCHEDULE_TIMER_3: 'jede Stunde', SCHEDULE_TIMER_3: 'jede Stunde',
CUSTOM_ENTITIES: 'Individuelle Entitäten', CUSTOM_ENTITIES: 'Individuelle Entitäten',
ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus', ENTITIES_HELP_1: 'Definition eigener EMS-Werte oder dynamischer Variablen',
ENTITIES_UPDATED: 'Entitäten gespeichert', ENTITIES_UPDATED: 'Entitäten gespeichert',
WRITEABLE: 'Schreibbar', WRITEABLE: 'Schreibbar',
SHOWING: 'Anzeigen von', SHOWING: 'Anzeigen von',
@@ -331,7 +322,9 @@ const de: Translation = {
ACTIVEHIGH: 'Aktiv Positiv', ACTIVEHIGH: 'Aktiv Positiv',
ACTIVELOW: 'Aktiv Negativ', ACTIVELOW: 'Aktiv Negativ',
UNCHANGED: 'Unverändert', UNCHANGED: 'Unverändert',
ALWAYS: 'Immer' ALWAYS: 'Immer',
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}' // TODO translate
}; };
export default de; export default de;

View File

@@ -12,7 +12,6 @@ const en: Translation = {
USERNAME: 'Username', USERNAME: 'Username',
PASSWORD: 'Password', PASSWORD: 'Password',
SU_PASSWORD: 'su Password', SU_PASSWORD: 'su Password',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Settings', SETTINGS_OF: '{0} Settings',
HELP_OF: '{0} Help', HELP_OF: '{0} Help',
LOGGED_IN: 'Logged in as {name}', LOGGED_IN: 'Logged in as {name}',
@@ -37,8 +36,6 @@ const en: Translation = {
BRAND: 'Brand', BRAND: 'Brand',
ENTITY_NAME: 'Entity Name', ENTITY_NAME: 'Entity Name',
VALUE: '{{Value|value}}', VALUE: '{{Value|value}}',
DEVICE_DATA: 'Device Data',
SENSOR_DATA: 'Sensor Data',
DEVICES: 'Devices', DEVICES: 'Devices',
SENSORS: 'Sensors', SENSORS: 'Sensors',
RUN_COMMAND: 'Call Command', RUN_COMMAND: 'Call Command',
@@ -63,7 +60,7 @@ const en: Translation = {
FREQ: 'Frequency', FREQ: 'Frequency',
DUTY_CYCLE: 'Duty Cycle', DUTY_CYCLE: 'Duty Cycle',
UNIT: 'UoM', UNIT: 'UoM',
STARTVALUE: 'Start value', STARTVALUE: 'Start Value',
WARN_GPIO: 'Warning: be careful when assigning a GPIO!', WARN_GPIO: 'Warning: be careful when assigning a GPIO!',
EDIT: 'Edit', EDIT: 'Edit',
SENSOR: 'Sensor', SENSOR: 'Sensor',
@@ -83,7 +80,6 @@ const en: Translation = {
FAIL: 'FAIL', FAIL: 'FAIL',
QUALITY: 'QUALITY', QUALITY: 'QUALITY',
SCAN_DEVICES: 'Scan for new devices', SCAN_DEVICES: 'Scan for new devices',
EMS_BUS_STATUS_TITLE: 'EMS Bus & Activity Status',
SCAN: 'Scan', SCAN: 'Scan',
STATUS_NAMES: [ STATUS_NAMES: [
'EMS Telegrams Received (Rx)', 'EMS Telegrams Received (Rx)',
@@ -158,18 +154,16 @@ const en: Translation = {
CUSTOMIZATIONS_HELP_4: 'exclude from MQTT and API', CUSTOMIZATIONS_HELP_4: 'exclude from MQTT and API',
CUSTOMIZATIONS_HELP_5: 'hide from Dashboard', CUSTOMIZATIONS_HELP_5: 'hide from Dashboard',
CUSTOMIZATIONS_HELP_6: 'remove from memory', CUSTOMIZATIONS_HELP_6: 'remove from memory',
SELECT_DEVICE: 'Select a device', SELECT_DEVICE: 'select a device',
SET_ALL: 'set all', SET_ALL: 'set all',
OPTIONS: 'Options', OPTIONS: 'Options',
NAME: 'Name', NAME: 'Name',
CUSTOMIZATIONS_RESET: 'Are you sure you want remove all customizations including the custom settings of the Temperature and Analog sensors?', CUSTOMIZATIONS_RESET: 'Are you sure you want remove all customizations including the custom settings of the Temperature and Analog sensors?',
DEVICE_ENTITIES: 'Device Entities',
SUPPORT_INFORMATION: 'Support Information', SUPPORT_INFORMATION: 'Support Information',
CLICK_HERE: 'Click Here',
HELP_INFORMATION_1: 'Visit the online wiki to get instructions on how to configure EMS-ESP', HELP_INFORMATION_1: 'Visit the online wiki to get instructions on how to configure EMS-ESP',
HELP_INFORMATION_2: 'For live community chat join our Discord server', HELP_INFORMATION_2: 'For live community chat join our Discord server',
HELP_INFORMATION_3: 'To request a feature or report a bug', HELP_INFORMATION_3: 'To request a feature or report a bug',
HELP_INFORMATION_4: 'Remember to download and attach your support information for a faster response when reporting an issue', HELP_INFORMATION_4: 'Download and attach your support information for a faster response when reporting an issue',
HELP_INFORMATION_5: 'EMS-ESP is a free and open-source project. Please support its future development by giving it a star on Github!', HELP_INFORMATION_5: 'EMS-ESP is a free and open-source project. Please support its future development by giving it a star on Github!',
UPLOAD: 'Upload', UPLOAD: 'Upload',
DOWNLOAD: '{{D|d|d}}ownload', DOWNLOAD: '{{D|d|d}}ownload',
@@ -181,26 +175,22 @@ const en: Translation = {
STATUS_OF: '{0} Status', STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Upload/Download', UPLOAD_DOWNLOAD: 'Upload/Download',
VERSION_ON: 'You are currently on version', VERSION_ON: 'You are currently on version',
SYSTEM_APPLY_FIRMWARE: 'to apply the new firmware',
CLOSE: 'Close', CLOSE: 'Close',
USE: 'Use', USE: 'Use',
FACTORY_RESET: 'Factory Reset', FACTORY_RESET: 'Factory Reset',
SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart', SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart',
SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset EMS-ESP to its factory defaults?', SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset EMS-ESP to its factory defaults?',
VERSION_CHECK: 'Version Check',
THE_LATEST: 'The latest', THE_LATEST: 'The latest',
OFFICIAL: 'official', OFFICIAL: 'official',
DEVELOPMENT: 'development', DEVELOPMENT: 'development',
RELEASE_IS: 'release is', RELEASE_IS: 'release is',
RELEASE_NOTES: 'release notes', RELEASE_NOTES: 'release notes',
EMS_ESP_VER: 'EMS-ESP Version', EMS_ESP_VER: 'EMS-ESP Version',
PLATFORM: 'Device (Platform / SDK)',
UPTIME: 'System Uptime', UPTIME: 'System Uptime',
CPU_FREQ: 'CPU Frequency',
HEAP: 'Heap (Free / Max Alloc)', HEAP: 'Heap (Free / Max Alloc)',
PSRAM: 'PSRAM (Size / Free)', PSRAM: 'PSRAM (Size / Free)',
FLASH: 'Flash Chip (Size / Speed)', FLASH: 'Flash Chip (Size / Speed)',
APPSIZE: 'Application (Used / Free)', APPSIZE: 'Application (Partition: Used / Free)',
FILESYSTEM: 'File System (Used / Free)', FILESYSTEM: 'File System (Used / Free)',
BUFFER_SIZE: 'Max Buffer Size', BUFFER_SIZE: 'Max Buffer Size',
COMPACT: 'Compact', COMPACT: 'Compact',
@@ -246,6 +236,7 @@ const en: Translation = {
MQTT_INT_THERMOSTATS: 'Thermostats', MQTT_INT_THERMOSTATS: 'Thermostats',
MQTT_INT_SOLAR: 'Solar Modules', MQTT_INT_SOLAR: 'Solar Modules',
MQTT_INT_MIXER: 'Mixer Modules', MQTT_INT_MIXER: 'Mixer Modules',
MQTT_INT_WATER: 'Water Modules',
MQTT_QUEUE: 'MQTT Queue', MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Default', DEFAULT: 'Default',
MQTT_ENTITY_FORMAT: 'Entity ID format', MQTT_ENTITY_FORMAT: 'Entity ID format',
@@ -318,7 +309,7 @@ const en: Translation = {
SCHEDULE_TIMER_2: 'every minute', SCHEDULE_TIMER_2: 'every minute',
SCHEDULE_TIMER_3: 'every hour', SCHEDULE_TIMER_3: 'every hour',
CUSTOM_ENTITIES: 'Custom Entities', CUSTOM_ENTITIES: 'Custom Entities',
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', ENTITIES_HELP_1: 'Define custom EMS entities or dynamic user variables',
ENTITIES_UPDATED: 'Entities Updated', ENTITIES_UPDATED: 'Entities Updated',
WRITEABLE: 'Writeable', WRITEABLE: 'Writeable',
SHOWING: 'Showing', SHOWING: 'Showing',
@@ -331,7 +322,9 @@ const en: Translation = {
ACTIVEHIGH: 'Active High', ACTIVEHIGH: 'Active High',
ACTIVELOW: 'Active Low', ACTIVELOW: 'Active Low',
UNCHANGED: 'Unchanged', UNCHANGED: 'Unchanged',
ALWAYS: 'Always' ALWAYS: 'Always',
ACTIVITY: 'Activity',
CONFIGURE: 'Configure {0}'
}; };
export default en; export default en;

View File

@@ -12,7 +12,6 @@ const fr: Translation = {
USERNAME: 'Nom d\'utilisateur', USERNAME: 'Nom d\'utilisateur',
PASSWORD: 'Mot de passe', PASSWORD: 'Mot de passe',
SU_PASSWORD: 'Mot de passe su', SU_PASSWORD: 'Mot de passe su',
DASHBOARD: 'Tableau de bord',
SETTINGS_OF: 'Paramètres {0}', SETTINGS_OF: 'Paramètres {0}',
HELP_OF: 'Aide {0}', HELP_OF: 'Aide {0}',
LOGGED_IN: 'Connecté en tant que {name}', LOGGED_IN: 'Connecté en tant que {name}',
@@ -37,8 +36,6 @@ const fr: Translation = {
BRAND: 'Marque', BRAND: 'Marque',
ENTITY_NAME: 'Nom de l\'entité', ENTITY_NAME: 'Nom de l\'entité',
VALUE: 'Valeur', VALUE: 'Valeur',
DEVICE_DATA: 'Données des appareils',
SENSOR_DATA: 'Données des capteurs',
DEVICES: 'Appareils', DEVICES: 'Appareils',
SENSORS: 'Capteurs', SENSORS: 'Capteurs',
RUN_COMMAND: 'Lancer une commande', RUN_COMMAND: 'Lancer une commande',
@@ -83,7 +80,6 @@ const fr: Translation = {
FAIL: 'ÉCHEC', FAIL: 'ÉCHEC',
QUALITY: 'QUALITÉ', QUALITY: 'QUALITÉ',
SCAN_DEVICES: 'Rechercher de nouveaux appareils', SCAN_DEVICES: 'Rechercher de nouveaux appareils',
EMS_BUS_STATUS_TITLE: 'Statut du bus et de l\'activité EMS',
SCAN: 'Scan', SCAN: 'Scan',
STATUS_NAMES: [ STATUS_NAMES: [
'Télégrammes EMS reçus (Rx)', 'Télégrammes EMS reçus (Rx)',
@@ -163,9 +159,7 @@ const fr: Translation = {
OPTIONS: 'Options', OPTIONS: 'Options',
NAME: 'Nom', NAME: 'Nom',
CUSTOMIZATIONS_RESET: 'Êtes-vous sûr de vouloir supprimer toutes les personnalisations, y compris les paramètres personnalisés des capteurs de température et analogiques ?', CUSTOMIZATIONS_RESET: 'Êtes-vous sûr de vouloir supprimer toutes les personnalisations, y compris les paramètres personnalisés des capteurs de température et analogiques ?',
DEVICE_ENTITIES: 'Entités de l\'appareil',
SUPPORT_INFORMATION: 'Information de support', SUPPORT_INFORMATION: 'Information de support',
CLICK_HERE: 'Cliquez ici',
HELP_INFORMATION_1: 'Visitez le wiki en ligne pour obtenir des instructions sur la façon de configurer EMS-ESP.', HELP_INFORMATION_1: 'Visitez le wiki en ligne pour obtenir des instructions sur la façon de configurer EMS-ESP.',
HELP_INFORMATION_2: 'Pour une discussion en direct avec la communauté, rejoignez notre serveur Discord', HELP_INFORMATION_2: 'Pour une discussion en direct avec la communauté, rejoignez notre serveur Discord',
HELP_INFORMATION_3: 'Pour demander une fonctionnalité ou signaler un problème', HELP_INFORMATION_3: 'Pour demander une fonctionnalité ou signaler un problème',
@@ -181,26 +175,22 @@ const fr: Translation = {
STATUS_OF: 'Statut {0}', STATUS_OF: 'Statut {0}',
UPLOAD_DOWNLOAD: 'Upload/Download', UPLOAD_DOWNLOAD: 'Upload/Download',
VERSION_ON: 'You are currently on', // TODO translate VERSION_ON: 'You are currently on', // TODO translate
SYSTEM_APPLY_FIRMWARE: 'pour appliquer le nouveau firmware',
CLOSE: 'Fermer', CLOSE: 'Fermer',
USE: 'Utiliser', USE: 'Utiliser',
FACTORY_RESET: 'Réinitialisation', FACTORY_RESET: 'Réinitialisation',
SYSTEM_FACTORY_TEXT: 'L\'appareil a été réinitialisé et va maintenant redémarrer', SYSTEM_FACTORY_TEXT: 'L\'appareil a été réinitialisé et va maintenant redémarrer',
SYSTEM_FACTORY_TEXT_DIALOG: 'Êtes-vous sûr de vouloir réinitialiser l\'appareil à ses paramètres d\'usine ?', SYSTEM_FACTORY_TEXT_DIALOG: 'Êtes-vous sûr de vouloir réinitialiser l\'appareil à ses paramètres d\'usine ?',
VERSION_CHECK: 'Vérification de la version',
THE_LATEST: 'La dernière', THE_LATEST: 'La dernière',
OFFICIAL: 'officielle', OFFICIAL: 'officielle',
DEVELOPMENT: 'développement', DEVELOPMENT: 'développement',
RELEASE_IS: 'release est', // TODO translate RELEASE_IS: 'release est', // TODO translate
RELEASE_NOTES: 'notes de version', RELEASE_NOTES: 'notes de version',
EMS_ESP_VER: 'Version EMS-ESP', EMS_ESP_VER: 'Version EMS-ESP',
PLATFORM: 'Appareil (Plateforme / SDK)',
UPTIME: 'Durée de fonctionnement du système', UPTIME: 'Durée de fonctionnement du système',
CPU_FREQ: 'Fréquence du CPU',
HEAP: 'Heap (Libre / Max Allouée)', HEAP: 'Heap (Libre / Max Allouée)',
PSRAM: 'PSRAM (Taille / Libre)', PSRAM: 'PSRAM (Taille / Libre)',
FLASH: 'Flash Chip (Taille / Vitesse)', FLASH: 'Flash Chip (Taille / Vitesse)',
APPSIZE: 'Application (Utilisée / Libre)', APPSIZE: 'Application (Partition: Utilisée / Libre)',
FILESYSTEM: 'File System (Utilisée / Libre)', FILESYSTEM: 'File System (Utilisée / Libre)',
BUFFER_SIZE: 'Max taille du buffer', BUFFER_SIZE: 'Max taille du buffer',
COMPACT: 'Compact', COMPACT: 'Compact',
@@ -246,6 +236,7 @@ const fr: Translation = {
MQTT_INT_THERMOSTATS: 'Thermostats', MQTT_INT_THERMOSTATS: 'Thermostats',
MQTT_INT_SOLAR: 'Modules solaires', MQTT_INT_SOLAR: 'Modules solaires',
MQTT_INT_MIXER: 'Modules mélangeurs', MQTT_INT_MIXER: 'Modules mélangeurs',
MQTT_INT_WATER: 'Modules eau',
MQTT_QUEUE: 'Queue MQTT', MQTT_QUEUE: 'Queue MQTT',
DEFAULT: 'Défaut', DEFAULT: 'Défaut',
MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate
@@ -331,7 +322,9 @@ const fr: Translation = {
ACTIVEHIGH: 'Active High', // TODO translate ACTIVEHIGH: 'Active High', // TODO translate
ACTIVELOW: 'Active Low', // TODO translate ACTIVELOW: 'Active Low', // TODO translate
UNCHANGED: 'Unchanged', // TODO translate UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always' // TODO translate ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}' // TODO translate
}; };
export default fr; export default fr;

View File

@@ -12,7 +12,6 @@ const it: Translation = {
USERNAME: 'Nome Utente', USERNAME: 'Nome Utente',
PASSWORD: 'Password', PASSWORD: 'Password',
SU_PASSWORD: 'su Password', SU_PASSWORD: 'su Password',
DASHBOARD: 'Pannello di Controllo',
SETTINGS_OF: 'Impostazioni {0}', SETTINGS_OF: 'Impostazioni {0}',
HELP_OF: '{0} Aiuto', HELP_OF: '{0} Aiuto',
LOGGED_IN: 'Registrato come {name}', LOGGED_IN: 'Registrato come {name}',
@@ -37,8 +36,6 @@ const it: Translation = {
BRAND: 'Marca', BRAND: 'Marca',
ENTITY_NAME: 'Nome Entità', ENTITY_NAME: 'Nome Entità',
VALUE: '{{Valore|valore}}', VALUE: '{{Valore|valore}}',
DEVICE_DATA: 'Device Data',
SENSOR_DATA: 'Sensor Data',
DEVICES: 'Dispositivi', DEVICES: 'Dispositivi',
SENSORS: 'Sensori', SENSORS: 'Sensori',
RUN_COMMAND: 'Esegui', RUN_COMMAND: 'Esegui',
@@ -51,7 +48,6 @@ const it: Translation = {
REMOVE: 'Elimina', REMOVE: 'Elimina',
PROBLEM_UPDATING: 'Problema aggiornamento', PROBLEM_UPDATING: 'Problema aggiornamento',
PROBLEM_LOADING: 'Problema caricamento', PROBLEM_LOADING: 'Problema caricamento',
ACCESS_DENIED: 'Accesso Negato',
ANALOG_SENSOR: 'Sensore Analogico', ANALOG_SENSOR: 'Sensore Analogico',
ANALOG_SENSORS: 'Sensori Analogici', ANALOG_SENSORS: 'Sensori Analogici',
SETTINGS: 'Settings', SETTINGS: 'Settings',
@@ -71,7 +67,6 @@ const it: Translation = {
TEMP_SENSOR: 'Sensore Temperatura', TEMP_SENSOR: 'Sensore Temperatura',
TEMP_SENSORS: 'Sensori Temperatura', TEMP_SENSORS: 'Sensori Temperatura',
WRITE_CMD_SENT: 'Scrittura comando inviata', WRITE_CMD_SENT: 'Scrittura comando inviata',
WRITE_CMD_FAILED: 'Scittura comando fallita',
EMS_BUS_WARNING: 'EMS bus disconnesso. Se questo avvertimento persiste dopo alcuni secondi prego verificare impostazioni scheda', EMS_BUS_WARNING: 'EMS bus disconnesso. Se questo avvertimento persiste dopo alcuni secondi prego verificare impostazioni scheda',
EMS_BUS_SCANNING: 'Scansione dispositivi EMS ...', EMS_BUS_SCANNING: 'Scansione dispositivi EMS ...',
CONNECTED: 'Connesso', CONNECTED: 'Connesso',
@@ -85,7 +80,6 @@ const it: Translation = {
FAIL: 'FALLITO', FAIL: 'FALLITO',
QUALITY: 'QUALITÂ', QUALITY: 'QUALITÂ',
SCAN_DEVICES: 'Scansione per nuovi dispositivi', SCAN_DEVICES: 'Scansione per nuovi dispositivi',
EMS_BUS_STATUS_TITLE: 'Bus EMS & Stato Attività',
SCAN: 'Scansione', SCAN: 'Scansione',
STATUS_NAMES: [ STATUS_NAMES: [
'Telegrammi EMS Ricevuti (Rx)', 'Telegrammi EMS Ricevuti (Rx)',
@@ -165,9 +159,7 @@ const it: Translation = {
OPTIONS: 'Opzioni', OPTIONS: 'Opzioni',
NAME: 'Nome', NAME: 'Nome',
CUSTOMIZATIONS_RESET: 'Sei sicuro di voler rimuovere tutte le personalizzazioni incluse le impostazioni personalizzate dei sensori di temperatura e analogici?', CUSTOMIZATIONS_RESET: 'Sei sicuro di voler rimuovere tutte le personalizzazioni incluse le impostazioni personalizzate dei sensori di temperatura e analogici?',
DEVICE_ENTITIES: 'Entità Dispositivo',
SUPPORT_INFORMATION: 'Informazioni di Supporto', SUPPORT_INFORMATION: 'Informazioni di Supporto',
CLICK_HERE: 'Clicca qui',
HELP_INFORMATION_1: 'Visita il wiki online per ottenere istruzioni su come configurare EMS-ESP', HELP_INFORMATION_1: 'Visita il wiki online per ottenere istruzioni su come configurare EMS-ESP',
HELP_INFORMATION_2: 'Per la chat della community dal vivo unisciti al nostro server Discord', HELP_INFORMATION_2: 'Per la chat della community dal vivo unisciti al nostro server Discord',
HELP_INFORMATION_3: 'Per richiedere una funzionalità o segnalare un errore', HELP_INFORMATION_3: 'Per richiedere una funzionalità o segnalare un errore',
@@ -183,26 +175,22 @@ const it: Translation = {
STATUS_OF: 'Stato {0}', STATUS_OF: 'Stato {0}',
UPLOAD_DOWNLOAD: 'Caricamento/Scaricamento', UPLOAD_DOWNLOAD: 'Caricamento/Scaricamento',
VERSION_ON: 'Attualmente stai eseguendo la versione', VERSION_ON: 'Attualmente stai eseguendo la versione',
SYSTEM_APPLY_FIRMWARE: 'per applicare il nuovo firmware',
CLOSE: 'Chiudere', CLOSE: 'Chiudere',
USE: 'Usa', USE: 'Usa',
FACTORY_RESET: 'Impostazioni di fabbrica', FACTORY_RESET: 'Impostazioni di fabbrica',
SYSTEM_FACTORY_TEXT: 'Il dispositivo è stato ripristinato alle impostazioni di fabbrica e ora verrà riavviato', SYSTEM_FACTORY_TEXT: 'Il dispositivo è stato ripristinato alle impostazioni di fabbrica e ora verrà riavviato',
SYSTEM_FACTORY_TEXT_DIALOG: 'Sei sicuro di voler ripristinare il dispositivo alle impostazioni di fabbrica??', SYSTEM_FACTORY_TEXT_DIALOG: 'Sei sicuro di voler ripristinare il dispositivo alle impostazioni di fabbrica??',
VERSION_CHECK: 'Verifica Versione',
THE_LATEST: 'Ultima', THE_LATEST: 'Ultima',
OFFICIAL: 'ufficiale', OFFICIAL: 'ufficiale',
DEVELOPMENT: 'sviluppo', DEVELOPMENT: 'sviluppo',
RELEASE_IS: 'rilascio é', RELEASE_IS: 'rilascio é',
RELEASE_NOTES: 'note rilascio', RELEASE_NOTES: 'note rilascio',
EMS_ESP_VER: 'Versione EMS-ESP', EMS_ESP_VER: 'Versione EMS-ESP',
PLATFORM: 'Dispositivo (Piattaforma / SDK)',
UPTIME: 'Tempo di attività del sistema', UPTIME: 'Tempo di attività del sistema',
CPU_FREQ: 'Frequenza CPU ',
HEAP: 'Heap (Free / Max Alloc)', HEAP: 'Heap (Free / Max Alloc)',
PSRAM: 'PSRAM (Size / Free)', PSRAM: 'PSRAM (Size / Free)',
FLASH: 'Flash Chip (Size / Speed)', FLASH: 'Flash Chip (Size / Speed)',
APPSIZE: 'Applicazione (Usata / Libera)', APPSIZE: 'Applicazione (Partizione: Usata / Libera)',
FILESYSTEM: 'Memoria Sistema (Usata / Libera)', FILESYSTEM: 'Memoria Sistema (Usata / Libera)',
BUFFER_SIZE: 'Max Buffer Size', BUFFER_SIZE: 'Max Buffer Size',
COMPACT: 'Compact', COMPACT: 'Compact',
@@ -248,6 +236,7 @@ const it: Translation = {
MQTT_INT_THERMOSTATS: 'Termostati', MQTT_INT_THERMOSTATS: 'Termostati',
MQTT_INT_SOLAR: 'Moduli solari', MQTT_INT_SOLAR: 'Moduli solari',
MQTT_INT_MIXER: 'Moduli Mixer', MQTT_INT_MIXER: 'Moduli Mixer',
MQTT_INT_WATER: 'Moduli Acqua',
MQTT_QUEUE: 'Coda MQTT', MQTT_QUEUE: 'Coda MQTT',
DEFAULT: 'Predefinito', DEFAULT: 'Predefinito',
MQTT_ENTITY_FORMAT: 'Formato ID entità', MQTT_ENTITY_FORMAT: 'Formato ID entità',
@@ -320,7 +309,7 @@ const it: Translation = {
SCHEDULE_TIMER_2: 'Ogni minuto', SCHEDULE_TIMER_2: 'Ogni minuto',
SCHEDULE_TIMER_3: 'Ogni ora', SCHEDULE_TIMER_3: 'Ogni ora',
CUSTOM_ENTITIES: 'Entità personalizzate', CUSTOM_ENTITIES: 'Entità personalizzate',
ENTITIES_HELP_1: 'Recupera entità personalizzate dal BUS EMS', ENTITIES_HELP_1: 'Recupera entità personalizzate dal BUS EMS', // TODO translate
ENTITIES_UPDATED: 'Entità aggiornate', ENTITIES_UPDATED: 'Entità aggiornate',
WRITEABLE: 'Scrivibile', WRITEABLE: 'Scrivibile',
SHOWING: 'Visualizza', SHOWING: 'Visualizza',
@@ -333,7 +322,9 @@ const it: Translation = {
ACTIVEHIGH: 'Active High', // TODO translate ACTIVEHIGH: 'Active High', // TODO translate
ACTIVELOW: 'Active Low', // TODO translate ACTIVELOW: 'Active Low', // TODO translate
UNCHANGED: 'Unchanged', // TODO translate UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always' // TODO translate ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}' // TODO translate
}; };
export default it; export default it;

View File

@@ -12,7 +12,6 @@ const nl: Translation = {
USERNAME: 'Gebruikersnaam', USERNAME: 'Gebruikersnaam',
PASSWORD: 'Wachtwoord', PASSWORD: 'Wachtwoord',
SU_PASSWORD: 'su Wachtwoord', SU_PASSWORD: 'su Wachtwoord',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Instellingen', SETTINGS_OF: '{0} Instellingen',
HELP_OF: '{0} Help', HELP_OF: '{0} Help',
LOGGED_IN: 'Ingelogd als {name}', LOGGED_IN: 'Ingelogd als {name}',
@@ -37,8 +36,6 @@ const nl: Translation = {
BRAND: 'Merk', BRAND: 'Merk',
ENTITY_NAME: 'Entiteit', ENTITY_NAME: 'Entiteit',
VALUE: '{{Waarde|waarde}}', VALUE: '{{Waarde|waarde}}',
SENSOR_DATA: 'Sensor data',
DEVICE_DATA: 'Apparaat data',
DEVICES: 'Apparaten', DEVICES: 'Apparaten',
SENSORS: 'Sensoren', SENSORS: 'Sensoren',
RUN_COMMAND: 'Call commando', RUN_COMMAND: 'Call commando',
@@ -83,7 +80,6 @@ const nl: Translation = {
FAIL: 'MISLUKT', FAIL: 'MISLUKT',
QUALITY: 'QUALITEIT', QUALITY: 'QUALITEIT',
SCAN_DEVICES: 'Scannen naar nieuwe apparaten', SCAN_DEVICES: 'Scannen naar nieuwe apparaten',
EMS_BUS_STATUS_TITLE: 'EMS Bus & Activiteitenstatus',
SCAN: 'Scan', SCAN: 'Scan',
STATUS_NAMES: [ STATUS_NAMES: [
'EMS Telegrammen ontvangen (Rx)', 'EMS Telegrammen ontvangen (Rx)',
@@ -126,7 +122,7 @@ const nl: Translation = {
BYPASS_TOKEN: 'API Access Token authenticatie uitschakelen', BYPASS_TOKEN: 'API Access Token authenticatie uitschakelen',
READONLY: 'Activeer read-only modus (blokkeert alle outgaande EMS Tx schrijf commandos)', READONLY: 'Activeer read-only modus (blokkeert alle outgaande EMS Tx schrijf commandos)',
UNDERCLOCK_CPU: 'Underclock CPU snelheid', UNDERCLOCK_CPU: 'Underclock CPU snelheid',
HEATINGOFF: 'Start boiler with forced heating off', // TODO translate HEATINGOFF: 'Start ketel met geforceerde verwarming uit',
ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)', ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)',
ENABLE_SHOWER_ALERT: 'Activeer Douchemelding', ENABLE_SHOWER_ALERT: 'Activeer Douchemelding',
TRIGGER_TIME: 'Trigger tijd', TRIGGER_TIME: 'Trigger tijd',
@@ -163,9 +159,7 @@ const nl: Translation = {
OPTIONS: 'Opties', OPTIONS: 'Opties',
NAME: 'Naam', NAME: 'Naam',
CUSTOMIZATIONS_RESET: 'Weet je zeker dat je alle custom aanpassingen wilt verwijderen inclusief de custom instellingen voor analoge temperatuursensoren?', CUSTOMIZATIONS_RESET: 'Weet je zeker dat je alle custom aanpassingen wilt verwijderen inclusief de custom instellingen voor analoge temperatuursensoren?',
DEVICE_ENTITIES: 'Apparaat Entiteiten',
SUPPORT_INFORMATION: 'Support Informatie', SUPPORT_INFORMATION: 'Support Informatie',
CLICK_HERE: 'Klik Hier',
HELP_INFORMATION_1: 'Bezoek de online wiki om instructies te vinden om EMS-ESP te configureren', HELP_INFORMATION_1: 'Bezoek de online wiki om instructies te vinden om EMS-ESP te configureren',
HELP_INFORMATION_2: 'Voor de live community ga naar de Discord server', HELP_INFORMATION_2: 'Voor de live community ga naar de Discord server',
HELP_INFORMATION_3: 'Om een nieuwe feature te vragen of een bug te rapporteren', HELP_INFORMATION_3: 'Om een nieuwe feature te vragen of een bug te rapporteren',
@@ -181,26 +175,22 @@ const nl: Translation = {
STATUS_OF: '{0} Status', STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Upload/Download', UPLOAD_DOWNLOAD: 'Upload/Download',
VERSION_ON: 'U bevindt zich momenteel op versie', VERSION_ON: 'U bevindt zich momenteel op versie',
SYSTEM_APPLY_FIRMWARE: 'om de nieuwe firmware te activeren',
CLOSE: 'Sluiten', CLOSE: 'Sluiten',
USE: 'Gebruik', USE: 'Gebruik',
FACTORY_RESET: 'Fabrieksinstellingen', FACTORY_RESET: 'Fabrieksinstellingen',
SYSTEM_FACTORY_TEXT: 'Gateway is gereset en start nu weer op met fabrieksinstellingen', SYSTEM_FACTORY_TEXT: 'Gateway is gereset en start nu weer op met fabrieksinstellingen',
SYSTEM_FACTORY_TEXT_DIALOG: 'Weet je zeker dat je een reset naar fabrieksinstellingen uit wilt voeren?', SYSTEM_FACTORY_TEXT_DIALOG: 'Weet je zeker dat je een reset naar fabrieksinstellingen uit wilt voeren?',
VERSION_CHECK: 'Versie Check',
THE_LATEST: 'De laatste', THE_LATEST: 'De laatste',
OFFICIAL: 'official', OFFICIAL: 'official',
DEVELOPMENT: 'development', DEVELOPMENT: 'development',
RELEASE_IS: 'release is', RELEASE_IS: 'release is',
RELEASE_NOTES: 'release notes', RELEASE_NOTES: 'release notes',
EMS_ESP_VER: 'EMS-ESP Versie', EMS_ESP_VER: 'EMS-ESP Versie',
PLATFORM: 'Apparaat (Platform / SDK)',
UPTIME: 'Systeem Uptime', UPTIME: 'Systeem Uptime',
CPU_FREQ: 'CPU Frequency',
HEAP: 'Heap (Free / Max Alloc)', HEAP: 'Heap (Free / Max Alloc)',
PSRAM: 'PSRAM (Size / Free)', PSRAM: 'PSRAM (Size / Free)',
FLASH: 'Flash Chip (Size / Speed)', FLASH: 'Flash Chip (Size / Speed)',
APPSIZE: 'Application (Used / Free)', APPSIZE: 'Application (Partition: Used / Free)',
FILESYSTEM: 'File System (Used / Free)', FILESYSTEM: 'File System (Used / Free)',
BUFFER_SIZE: 'Max Buffer Size', BUFFER_SIZE: 'Max Buffer Size',
COMPACT: 'Compact', COMPACT: 'Compact',
@@ -246,6 +236,7 @@ const nl: Translation = {
MQTT_INT_THERMOSTATS: 'Thermostaten', MQTT_INT_THERMOSTATS: 'Thermostaten',
MQTT_INT_SOLAR: 'Solar Modules', MQTT_INT_SOLAR: 'Solar Modules',
MQTT_INT_MIXER: 'Mixer Modules', MQTT_INT_MIXER: 'Mixer Modules',
MQTT_INT_WATER: 'Water Modules',
MQTT_QUEUE: 'MQTT Queue', MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Default', DEFAULT: 'Default',
MQTT_ENTITY_FORMAT: 'Entity ID formaat', MQTT_ENTITY_FORMAT: 'Entity ID formaat',
@@ -282,7 +273,7 @@ const nl: Translation = {
NETWORK_SCANNER: 'Netwerk Scanner', NETWORK_SCANNER: 'Netwerk Scanner',
NETWORK_NO_WIFI: 'Geen WiFi networken gevonden', NETWORK_NO_WIFI: 'Geen WiFi networken gevonden',
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen', NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen',
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate NETWORK_BLANK_BSSID: 'laat leeg om alleen SSID te bebruiken',
TX_POWER: 'Tx Vermogen', TX_POWER: 'Tx Vermogen',
HOSTNAME: 'Hostname', HOSTNAME: 'Hostname',
NETWORK_DISABLE_SLEEP: 'WiFi Sleep Mode uitzetten', NETWORK_DISABLE_SLEEP: 'WiFi Sleep Mode uitzetten',
@@ -323,15 +314,17 @@ const nl: Translation = {
WRITEABLE: 'Beschrijfbare', WRITEABLE: 'Beschrijfbare',
SHOWING: 'Tonen', SHOWING: 'Tonen',
SEARCH: 'Zoek', SEARCH: 'Zoek',
CERT: 'TLS rootcertificaat (laat leeg om TLS-insecure)', // TODO translate CERT: 'TLS rootcertificaat (laat leeg om TLS-insecure)',
ENABLE_TLS: 'Activeer TLS', ENABLE_TLS: 'Activeer TLS',
ON: 'On', // TODO translate ON: 'Aan',
OFF: 'Off', // TODO translate OFF: 'Uit',
POLARITY: 'Polarity', // TODO translate POLARITY: 'Polariteit',
ACTIVEHIGH: 'Active High', // TODO translate ACTIVEHIGH: 'Actiev Hoog',
ACTIVELOW: 'Active Low', // TODO translate ACTIVELOW: 'Actiev Laag',
UNCHANGED: 'Unchanged', // TODO translate UNCHANGED: 'Ongewijzigd',
ALWAYS: 'Always' // TODO translate ALWAYS: 'Altijd',
ACTIVITY: 'Activiteit',
CONFIGURE: '{0} Configureren'
}; };
export default nl; export default nl;

View File

@@ -12,7 +12,6 @@ const no: Translation = {
USERNAME: 'Brukernavn', USERNAME: 'Brukernavn',
PASSWORD: 'Passord', PASSWORD: 'Passord',
SU_PASSWORD: 'su Passord', SU_PASSWORD: 'su Passord',
DASHBOARD: 'Dashboard',
SETTINGS_OF: '{0} Innstillinger', SETTINGS_OF: '{0} Innstillinger',
HELP_OF: '{0} Hjelp', HELP_OF: '{0} Hjelp',
LOGGED_IN: 'Logget in som {name}', LOGGED_IN: 'Logget in som {name}',
@@ -37,8 +36,6 @@ const no: Translation = {
BRAND: 'Fabrikat', BRAND: 'Fabrikat',
ENTITY_NAME: 'Objektsnavn', ENTITY_NAME: 'Objektsnavn',
VALUE: '{{Verdi|verdi}}', VALUE: '{{Verdi|verdi}}',
DEVICE_DATA: 'Enheterdata',
SENSOR_DATA: 'Sensordata',
DEVICES: 'Enheter', DEVICES: 'Enheter',
SENSORS: 'Sensorer', SENSORS: 'Sensorer',
RUN_COMMAND: 'Kjør kommando', RUN_COMMAND: 'Kjør kommando',
@@ -83,7 +80,6 @@ const no: Translation = {
FAIL: 'MISLYKKET', FAIL: 'MISLYKKET',
QUALITY: 'KVALITET', QUALITY: 'KVALITET',
SCAN_DEVICES: 'Søk etter nye enheter', SCAN_DEVICES: 'Søk etter nye enheter',
EMS_BUS_STATUS_TITLE: 'EMS Buss & Aktivitet Status',
SCAN: 'Søk', SCAN: 'Søk',
STATUS_NAMES: [ STATUS_NAMES: [
'EMS Telegrammer Mottatt (Rx)', 'EMS Telegrammer Mottatt (Rx)',
@@ -163,9 +159,7 @@ const no: Translation = {
OPTIONS: 'Alternativ', OPTIONS: 'Alternativ',
NAME: 'Navn', NAME: 'Navn',
CUSTOMIZATIONS_RESET: 'Er du sikker på att du vil fjerne tilpassninger inkludert innstillinger for Temperatur og Analoge sensorer?', CUSTOMIZATIONS_RESET: 'Er du sikker på att du vil fjerne tilpassninger inkludert innstillinger for Temperatur og Analoge sensorer?',
DEVICE_ENTITIES: 'Enhets objekter',
SUPPORT_INFORMATION: 'Supportinformasjon', SUPPORT_INFORMATION: 'Supportinformasjon',
CLICK_HERE: 'Klikk her',
HELP_INFORMATION_1: 'Besøk wiki for instruksjoner for å konfigurere EMS-ESP', HELP_INFORMATION_1: 'Besøk wiki for instruksjoner for å konfigurere EMS-ESP',
HELP_INFORMATION_2: 'For community-support besøk vår Discord-server', HELP_INFORMATION_2: 'For community-support besøk vår Discord-server',
HELP_INFORMATION_3: 'For å be om en ny funksjon eller melde feil', HELP_INFORMATION_3: 'For å be om en ny funksjon eller melde feil',
@@ -181,26 +175,22 @@ const no: Translation = {
STATUS_OF: '{0} Status', STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Opp/Nedlasting', UPLOAD_DOWNLOAD: 'Opp/Nedlasting',
VERSION_ON: 'You are currently on', // TODO translate VERSION_ON: 'You are currently on', // TODO translate
SYSTEM_APPLY_FIRMWARE: 'for å aktivere ny firmware',
CLOSE: 'Steng', CLOSE: 'Steng',
USE: 'Bruk', USE: 'Bruk',
FACTORY_RESET: 'Sett tilbake til fabrikkinstilling', FACTORY_RESET: 'Sett tilbake til fabrikkinstilling',
SYSTEM_FACTORY_TEXT: 'Enhet har blitt satt tilbake til fabrikkinstilling og vil restarte', SYSTEM_FACTORY_TEXT: 'Enhet har blitt satt tilbake til fabrikkinstilling og vil restarte',
SYSTEM_FACTORY_TEXT_DIALOG: 'Er du sikker på at du vil resette enheten til fabrikkinstillinger?', SYSTEM_FACTORY_TEXT_DIALOG: 'Er du sikker på at du vil resette enheten til fabrikkinstillinger?',
VERSION_CHECK: 'Versjonsjekk',
THE_LATEST: 'Den nyeste', THE_LATEST: 'Den nyeste',
OFFICIAL: 'official', OFFICIAL: 'official',
DEVELOPMENT: 'development', DEVELOPMENT: 'development',
RELEASE_IS: 'release er', RELEASE_IS: 'release er',
RELEASE_NOTES: 'release notes', RELEASE_NOTES: 'release notes',
EMS_ESP_VER: 'EMS-ESP Version', EMS_ESP_VER: 'EMS-ESP Version',
PLATFORM: 'Enhet (Platform / SDK)',
UPTIME: 'System Oppetid', UPTIME: 'System Oppetid',
CPU_FREQ: 'CPU Frekvens',
HEAP: 'Heap (Ledig / Max Allokert)', HEAP: 'Heap (Ledig / Max Allokert)',
PSRAM: 'PSRAM (Størrelse / Ledig)', PSRAM: 'PSRAM (Størrelse / Ledig)',
FLASH: 'Flash Chip (Størrelse / Hastighet)', FLASH: 'Flash Chip (Størrelse / Hastighet)',
APPSIZE: 'Applikasjon (Brukt / Ledig)', APPSIZE: 'Applikasjon (Partition: Brukt / Ledig)',
FILESYSTEM: 'File System (Brukt / Ledig)', FILESYSTEM: 'File System (Brukt / Ledig)',
BUFFER_SIZE: 'Max Buffer Størrelse', BUFFER_SIZE: 'Max Buffer Størrelse',
COMPACT: 'Komprimere', COMPACT: 'Komprimere',
@@ -246,6 +236,7 @@ const no: Translation = {
MQTT_INT_THERMOSTATS: 'Termostat', MQTT_INT_THERMOSTATS: 'Termostat',
MQTT_INT_SOLAR: 'Solpaneler', MQTT_INT_SOLAR: 'Solpaneler',
MQTT_INT_MIXER: 'Blandeventil', MQTT_INT_MIXER: 'Blandeventil',
MQTT_INT_WATER: 'Water Modules', // TODO translate
MQTT_QUEUE: 'MQTT Queue', MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Standard', DEFAULT: 'Standard',
MQTT_ENTITY_FORMAT: 'Enhets ID format', MQTT_ENTITY_FORMAT: 'Enhets ID format',
@@ -331,7 +322,9 @@ const no: Translation = {
ACTIVEHIGH: 'Active High', // TODO translate ACTIVEHIGH: 'Active High', // TODO translate
ACTIVELOW: 'Active Low', // TODO translate ACTIVELOW: 'Active Low', // TODO translate
UNCHANGED: 'Unchanged', // TODO translate UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always' // TODO translate ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}' // TODO translate
}; };
export default no; export default no;

View File

@@ -12,7 +12,6 @@ const pl: BaseTranslation = {
USERNAME: '{{Użytkownik|Nazwa użytkownika|}}', USERNAME: '{{Użytkownik|Nazwa użytkownika|}}',
PASSWORD: 'Hasło', PASSWORD: 'Hasło',
SU_PASSWORD: 'Hasło "su"', SU_PASSWORD: 'Hasło "su"',
DASHBOARD: 'Pulpit',
SETTINGS_OF: 'Ustawienia {0}', SETTINGS_OF: 'Ustawienia {0}',
HELP_OF: 'Pomoc {0}', HELP_OF: 'Pomoc {0}',
LOGGED_IN: 'Zalogowano użytkownika {name}.', LOGGED_IN: 'Zalogowano użytkownika {name}.',
@@ -37,8 +36,6 @@ const pl: BaseTranslation = {
VERSION: 'Wersja', VERSION: 'Wersja',
ENTITY_NAME: '{{N|n|}}azwa encji', ENTITY_NAME: '{{N|n|}}azwa encji',
VALUE: '{{W|w|}}artość', VALUE: '{{W|w|}}artość',
DEVICE_DATA: 'Dane z urządzeń',
SENSOR_DATA: 'Dane z czujników',
DEVICES: 'Urządzenia', DEVICES: 'Urządzenia',
SENSORS: 'Czujniki', SENSORS: 'Czujniki',
RUN_COMMAND: 'Wykonaj komendę', RUN_COMMAND: 'Wykonaj komendę',
@@ -53,7 +50,7 @@ const pl: BaseTranslation = {
PROBLEM_LOADING: 'Problem z załadowaniem!', PROBLEM_LOADING: 'Problem z załadowaniem!',
ANALOG_SENSOR: '{{u|u||ustawienia u|ustawień u}}rządzeni{{a podłączonego do EMS-ESP|e||a podłączonego do EMS-ESP|a podłączonego do EMS-ESP}}', ANALOG_SENSOR: '{{u|u||ustawienia u|ustawień u}}rządzeni{{a podłączonego do EMS-ESP|e||a podłączonego do EMS-ESP|a podłączonego do EMS-ESP}}',
ANALOG_SENSORS: 'Urządzenia podłączone do EMS-ESP', ANALOG_SENSORS: 'Urządzenia podłączone do EMS-ESP',
SETTINGS: 'ustawienia', SETTINGS: 'ustawie{{nia|ń|}}',
UPDATED_OF: 'Zaktualizowano {0}.', UPDATED_OF: 'Zaktualizowano {0}.',
UPDATE_OF: 'Aktualizacja {0}', UPDATE_OF: 'Aktualizacja {0}',
REMOVED_OF: 'Usunięto ustawienia {0}.', REMOVED_OF: 'Usunięto ustawienia {0}.',
@@ -83,7 +80,6 @@ const pl: BaseTranslation = {
FAIL: 'Nieudane', FAIL: 'Nieudane',
QUALITY: 'Jakość', QUALITY: 'Jakość',
SCAN_DEVICES: 'Wyszukiwanie nowych urządzeń', SCAN_DEVICES: 'Wyszukiwanie nowych urządzeń',
EMS_BUS_STATUS_TITLE: 'Aktywność',
SCAN: 'Skanuj', SCAN: 'Skanuj',
STATUS_NAMES: [ STATUS_NAMES: [
'EMS, telegramy odebrane (Rx)', 'EMS, telegramy odebrane (Rx)',
@@ -126,7 +122,7 @@ const pl: BaseTranslation = {
BYPASS_TOKEN: 'Pomiń autoryzację tokenem w wywołaniach API', BYPASS_TOKEN: 'Pomiń autoryzację tokenem w wywołaniach API',
READONLY: 'Tryb pracy "tylko do odczytu" (blokuje wszystkie komendy zapisu na magistralę EMS)', READONLY: 'Tryb pracy "tylko do odczytu" (blokuje wszystkie komendy zapisu na magistralę EMS)',
UNDERCLOCK_CPU: 'Obniż taktowanie CPU', UNDERCLOCK_CPU: 'Obniż taktowanie CPU',
HEATINGOFF: 'Uruchom boiler z wymuszonym wyłączonym grzaniem', HEATINGOFF: 'Uruchom kocioł z wymuszonym wyłączonym grzaniem',
ENABLE_SHOWER_TIMER: 'Aktywuj minutnik prysznica', ENABLE_SHOWER_TIMER: 'Aktywuj minutnik prysznica',
ENABLE_SHOWER_ALERT: 'Aktywuj alarm prysznica', ENABLE_SHOWER_ALERT: 'Aktywuj alarm prysznica',
TRIGGER_TIME: 'Wyzwalaj po czasie', TRIGGER_TIME: 'Wyzwalaj po czasie',
@@ -146,13 +142,13 @@ const pl: BaseTranslation = {
MINUTES: 'minut', MINUTES: 'minut',
HOURS: 'godzin', HOURS: 'godzin',
RESTART: 'Restart', RESTART: 'Restart',
RESTART_TEXT: 'Aby zastosować wprowadzone zmiany interfejs EMS-ESP musi zostać zrestartowany.', RESTART_TEXT: 'Aby zastosować wprowadzone zmiany, interfejs EMS-ESP {{musi zostać|zostanie|}} uruchomiony ponowni{{e.|e|}}',
RESTART_CONFIRM: 'Na pewno chcesz zrestartować interfejs EMS-ESP?', RESTART_CONFIRM: 'Na pewno chcesz zrestartować interfejs EMS-ESP?',
COMMAND: '{{Komenda|KOMENDA|}}', COMMAND: '{{Komenda|KOMENDA|}}',
CUSTOMIZATIONS_RESTART: 'Wszystkie personalizacje zostały usunięte. Restartuję...', CUSTOMIZATIONS_RESTART: 'Wszystkie personalizacje zostały usunięte. Restartuję...',
CUSTOMIZATIONS_FULL: 'Wybrano za dużo obiektów. Wprowadź zmiany w mniejszych partiach.', CUSTOMIZATIONS_FULL: 'Wybrano za dużo obiektów. Wprowadź zmiany w mniejszych partiach.',
CUSTOMIZATIONS_SAVED: 'Personalizacje zostały zapisane.', CUSTOMIZATIONS_SAVED: 'Personalizacje zostały zapisane.',
CUSTOMIZATIONS_HELP_1: 'Wybierz urządzenie EMS, dostosuj opcje lub kliknij by zmienić nazwę encji.', CUSTOMIZATIONS_HELP_1: 'Wybierz urządzenie EMS, a następnie dostosuj opcje lub kliknij na nazwie encji by tę nazwę zmienić',
CUSTOMIZATIONS_HELP_2: 'oznacz jako ulubioną', CUSTOMIZATIONS_HELP_2: 'oznacz jako ulubioną',
CUSTOMIZATIONS_HELP_3: 'zablokuj akcje zapisu', CUSTOMIZATIONS_HELP_3: 'zablokuj akcje zapisu',
CUSTOMIZATIONS_HELP_4: 'wyklucz z MQTT i API', CUSTOMIZATIONS_HELP_4: 'wyklucz z MQTT i API',
@@ -163,13 +159,11 @@ const pl: BaseTranslation = {
OPTIONS: 'Opcje', OPTIONS: 'Opcje',
NAME: '{{Nazwa|nazwa|}}', NAME: '{{Nazwa|nazwa|}}',
CUSTOMIZATIONS_RESET: 'Na pewno chcesz usunąć wszystkie personalizacje łącznie z ustawieniami dla czujników temperatury 1-Wire® i urządzeń podłączonych do EMS-ESP?', CUSTOMIZATIONS_RESET: 'Na pewno chcesz usunąć wszystkie personalizacje łącznie z ustawieniami dla czujników temperatury 1-Wire® i urządzeń podłączonych do EMS-ESP?',
DEVICE_ENTITIES: 'Encje urządzenia', SUPPORT_INFORMATION: '{{I|i|}}nformacj{{e|i|}} o systemie',
SUPPORT_INFORMATION: 'Informacje dotyczące wsparcia', HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP, skorzystaj z wiki w internecie',
CLICK_HERE: 'Kliknij tu',
HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP skorzystaj z wiki w internecie',
HELP_INFORMATION_2: 'Aby dołączyć do naszego serwera Discord i komunikować się na żywo ze społecznością', HELP_INFORMATION_2: 'Aby dołączyć do naszego serwera Discord i komunikować się na żywo ze społecznością',
HELP_INFORMATION_3: 'Aby zaproponować nową funkcjonalność lub zgłosić problem', HELP_INFORMATION_3: 'Aby zaproponować nową funkcjonalność lub zgłosić problem',
HELP_INFORMATION_4: 'Zgłaszając problem, nie zapomnij dołączyć informacji o swoim systemie!', HELP_INFORMATION_4: 'Zgłaszając problem, nie zapomnij pobrać i dołączyć informacji o swoim systemie!',
HELP_INFORMATION_5: 'EMS-ESP jest darmowym projektem typu open-source. Aby go wesprzeć, rozważ przyznanie nam gwiazdki na Github!', HELP_INFORMATION_5: 'EMS-ESP jest darmowym projektem typu open-source. Aby go wesprzeć, rozważ przyznanie nam gwiazdki na Github!',
UPLOAD: 'Wysyłanie', UPLOAD: 'Wysyłanie',
DOWNLOAD: '{{P|p||P}}obier{{anie|z||z}}', DOWNLOAD: '{{P|p||P}}obier{{anie|z||z}}',
@@ -181,34 +175,30 @@ const pl: BaseTranslation = {
STATUS_OF: 'Status {0}', STATUS_OF: 'Status {0}',
UPLOAD_DOWNLOAD: 'Przesyłanie plików', UPLOAD_DOWNLOAD: 'Przesyłanie plików',
VERSION_ON: 'Aktualnie używasz', VERSION_ON: 'Aktualnie używasz',
SYSTEM_APPLY_FIRMWARE: '',
CLOSE: 'Zamknij', CLOSE: 'Zamknij',
USE: 'Aby zaktualizować firmware skorzystaj z funkcji', USE: 'Aby zaktualizować firmware skorzystaj z funkcji',
FACTORY_RESET: 'Ustawienia fabryczne', FACTORY_RESET: 'Ustawienia fabryczne',
SYSTEM_FACTORY_TEXT: 'Interfejs EMS-ESP został przywrócony do ustawień fabrycznych i zostanie teraz ponownie uruchomiony.', SYSTEM_FACTORY_TEXT: 'Interfejs EMS-ESP został przywrócony do ustawień fabrycznych i zostanie teraz ponownie uruchomiony.',
SYSTEM_FACTORY_TEXT_DIALOG: 'Na pewno chcesz przywrócić ustawienia fabryczne interfejsu EMS-ESP? ', SYSTEM_FACTORY_TEXT_DIALOG: 'Na pewno chcesz przywrócić ustawienia fabryczne interfejsu EMS-ESP? ',
VERSION_CHECK: 'Sprawd{{ź|zanie|}} wersj{{ę|i|}}',
THE_LATEST: 'Najnowsze', THE_LATEST: 'Najnowsze',
OFFICIAL: 'oficjalne', OFFICIAL: 'oficjalne',
DEVELOPMENT: 'testowe', DEVELOPMENT: 'testowe',
RELEASE_IS: 'wydanie to', RELEASE_IS: 'wydanie to',
RELEASE_NOTES: 'lista zmian', RELEASE_NOTES: 'lista zmian',
EMS_ESP_VER: 'Wersja EMS-ESP', EMS_ESP_VER: 'Wersja EMS-ESP',
PLATFORM: 'Urządzenie (platforma / SDK)',
UPTIME: 'Czas działania systemu', UPTIME: 'Czas działania systemu',
CPU_FREQ: 'Taktowanie CPU',
HEAP: 'HEAP (wolne / maksymalny przydział)', HEAP: 'HEAP (wolne / maksymalny przydział)',
PSRAM: 'PSRAM (rozmiar / wolne)', PSRAM: 'PSRAM (rozmiar / wolne)',
FLASH: 'FLASH (rozmiar / taktowanie)', FLASH: 'FLASH (rozmiar / taktowanie)',
APPSIZE: 'Aplikacja (wykorzystane / wolne)', APPSIZE: 'Aplikacja (partycja: wykorzystane / wolne)',
FILESYSTEM: 'System plików (wykorzystane / wolne)', FILESYSTEM: 'System plików (wykorzystane / wolne)',
BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)', BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)',
COMPACT: 'Kompaktowy', COMPACT: 'Kompaktowy',
ENABLE_OTA: 'Aktywuj aktualizację OTA', ENABLE_OTA: 'Aktywuj aktualizację OTA',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Pobierz personalizacje.', DOWNLOAD_CUSTOMIZATION_TEXT: 'Pobierz personalizacje.',
DOWNLOAD_SCHEDULE_TEXT: 'Pobierz harmonogram zdarzeń.', DOWNLOAD_SCHEDULE_TEXT: 'Pobierz harmonogram zdarzeń.',
DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uważaj jeśli udostępniasz plik z ustawieniami, ponieważ zawiera on hasła oraz inne wrażliwe informacje!', DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uwaga! Plik z ustawieniami zawiera hasła oraz inne wrażliwe informacje systemowe! Nie udostepniaj go pochopnie!',
UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji (.md5).', UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji z sumą kontrolną (.md5).',
UPLOADING: 'Wysłano', UPLOADING: 'Wysłano',
UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij', UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij',
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!', ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
@@ -246,6 +236,7 @@ const pl: BaseTranslation = {
MQTT_INT_THERMOSTATS: 'Termostaty', MQTT_INT_THERMOSTATS: 'Termostaty',
MQTT_INT_SOLAR: 'Panele solarne', MQTT_INT_SOLAR: 'Panele solarne',
MQTT_INT_MIXER: 'Mieszacze', MQTT_INT_MIXER: 'Mieszacze',
MQTT_INT_WATER: 'Woda',
MQTT_QUEUE: 'Kolejka MQTT', MQTT_QUEUE: 'Kolejka MQTT',
DEFAULT: '{{Pozostałe|Domyślna|}}', DEFAULT: '{{Pozostałe|Domyślna|}}',
MQTT_ENTITY_FORMAT: 'Format "Entity ID"', MQTT_ENTITY_FORMAT: 'Format "Entity ID"',
@@ -320,10 +311,10 @@ const pl: BaseTranslation = {
CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}', CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}',
ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje dla magistrali EMS.', ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje dla magistrali EMS.',
ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.', ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.',
WRITEABLE: 'zapisywalna', WRITEABLE: 'Zapisywalna',
SHOWING: 'Wyświetlane', SHOWING: 'Wyświetlane',
SEARCH: 'Szukaj', SEARCH: 'Szukaj',
CERT: 'Certyfikat główny TLS (pozostaw puste zby wyłączyć TLS-insecure)', CERT: 'Certyfikat główny TLS (pozostaw puste dla TLS-insecure)',
ENABLE_TLS: 'Włącz wsparcie dla TLS', ENABLE_TLS: 'Włącz wsparcie dla TLS',
ON: 'włączony', ON: 'włączony',
OFF: 'wyłączony', OFF: 'wyłączony',
@@ -331,7 +322,9 @@ const pl: BaseTranslation = {
ACTIVEHIGH: 'Wyzwalany stanem wysokim', ACTIVEHIGH: 'Wyzwalany stanem wysokim',
ACTIVELOW: 'Wyzwalany stanem niskim', ACTIVELOW: 'Wyzwalany stanem niskim',
UNCHANGED: 'Zachowaj stan', UNCHANGED: 'Zachowaj stan',
ALWAYS: 'Zawsze' ALWAYS: 'Zawsze',
ACTIVITY: 'Aktywność',
CONFIGURE: 'Konfiguracja {0}'
}; };
export default pl; export default pl;

View File

@@ -0,0 +1,331 @@
import type { Translation } from '../i18n-types';
/* prettier-ignore */
/* eslint-disable */
const sk: Translation = {
LANGUAGE: 'Jazyk',
RETRY: 'Opakovať',
LOADING: 'Načítanie',
IS_REQUIRED: '{0} je požadovaných',
SIGN_IN: 'Prihlásiť sa',
SIGN_OUT: 'Odhlásiť sa',
USERNAME: 'Užívateľské meno',
PASSWORD: 'Heslo',
SU_PASSWORD: 'su heslo',
SETTINGS_OF: '{0} Nastavenia',
HELP_OF: '{0} Pomoc',
LOGGED_IN: 'Prihlásený ako {name}',
PLEASE_SIGNIN: 'Ak chcete pokračovať, prihláste sa',
UPLOAD_SUCCESSFUL: 'Nahratie úspešné',
DOWNLOAD_SUCCESSFUL: 'Stiahnutie úspešné',
INVALID_LOGIN: 'Nesprávne prihlasovacie údaje',
NETWORK: 'Sieť',
SECURITY: 'Zabezpečenie',
ONOFF_CAP: 'ZAP/VYP',
ONOFF: 'zap/vyp',
TYPE: 'Typ',
DESCRIPTION: 'Popis',
ENTITIES: 'Entity',
REFRESH: 'Obnoviť',
EXPORT: 'Export',
DEVICE_DETAILS: 'Detaily zariadenia',
ID_OF: '{0} ID',
DEVICE: 'Zariadenie',
PRODUCT: 'Produkt',
VERSION: 'Verzia',
BRAND: 'Značka',
ENTITY_NAME: 'Názov entity',
VALUE: '{{Hodnota|hodnota}}',
DEVICES: 'Zariadenia',
SENSORS: 'Snímače',
RUN_COMMAND: 'Volať príkaz',
CHANGE_VALUE: 'Zmena hodnoty',
CANCEL: 'Zrušiť',
RESET: 'Reset',
APPLY_CHANGES: 'Aplikovať zmeny ({0})',
UPDATE: 'Aktualizovať',
EXECUTE: 'Spustiť',
REMOVE: 'Odstrániť',
PROBLEM_UPDATING: 'Problém s aktualizáciou',
PROBLEM_LOADING: 'Problém s načítaním',
ANALOG_SENSOR: 'Analógový snímač',
ANALOG_SENSORS: 'Analógové snímače',
SETTINGS: 'Nastavenia',
UPDATED_OF: '{0} aktualizovaných',
UPDATE_OF: '{0} aktualizované',
REMOVED_OF: '{0} odstránených',
DELETION_OF: '{0} zmazaných',
OFFSET: 'Ofset',
FACTOR: 'Faktor',
FREQ: 'Frekvencia',
DUTY_CYCLE: 'Duty Cycle',
UNIT: 'UoM',
STARTVALUE: 'Počiatočná hodnota',
WARN_GPIO: 'Upozornenie: Buďte opatrní pri priraďovaní GPIO!',
EDIT: 'Editovať',
SENSOR: 'Snímač',
TEMP_SENSOR: 'Snímač teploty',
TEMP_SENSORS: 'Snímače teploty',
WRITE_CMD_SENT: 'Príkaz zápisu bol odoslaný',
EMS_BUS_WARNING: 'Zbernica EMS odpojená. Ak toto upozornenie pretrváva aj po niekoľkých sekundách, skontrolujte nastavenia a profil dosky',
EMS_BUS_SCANNING: 'Zisťovanie EMS zariadení...',
CONNECTED: 'Pripojené',
TX_ISSUES: 'Problémy s Tx skontrolujte Tx režim',
DISCONNECTED: 'Odpojené',
EMS_SCAN: 'Naozaj chcete spustiť úplnú kontrolu zariadenia zbernice EMS?',
EMS_BUS_STATUS: 'Stav zbernice EMS',
ACTIVE_DEVICES: 'Aktívne zariadenia a snímače',
EMS_DEVICE: 'EMS zariadenie',
SUCCESS: 'ÚSPEŠNÉ',
FAIL: 'ZLÝHANIE',
QUALITY: 'KVALITA',
SCAN_DEVICES: 'Scan pre nové zariadenia',
SCAN: 'Scan',
STATUS_NAMES: [
'EMS Telegramy prijaté (Rx)',
'EMS Čítania (Tx)',
'EMS Zápisy (Tx)',
'Čítanie snímačov teploty',
'Čítanie analógových snímačov',
'MQTT Publikovanie',
'Externé API volania',
'Syslog správy'
],
NUM_DEVICES: '{num} Zariaden{{í|ie|ia|ia|í|í}}',
NUM_TEMP_SENSORS: '{num} Teplotn{{ých|ý|é|é|ých|ých}} sníma{{čov|č|če|če|čov|čov}}',
NUM_ANALOG_SENSORS: '{num} Analogov{{ých|ý|é|é|ých|ých}} sníma{{čov|č|če|če|čov|čov}}',
NUM_DAYS: '{num} d{{ní|eň|ní|ní|ní|ní}}',
NUM_SECONDS: '{num} sek{{únd|unda|undy|undy|únd|únd}}',
NUM_HOURS: '{num} hod{{ín|ina|iny|iny|ín|ín}}',
NUM_MINUTES: '{num} minú{{t|ta|ty|ty|t|t}}',
APPLICATION_SETTINGS: 'Nastavenia aplikácie',
CUSTOMIZATIONS: 'Prispôsobenia',
APPLICATION_RESTARTING: 'EMS-ESP sa reštartuje',
INTERFACE_BOARD_PROFILE: 'Profil dosky rozhrania',
BOARD_PROFILE_TEXT: 'Vyberte vopred nakonfigurovaný profil dosky rozhrania zo zoznamu nižšie, alebo vyberte možnosť Vlastné a nakonfigurujte svoje vlastné hardvérové nastavenia',
BOARD_PROFILE: 'Profil dosky',
CUSTOM: 'Vlastné',
GPIO_OF: '{0} GPIO',
BUTTON: 'Tlačidlo',
TEMPERATURE: 'Teplota',
PHY_TYPE: 'Eth PHY Typ',
DISABLED: 'zakázané',
TX_MODE: 'Tx režim',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
GENERAL_OPTIONS: 'Všeobecné možnosti',
LANGUAGE_ENTITIES: 'Jazyk (pre entity zariadenia)',
HIDE_LED: 'Skryť LED',
ENABLE_TELNET: 'Povoliť Telnet konzolu',
ENABLE_ANALOG: 'Povoliť analógové snímače',
CONVERT_FAHRENHEIT: 'Previesť hodnoty teploty na °F',
BYPASS_TOKEN: 'Vynechajte autorizáciu prístupového tokenu pri volaniach API',
READONLY: 'Povoliť režim len na čítanie (blokuje všetky odchádzajúce príkazy EMS Tx Write)',
UNDERCLOCK_CPU: 'Podtaktovanie rýchlosti procesora',
HEATINGOFF: 'Spustiť kotol s vynúteným vykurovaním',
ENABLE_SHOWER_TIMER: 'Povoliť časovač sprchovania',
ENABLE_SHOWER_ALERT: 'Povoliť upozornenie na sprchu',
TRIGGER_TIME: 'Čas spustenia',
COLD_SHOT_DURATION: 'Trvanie studeného záberu',
FORMATTING_OPTIONS: 'Možnosti formátovania',
BOOLEAN_FORMAT_DASHBOARD: 'Panel Boolean formát',
BOOLEAN_FORMAT_API: 'Boolean formát API/MQTT',
ENUM_FORMAT: 'Enum formát API/MQTT',
INDEX: 'Index',
ENABLE_PARASITE: 'Povoliť parazité napájanie DS18B20',
LOGGING: 'Logovanie',
LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave',
ENABLE_SYSLOG: 'Povoliť Syslog',
LOG_LEVEL: 'Log úroveň',
MARK_INTERVAL: 'Označenie intervalu',
SECONDS: 'sekundy',
MINUTES: 'minúty',
HOURS: 'hodiny',
RESTART: 'Reštart',
RESTART_TEXT: 'EMS-ESP sa musí reštartovať, aby sa použili zmenené systémové nastavenia',
RESTART_CONFIRM: 'Ste si istí, že chcete reštartovať EMS-ESP?',
COMMAND: 'Príkaz',
CUSTOMIZATIONS_RESTART: 'Ste si istí, že chcete reštartovať EMS-ESP?',
CUSTOMIZATIONS_FULL: 'Vybrané subjekty prekročili limit. Prosím, ukladajte v dávkach',
CUSTOMIZATIONS_SAVED: 'Uložené prispôsobenia',
CUSTOMIZATIONS_HELP_1: 'Vyberte zariadenie a prispôsobte možnosti entít alebo kliknutím premenujte',
CUSTOMIZATIONS_HELP_2: 'označiť ako obľúbené',
CUSTOMIZATIONS_HELP_3: 'zakázať akciu zápisu',
CUSTOMIZATIONS_HELP_4: 'vylúčiť z MQTT a API',
CUSTOMIZATIONS_HELP_5: 'skryť z panela',
CUSTOMIZATIONS_HELP_6: 'odstrániť z pamäte',
SELECT_DEVICE: 'Zvoliť zariadenie',
SET_ALL: 'nastaviť všetko',
OPTIONS: 'Možnosti',
NAME: 'Názov',
CUSTOMIZATIONS_RESET: 'Naozaj chcete odstrániť všetky prispôsobenia vrátane vlastných nastavení snímačov teploty a analógových snímačov?',
SUPPORT_INFORMATION: 'Informácie pre podporu',
HELP_INFORMATION_1: 'Navštívte online wiki, kde nájdete pokyny na konfiguráciu EMS-ESP',
HELP_INFORMATION_2: 'Pre živý komunitný chat sa pripojte na náš Discord server',
HELP_INFORMATION_3: 'Ak chcete požiadať o funkciu alebo nahlásiť chybu',
HELP_INFORMATION_4: 'nezabudnite si stiahnuť a pripojiť informácie o vašom systéme, aby ste mohli rýchlejšie reagovať pri nahlasovaní problému',
HELP_INFORMATION_5: 'EMS-ESP je bezplatný a open source projekt. Podporte jeho budúci vývoj tým, že mu dáte hviezdičku na Github!',
UPLOAD: 'Nahrať',
DOWNLOAD: '{{S|s|s}}tiahnuť',
ABORTED: 'zrušené',
FAILED: 'chybné',
SUCCESSFUL: 'úspešné',
SYSTEM: 'Systém',
LOG_OF: '{0} Log',
STATUS_OF: '{0} Stav',
UPLOAD_DOWNLOAD: 'Nahrať/Stiahnuť',
VERSION_ON: 'Momentálne nainštalovaná verzia: ',
CLOSE: 'Zatvoriť',
USE: 'Použiť',
FACTORY_RESET: 'Továrenské nastavenia',
SYSTEM_FACTORY_TEXT: 'Zariadenie bolo obnovené z výroby a teraz sa reštartuje',
SYSTEM_FACTORY_TEXT_DIALOG: 'Naozaj chcete resetovať EMS-ESP na predvolené výrobné nastavenia?',
THE_LATEST: 'Posledná',
OFFICIAL: 'officiálna',
DEVELOPMENT: 'vývojárska',
RELEASE_IS: 'verzia je',
RELEASE_NOTES: 'poznámky k verzii',
EMS_ESP_VER: 'EMS-ESP verzia',
UPTIME: 'Beh systému',
HEAP: 'Zásobník (voľné / max pridelenie)',
PSRAM: 'PSRAM (Veľkosť / Voľné)',
FLASH: 'Flash chip (Veľkosť / Rýchlosť)',
APPSIZE: 'Applikácia (Oddiel: Použité / Voľné)',
FILESYSTEM: 'Súborový systém (Použité / Voľné)',
BUFFER_SIZE: 'Buffer-max.veľkosť',
COMPACT: 'Kompaktné',
ENABLE_OTA: 'Povoliť OTA aktualizácie',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Stiahnutie prispôsobení entity',
DOWNLOAD_SCHEDULE_TEXT: 'Stiahnutie plánovača udalostí',
DOWNLOAD_SETTINGS_TEXT: 'Stiahnite si nastavenia aplikácie. Pri zdieľaní nastavení buďte opatrní, pretože tento súbor obsahuje heslá a iné citlivé systémové informácie.',
UPLOAD_TEXT: 'Najskôr nahrajte nový súbor firmvéru (.bin), nastavenia alebo prispôsobenia (.json), pre voliteľné overenie nahrajte súbor (.md5)',
UPLOADING: 'Nahrávanie',
UPLOAD_DROP_TEXT: 'Potiahnúť a pripnúť súbor alebo kliknúť sem',
ERROR: 'Neočakávaná chyba, prosím skúste to znova',
TIME_SET: 'Nastavený čas',
MANAGE_USERS: 'Správa používateľov',
IS_ADMIN: 'je Admin',
USER_WARNING: 'Musíte mať nakonfigurovaného aspoň jedného používateľa administrátora',
ADD: 'Pridať',
ACCESS_TOKEN_FOR: 'Prístupový token pre',
ACCESS_TOKEN_TEXT: 'Nižšie uvedený token sa používa pri volaniach REST API, ktoré vyžadujú autorizáciu. Môže byť odovzdaný buď ako token Bearer v hlavičke Authorization (Autorizácia), alebo v parametri dotazu URL access_token.',
GENERATING_TOKEN: 'Generovanie tokenu',
USER: 'Užívateľ',
MODIFY: 'Upraviť',
SU_TEXT: 'Heslo su (superužívateľ) sa používa na podpisovanie autentifikačných tokenov a tiež na povolenie oprávnení správcu v rámci konzoly.',
NOT_ENABLED: 'Nie je povolené',
ERRORS_OF: '{0} errory',
DISCONNECT_REASON: 'Dôvod odpojenia',
ENABLE_MQTT: 'Povoliť MQTT',
BROKER: 'Broker',
CLIENT: 'Klient',
BASE_TOPIC: 'Base',
OPTIONAL: 'voliteľné',
FORMATTING: 'Formátovanie',
MQTT_FORMAT: 'Formát témy/záťaže',
MQTT_NEST_1: 'Vnorené do jednej témy',
MQTT_NEST_2: 'Ako jednotlivé témy',
MQTT_RESPONSE: 'Publikovanie výstupu príkazu do témy `response`',
MQTT_PUBLISH_TEXT_1: 'Zverejňovanie tém jednotlivých hodnôt pri zmene',
MQTT_PUBLISH_TEXT_2: 'Publikovanie do tém príkazov (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Povolenie zisťovania MQTT',
MQTT_PUBLISH_TEXT_4: 'Predpona tém Discovery',
MQTT_PUBLISH_TEXT_5: 'Typ zistenia',
MQTT_PUBLISH_INTERVALS: 'Intervaly zverejňovania',
MQTT_INT_BOILER: 'Kotly a tepelné čerpadlá',
MQTT_INT_THERMOSTATS: 'Termostaty',
MQTT_INT_SOLAR: 'Solárne moduly',
MQTT_INT_MIXER: 'Zmiešavacie moduley',
MQTT_INT_WATER: 'Voda moduley',
MQTT_QUEUE: 'Fronta MQTT',
DEFAULT: 'Predvolené',
MQTT_ENTITY_FORMAT: 'ID formát entity',
MQTT_ENTITY_FORMAT_0: 'Jedna inštancia, dlhý názov (v3.4)',
MQTT_ENTITY_FORMAT_1: 'Jedna inštancia, krátky názov',
MQTT_ENTITY_FORMAT_2: 'Viacero inštancií, krátky názov',
MQTT_CLEAN_SESSION: 'Nastavenie čistej relácie',
MQTT_RETAIN_FLAG: 'Vždy nastaviť príznak Retain',
INACTIVE: 'Neaktívne',
ACTIVE: 'Aktívne',
UNKNOWN: 'Neznáme',
SET_TIME: 'Nastavený čas',
SET_TIME_TEXT: 'Na nastavenie času zadajte miestny dátum a čas nižšie',
LOCAL_TIME: 'Lokálny čas',
UTC_TIME: 'UTC čas',
ENABLE_NTP: 'Povoliť NTP',
NTP_SERVER: 'NTP Server',
TIME_ZONE: 'Časová zóna',
ACCESS_POINT: 'Prístupový bod',
AP_PROVIDE: 'Povoliť prístupový bod',
AP_PROVIDE_TEXT_1: 'vždy',
AP_PROVIDE_TEXT_2: 'keď je WiFi odpojená',
AP_PROVIDE_TEXT_3: 'nikdy',
AP_PREFERRED_CHANNEL: 'Preferovaný kanál',
AP_HIDE_SSID: 'Skryť SSID',
AP_CLIENTS: 'AP klienti',
AP_MAX_CLIENTS: 'Max klientov',
AP_LOCAL_IP: 'Lokálna IP',
NETWORK_SCAN: 'Scan WiFi siete',
IDLE: 'Nečinné',
LOST: 'Stratené',
SCANNING: 'Scanovanie',
SCAN_AGAIN: 'Scanovať znova',
NETWORK_SCANNER: 'Sieťový scanner',
NETWORK_NO_WIFI: 'WiFi siete nenájdené',
NETWORK_BLANK_SSID: 'nechajte prázdne, ak chcete zakázať WiFi a povoliť ETH',
NETWORK_BLANK_BSSID: 'ponechajte prázdne, ak chcete používať iba SSID',
TX_POWER: 'Tx výkon',
HOSTNAME: 'Hostname',
NETWORK_DISABLE_SLEEP: 'Zakázanie režimu spánku WiFi',
NETWORK_LOW_BAND: 'Používanie menšej šírky pásma WiFi',
NETWORK_USE_DNS: 'Povoliť mDNS službu',
NETWORK_ENABLE_CORS: 'Povoliť CORS',
NETWORK_CORS_ORIGIN: 'CORS origin',
NETWORK_ENABLE_IPV6: 'Povoliť podporu IPv6',
NETWORK_FIXED_IP: 'Použiť fixnú IP adresu',
NETWORK_GATEWAY: 'Brána',
NETWORK_SUBNET: 'Maska podsiete',
NETWORK_DNS: 'DNS servery',
ADDRESS_OF: '{0} adresa',
ADMIN: 'Admin',
GUEST: 'Hosť',
NEW: 'Nová',
NEW_NAME_OF: 'Nový názov {0}',
ENTITY: 'entita',
MIN: 'min',
MAX: 'max',
BLOCK_NAVIGATE_1: 'Máte neuložené zmeny',
BLOCK_NAVIGATE_2: 'Ak prejdete na inú stránku, neuložené zmeny sa stratia. Ste si istí, že chcete opustiť túto stránku?',
STAY: 'Zostať',
LEAVE: 'Opustiť',
SCHEDULER: 'Plánovač',
SCHEDULER_HELP_1: 'Automatizujte príkazy pridaním naplánovaných udalostí nižšie. Nastavte jedinečné meno na aktiváciu/deaktiváciu cez API/MQTT.',
SCHEDULER_HELP_2: 'Použite 00:00 na jednorazové spustenie pri štarte',
SCHEDULE: 'Plánovač',
TIME: 'Čas',
TIMER: 'Časovač',
SCHEDULE_UPDATED: 'Plánovanie aktualizované',
SCHEDULE_TIMER_1: 'pri spustení',
SCHEDULE_TIMER_2: 'každú minútu',
SCHEDULE_TIMER_3: 'každú hodinu',
CUSTOM_ENTITIES: 'Vlastné entity',
ENTITIES_HELP_1: 'Získavanie vlastných entít zo zbernice EMS',
ENTITIES_UPDATED: 'Aktualizované entity',
WRITEABLE: 'Zapísateľný',
SHOWING: 'Zobrazenie',
SEARCH: 'Vyhľadať',
CERT: 'Koreňový certifikát TLS (ak chcete vypnúť TLS, nechajte prázdne)',
ENABLE_TLS: 'Povoliť TLS',
ON: 'Zap',
OFF: 'Vyp',
POLARITY: 'Polarita',
ACTIVEHIGH: 'Aktívny Vysoký',
ACTIVELOW: 'Aktívny Nízky',
UNCHANGED: 'Nezmenené',
ALWAYS: 'Vždy',
ACTIVITY: 'Aktivita',
CONFIGURE: 'Konfiguracia {0}'
};
export default sk;

View File

@@ -12,7 +12,6 @@ const sv: Translation = {
USERNAME: 'Användarnamn', USERNAME: 'Användarnamn',
PASSWORD: 'Lösenord', PASSWORD: 'Lösenord',
SU_PASSWORD: 'su Lösenord', SU_PASSWORD: 'su Lösenord',
DASHBOARD: 'Kontrollpanel',
SETTINGS_OF: '{0} Inställningar', SETTINGS_OF: '{0} Inställningar',
HELP_OF: '{0} Hjälp', HELP_OF: '{0} Hjälp',
LOGGED_IN: 'Inloggad som {name}', LOGGED_IN: 'Inloggad som {name}',
@@ -37,8 +36,6 @@ const sv: Translation = {
BRAND: 'Fabrikat', BRAND: 'Fabrikat',
ENTITY_NAME: 'Entitetsnamn', ENTITY_NAME: 'Entitetsnamn',
VALUE: '{{Värde|värde}}', VALUE: '{{Värde|värde}}',
DEVICE_DATA: 'Enhets data',
SENSOR_DATA: 'Sensor data',
DEVICES: 'Enheter', DEVICES: 'Enheter',
SENSORS: 'Sensorer', SENSORS: 'Sensorer',
RUN_COMMAND: 'Kör Kommando', RUN_COMMAND: 'Kör Kommando',
@@ -83,7 +80,6 @@ const sv: Translation = {
FAIL: 'Misslyckades', FAIL: 'Misslyckades',
QUALITY: 'Kvalitet', QUALITY: 'Kvalitet',
SCAN_DEVICES: 'Sök efter nya enheter', SCAN_DEVICES: 'Sök efter nya enheter',
EMS_BUS_STATUS_TITLE: 'EMS-buss & aktivitetsstatus',
SCAN: 'Sök', SCAN: 'Sök',
STATUS_NAMES: [ STATUS_NAMES: [
'EMS-telegram (Rx)', 'EMS-telegram (Rx)',
@@ -163,9 +159,7 @@ const sv: Translation = {
OPTIONS: 'Alternativ', OPTIONS: 'Alternativ',
NAME: 'Namn', NAME: 'Namn',
CUSTOMIZATIONS_RESET: 'Är du säker på att du vill ta bort alla anpassningar inklusive inställningar för Temperatur och Analoga sensorer?', CUSTOMIZATIONS_RESET: 'Är du säker på att du vill ta bort alla anpassningar inklusive inställningar för Temperatur och Analoga sensorer?',
DEVICE_ENTITIES: 'Enhets-entiteter',
SUPPORT_INFORMATION: 'Supportinformation', SUPPORT_INFORMATION: 'Supportinformation',
CLICK_HERE: 'Klicka Här',
HELP_INFORMATION_1: 'Besök Wikin för instruktioner för hur du kan konfigurera EMS-ESP', HELP_INFORMATION_1: 'Besök Wikin för instruktioner för hur du kan konfigurera EMS-ESP',
HELP_INFORMATION_2: 'För community-support besök vår Discord-server', HELP_INFORMATION_2: 'För community-support besök vår Discord-server',
HELP_INFORMATION_3: 'Önska en ny funktion eller rapportera en bugg', HELP_INFORMATION_3: 'Önska en ny funktion eller rapportera en bugg',
@@ -181,26 +175,22 @@ const sv: Translation = {
STATUS_OF: '{0} Status', STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Upp/Nedladdning', UPLOAD_DOWNLOAD: 'Upp/Nedladdning',
VERSION_ON: 'You are currently on', // TODO translate VERSION_ON: 'You are currently on', // TODO translate
SYSTEM_APPLY_FIRMWARE: 'för att aktivera ny firmware',
CLOSE: 'Stäng', CLOSE: 'Stäng',
USE: 'Använd', USE: 'Använd',
FACTORY_RESET: 'Fabriksåterställning', FACTORY_RESET: 'Fabriksåterställning',
SYSTEM_FACTORY_TEXT: 'Enheten har blivit fabriksåterställd och startar nu om', SYSTEM_FACTORY_TEXT: 'Enheten har blivit fabriksåterställd och startar nu om',
SYSTEM_FACTORY_TEXT_DIALOG: 'Är du säker att du vill fabriksåterställa enheten?', SYSTEM_FACTORY_TEXT_DIALOG: 'Är du säker att du vill fabriksåterställa enheten?',
VERSION_CHECK: 'Senaste versioner',
THE_LATEST: 'Den senaste', THE_LATEST: 'Den senaste',
OFFICIAL: 'officiell', OFFICIAL: 'officiell',
DEVELOPMENT: 'utveckling', DEVELOPMENT: 'utveckling',
RELEASE_IS: 'release är', // TODO translate RELEASE_IS: 'release är', // TODO translate
RELEASE_NOTES: 'release-logg', RELEASE_NOTES: 'release-logg',
EMS_ESP_VER: 'EMS-ESP Version', EMS_ESP_VER: 'EMS-ESP Version',
PLATFORM: 'Enhet (Plattform / SDK)',
UPTIME: 'Systemets Upptid', UPTIME: 'Systemets Upptid',
CPU_FREQ: 'CPU-frekvens',
HEAP: 'Heap (Ledigt / Max allokerat)', HEAP: 'Heap (Ledigt / Max allokerat)',
PSRAM: 'PSRAM (Storlek / Ledigt)', PSRAM: 'PSRAM (Storlek / Ledigt)',
FLASH: 'Flashminne (Storlek / Hastighet)', FLASH: 'Flashminne (Storlek / Hastighet)',
APPSIZE: 'Applikationer (Använt / Ledigt)', APPSIZE: 'Applikationer (Partition: Använt / Ledigt)',
FILESYSTEM: 'Filsystem (Använt / Ledigt)', FILESYSTEM: 'Filsystem (Använt / Ledigt)',
BUFFER_SIZE: 'Max Bufferstorlek', BUFFER_SIZE: 'Max Bufferstorlek',
COMPACT: 'Komprimera', COMPACT: 'Komprimera',
@@ -246,6 +236,7 @@ const sv: Translation = {
MQTT_INT_THERMOSTATS: 'Termostater', MQTT_INT_THERMOSTATS: 'Termostater',
MQTT_INT_SOLAR: 'Solpaneler', MQTT_INT_SOLAR: 'Solpaneler',
MQTT_INT_MIXER: 'Blandningsventiler', MQTT_INT_MIXER: 'Blandningsventiler',
MQTT_INT_WATER: 'Water Modules', // TODO translate
MQTT_QUEUE: 'MQTT-kö', MQTT_QUEUE: 'MQTT-kö',
DEFAULT: 'Standard', DEFAULT: 'Standard',
MQTT_ENTITY_FORMAT: 'Entitets-ID format', MQTT_ENTITY_FORMAT: 'Entitets-ID format',
@@ -331,7 +322,9 @@ const sv: Translation = {
ACTIVEHIGH: 'Active High', // TODO translate ACTIVEHIGH: 'Active High', // TODO translate
ACTIVELOW: 'Active Low', // TODO translate ACTIVELOW: 'Active Low', // TODO translate
UNCHANGED: 'Unchanged', // TODO translate UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always' // TODO translate ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}' // TODO translate
}; };
export default sv; export default sv;

View File

@@ -12,7 +12,6 @@ const tr: Translation = {
USERNAME: 'Kullanıcı Adı', USERNAME: 'Kullanıcı Adı',
PASSWORD: 'Şifre', PASSWORD: 'Şifre',
SU_PASSWORD: 'SK Şifresi', SU_PASSWORD: 'SK Şifresi',
DASHBOARD: 'Gösterge Paneli',
SETTINGS_OF: '{0} Ayarlar', SETTINGS_OF: '{0} Ayarlar',
HELP_OF: '{0} Yardım', HELP_OF: '{0} Yardım',
LOGGED_IN: '{name} olarak giriş yapıldı', LOGGED_IN: '{name} olarak giriş yapıldı',
@@ -37,8 +36,6 @@ const tr: Translation = {
BRAND: 'Marka', BRAND: 'Marka',
ENTITY_NAME: 'Valık Adı', ENTITY_NAME: 'Valık Adı',
VALUE: '{{Değer|değer}}', VALUE: '{{Değer|değer}}',
DEVICE_DATA: 'Cihaz Bilgisi',
SENSOR_DATA: 'Sensör Bilgisi',
DEVICES: 'Cihazlar', DEVICES: 'Cihazlar',
SENSORS: 'Sensörler', SENSORS: 'Sensörler',
RUN_COMMAND: 'Çalıştırma Komutu', RUN_COMMAND: 'Çalıştırma Komutu',
@@ -83,7 +80,6 @@ const tr: Translation = {
FAIL: 'HATA', FAIL: 'HATA',
QUALITY: 'KALİTE', QUALITY: 'KALİTE',
SCAN_DEVICES: 'Yeni cihaz taraması', SCAN_DEVICES: 'Yeni cihaz taraması',
EMS_BUS_STATUS_TITLE: 'EMS Hattı ve Aktivite Durumu',
SCAN: 'Tara', SCAN: 'Tara',
STATUS_NAMES: [ STATUS_NAMES: [
'EMS Telegramlar Alındı (Rx)', 'EMS Telegramlar Alındı (Rx)',
@@ -163,9 +159,7 @@ const tr: Translation = {
OPTIONS: 'Seçenekler', OPTIONS: 'Seçenekler',
NAME: 'İsim', NAME: 'İsim',
CUSTOMIZATIONS_RESET: 'Sıcaklık ve Analog Sensörlerin özelleştirilmiş seçenekleri dahil bütün özelleştirmeleri kaldırmak istediğinizden emin misiniz?', CUSTOMIZATIONS_RESET: 'Sıcaklık ve Analog Sensörlerin özelleştirilmiş seçenekleri dahil bütün özelleştirmeleri kaldırmak istediğinizden emin misiniz?',
DEVICE_ENTITIES: 'Cihaz Varlıkları',
SUPPORT_INFORMATION: 'Destek Bilgileri', SUPPORT_INFORMATION: 'Destek Bilgileri',
CLICK_HERE: 'Buraya Tıklayın',
HELP_INFORMATION_1: 'EMS-ESPnin nasıl ayarlanacağı ile ilgili bilgileri edinmek için çevrimiçi WIKI sayfasını ziyaret edin', HELP_INFORMATION_1: 'EMS-ESPnin nasıl ayarlanacağı ile ilgili bilgileri edinmek için çevrimiçi WIKI sayfasını ziyaret edin',
HELP_INFORMATION_2: 'Canlı topluluk sohbeti için Discord sunucumuza katılın', HELP_INFORMATION_2: 'Canlı topluluk sohbeti için Discord sunucumuza katılın',
HELP_INFORMATION_3: 'Yeni bir özellik talep etmek yada hata bildirmek için', HELP_INFORMATION_3: 'Yeni bir özellik talep etmek yada hata bildirmek için',
@@ -181,26 +175,22 @@ const tr: Translation = {
STATUS_OF: '{0} Durumu', STATUS_OF: '{0} Durumu',
UPLOAD_DOWNLOAD: 'Yükleme/İndirme', UPLOAD_DOWNLOAD: 'Yükleme/İndirme',
VERSION_ON: 'You are currently on', // TODO translate VERSION_ON: 'You are currently on', // TODO translate
SYSTEM_APPLY_FIRMWARE: 'yeni bellenimi uygulamak için',
CLOSE: 'Kapat', CLOSE: 'Kapat',
USE: 'KUllan', USE: 'KUllan',
FACTORY_RESET: 'Fabrika ayarına dönme', FACTORY_RESET: 'Fabrika ayarına dönme',
SYSTEM_FACTORY_TEXT: 'Cihaz fabrika ayarlarına döndü ve şimdi yendiden başlatılacak', SYSTEM_FACTORY_TEXT: 'Cihaz fabrika ayarlarına döndü ve şimdi yendiden başlatılacak',
SYSTEM_FACTORY_TEXT_DIALOG: 'Cihazı fabrika ayarlarına döndürmek istediğinize emin misiniz?', SYSTEM_FACTORY_TEXT_DIALOG: 'Cihazı fabrika ayarlarına döndürmek istediğinize emin misiniz?',
VERSION_CHECK: 'Sürüm Kontrolü',
THE_LATEST: 'En son', THE_LATEST: 'En son',
OFFICIAL: 'resmi', OFFICIAL: 'resmi',
DEVELOPMENT: 'geliştirme', DEVELOPMENT: 'geliştirme',
RELEASE_IS: 'release is', // TODO translate RELEASE_IS: 'release is', // TODO translate
RELEASE_NOTES: 'yayınlanma notları', RELEASE_NOTES: 'yayınlanma notları',
EMS_ESP_VER: 'EMS-ESP Sürümü', EMS_ESP_VER: 'EMS-ESP Sürümü',
PLATFORM: 'Cihaz (Platform / SDK)',
UPTIME: 'Sistem Çalışma Süresi', UPTIME: 'Sistem Çalışma Süresi',
CPU_FREQ: 'İşlemci frekansı',
HEAP: 'Yığın (Boş / Maksimum Tahsis)', HEAP: 'Yığın (Boş / Maksimum Tahsis)',
PSRAM: 'PSRAM (Boyut / Boş)', PSRAM: 'PSRAM (Boyut / Boş)',
FLASH: 'Flash Çipi (Boyut / Hız)', FLASH: 'Flash Çipi (Boyut / Hız)',
APPSIZE: 'Uygulama (Kullanılmış / Boş)', APPSIZE: 'Uygulama (Bölme: Kullanılmış / Boş)',
FILESYSTEM: 'Dosya Sistemi (Kullanılmış / Boş)', FILESYSTEM: 'Dosya Sistemi (Kullanılmış / Boş)',
BUFFER_SIZE: 'En fazla bellek boyutu', BUFFER_SIZE: 'En fazla bellek boyutu',
COMPACT: 'Sıkışık', COMPACT: 'Sıkışık',
@@ -246,6 +236,7 @@ const tr: Translation = {
MQTT_INT_THERMOSTATS: 'Termostatlar', MQTT_INT_THERMOSTATS: 'Termostatlar',
MQTT_INT_SOLAR: 'Güneş Enerjisi Modülleri', MQTT_INT_SOLAR: 'Güneş Enerjisi Modülleri',
MQTT_INT_MIXER: 'Karışım Modülleri', MQTT_INT_MIXER: 'Karışım Modülleri',
MQTT_INT_WATER: 'Water Modules', // TODO translate
MQTT_QUEUE: 'MQTT Sırası', MQTT_QUEUE: 'MQTT Sırası',
DEFAULT: 'Varsayılan', DEFAULT: 'Varsayılan',
MQTT_ENTITY_FORMAT: 'Varlık Kimlik biçimi', MQTT_ENTITY_FORMAT: 'Varlık Kimlik biçimi',
@@ -331,7 +322,9 @@ const tr: Translation = {
ACTIVEHIGH: 'Active High', // TODO translate ACTIVEHIGH: 'Active High', // TODO translate
ACTIVELOW: 'Active Low', // TODO translate ACTIVELOW: 'Active Low', // TODO translate
UNCHANGED: 'Unchanged', // TODO translate UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always' // TODO translate ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}' // TODO translate
}; };
export default tr; export default tr;

View File

@@ -20,7 +20,8 @@ import {
ValidatedTextField, ValidatedTextField,
ButtonRow, ButtonRow,
MessageBox, MessageBox,
BlockNavigation BlockNavigation,
useLayoutTitle
} from 'components'; } from 'components';
import RestartMonitor from 'framework/system/RestartMonitor'; import RestartMonitor from 'framework/system/RestartMonitor';
@@ -36,7 +37,7 @@ export function boardProfileSelectItems() {
)); ));
} }
const SettingsApplication: FC = () => { const ApplicationSettings: FC = () => {
const { const {
loadData, loadData,
saveData, saveData,
@@ -97,6 +98,8 @@ const SettingsApplication: FC = () => {
}); });
}; };
useLayoutTitle(LL.APPLICATION_SETTINGS());
const content = () => { const content = () => {
if (!data) { if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />; return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
@@ -136,7 +139,7 @@ const SettingsApplication: FC = () => {
return ( return (
<> <>
<Typography sx={{ pt: 2 }} variant="h6" color="primary"> <Typography sx={{ pb: 1 }} variant="h6" color="primary">
{LL.INTERFACE_BOARD_PROFILE()} {LL.INTERFACE_BOARD_PROFILE()}
</Typography> </Typography>
<Box color="warning.main"> <Box color="warning.main">
@@ -384,6 +387,7 @@ const SettingsApplication: FC = () => {
<MenuItem value="nl">Nederlands (NL)</MenuItem> <MenuItem value="nl">Nederlands (NL)</MenuItem>
<MenuItem value="no">Norsk (NO)</MenuItem> <MenuItem value="no">Norsk (NO)</MenuItem>
<MenuItem value="pl">Polski (PL)</MenuItem> <MenuItem value="pl">Polski (PL)</MenuItem>
<MenuItem value="sk">Slovenčina (SK)</MenuItem>
<MenuItem value="sv">Svenska (SV)</MenuItem> <MenuItem value="sv">Svenska (SV)</MenuItem>
<MenuItem value="tr">Türk (TR)</MenuItem> <MenuItem value="tr">Türk (TR)</MenuItem>
</TextField> </TextField>
@@ -644,7 +648,7 @@ const SettingsApplication: FC = () => {
</Grid> </Grid>
)} )}
{restartNeeded && ( {restartNeeded && (
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}> <MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}> <Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
{LL.RESTART()} {LL.RESTART()}
</Button> </Button>
@@ -679,11 +683,11 @@ const SettingsApplication: FC = () => {
}; };
return ( return (
<SectionContent title={LL.APPLICATION_SETTINGS()} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : content()} {restarting ? <RestartMonitor /> : content()}
</SectionContent> </SectionContent>
); );
}; };
export default SettingsApplication; export default ApplicationSettings;

View File

@@ -1,5 +1,7 @@
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import RefreshIcon from '@mui/icons-material/Refresh';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Button, Typography, Box } from '@mui/material'; import { Button, Typography, Box } from '@mui/material';
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
@@ -11,17 +13,17 @@ import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import SettingsEntitiesDialog from './SettingsEntitiesDialog'; import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
import * as EMSESP from './api'; import * as EMSESP from './api';
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types'; import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
import { entityItemValidation } from './validators'; import { entityItemValidation } from './validators';
import type { EntityItem } from './types'; import type { EntityItem } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; import { ButtonRow, FormLoader, SectionContent, BlockNavigation, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const SettingsEntities: FC = () => { const CustomEntities: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
@@ -29,6 +31,8 @@ const SettingsEntities: FC = () => {
const [creating, setCreating] = useState<boolean>(false); const [creating, setCreating] = useState<boolean>(false);
const [dialogOpen, setDialogOpen] = useState<boolean>(false); const [dialogOpen, setDialogOpen] = useState<boolean>(false);
useLayoutTitle(LL.CUSTOM_ENTITIES(0));
const { const {
data: entities, data: entities,
send: fetchEntities, send: fetchEntities,
@@ -43,6 +47,7 @@ const SettingsEntities: FC = () => {
function hasEntityChanged(ei: EntityItem) { function hasEntityChanged(ei: EntityItem) {
return ( return (
ei.id !== ei.o_id || ei.id !== ei.o_id ||
ei.ram !== ei.o_ram ||
(ei?.name || '') !== (ei?.o_name || '') || (ei?.name || '') !== (ei?.o_name || '') ||
ei.device_id !== ei.o_device_id || ei.device_id !== ei.o_device_id ||
ei.type_id !== ei.o_type_id || ei.type_id !== ei.o_type_id ||
@@ -51,7 +56,8 @@ const SettingsEntities: FC = () => {
ei.factor !== ei.o_factor || ei.factor !== ei.o_factor ||
ei.value_type !== ei.o_value_type || ei.value_type !== ei.o_value_type ||
ei.writeable !== ei.o_writeable || ei.writeable !== ei.o_writeable ||
ei.deleted !== ei.o_deleted ei.deleted !== ei.o_deleted ||
(ei.value || '') !== (ei.o_value || '')
); );
} }
@@ -118,6 +124,7 @@ const SettingsEntities: FC = () => {
.filter((ei) => !ei.deleted) .filter((ei) => !ei.deleted)
.map((condensed_ei) => ({ .map((condensed_ei) => ({
id: condensed_ei.id, id: condensed_ei.id,
ram: condensed_ei.ram,
name: condensed_ei.name, name: condensed_ei.name,
device_id: condensed_ei.device_id, device_id: condensed_ei.device_id,
type_id: condensed_ei.type_id, type_id: condensed_ei.type_id,
@@ -125,7 +132,8 @@ const SettingsEntities: FC = () => {
factor: condensed_ei.factor, factor: condensed_ei.factor,
uom: condensed_ei.uom, uom: condensed_ei.uom,
writeable: condensed_ei.writeable, writeable: condensed_ei.writeable,
value_type: condensed_ei.value_type value_type: condensed_ei.value_type,
value: condensed_ei.value
})) }))
}) })
.then(() => { .then(() => {
@@ -173,14 +181,16 @@ const SettingsEntities: FC = () => {
setSelectedEntityItem({ setSelectedEntityItem({
id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100), id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100),
name: '', name: '',
device_id: '', ram: 0,
type_id: '', device_id: '0',
type_id: '0',
offset: 0, offset: 0,
factor: 1, factor: 1,
uom: 0, uom: 0,
value_type: 0, value_type: 0,
writeable: false, writeable: false,
deleted: false deleted: false,
value: ''
}); });
setDialogOpen(true); setDialogOpen(true);
}; };
@@ -212,18 +222,21 @@ const SettingsEntities: FC = () => {
<HeaderCell stiff>{LL.ID_OF(LL.DEVICE())}</HeaderCell> <HeaderCell stiff>{LL.ID_OF(LL.DEVICE())}</HeaderCell>
<HeaderCell stiff>{LL.ID_OF(LL.TYPE(1))}</HeaderCell> <HeaderCell stiff>{LL.ID_OF(LL.TYPE(1))}</HeaderCell>
<HeaderCell stiff>{LL.OFFSET()}</HeaderCell> <HeaderCell stiff>{LL.OFFSET()}</HeaderCell>
<HeaderCell stiff>{LL.VALUE(1) + ' ' + LL.TYPE(1)}</HeaderCell> <HeaderCell stiff>{LL.TYPE(1)}</HeaderCell>
<HeaderCell stiff>{LL.VALUE(1)}</HeaderCell> <HeaderCell stiff>{LL.VALUE(1)}</HeaderCell>
</HeaderRow> </HeaderRow>
</Header> </Header>
<Body> <Body>
{tableList.map((ei: EntityItem) => ( {tableList.map((ei: EntityItem) => (
<Row key={ei.name} item={ei} onClick={() => editEntityItem(ei)}> <Row key={ei.name} item={ei} onClick={() => editEntityItem(ei)}>
<Cell>{ei.name}</Cell> <Cell>
<Cell>{showHex(ei.device_id as number, 2)}</Cell> {ei.name}&nbsp;
<Cell>{showHex(ei.type_id as number, 3)}</Cell> {ei.writeable && <EditOutlinedIcon color="primary" sx={{ fontSize: 12 }} />}
<Cell>{ei.offset}</Cell> </Cell>
<Cell>{DeviceValueTypeNames[ei.value_type]}</Cell> <Cell>{ei.ram === 1 ? '' : showHex(ei.device_id as number, 2)}</Cell>
<Cell>{ei.ram === 1 ? '' : showHex(ei.type_id as number, 3)}</Cell>
<Cell>{ei.ram === 1 ? '' : ei.offset}</Cell>
<Cell>{ei.ram === 1 ? 'RAM' : DeviceValueTypeNames[ei.value_type]}</Cell>
<Cell>{formatValue(ei.value, ei.uom)}</Cell> <Cell>{formatValue(ei.value, ei.uom)}</Cell>
</Row> </Row>
))} ))}
@@ -235,7 +248,7 @@ const SettingsEntities: FC = () => {
}; };
return ( return (
<SectionContent title={LL.CUSTOM_ENTITIES(0)} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
<Box mb={2} color="warning.main"> <Box mb={2} color="warning.main">
<Typography variant="body2">{LL.ENTITIES_HELP_1()}</Typography> <Typography variant="body2">{LL.ENTITIES_HELP_1()}</Typography>
@@ -244,7 +257,7 @@ const SettingsEntities: FC = () => {
{renderEntity()} {renderEntity()}
{selectedEntityItem && ( {selectedEntityItem && (
<SettingsEntitiesDialog <SettingsCustomEntitiesDialog
open={dialogOpen} open={dialogOpen}
creating={creating} creating={creating}
onClose={onDialogClose} onClose={onDialogClose}
@@ -254,7 +267,7 @@ const SettingsEntities: FC = () => {
/> />
)} )}
<Box display="flex" flexWrap="wrap"> <Box mt={1} display="flex" flexWrap="wrap">
<Box flexGrow={1}> <Box flexGrow={1}>
{numChanges > 0 && ( {numChanges > 0 && (
<ButtonRow> <ButtonRow>
@@ -274,7 +287,10 @@ const SettingsEntities: FC = () => {
</Box> </Box>
<Box flexWrap="nowrap" whiteSpace="nowrap"> <Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow> <ButtonRow>
<Button startIcon={<AddIcon />} variant="outlined" color="secondary" onClick={addEntityItem}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={fetchEntities}>
{LL.REFRESH()}
</Button>
<Button startIcon={<AddIcon />} variant="outlined" color="primary" onClick={addEntityItem}>
{LL.ADD(0)} {LL.ADD(0)}
</Button> </Button>
</ButtonRow> </ButtonRow>
@@ -284,4 +300,4 @@ const SettingsEntities: FC = () => {
); );
}; };
export default SettingsEntities; export default CustomEntities;

View File

@@ -0,0 +1,284 @@
import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
import {
Box,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
InputAdornment,
MenuItem,
TextField
} from '@mui/material';
import { useEffect, useState } from 'react';
import { DeviceValueUOM_s, DeviceValueType } from './types';
import type { EntityItem } from './types';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { dialogStyle } from 'CustomTheme';
import { BlockFormControlLabel, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
type CustomEntitiesDialogProps = {
open: boolean;
creating: boolean;
onClose: () => void;
onSave: (ei: EntityItem) => void;
selectedItem: EntityItem;
validator: Schema;
};
const CustomEntitiesDialog = ({
open,
creating,
onClose,
onSave,
selectedItem,
validator
}: CustomEntitiesDialogProps) => {
const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<EntityItem>(selectedItem);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const updateFormValue = updateValue(setEditItem);
useEffect(() => {
if (open) {
setFieldErrors(undefined);
setEditItem(selectedItem);
// convert to hex strings straight away
setEditItem({
...selectedItem,
device_id: selectedItem.device_id.toString(16).toUpperCase(),
type_id: selectedItem.type_id.toString(16).toUpperCase()
});
}
}, [open, selectedItem]);
const close = () => {
onClose();
};
const save = async () => {
try {
setFieldErrors(undefined);
await validate(validator, editItem);
if (typeof editItem.device_id === 'string') {
editItem.device_id = parseInt(editItem.device_id, 16);
}
if (typeof editItem.type_id === 'string') {
editItem.type_id = parseInt(editItem.type_id, 16);
}
onSave(editItem);
} catch (errors: any) {
setFieldErrors(errors);
}
};
const remove = () => {
editItem.deleted = true;
onSave(editItem);
};
return (
<Dialog sx={dialogStyle} open={open} onClose={close}>
<DialogTitle>
{creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()}&nbsp;{LL.ENTITY()}
</DialogTitle>
<DialogContent dividers>
<Box display="flex" flexWrap="wrap" mb={1}>
<Box flexWrap="nowrap" whiteSpace="nowrap" />
</Box>
<Grid container spacing={2}>
<Grid item xs={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="name"
label={LL.NAME(0)}
value={editItem.name}
margin="normal"
fullWidth
onChange={updateFormValue}
/>
</Grid>
<Grid item xs={4}>
<TextField
name="ram"
label={LL.VALUE(1) + ' ' + LL.TYPE(1)}
value={editItem.ram}
variant="outlined"
onChange={updateFormValue}
margin="normal"
fullWidth
select
>
<MenuItem value={0}>EMS-{LL.VALUE(1)}</MenuItem>
<MenuItem value={1}>RAM-{LL.VALUE(1)}</MenuItem>
</TextField>
</Grid>
{editItem.ram === 1 && (
<Grid item xs={4}>
<TextField
name="value"
label={LL.DEFAULT(0) + ' ' + LL.VALUE(1)}
value={editItem.value}
variant="outlined"
onChange={updateFormValue}
fullWidth
margin="normal"
/>
</Grid>
)}
{editItem.ram === 0 && (
<>
<Grid item xs={4} mt={3}>
<BlockFormControlLabel
control={<Checkbox checked={editItem.writeable} onChange={updateFormValue} name="writeable" />}
label={LL.WRITEABLE()}
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="device_id"
label={LL.ID_OF(LL.DEVICE())}
margin="normal"
type="string"
fullWidth
value={editItem.device_id as string}
onChange={updateFormValue}
inputProps={{ style: { textTransform: 'uppercase' } }}
InputProps={{ startAdornment: <InputAdornment position="start">0x</InputAdornment> }}
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="type_id"
label={LL.ID_OF(LL.TYPE(1))}
margin="normal"
fullWidth
value={editItem.type_id}
onChange={updateFormValue}
inputProps={{ style: { textTransform: 'uppercase' } }}
InputProps={{ startAdornment: <InputAdornment position="start">0x</InputAdornment> }}
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="offset"
label={LL.OFFSET()}
margin="normal"
fullWidth
type="number"
value={editItem.offset}
onChange={updateFormValue}
/>
</Grid>
<Grid item xs={4}>
<TextField
name="value_type"
label={LL.VALUE(1) + ' ' + LL.TYPE(1)}
value={editItem.value_type}
variant="outlined"
onChange={updateFormValue}
margin="normal"
fullWidth
select
>
<MenuItem value={DeviceValueType.BOOL}>BOOL</MenuItem>
<MenuItem value={DeviceValueType.INT}>INT</MenuItem>
<MenuItem value={DeviceValueType.UINT}>UINT</MenuItem>
<MenuItem value={DeviceValueType.SHORT}>SHORT</MenuItem>
<MenuItem value={DeviceValueType.USHORT}>USHORT</MenuItem>
<MenuItem value={DeviceValueType.ULONG}>ULONG</MenuItem>
<MenuItem value={DeviceValueType.TIME}>TIME</MenuItem>
<MenuItem value={DeviceValueType.STRING}>RAW</MenuItem>
</TextField>
</Grid>
{editItem.value_type !== DeviceValueType.BOOL && editItem.value_type !== DeviceValueType.STRING && (
<>
<Grid item xs={4}>
<TextField
name="factor"
label={LL.FACTOR()}
value={numberValue(editItem.factor)}
variant="outlined"
onChange={updateFormValue}
fullWidth
margin="normal"
type="number"
inputProps={{ step: '0.001' }}
/>
</Grid>
<Grid item xs={4}>
<TextField
name="uom"
label={LL.UNIT()}
value={editItem.uom}
margin="normal"
fullWidth
onChange={updateFormValue}
select
>
{DeviceValueUOM_s.map((val, i) => (
<MenuItem key={i} value={i}>
{val}
</MenuItem>
))}
</TextField>
</Grid>
</>
)}
{editItem.value_type === DeviceValueType.STRING && editItem.device_id !== '0' && (
<Grid item xs={4}>
<TextField
name="factor"
label="Bytes"
value={editItem.factor}
variant="outlined"
onChange={updateFormValue}
fullWidth
margin="normal"
type="number"
inputProps={{ min: '1', max: '27', step: '1' }}
/>
</Grid>
)}
</>
)}
</Grid>
</DialogContent>
<DialogActions>
{!creating && (
<Box flexGrow={1}>
<Button startIcon={<RemoveIcon />} variant="outlined" color="warning" onClick={remove}>
{LL.REMOVE()}
</Button>
</Box>
)}
<Button startIcon={<CancelIcon />} variant="outlined" onClick={close} color="secondary">
{LL.CANCEL()}
</Button>
<Button startIcon={creating ? <AddIcon /> : <DoneIcon />} variant="outlined" onClick={save} color="primary">
{creating ? LL.ADD(0) : LL.UPDATE()}
</Button>
</DialogActions>
</Dialog>
);
};
export default CustomEntitiesDialog;

View File

@@ -26,9 +26,9 @@ import { useState, useEffect, useCallback } from 'react';
import { useBlocker, useLocation } from 'react-router-dom'; import { useBlocker, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import SettingsCustomizationDialog from './CustomizationDialog';
import EntityMaskToggle from './EntityMaskToggle'; import EntityMaskToggle from './EntityMaskToggle';
import OptionIcon from './OptionIcon'; import OptionIcon from './OptionIcon';
import SettingsCustomizationDialog from './SettingsCustomizationDialog';
import * as EMSESP from './api'; import * as EMSESP from './api';
@@ -37,14 +37,14 @@ import type { DeviceShort, DeviceEntity } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { ButtonRow, SectionContent, MessageBox, BlockNavigation } from 'components'; import { ButtonRow, SectionContent, MessageBox, BlockNavigation, useLayoutTitle } from 'components';
import RestartMonitor from 'framework/system/RestartMonitor'; import RestartMonitor from 'framework/system/RestartMonitor';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
export const APIURL = window.location.origin + '/api/'; export const APIURL = window.location.origin + '/api/';
const SettingsCustomization: FC = () => { const Customization: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
@@ -58,6 +58,8 @@ const SettingsCustomization: FC = () => {
const [selectedDeviceEntity, setSelectedDeviceEntity] = useState<DeviceEntity>(); const [selectedDeviceEntity, setSelectedDeviceEntity] = useState<DeviceEntity>();
const [dialogOpen, setDialogOpen] = useState<boolean>(false); const [dialogOpen, setDialogOpen] = useState<boolean>(false);
useLayoutTitle(LL.CUSTOMIZATIONS());
// fetch devices first // fetch devices first
const { data: devices } = useRequest(EMSESP.readDevices); const { data: devices } = useRequest(EMSESP.readDevices);
@@ -508,13 +510,10 @@ const SettingsCustomization: FC = () => {
const renderContent = () => ( const renderContent = () => (
<> <>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.DEVICE_ENTITIES()}
</Typography>
{devices && renderDeviceList()} {devices && renderDeviceList()}
{deviceEntities && renderDeviceData()} {deviceEntities && renderDeviceData()}
{restartNeeded && ( {restartNeeded && (
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}> <MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}> <Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
{LL.RESTART()} {LL.RESTART()}
</Button> </Button>
@@ -544,7 +543,7 @@ const SettingsCustomization: FC = () => {
</ButtonRow> </ButtonRow>
)} )}
</Box> </Box>
<ButtonRow> <ButtonRow mt={1}>
<Button <Button
startIcon={<SettingsBackupRestoreIcon />} startIcon={<SettingsBackupRestoreIcon />}
variant="outlined" variant="outlined"
@@ -561,7 +560,7 @@ const SettingsCustomization: FC = () => {
); );
return ( return (
<SectionContent title={LL.CUSTOMIZATIONS()} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <RestartMonitor /> : renderContent()} {restarting ? <RestartMonitor /> : renderContent()}
{selectedDeviceEntity && ( {selectedDeviceEntity && (
@@ -576,4 +575,4 @@ const SettingsCustomization: FC = () => {
); );
}; };
export default SettingsCustomization; export default Customization;

View File

@@ -31,7 +31,7 @@ type SettingsCustomizationDialogProps = {
selectedItem: DeviceEntity; selectedItem: DeviceEntity;
}; };
const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: SettingsCustomizationDialogProps) => { const CustomizationDialog = ({ open, onClose, onSave, selectedItem }: SettingsCustomizationDialogProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<DeviceEntity>(selectedItem); const [editItem, setEditItem] = useState<DeviceEntity>(selectedItem);
const [error, setError] = useState<boolean>(false); const [error, setError] = useState<boolean>(false);
@@ -70,7 +70,7 @@ const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: Se
<DialogContent dividers> <DialogContent dividers>
<Grid container direction="row"> <Grid container direction="row">
<Typography variant="body2" color="warning.main"> <Typography variant="body2" color="warning.main">
{LL.ENTITY() + ' ID'}:&nbsp; {LL.ID_OF(LL.ENTITY())}:&nbsp;
</Typography> </Typography>
<Typography variant="body2">{editItem.id}</Typography> <Typography variant="body2">{editItem.id}</Typography>
</Grid> </Grid>
@@ -152,4 +152,4 @@ const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: Se
); );
}; };
export default SettingsCustomizationDialog; export default CustomizationDialog;

View File

@@ -1,37 +0,0 @@
import { Tab } from '@mui/material';
import { Navigate, Route, Routes } from 'react-router-dom';
import DashboardDevices from './DashboardDevices';
import DashboardSensors from './DashboardSensors';
import DashboardStatus from './DashboardStatus';
import type { FC } from 'react';
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const Dashboard: FC = () => {
const { routerTab } = useRouterTab();
const { LL } = useI18nContext();
useLayoutTitle(LL.DASHBOARD());
return (
<>
<RouterTabs value={routerTab}>
<Tab value="/dashboard/devices" label={LL.DEVICES()} />
<Tab value="/dashboard/sensors" label={LL.SENSORS()} />
<Tab value="/dashboard/status" label="Status" />
</RouterTabs>
<Routes>
<Route path="devices" element={<DashboardDevices />} />
<Route path="sensors" element={<DashboardSensors />} />
<Route path="status" element={<DashboardStatus />} />
<Route path="*" element={<Navigate replace to="/dashboard/devices" />} />
</Routes>
</>
);
};
export default Dashboard;

View File

@@ -1,282 +0,0 @@
import CancelIcon from '@mui/icons-material/Cancel';
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
import RefreshIcon from '@mui/icons-material/Refresh';
import {
Avatar,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
List,
ListItem,
ListItemAvatar,
ListItemText,
useTheme
} from '@mui/material';
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
import { useRequest } from 'alova';
import { useContext, useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import * as EMSESP from './api';
import { busConnectionStatus } from './types';
import type { Stat, Status } from './types';
import type { Theme } from '@mui/material';
import type { Translation } from 'i18n/i18n-types';
import type { FC } from 'react';
import { dialogStyle } from 'CustomTheme';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE;
const busStatusHighlight = ({ status }: Status, theme: Theme) => {
switch (status) {
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
return theme.palette.warning.main;
case busConnectionStatus.BUS_STATUS_CONNECTED:
return theme.palette.success.main;
case busConnectionStatus.BUS_STATUS_OFFLINE:
return theme.palette.error.main;
default:
return theme.palette.warning.main;
}
};
const showQuality = (stat: Stat) => {
if (stat.q === 0 || stat.s + stat.f === 0) {
return;
}
if (stat.q === 100) {
return <div style={{ color: '#00FF7F' }}>{stat.q}%</div>;
}
if (stat.q >= 95) {
return <div style={{ color: 'orange' }}>{stat.q}%</div>;
} else {
return <div style={{ color: 'red' }}>{stat.q}%</div>;
}
};
const DashboardStatus: FC = () => {
const { data: data, send: loadData, error } = useRequest(EMSESP.readStatus);
const { LL } = useI18nContext();
const theme = useTheme();
const [confirmScan, setConfirmScan] = useState<boolean>(false);
const { me } = useContext(AuthenticatedContext);
const { send: scanDevices } = useRequest(EMSESP.scanDevices, {
immediate: false
});
const stats_theme = tableTheme({
Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px;
`,
BaseRow: `
font-size: 14px;
`,
HeaderRow: `
text-transform: uppercase;
background-color: black;
color: #90CAF9;
.th {
height: 36px;
border-bottom: 1px solid #565656;
}
`,
Row: `
.td {
padding: 8px;
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
}
&:nth-of-type(odd) .td {
background-color: #303030;
}
&:nth-of-type(even) .td {
background-color: #1e1e1e;
}
`,
BaseCell: `
&:not(:first-of-type) {
text-align: center;
}
`
});
useEffect(() => {
const timer = setInterval(() => loadData(), 30000);
return () => {
clearInterval(timer);
};
});
const showName = (id: any) => {
const name: keyof Translation['STATUS_NAMES'] = id;
return LL.STATUS_NAMES[name]();
};
const formatDurationSec = (duration_sec: number) => {
const days = Math.trunc((duration_sec * 1000) / 86400000);
const hours = Math.trunc((duration_sec * 1000) / 3600000) % 24;
const minutes = Math.trunc((duration_sec * 1000) / 60000) % 60;
const seconds = Math.trunc((duration_sec * 1000) / 1000) % 60;
let formatted = '';
if (days) {
formatted += LL.NUM_DAYS({ num: days }) + ' ';
}
if (hours) {
formatted += LL.NUM_HOURS({ num: hours }) + ' ';
}
if (minutes) {
formatted += LL.NUM_MINUTES({ num: minutes }) + ' ';
}
formatted += LL.NUM_SECONDS({ num: seconds });
return formatted;
};
const busStatus = () => {
if (data) {
switch (data.status) {
case busConnectionStatus.BUS_STATUS_CONNECTED:
return LL.CONNECTED(0) + ' (' + formatDurationSec(data.uptime) + ')';
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
return LL.TX_ISSUES();
case busConnectionStatus.BUS_STATUS_OFFLINE:
return LL.DISCONNECTED();
}
}
return 'Unknown';
};
const scan = async () => {
await scanDevices()
.then(() => {
toast.info(LL.SCANNING() + '...');
})
.catch((err) => {
toast.error(err.message);
});
setConfirmScan(false);
};
const renderScanDialog = () => (
<Dialog sx={dialogStyle} open={confirmScan} onClose={() => setConfirmScan(false)}>
<DialogTitle>{LL.SCAN_DEVICES()}</DialogTitle>
<DialogContent dividers>{LL.EMS_SCAN()}</DialogContent>
<DialogActions>
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmScan(false)} color="secondary">
{LL.CANCEL()}
</Button>
<Button startIcon={<PermScanWifiIcon />} variant="outlined" onClick={scan} color="primary">
{LL.SCAN()}
</Button>
</DialogActions>
</Dialog>
);
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (
<>
<List>
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: busStatusHighlight(data, theme) }}>
<DirectionsBusIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.EMS_BUS_STATUS()} secondary={busStatus()} />
</ListItem>
<ListItem>
<ListItemAvatar>
<Avatar>
<DeviceHubIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={LL.ACTIVE_DEVICES()}
secondary={
LL.NUM_DEVICES({ num: data.num_devices }) +
', ' +
LL.NUM_TEMP_SENSORS({ num: data.num_sensors }) +
', ' +
LL.NUM_ANALOG_SENSORS({ num: data.num_analogs })
}
/>
</ListItem>
<Box m={3} />
<Table data={{ nodes: data.stats }} theme={stats_theme} layout={{ custom: true }}>
{(tableList: any) => (
<>
<Header>
<HeaderRow>
<HeaderCell resize />
<HeaderCell stiff>{LL.SUCCESS()}</HeaderCell>
<HeaderCell stiff>{LL.FAIL()}</HeaderCell>
<HeaderCell stiff>{LL.QUALITY()}</HeaderCell>
</HeaderRow>
</Header>
<Body>
{tableList.map((stat: Stat) => (
<Row key={stat.id} item={stat}>
<Cell>{showName(stat.id)}</Cell>
<Cell stiff>{Intl.NumberFormat().format(stat.s)}</Cell>
<Cell stiff>{Intl.NumberFormat().format(stat.f)}</Cell>
<Cell stiff>{showQuality(stat)}</Cell>
</Row>
))}
</Body>
</>
)}
</Table>
</List>
{renderScanDialog()}
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
{LL.REFRESH()}
</Button>
</Box>
<Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow>
<Button
startIcon={<PermScanWifiIcon />}
variant="outlined"
color="primary"
disabled={!me.admin}
onClick={() => setConfirmScan(true)}
>
{LL.SCAN_DEVICES()}
</Button>
</ButtonRow>
</Box>
</Box>
</>
);
};
return (
<SectionContent title={LL.EMS_BUS_STATUS_TITLE()} titleGutter>
{content()}
</SectionContent>
);
};
export default DashboardStatus;

View File

@@ -1,12 +1,13 @@
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import { AiOutlineControl, AiOutlineGateway, AiOutlineAlert } from 'react-icons/ai'; import { AiOutlineControl, AiOutlineGateway, AiOutlineAlert } from 'react-icons/ai';
import { CgSmartHomeBoiler } from 'react-icons/cg'; import { CgSmartHomeBoiler } from 'react-icons/cg';
import { FaSolarPanel } from 'react-icons/fa'; import { FaSolarPanel } from 'react-icons/fa';
import { GiHeatHaze } from 'react-icons/gi'; import { GiHeatHaze, GiTap } from 'react-icons/gi';
import { MdThermostatAuto, MdOutlineSensors, MdOutlineExtension, MdOutlineDevices } from 'react-icons/md'; import { MdThermostatAuto, MdOutlineSensors, MdOutlineDevices, MdOutlinePool } from 'react-icons/md';
import { TiFlowSwitch } from 'react-icons/ti'; import { TiFlowSwitch } from 'react-icons/ti';
import { VscVmConnect } from 'react-icons/vsc'; import { VscVmConnect } from 'react-icons/vsc';
import { DeviceType } from './types'; import { DeviceType } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
interface DeviceIconProps { interface DeviceIconProps {
@@ -40,8 +41,12 @@ const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
return <AiOutlineAlert />; return <AiOutlineAlert />;
case DeviceType.EXTENSION: case DeviceType.EXTENSION:
return <MdOutlineDevices />; return <MdOutlineDevices />;
case DeviceType.WATER:
return <GiTap />;
case DeviceType.POOL:
return <MdOutlinePool />;
case DeviceType.CUSTOM: case DeviceType.CUSTOM:
return <MdOutlineExtension />; return <PlaylistAddIcon sx={{ color: 'lightblue', fontSize: 22, verticalAlign: 'middle' }} />;
default: default:
return null; return null;
} }

View File

@@ -33,13 +33,13 @@ import { useSort, SortToggleType } from '@table-library/react-table-library/sort
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme'; import { useTheme } from '@table-library/react-table-library/theme';
import { useRequest } from 'alova'; import { useRequest } from 'alova';
import { useState, useContext, useEffect, useCallback, useLayoutEffect } from 'react'; import { useState, useEffect, useCallback, useLayoutEffect, useContext } from 'react';
import { IconContext } from 'react-icons'; import { IconContext } from 'react-icons';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import DashboardDevicesDialog from './DashboardDevicesDialog';
import DeviceIcon from './DeviceIcon'; import DeviceIcon from './DeviceIcon';
import DashboardDevicesDialog from './DevicesDialog';
import * as EMSESP from './api'; import * as EMSESP from './api';
import { formatValue } from './deviceValue'; import { formatValue } from './deviceValue';
@@ -49,14 +49,15 @@ import { deviceValueItemValidation } from './validators';
import type { Device, DeviceValue } from './types'; import type { Device, DeviceValue } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { ButtonRow, SectionContent, MessageBox } from 'components'; import { ButtonRow, SectionContent, MessageBox, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const DashboardDevices: FC = () => { const Devices: FC = () => {
const { me } = useContext(AuthenticatedContext);
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext);
const [size, setSize] = useState([0, 0]); const [size, setSize] = useState([0, 0]);
const [selectedDeviceValue, setSelectedDeviceValue] = useState<DeviceValue>(); const [selectedDeviceValue, setSelectedDeviceValue] = useState<DeviceValue>();
const [onlyFav, setOnlyFav] = useState(false); const [onlyFav, setOnlyFav] = useState(false);
@@ -66,6 +67,8 @@ const DashboardDevices: FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
useLayoutTitle(LL.DEVICES());
const { data: coreData, send: readCoreData } = useRequest(() => EMSESP.readCoreData(), { const { data: coreData, send: readCoreData } = useRequest(() => EMSESP.readCoreData(), {
initialData: { initialData: {
connected: true, connected: true,
@@ -94,8 +97,14 @@ const DashboardDevices: FC = () => {
}, []); }, []);
const leftOffset = () => { const leftOffset = () => {
const left = document.getElementById('devices-window')?.getBoundingClientRect().left; const devicesWindow = document.getElementById('devices-window');
const right = document.getElementById('devices-window')?.getBoundingClientRect().right; if (!devicesWindow) {
return 0;
}
const clientRect = devicesWindow.getBoundingClientRect();
const left = clientRect.left;
const right = clientRect.right;
if (!left || !right) { if (!left || !right) {
return 0; return 0;
@@ -275,9 +284,9 @@ const DashboardDevices: FC = () => {
const customize = () => { const customize = () => {
if (selectedDevice == 99) { if (selectedDevice == 99) {
navigate('/settings/customentities'); navigate('/customentities');
} else { } else {
navigate('/settings/customization', { state: selectedDevice }); navigate('/customizations', { state: selectedDevice });
} }
}; };
@@ -414,12 +423,10 @@ const DashboardDevices: FC = () => {
}; };
const renderCoreData = () => ( const renderCoreData = () => (
<IconContext.Provider value={{ color: 'lightblue', size: '24', style: { verticalAlign: 'middle' } }}> <IconContext.Provider value={{ color: 'lightblue', size: '18', style: { verticalAlign: 'middle' } }}>
{!coreData.connected && <MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />} {!coreData.connected && <MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />}
{coreData.connected && coreData.devices.length === 0 && (
<MessageBox my={2} level="warning" message={LL.EMS_BUS_SCANNING()} />
)}
{coreData.connected && (
<Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}> <Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}>
{(tableList: any) => ( {(tableList: any) => (
<> <>
@@ -447,6 +454,7 @@ const DashboardDevices: FC = () => {
</> </>
)} )}
</Table> </Table>
)}
</IconContext.Provider> </IconContext.Provider>
); );
@@ -515,9 +523,11 @@ const DashboardDevices: FC = () => {
<IconButton onClick={() => setShowDeviceInfo(true)}> <IconButton onClick={() => setShowDeviceInfo(true)}>
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} /> <InfoOutlinedIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} />
</IconButton> </IconButton>
{me.admin && (
<IconButton onClick={customize}> <IconButton onClick={customize}>
<FormatListNumberedIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} /> <FormatListNumberedIcon sx={{ fontSize: 18, verticalAlign: 'middle' }} />
</IconButton> </IconButton>
)}
<IconButton onClick={handleDownloadCsv}> <IconButton onClick={handleDownloadCsv}>
<DownloadIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} /> <DownloadIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} />
</IconButton> </IconButton>
@@ -579,7 +589,7 @@ const DashboardDevices: FC = () => {
<Cell>{renderNameCell(dv)}</Cell> <Cell>{renderNameCell(dv)}</Cell>
<Cell>{formatValue(LL, dv.v, dv.u)}</Cell> <Cell>{formatValue(LL, dv.v, dv.u)}</Cell>
<Cell stiff> <Cell stiff>
{dv.c && me.admin && !hasMask(dv.id, DeviceEntityMask.DV_READONLY) && ( {me.admin && dv.c && !hasMask(dv.id, DeviceEntityMask.DV_READONLY) && (
<IconButton size="small" onClick={() => showDeviceValue(dv)}> <IconButton size="small" onClick={() => showDeviceValue(dv)}>
{dv.v === '' && dv.c ? ( {dv.v === '' && dv.c ? (
<PlayArrowIcon color="primary" sx={{ fontSize: 16 }} /> <PlayArrowIcon color="primary" sx={{ fontSize: 16 }} />
@@ -600,7 +610,7 @@ const DashboardDevices: FC = () => {
}; };
return ( return (
<SectionContent title={LL.DEVICE_DATA()} titleGutter id="devices-window"> <SectionContent id="devices-window">
{renderCoreData()} {renderCoreData()}
{renderDeviceData()} {renderDeviceData()}
{renderDeviceDetails()} {renderDeviceDetails()}
@@ -611,15 +621,13 @@ const DashboardDevices: FC = () => {
onSave={deviceValueDialogSave} onSave={deviceValueDialogSave}
selectedItem={selectedDeviceValue} selectedItem={selectedDeviceValue}
writeable={ writeable={
me.admin && selectedDeviceValue.c !== undefined && !hasMask(selectedDeviceValue.id, DeviceEntityMask.DV_READONLY)
selectedDeviceValue.c !== undefined &&
!hasMask(selectedDeviceValue.id, DeviceEntityMask.DV_READONLY)
} }
validator={deviceValueItemValidation(selectedDeviceValue)} validator={deviceValueItemValidation(selectedDeviceValue)}
progress={submitting} progress={submitting}
/> />
)} )}
<ButtonRow> <ButtonRow mt={1}>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={refreshData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={refreshData}>
{LL.REFRESH()} {LL.REFRESH()}
</Button> </Button>
@@ -628,4 +636,4 @@ const DashboardDevices: FC = () => {
); );
}; };
export default DashboardDevices; export default Devices;

View File

@@ -26,7 +26,7 @@ import type { ValidateFieldsError } from 'async-validator';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { ValidatedTextField } from 'components'; import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils'; import { updateValue, numberValue } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
@@ -40,7 +40,7 @@ type DashboardDevicesDialogProps = {
progress: boolean; progress: boolean;
}; };
const DashboardDevicesDialog = ({ const DevicesDialog = ({
open, open,
onClose, onClose,
onSave, onSave,
@@ -133,7 +133,7 @@ const DashboardDevicesDialog = ({
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="v" name="v"
label={LL.VALUE(1)} label={LL.VALUE(1)}
value={Math.round(editItem.v * 10) / 10} value={numberValue(Math.round(editItem.v * 10) / 10)}
autoFocus autoFocus
disabled={!writeable} disabled={!writeable}
type="number" type="number"
@@ -204,4 +204,4 @@ const DashboardDevicesDialog = ({
); );
}; };
export default DashboardDevicesDialog; export default DevicesDialog;

View File

@@ -1,9 +1,19 @@
import CommentIcon from '@mui/icons-material/CommentTwoTone'; import CommentIcon from '@mui/icons-material/CommentTwoTone';
import EastIcon from '@mui/icons-material/East';
import DownloadIcon from '@mui/icons-material/GetApp'; import DownloadIcon from '@mui/icons-material/GetApp';
import GitHubIcon from '@mui/icons-material/GitHub'; import GitHubIcon from '@mui/icons-material/GitHub';
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone'; import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
import { Box, List, ListItem, ListItemAvatar, ListItemText, Link, Typography, Button } from '@mui/material'; import {
Box,
List,
ListItem,
ListItemAvatar,
ListItemText,
Link,
Typography,
Button,
ListItemButton,
Avatar
} from '@mui/material';
import { useRequest } from 'alova'; import { useRequest } from 'alova';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import type { FC } from 'react'; import type { FC } from 'react';
@@ -39,59 +49,56 @@ const Help: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SUPPORT_INFORMATION()} titleGutter> <SectionContent>
<List> <List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListItem> <ListItem>
<ListItemButton component="a" href="https://emsesp.github.io/docs">
<ListItemAvatar> <ListItemAvatar>
<MenuBookIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> <Avatar sx={{ bgcolor: '#72caf9' }}>
<MenuBookIcon />
</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText> <ListItemText primary={LL.HELP_INFORMATION_1()} />
{LL.HELP_INFORMATION_1()}&nbsp; </ListItemButton>
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
&nbsp;
<Link target="_blank" href="https://emsesp.github.io/docs" color="primary">
{LL.CLICK_HERE()}
</Link>
</ListItemText>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemButton component="a" href="https://discord.gg/3J3GgnzpyT">
<ListItemAvatar> <ListItemAvatar>
<CommentIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> <Avatar sx={{ bgcolor: '#72caf9' }}>
<CommentIcon />
</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText> <ListItemText primary={LL.HELP_INFORMATION_2()} />
{LL.HELP_INFORMATION_2()}&nbsp; </ListItemButton>
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
&nbsp;
<Link target="_blank" href="https://discord.gg/3J3GgnzpyT" color="primary">
{LL.CLICK_HERE()}
</Link>
</ListItemText>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemButton component="a" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose">
<ListItemAvatar> <ListItemAvatar>
<GitHubIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> <Avatar sx={{ bgcolor: '#72caf9' }}>
<GitHubIcon />
</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText> <ListItemText primary={LL.HELP_INFORMATION_3()} />
{LL.HELP_INFORMATION_3()}&nbsp; </ListItemButton>
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" color="primary">
{LL.CLICK_HERE()}
</Link>
<br />
</ListItemText>
</ListItem> </ListItem>
</List> </List>
<Box color="warning.main"> <Box p={2} color="warning.main">
<Typography mb={1} variant="body2"> <Typography mb={1} variant="body2">
{LL.HELP_INFORMATION_4()} {LL.HELP_INFORMATION_4()}
</Typography> </Typography>
</Box> <Button
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => callAPI('system', 'info')}> startIcon={<DownloadIcon />}
{LL.SUPPORT_INFORMATION()} variant="outlined"
color="primary"
onClick={() => callAPI('system', 'info')}
>
{LL.SUPPORT_INFORMATION(0)}
</Button> </Button>
</Box>
<Button <Button
sx={{ ml: 2 }} sx={{ ml: 2 }}
startIcon={<DownloadIcon />} startIcon={<DownloadIcon />}
@@ -102,7 +109,7 @@ const Help: FC = () => {
All Values All Values
</Button> </Button>
<Box border={1} p={1} mt={4} color="orange"> <Box border={1} p={1} mt={4}>
<Typography align="center" variant="subtitle1" color="orange"> <Typography align="center" variant="subtitle1" color="orange">
<b>{LL.HELP_INFORMATION_5()}</b> <b>{LL.HELP_INFORMATION_5()}</b>
</Typography> </Typography>
@@ -111,7 +118,7 @@ const Help: FC = () => {
{'github.com/emsesp/EMS-ESP32'} {'github.com/emsesp/EMS-ESP32'}
</Link> </Link>
</Typography> </Typography>
<Typography color="white" align="center"> <Typography color="white" variant="subtitle2" align="center">
@proddy @MichaelDvP @proddy @MichaelDvP
</Typography> </Typography>
</Box> </Box>

View File

@@ -11,18 +11,18 @@ import { updateState, useRequest } from 'alova';
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useBlocker } from 'react-router-dom'; import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import SettingsSchedulerDialog from './SettingsSchedulerDialog'; import SettingsSchedulerDialog from './SchedulerDialog';
import * as EMSESP from './api'; import * as EMSESP from './api';
import { ScheduleFlag } from './types'; import { ScheduleFlag } from './types';
import { schedulerItemValidation } from './validators'; import { schedulerItemValidation } from './validators';
import type { ScheduleItem } from './types'; import type { ScheduleItem } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; import { ButtonRow, FormLoader, SectionContent, BlockNavigation, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const SettingsScheduler: FC = () => { const Scheduler: FC = () => {
const { LL, locale } = useI18nContext(); const { LL, locale } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
@@ -194,6 +194,8 @@ const SettingsScheduler: FC = () => {
</> </>
); );
useLayoutTitle(LL.SCHEDULER());
return ( return (
<Table <Table
data={{ nodes: schedule.filter((si) => !si.deleted).sort((a, b) => a.time.localeCompare(b.time)) }} data={{ nodes: schedule.filter((si) => !si.deleted).sort((a, b) => a.time.localeCompare(b.time)) }}
@@ -208,7 +210,7 @@ const SettingsScheduler: FC = () => {
<HeaderCell stiff>{LL.SCHEDULE(0)}</HeaderCell> <HeaderCell stiff>{LL.SCHEDULE(0)}</HeaderCell>
<HeaderCell stiff>{LL.TIME(0)}</HeaderCell> <HeaderCell stiff>{LL.TIME(0)}</HeaderCell>
<HeaderCell stiff>{LL.COMMAND(0)}</HeaderCell> <HeaderCell stiff>{LL.COMMAND(0)}</HeaderCell>
<HeaderCell stiff>{LL.VALUE(0)}</HeaderCell> <HeaderCell stiff>{LL.VALUE(1)}</HeaderCell>
<HeaderCell stiff>{LL.NAME(0)}</HeaderCell> <HeaderCell stiff>{LL.NAME(0)}</HeaderCell>
</HeaderRow> </HeaderRow>
</Header> </Header>
@@ -249,7 +251,7 @@ const SettingsScheduler: FC = () => {
}; };
return ( return (
<SectionContent title={LL.SCHEDULER()} titleGutter> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
<Box mb={2} color="warning.main"> <Box mb={2} color="warning.main">
<Typography variant="body2">{LL.SCHEDULER_HELP_1()}</Typography> <Typography variant="body2">{LL.SCHEDULER_HELP_1()}</Typography>
@@ -268,7 +270,7 @@ const SettingsScheduler: FC = () => {
/> />
)} )}
<Box display="flex" flexWrap="wrap"> <Box mt={1} display="flex" flexWrap="wrap">
<Box flexGrow={1}> <Box flexGrow={1}>
{numChanges !== 0 && ( {numChanges !== 0 && (
<ButtonRow> <ButtonRow>
@@ -298,4 +300,4 @@ const SettingsScheduler: FC = () => {
); );
}; };
export default SettingsScheduler; export default Scheduler;

View File

@@ -32,7 +32,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils'; import { updateValue } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
type SettingsSchedulerDialogProps = { type SchedulerDialogProps = {
open: boolean; open: boolean;
creating: boolean; creating: boolean;
onClose: () => void; onClose: () => void;
@@ -42,15 +42,7 @@ type SettingsSchedulerDialogProps = {
dow: string[]; dow: string[];
}; };
const SettingsSchedulerDialog = ({ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, validator, dow }: SchedulerDialogProps) => {
open,
creating,
onClose,
onSave,
selectedItem,
validator,
dow
}: SettingsSchedulerDialogProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<ScheduleItem>(selectedItem); const [editItem, setEditItem] = useState<ScheduleItem>(selectedItem);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -246,4 +238,4 @@ const SettingsSchedulerDialog = ({
); );
}; };
export default SettingsSchedulerDialog; export default SchedulerDialog;

View File

@@ -8,26 +8,27 @@ import { useSort, SortToggleType } from '@table-library/react-table-library/sort
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme'; import { useTheme } from '@table-library/react-table-library/theme';
import { useRequest } from 'alova'; import { useRequest } from 'alova';
import { useState, useContext, useEffect } from 'react'; import { useState, useEffect, useContext } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import DashboardSensorsAnalogDialog from './DashboardSensorsAnalogDialog'; import DashboardSensorsAnalogDialog from './SensorsAnalogDialog';
import DashboardSensorsTemperatureDialog from './DashboardSensorsTemperatureDialog'; import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog';
import * as EMSESP from './api'; import * as EMSESP from './api';
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames, AnalogType } from './types'; import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames, AnalogType } from './types';
import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators'; import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators';
import type { TemperatureSensor, AnalogSensor } from './types'; import type { TemperatureSensor, AnalogSensor } from './types';
import type { FC } from 'react'; import type { FC } from 'react';
import { ButtonRow, SectionContent } from 'components'; import { ButtonRow, SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
const DashboardSensors: FC = () => { const Sensors: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState<TemperatureSensor>(); const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState<TemperatureSensor>();
const [selectedAnalogSensor, setSelectedAnalogSensor] = useState<AnalogSensor>(); const [selectedAnalogSensor, setSelectedAnalogSensor] = useState<AnalogSensor>();
const [temperatureDialogOpen, setTemperatureDialogOpen] = useState<boolean>(false); const [temperatureDialogOpen, setTemperatureDialogOpen] = useState<boolean>(false);
@@ -51,8 +52,6 @@ const DashboardSensors: FC = () => {
immediate: false immediate: false
}); });
const isAdmin = me.admin;
const common_theme = useTheme({ const common_theme = useTheme({
BaseRow: ` BaseRow: `
font-size: 14px; font-size: 14px;
@@ -170,6 +169,8 @@ const DashboardSensors: FC = () => {
}; };
}); });
useLayoutTitle(LL.SENSORS());
const formatDurationMin = (duration_min: number) => { const formatDurationMin = (duration_min: number) => {
const days = Math.trunc((duration_min * 60000) / 86400000); const days = Math.trunc((duration_min * 60000) / 86400000);
const hours = Math.trunc((duration_min * 60000) / 3600000) % 24; const hours = Math.trunc((duration_min * 60000) / 3600000) % 24;
@@ -220,7 +221,7 @@ const DashboardSensors: FC = () => {
} }
const updateTemperatureSensor = (ts: TemperatureSensor) => { const updateTemperatureSensor = (ts: TemperatureSensor) => {
if (isAdmin) { if (me.admin) {
setSelectedTemperatureSensor(ts); setSelectedTemperatureSensor(ts);
setTemperatureDialogOpen(true); setTemperatureDialogOpen(true);
} }
@@ -246,7 +247,7 @@ const DashboardSensors: FC = () => {
}; };
const updateAnalogSensor = (as: AnalogSensor) => { const updateAnalogSensor = (as: AnalogSensor) => {
if (isAdmin) { if (me.admin) {
setCreating(false); setCreating(false);
setSelectedAnalogSensor(as); setSelectedAnalogSensor(as);
setAnalogDialogOpen(true); setAnalogDialogOpen(true);
@@ -262,7 +263,7 @@ const DashboardSensors: FC = () => {
setSelectedAnalogSensor({ setSelectedAnalogSensor({
id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100), id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100),
n: '', n: '',
g: 40, g: 21, // default GPIO 21 which is safe for all platforms
u: 0, u: 0,
v: 0, v: 0,
o: 0, o: 0,
@@ -406,10 +407,8 @@ const DashboardSensors: FC = () => {
); );
return ( return (
<SectionContent title={LL.SENSOR_DATA()} titleGutter> <SectionContent>
{sensorData.ts.length > 0 && ( <Typography sx={{ pb: 1 }} variant="h6" color="secondary">
<>
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
{LL.TEMP_SENSORS()} {LL.TEMP_SENSORS()}
</Typography> </Typography>
<RenderTemperatureSensors /> <RenderTemperatureSensors />
@@ -422,9 +421,6 @@ const DashboardSensors: FC = () => {
validator={temperatureSensorItemValidation()} validator={temperatureSensorItemValidation()}
/> />
)} )}
</>
)}
{sensorData?.analog_enabled === true && ( {sensorData?.analog_enabled === true && (
<> <>
<Typography sx={{ pt: 4, pb: 1 }} variant="h6" color="secondary"> <Typography sx={{ pt: 4, pb: 1 }} variant="h6" color="secondary">
@@ -443,15 +439,14 @@ const DashboardSensors: FC = () => {
)} )}
</> </>
)} )}
<ButtonRow> <ButtonRow>
<Box mt={2} display="flex" flexWrap="wrap"> <Box mt={1} display="flex" flexWrap="wrap">
<Box flexGrow={1}> <Box flexGrow={1}>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={fetchSensorData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={fetchSensorData}>
{LL.REFRESH()} {LL.REFRESH()}
</Button> </Button>
</Box> </Box>
{sensorData?.analog_enabled === true && ( {sensorData?.analog_enabled === true && me.admin && (
<Button <Button
variant="outlined" variant="outlined"
color="primary" color="primary"
@@ -467,4 +462,4 @@ const DashboardSensors: FC = () => {
); );
}; };
export default DashboardSensors; export default Sensors;

View File

@@ -38,7 +38,7 @@ type DashboardSensorsAnalogDialogProps = {
validator: Schema; validator: Schema;
}; };
const DashboardSensorsAnalogDialog = ({ const SensorsAnalogDialog = ({
open, open,
onClose, onClose,
onSave, onSave,
@@ -182,7 +182,7 @@ const DashboardSensorsAnalogDialog = ({
<Grid item xs={4}> <Grid item xs={4}>
<TextField <TextField
name="o" name="o"
label={LL.VALUE(0)} label={LL.VALUE(1)}
value={numberValue(editItem.o)} value={numberValue(editItem.o)}
fullWidth fullWidth
type="number" type="number"
@@ -197,7 +197,7 @@ const DashboardSensorsAnalogDialog = ({
<Grid item xs={4}> <Grid item xs={4}>
<TextField <TextField
name="o" name="o"
label={LL.VALUE(0)} label={LL.VALUE(1)}
value={numberValue(editItem.o)} value={numberValue(editItem.o)}
fullWidth fullWidth
select select
@@ -296,4 +296,4 @@ const DashboardSensorsAnalogDialog = ({
); );
}; };
export default DashboardSensorsAnalogDialog; export default SensorsAnalogDialog;

View File

@@ -26,7 +26,7 @@ import { numberValue, updateValue } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
type DashboardSensorsTemperatureDialogProps = { type SensorsTemperatureDialogProps = {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
onSave: (ts: TemperatureSensor) => void; onSave: (ts: TemperatureSensor) => void;
@@ -34,13 +34,13 @@ type DashboardSensorsTemperatureDialogProps = {
validator: Schema; validator: Schema;
}; };
const DashboardSensorsTemperatureDialog = ({ const SensorsTemperatureDialog = ({
open, open,
onClose, onClose,
onSave, onSave,
selectedItem, selectedItem,
validator validator
}: DashboardSensorsTemperatureDialogProps) => { }: SensorsTemperatureDialogProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const [editItem, setEditItem] = useState<TemperatureSensor>(selectedItem); const [editItem, setEditItem] = useState<TemperatureSensor>(selectedItem);
@@ -119,4 +119,4 @@ const DashboardSensorsTemperatureDialog = ({
); );
}; };
export default DashboardSensorsTemperatureDialog; export default SensorsTemperatureDialog;

View File

@@ -1,37 +0,0 @@
import { Tab } from '@mui/material';
import { Navigate, Route, Routes } from 'react-router-dom';
import SettingsApplication from './SettingsApplication';
import SettingsCustomization from './SettingsCustomization';
import SettingsEntities from './SettingsEntities';
import SettingsScheduler from './SettingsScheduler';
import type { FC } from 'react';
import { RouterTabs, useRouterTab, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const Settings: FC = () => {
const { LL } = useI18nContext();
const { routerTab } = useRouterTab();
useLayoutTitle(LL.SETTINGS_OF(''));
return (
<>
<RouterTabs value={routerTab}>
<Tab value="/settings/application" label={LL.APPLICATION_SETTINGS()} />
<Tab value="/settings/customization" label={LL.CUSTOMIZATIONS()} />
<Tab value="/settings/scheduler" label={LL.SCHEDULER()} />
<Tab value="/settings/customentities" label={LL.CUSTOM_ENTITIES(0)} />
</RouterTabs>
<Routes>
<Route path="application" element={<SettingsApplication />} />
<Route path="customization" element={<SettingsCustomization />} />
<Route path="scheduler" element={<SettingsScheduler />} />
<Route path="customentities" element={<SettingsEntities />} />
<Route path="*" element={<Navigate replace to="/settings/application" />} />
</Routes>
</>
);
};
export default Settings;

View File

@@ -1,252 +0,0 @@
import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
import {
Box,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
InputAdornment,
MenuItem,
TextField
} from '@mui/material';
import { useEffect, useState } from 'react';
import { DeviceValueUOM_s, DeviceValueType } from './types';
import type { EntityItem } from './types';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
import { dialogStyle } from 'CustomTheme';
import { BlockFormControlLabel, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils';
import { validate } from 'validators';
type SettingsEntitiesDialogProps = {
open: boolean;
creating: boolean;
onClose: () => void;
onSave: (ei: EntityItem) => void;
selectedItem: EntityItem;
validator: Schema;
};
const SettingsEntitiesDialog = ({
open,
creating,
onClose,
onSave,
selectedItem,
validator
}: SettingsEntitiesDialogProps) => {
const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<EntityItem>(selectedItem);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const updateFormValue = updateValue(setEditItem);
useEffect(() => {
if (open) {
setFieldErrors(undefined);
setEditItem(selectedItem);
// convert to hex strings straight away
setEditItem({
...selectedItem,
device_id: selectedItem.device_id.toString(16).toUpperCase(),
type_id: selectedItem.type_id.toString(16).toUpperCase()
});
}
}, [open, selectedItem]);
const close = () => {
onClose();
};
const save = async () => {
try {
setFieldErrors(undefined);
await validate(validator, editItem);
if (typeof editItem.device_id === 'string') {
editItem.device_id = parseInt(editItem.device_id, 16);
}
if (typeof editItem.type_id === 'string') {
editItem.type_id = parseInt(editItem.type_id, 16);
}
onSave(editItem);
} catch (errors: any) {
setFieldErrors(errors);
}
};
const remove = () => {
editItem.deleted = true;
onSave(editItem);
};
return (
<Dialog sx={dialogStyle} open={open} onClose={close}>
<DialogTitle>
{creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()}&nbsp;{LL.ENTITY()}
</DialogTitle>
<DialogContent dividers>
<Box display="flex" flexWrap="wrap" mb={1}>
<Box flexWrap="nowrap" whiteSpace="nowrap" />
</Box>
<Grid container spacing={2}>
<Grid item xs={8}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="name"
label={LL.NAME(0)}
value={editItem.name}
margin="normal"
fullWidth
onChange={updateFormValue}
/>
</Grid>
<Grid item xs={4} mt={3}>
<BlockFormControlLabel
control={<Checkbox checked={editItem.writeable} onChange={updateFormValue} name="writeable" />}
label={LL.WRITEABLE()}
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="device_id"
label={LL.ID_OF(LL.DEVICE())}
margin="normal"
type="string"
fullWidth
value={editItem.device_id as string}
onChange={updateFormValue}
inputProps={{ style: { textTransform: 'uppercase' } }}
InputProps={{ startAdornment: <InputAdornment position="start">0x</InputAdornment> }}
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="type_id"
label={LL.ID_OF(LL.TYPE(1))}
margin="normal"
fullWidth
value={editItem.type_id}
onChange={updateFormValue}
inputProps={{ style: { textTransform: 'uppercase' } }}
InputProps={{ startAdornment: <InputAdornment position="start">0x</InputAdornment> }}
/>
</Grid>
<Grid item xs={4}>
<ValidatedTextField
fieldErrors={fieldErrors}
name="offset"
label={LL.OFFSET()}
margin="normal"
fullWidth
type="number"
value={editItem.offset}
onChange={updateFormValue}
/>
</Grid>
<Grid item xs={4}>
<TextField
name="value_type"
label={LL.VALUE(1) + ' ' + LL.TYPE(1)}
value={editItem.value_type}
variant="outlined"
onChange={updateFormValue}
margin="normal"
fullWidth
select
>
<MenuItem value={DeviceValueType.BOOL}>BOOL</MenuItem>
<MenuItem value={DeviceValueType.INT}>INT</MenuItem>
<MenuItem value={DeviceValueType.UINT}>UINT</MenuItem>
<MenuItem value={DeviceValueType.SHORT}>SHORT</MenuItem>
<MenuItem value={DeviceValueType.USHORT}>USHORT</MenuItem>
<MenuItem value={DeviceValueType.ULONG}>ULONG</MenuItem>
<MenuItem value={DeviceValueType.TIME}>TIME</MenuItem>
<MenuItem value={DeviceValueType.STRING}>RAW</MenuItem>
</TextField>
</Grid>
{editItem.value_type !== DeviceValueType.BOOL && editItem.value_type !== DeviceValueType.STRING && (
<>
<Grid item xs={4}>
<TextField
name="factor"
label={LL.FACTOR()}
value={editItem.factor}
variant="outlined"
onChange={updateFormValue}
fullWidth
margin="normal"
type="number"
inputProps={{ step: '0.001' }}
/>
</Grid>
<Grid item xs={4}>
<TextField
name="uom"
label={LL.UNIT()}
value={editItem.uom}
margin="normal"
fullWidth
onChange={updateFormValue}
select
>
{DeviceValueUOM_s.map((val, i) => (
<MenuItem key={i} value={i}>
{val}
</MenuItem>
))}
</TextField>
</Grid>
</>
)}
{editItem.value_type === DeviceValueType.STRING && (
<Grid item xs={4}>
<TextField
name="factor"
label="Bytes"
value={editItem.factor}
variant="outlined"
onChange={updateFormValue}
fullWidth
margin="normal"
type="number"
inputProps={{ min: '1', max: '27', step: '1' }}
/>
</Grid>
)}
</Grid>
</DialogContent>
<DialogActions>
{!creating && (
<Box flexGrow={1}>
<Button startIcon={<RemoveIcon />} variant="outlined" color="warning" onClick={remove}>
{LL.REMOVE()}
</Button>
</Box>
)}
<Button startIcon={<CancelIcon />} variant="outlined" onClick={close} color="secondary">
{LL.CANCEL()}
</Button>
<Button startIcon={creating ? <AddIcon /> : <DoneIcon />} variant="outlined" onClick={save} color="primary">
{creating ? LL.ADD(0) : LL.UPDATE()}
</Button>
</DialogActions>
</Dialog>
);
};
export default SettingsEntitiesDialog;

View File

@@ -0,0 +1,130 @@
import RefreshIcon from '@mui/icons-material/Refresh';
import { Button } from '@mui/material';
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
import { useRequest } from 'alova';
import { useEffect } from 'react';
import * as EMSESP from './api';
import type { Stat } from './types';
import type { Translation } from 'i18n/i18n-types';
import type { FC } from 'react';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
const SystemActivity: FC = () => {
const { data: data, send: loadData, error } = useRequest(EMSESP.readActivity);
const { LL } = useI18nContext();
useLayoutTitle(LL.ACTIVITY());
const stats_theme = tableTheme({
Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px;
`,
BaseRow: `
font-size: 14px;
`,
HeaderRow: `
text-transform: uppercase;
background-color: black;
color: #90CAF9;
.th {
height: 36px;
border-bottom: 1px solid #565656;
}
`,
Row: `
.td {
padding: 8px;
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
}
&:nth-of-type(odd) .td {
background-color: #303030;
}
&:nth-of-type(even) .td {
background-color: #1e1e1e;
}
`,
BaseCell: `
&:not(:first-of-type) {
text-align: center;
}
`
});
useEffect(() => {
const timer = setInterval(() => loadData(), 30000);
return () => {
clearInterval(timer);
};
});
const showName = (id: any) => {
const name: keyof Translation['STATUS_NAMES'] = id;
return LL.STATUS_NAMES[name]();
};
const showQuality = (stat: Stat) => {
if (stat.q === 0 || stat.s + stat.f === 0) {
return;
}
if (stat.q === 100) {
return <div style={{ color: '#00FF7F' }}>{stat.q}%</div>;
}
if (stat.q >= 95) {
return <div style={{ color: 'orange' }}>{stat.q}%</div>;
} else {
return <div style={{ color: 'red' }}>{stat.q}%</div>;
}
};
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (
<>
<Table data={{ nodes: data.stats }} theme={stats_theme} layout={{ custom: true }}>
{(tableList: any) => (
<>
<Header>
<HeaderRow>
<HeaderCell resize />
<HeaderCell stiff>{LL.SUCCESS()}</HeaderCell>
<HeaderCell stiff>{LL.FAIL()}</HeaderCell>
<HeaderCell stiff>{LL.QUALITY()}</HeaderCell>
</HeaderRow>
</Header>
<Body>
{tableList.map((stat: Stat) => (
<Row key={stat.id} item={stat}>
<Cell>{showName(stat.id)}</Cell>
<Cell stiff>{Intl.NumberFormat().format(stat.s)}</Cell>
<Cell stiff>{Intl.NumberFormat().format(stat.f)}</Cell>
<Cell stiff>{showQuality(stat)}</Cell>
</Row>
))}
</Body>
</>
)}
</Table>
<ButtonRow mt={1}>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
{LL.REFRESH()}
</Button>
</ButtonRow>
</>
);
};
return <SectionContent>{content()}</SectionContent>;
};
export default SystemActivity;

View File

@@ -1,7 +1,7 @@
import type { import type {
APIcall, APIcall,
Settings, Settings,
Status, Activity,
CoreData, CoreData,
Devices, Devices,
DeviceEntity, DeviceEntity,
@@ -20,12 +20,12 @@ export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`);
export const readDeviceData = (id: number) => export const readDeviceData = (id: number) =>
alovaInstance.Get<DeviceData>('/rest/deviceData', { alovaInstance.Get<DeviceData>('/rest/deviceData', {
// alovaInstance.Get<DeviceData>(`/rest/deviceData/${id}`, { // alovaInstance.Get<DeviceData>(`/rest/deviceData/${id}`, {
params: { id }, // TODO remove later params: { id }, // TODO replace later with id
responseType: 'arraybuffer' // uses msgpack responseType: 'arraybuffer' // uses msgpack
}); });
export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data); export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data);
// SettingsApplication // Application Settings
export const readSettings = () => alovaInstance.Get<Settings>('/rest/settings'); export const readSettings = () => alovaInstance.Get<Settings>('/rest/settings');
export const writeSettings = (data: any) => alovaInstance.Post('/rest/settings', data); export const writeSettings = (data: any) => alovaInstance.Post('/rest/settings', data);
export const getBoardProfile = (boardProfile: string) => export const getBoardProfile = (boardProfile: string) =>
@@ -33,17 +33,18 @@ export const getBoardProfile = (boardProfile: string) =>
params: { boardProfile } params: { boardProfile }
}); });
// DashboardSensors // Sensors
export const readSensorData = () => alovaInstance.Get<SensorData>('/rest/sensorData'); export const readSensorData = () => alovaInstance.Get<SensorData>('/rest/sensorData');
export const writeTemperatureSensor = (ts: WriteTemperatureSensor) => export const writeTemperatureSensor = (ts: WriteTemperatureSensor) =>
alovaInstance.Post('/rest/writeTemperatureSensor', ts); alovaInstance.Post('/rest/writeTemperatureSensor', ts);
export const writeAnalogSensor = (as: WriteAnalogSensor) => alovaInstance.Post('/rest/writeAnalogSensor', as); export const writeAnalogSensor = (as: WriteAnalogSensor) => alovaInstance.Post('/rest/writeAnalogSensor', as);
// DashboardStatus // Activity
export const readStatus = () => alovaInstance.Get<Status>('/rest/status'); export const readActivity = () => alovaInstance.Get<Activity>('/rest/activity');
export const scanDevices = () => alovaInstance.Post('/rest/scanDevices'); export const scanDevices = () => alovaInstance.Post('/rest/scanDevices');
// HelpInformation // API, used in HelpInformation
export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall); export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall);
// UploadFileForm // UploadFileForm
@@ -56,7 +57,7 @@ export const getSchedule = () => alovaInstance.Get('/rest/getSchedule');
export const readDeviceEntities = (id: number) => export const readDeviceEntities = (id: number) =>
// alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities/${id}`, { // alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities/${id}`, {
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, { alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
params: { id }, // TODO remove later params: { id }, // TODO replace later with id
responseType: 'arraybuffer', responseType: 'arraybuffer',
transformData(data: any) { transformData(data: any) {
return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma }));
@@ -88,7 +89,7 @@ export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule',
// SettingsEntities // SettingsEntities
export const readCustomEntities = () => export const readCustomEntities = () =>
alovaInstance.Get<EntityItem[]>('/rest/customentities', { alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
name: 'entities', name: 'entities',
transformData(data: any) { transformData(data: any) {
return data.entities.map((ei: EntityItem) => ({ return data.entities.map((ei: EntityItem) => ({
@@ -106,4 +107,4 @@ export const readCustomEntities = () =>
})); }));
} }
}); });
export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customentities', data); export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data);

View File

@@ -50,13 +50,7 @@ export interface Stat {
q: number; // quality q: number; // quality
} }
export interface Status { export interface Activity {
status: busConnectionStatus;
tx_mode: number;
uptime: number;
num_devices: number;
num_sensors: number;
num_analogs: number;
stats: Stat[]; stats: Stat[];
} }
@@ -177,7 +171,8 @@ export enum DeviceValueUOM {
L, L,
KMIN, KMIN,
K, K,
VOLTS VOLTS,
MBAR
} }
export const DeviceValueUOM_s = [ export const DeviceValueUOM_s = [
@@ -204,7 +199,8 @@ export const DeviceValueUOM_s = [
'l', 'l',
'K*min', 'K*min',
'K', 'K',
'V' 'V',
'mbar'
]; ];
export enum AnalogType { export enum AnalogType {
@@ -223,12 +219,12 @@ export enum AnalogType {
export const AnalogTypeNames = [ export const AnalogTypeNames = [
'(disabled)', '(disabled)',
'Digital in', 'Digital In',
'Counter', 'Counter',
'ADC', 'ADC',
'Timer', 'Timer',
'Rate', 'Rate',
'Digital out', 'Digital Out',
'PWM 0', 'PWM 0',
'PWM 1', 'PWM 1',
'PWM 2' 'PWM 2'
@@ -323,6 +319,7 @@ export enum ScheduleFlag {
export interface EntityItem { export interface EntityItem {
id: number; // unique number id: number; // unique number
ram: number;
name: string; name: string;
device_id: number | string; device_id: number | string;
type_id: number | string; type_id: number | string;
@@ -334,6 +331,7 @@ export interface EntityItem {
writeable: boolean; writeable: boolean;
deleted?: boolean; deleted?: boolean;
o_id?: number; o_id?: number;
o_ram?: number;
o_name?: string; o_name?: string;
o_device_id?: number | string; o_device_id?: number | string;
o_type_id?: number | string; o_type_id?: number | string;
@@ -343,6 +341,7 @@ export interface EntityItem {
o_value_type?: number; o_value_type?: number;
o_deleted?: boolean; o_deleted?: boolean;
o_writeable?: boolean; o_writeable?: boolean;
o_value?: any;
} }
export interface Entities { export interface Entities {
@@ -355,6 +354,7 @@ export const enum DeviceType {
TEMPERATURESENSOR, TEMPERATURESENSOR,
ANALOGSENSOR, ANALOGSENSOR,
SCHEDULER, SCHEDULER,
CUSTOM,
BOILER, BOILER,
THERMOSTAT, THERMOSTAT,
MIXER, MIXER,
@@ -368,7 +368,9 @@ export const enum DeviceType {
EXTENSION, EXTENSION,
GENERIC, GENERIC,
HEATSOURCE, HEATSOURCE,
CUSTOM, VENTILATION,
WATER,
POOL,
UNKNOWN UNKNOWN
} }
@@ -387,6 +389,7 @@ export const enum DeviceValueType {
} }
export const DeviceValueTypeNames = [ export const DeviceValueTypeNames = [
//
'BOOL', 'BOOL',
'INT', 'INT',
'UINT', 'UINT',
@@ -395,6 +398,6 @@ export const DeviceValueTypeNames = [
'ULONG', 'ULONG',
'TIME', 'TIME',
'ENUM', 'ENUM',
'STRING', 'RAW',
'CMD' 'CMD'
]; ];

View File

@@ -22,6 +22,26 @@ export const GPIO_VALIDATOR = {
} }
}; };
export const GPIO_VALIDATORR = {
validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) {
if (
value &&
(value === 1 ||
(value >= 6 && value <= 11) ||
(value >= 16 && value <= 17) ||
value === 20 ||
value === 24 ||
(value >= 28 && value <= 31) ||
value > 40 ||
value < 0)
) {
callback('Must be an valid GPIO port');
} else {
callback();
}
}
};
export const GPIO_VALIDATORC3 = { export const GPIO_VALIDATORC3 = {
validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) { validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) {
if (value && ((value >= 11 && value <= 19) || value > 21 || value < 0)) { if (value && ((value >= 11 && value <= 19) || value > 21 || value < 0)) {
@@ -69,6 +89,14 @@ export const createSettingsValidator = (settings: Settings) =>
tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATOR], tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATOR],
rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATOR] rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATOR]
}), }),
...(settings.board_profile === 'CUSTOM' &&
settings.platform === 'ESP32R' && {
led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATORR],
dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATORR],
pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATORR],
tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATORR],
rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATORR]
}),
...(settings.board_profile === 'CUSTOM' && ...(settings.board_profile === 'CUSTOM' &&
settings.platform === 'ESP32-C3' && { settings.platform === 'ESP32-C3' && {
led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATORC3], led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATORC3],
@@ -193,7 +221,15 @@ export const analogSensorItemValidation = (sensors: AnalogSensor[], creating: bo
n: [{ required: true, message: 'Name is required' }], n: [{ required: true, message: 'Name is required' }],
g: [ g: [
{ required: true, message: 'GPIO is required' }, { required: true, message: 'GPIO is required' },
platform === 'ESP32-S3' ? GPIO_VALIDATORS3 : platform === 'ESP32-C3' ? GPIO_VALIDATORC3 : GPIO_VALIDATOR, platform === 'ESP32-S3'
? GPIO_VALIDATORS3
: platform === 'ESP32-S2'
? GPIO_VALIDATORS2
: platform === 'ESP32-C3'
? GPIO_VALIDATORC3
: platform === 'ESP32R'
? GPIO_VALIDATORR
: GPIO_VALIDATOR,
...(creating ? [isGPIOUniqueValidator(sensors)] : []) ...(creating ? [isGPIOUniqueValidator(sensors)] : [])
] ]
}); });

Some files were not shown because too many files have changed in this diff Show More