211 Commits
v3.6.5 ... test

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
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
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
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
MichaelDvP
f77fb12c80 revert uart change, event.size not set for break. 2024-03-02 10:41:15 +01:00
MichaelDvP
f1342e4d62 Merge branch 'dev' into dev2 2024-02-29 15:06:05 +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
MichaelDvP
d8b77fc056 Merge branch 'dev' into dev2 2024-02-27 07:58:21 +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
87542fb9df update packages 2024-02-24 09:47:34 +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
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
MichaelDvP
ce33fa6535 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2 2024-02-10 17:25:50 +01:00
MichaelDvP
c2be9b210e GPIO check, typo and missing platform 2024-02-10 14:35:53 +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
MichaelDvP
355c7cbd92 fix ht3-reply 2024-02-05 08:52:29 +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
MichaelDvP
faa888ff36 remote emulation FB10, #1602 2024-02-04 14:38:34 +01:00
MichaelDvP
b834c8fd89 Merge branch 'dev' into dev2 2024-02-03 18:38:34 +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
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
ce6e89338f reset wait_validate 2024-01-28 12:15:16 +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
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
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
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
MichaelDvP
e88ede2d8b typo 2024-01-20 08:29:17 +01:00
MichaelDvP
6c398109f4 Mixer set message to 0x2CD, .. 2024-01-14 10:20:54 +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
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
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
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
f25ab5f293 Merge pull request #1543 from MichaelDvP/dev2
Update dev2 to latest changes from dev
2024-01-06 17:13:03 +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
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
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
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
MichaelDvP
ddd1f5de5b Merge branch 'dev' into dev2 2023-12-19 18:35:10 +01:00
MichaelDvP
e2544947f7 sk-translation 2023-12-18 10:47:58 +01:00
MichaelDvP
854a39fe6b ethernet working with arduino_3.0 2023-12-17 12:35:07 +01:00
MichaelDvP
a684a46404 Merge branch 'dev' into dev2 2023-12-14 07:59:01 +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
155 changed files with 4375 additions and 3394 deletions

View File

@@ -1,7 +1,6 @@
name: 'github-releases-to-discord' name: 'github-releases-to-discord'
on: on:
workflow_dispatch:
release: release:
types: [published] types: [published]

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

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

7
.gitignore vendored
View File

@@ -1,5 +1,8 @@
# vscode # vscode
.vscode/* .vscode/c_cpp_properties.json
.vscode/extensions.json
.vscode/launch.json
#.vscode/settings.json
# c++ compiling # c++ compiling
.clang_complete .clang_complete
@@ -34,7 +37,7 @@ stats.html
!.yarn/sdks !.yarn/sdks
!.yarn/versions !.yarn/versions
yarn.lock yarn.lock
interface/analyse.html analyse.html
interface/vite.config.ts.timestamp* interface/vite.config.ts.timestamp*
# scripts # scripts

91
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,91 @@
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
},
"eslint.nodePath": "interface/.yarn/sdks",
"eslint.workingDirectories": ["interface"],
"prettier.prettierPath": "",
"typescript.enablePromptUseWorkspaceTsdk": true,
"files.associations": {
"*.tsx": "typescriptreact",
"*.tcc": "cpp",
"optional": "cpp",
"istream": "cpp",
"ostream": "cpp",
"ratio": "cpp",
"system_error": "cpp",
"array": "cpp",
"functional": "cpp",
"regex": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"string": "cpp",
"string_view": "cpp",
"atomic": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"list": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"iterator": "cpp",
"map": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"random": "cpp",
"set": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp"
},
"todo-tree.filtering.excludeGlobs": [
"**/vendor/**",
"**/node_modules/**",
"**/dist/**",
"**/bower_components/**",
"**/build/**",
"**/.vscode/**",
"**/.github/**",
"**/_output/**",
"**/*.min.*",
"**/*.map",
"**/ArduinoJson/**"
],
"cSpell.enableFiletypes": [
"!cpp",
"!typescript"
]
}

View File

@@ -59,7 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## **IMPORTANT! BREAKING CHANGES** ## **IMPORTANT! BREAKING CHANGES**
Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`. You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer. Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`... You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer.
## Added ## Added

View File

@@ -1,11 +1,23 @@
# Changelog # Changelog
## [3.x] ## [3.7.0]
## **IMPORTANT! BREAKING CHANGES** ## **IMPORTANT! BREAKING CHANGES**
- new device WATER shows dhw entities from MM100 and SM100 in dhw setting
## Added ## Added
- some more entities for dhw with SM100 module
- thermostat second dhw circuit [#1634](https://github.com/emsesp/EMS-ESP32/issues/1634)
- 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)
- heatpump dhw stop temperatures [#1624](https://github.com/emsesp/EMS-ESP32/issues/1624)
## Fixed ## Fixed
## Changed ## Changed
- 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.5-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,8 +1,9 @@
# Name, Type, SubType, Offset, Size, Flags # Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x005000, nvs, data, nvs, 0x9000, 0x005000,
otadata, data, ota, , 0x002000, otadata, data, ota, , 0x002000,
app0, app, ota_0, , 0x5D0000, boot, app, factory, , 0x280000,
app1, app, ota_1, , 0x5D0000, app0, app, ota_0, , 0x590000,
app1, app, ota_1, , 0x590000,
nvs1, data, nvs, , 0x040000, nvs1, data, nvs, , 0x040000,
spiffs, data, spiffs, , 0x400000, spiffs, data, spiffs, , 0x200000,
coredump, data, coredump,, 0x010000, coredump, data, coredump,, 0x010000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x005000
3 otadata data ota 0x002000
4 app0 boot app ota_0 factory 0x5D0000 0x280000
5 app1 app0 app ota_1 ota_0 0x5D0000 0x590000
6 app1 app ota_1 0x590000
7 nvs1 data nvs 0x040000
8 spiffs data spiffs 0x400000 0x200000
9 coredump data coredump 0x010000

View File

@@ -1,9 +0,0 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x005000,
otadata, data, ota, , 0x002000,
boot, app, factory, , 0x280000,
app0, app, ota_0, , 0x590000,
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 0x005000
3 otadata data ota 0x002000
4 boot app factory 0x280000
5 app0 app ota_0 0x590000
6 app1 app ota_1 0x590000
7 nvs1 data nvs 0x040000
8 spiffs data spiffs 0x200000
9 coredump data coredump 0x010000

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",
@@ -32,7 +32,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.11.30", "@types/node": "^20.11.30",
"@types/react": "^18.2.69", "@types/react": "^18.2.72",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^18.2.22",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"alova": "^2.18.0", "alova": "^2.18.0",
@@ -54,8 +54,8 @@
"devDependencies": { "devDependencies": {
"@preact/compat": "^17.1.2", "@preact/compat": "^17.1.2",
"@preact/preset-vite": "^2.8.2", "@preact/preset-vite": "^2.8.2",
"@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.3.1", "@typescript-eslint/parser": "^7.4.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
@@ -70,7 +70,7 @@
"prettier": "^3.2.5", "prettier": "^3.2.5",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.29.2", "terser": "^5.29.2",
"vite": "^5.2.4", "vite": "^5.2.6",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^4.3.2" "vite-tsconfig-paths": "^4.3.2"
}, },

View File

@@ -28,7 +28,7 @@ const App: FC = () => {
<CustomTheme> <CustomTheme>
<AppRouting /> <AppRouting />
<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( <Layout>
// (error: AxiosError) => { <Routes>
// if (error.response && error.response.status === 401) { <Route path="/devices/*" element={<Devices />} />
// AuthenticationApi.storeLoginRedirect(location); <Route path="/sensors/*" element={<Sensors />} />
// navigate('/unauthorized'); <Route path="/system/*" element={<System />} />
// } <Route path="/help/*" element={<Help />} />
// return Promise.reject(error); <Route path="/*" element={<Navigate to="/" />} />
// }, {me.admin && (
// [location, navigate] <>
// ); <Route path="/customizations/*" element={<Customization />} />
// useEffect(() => { <Route path="/scheduler/*" element={<Scheduler />} />
// const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError); <Route path="/customentities/*" element={<CustomEntities />} />
// return () => AXIOS.interceptors.response.eject(axiosHandlerId); <Route path="/settings/*" element={<Settings />} />
// }, [handleApiResponseError]); <Route path="/settings/network/*" element={<Network />} />
<Route path="/settings/ems-esp/*" element={<ApplicationSettings />} />
<Layout> <Route path="/settings/ap/*" element={<AccessPoint />} />
<Routes> <Route path="/settings/ntp/*" element={<NetworkTime />} />
<Route path="/dashboard/*" element={<Dashboard />} /> <Route path="/settings/mqtt/*" element={<Mqtt />} />
<Route <Route path="/settings/ota/*" element={<OTASettings />} />
path="/settings/*" <Route path="/settings/security/*" element={<Security />} />
element={ <Route path="/settings/espsystemstatus/*" element={<ESPSystemStatus />} />
<RequireAdmin> <Route path="/settings/upload/*" element={<UploadDownload />} />
<Settings /> </>
</RequireAdmin> )}
} </Routes>
/> </Layout>
<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="/*" element={<Navigate to="/" />} />
</Routes>
</Layout>
);
export default AuthenticatedRouting; export default AuthenticatedRouting;

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,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 }}>
<Divider sx={{ pb: 2, borderColor: 'primary.main', fontSize: 20, color: 'primary.main' }}>{title}</Divider> {title && (
<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,165 +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 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 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="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>
<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 />
<Box
sx={{
bgcolor: menuOpen ? 'rgba(71, 98, 130, 0.2)' : null,
pb: menuOpen ? 2 : 0
}}
>
<ListItemButton
alignItems="flex-start"
onClick={() => setMenuOpen(!menuOpen)}
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 }}
/>
<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 disablePadding component="nav">
<LayoutMenuItem icon={SettingsEthernetIcon} label={LL.NETWORK(0)} to="/network" /> <List style={{ marginTop: `auto` }}>
<LayoutMenuItem icon={SettingsInputAntennaIcon} label={LL.ACCESS_POINT(0)} to="/ap" /> <LayoutMenuItem icon={AssessmentIcon} label={LL.SYSTEM(0)} to="/system" />
<LayoutMenuItem icon={AccessTimeIcon} label="NTP" to="/ntp" /> <LayoutMenuItem icon={SettingsIcon} label={LL.SETTINGS(0)} disabled={!me.admin} to="/settings" />
<LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" /> <LayoutMenuItem icon={LiveHelpIcon} label={LL.HELP_OF('')} to={`/help`} />
<LayoutMenuItem
icon={LockIcon}
label={LL.SECURITY(0)}
to="/security"
disabled={!authenticatedContext.me.admin}
/>
<LayoutMenuItem icon={SettingsIcon} label={LL.SYSTEM(0)} to="/system" />
</List> </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

@@ -63,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,

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
}); });
@@ -388,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"
@@ -449,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

@@ -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 {
@@ -48,7 +48,7 @@ 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
}); });
@@ -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 ? (
@@ -360,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

@@ -11,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:
@@ -39,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) {
@@ -48,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;
} }
@@ -68,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);
@@ -193,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,44 +22,26 @@ 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;
export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => {
switch (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 NTPStatusForm: FC = () => {
const { data: data, send: loadData, error } = useRequest(NTPApi.readNTPStatus); const { data: data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
const [localTime, setLocalTime] = useState<string>(''); const [localTime, setLocalTime] = useState<string>('');
const [settingTime, setSettingTime] = useState<boolean>(false); const [settingTime, setSettingTime] = useState<boolean>(false);
const [processing, setProcessing] = useState<boolean>(false); const [processing, setProcessing] = useState<boolean>(false);
const { me } = useContext(AuthenticatedContext);
const { LL } = useI18nContext(); const { LL } = useI18nContext();
@@ -69,6 +51,22 @@ const NTPStatusForm: FC = () => {
NTPApi.updateTime; 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) {
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 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

@@ -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,362 +0,0 @@
import AppsIcon from '@mui/icons-material/Apps';
import BuildIcon from '@mui/icons-material/Build';
import CancelIcon from '@mui/icons-material/Cancel';
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 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 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 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 { 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>
<TimerIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.UPTIME()} secondary={data.uptime} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<DevicesIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="SDK" secondary={data.arduino_version + ' / ESP-IDF ' + data.sdk_version} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar>
<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>
<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={
data.partition + ': ' + 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={data.esp_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,224 +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 />
<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)}&nbsp;{LL.SUPPORT_INFORMATION(1)}
</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(0)}
</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(1)}
</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

@@ -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,13 +175,11 @@ 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',
@@ -244,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',
@@ -329,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',
@@ -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,20 +175,17 @@ 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',
HEAP: 'Heap (Free / Max Alloc)', HEAP: 'Heap (Free / Max Alloc)',
PSRAM: 'PSRAM (Size / Free)', PSRAM: 'PSRAM (Size / Free)',
@@ -245,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',
@@ -330,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,13 +175,11 @@ 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',
@@ -244,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
@@ -329,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,13 +175,11 @@ 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',
@@ -246,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à',
@@ -331,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,13 +175,11 @@ 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',
@@ -244,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',
@@ -280,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',
@@ -316,20 +309,22 @@ const nl: Translation = {
SCHEDULE_TIMER_2: 'elke minuut', SCHEDULE_TIMER_2: 'elke minuut',
SCHEDULE_TIMER_3: 'elke huur', SCHEDULE_TIMER_3: 'elke huur',
CUSTOM_ENTITIES: 'Aangepaste Entiteiten', CUSTOM_ENTITIES: 'Aangepaste Entiteiten',
ENTITIES_HELP_1: 'Aangepaste entiteiten ophalen uit de EMS-bus', // TODO translate ENTITIES_HELP_1: 'Aangepaste entiteiten ophalen uit de EMS-bus',
ENTITIES_UPDATED: 'Entiteiten bijgewerkt', ENTITIES_UPDATED: 'Entiteiten bijgewerkt',
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,13 +175,11 @@ 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',
@@ -244,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',
@@ -329,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ę',
@@ -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)',
@@ -163,9 +159,7 @@ 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: '{{I|i|}}nformacj{{e|i|}} o systemie',
CLICK_HERE: 'Kliknij tu',
HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP, skorzystaj z wiki w internecie', 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',
@@ -181,13 +175,11 @@ 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',
@@ -244,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"',
@@ -316,7 +309,7 @@ const pl: BaseTranslation = {
SCHEDULE_TIMER_2: 'co minutę', SCHEDULE_TIMER_2: 'co minutę',
SCHEDULE_TIMER_3: 'co godzinę', SCHEDULE_TIMER_3: 'co godzinę',
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.', // TODO translate 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',
@@ -329,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

@@ -12,7 +12,6 @@ const sk: Translation = {
USERNAME: 'Užívateľské meno', USERNAME: 'Užívateľské meno',
PASSWORD: 'Heslo', PASSWORD: 'Heslo',
SU_PASSWORD: 'su heslo', SU_PASSWORD: 'su heslo',
DASHBOARD: 'Panel',
SETTINGS_OF: '{0} Nastavenia', SETTINGS_OF: '{0} Nastavenia',
HELP_OF: '{0} Pomoc', HELP_OF: '{0} Pomoc',
LOGGED_IN: 'Prihlásený ako {name}', LOGGED_IN: 'Prihlásený ako {name}',
@@ -36,9 +35,7 @@ const sk: Translation = {
VERSION: 'Verzia', VERSION: 'Verzia',
BRAND: 'Značka', BRAND: 'Značka',
ENTITY_NAME: 'Názov entity', ENTITY_NAME: 'Názov entity',
VALUE: '{{Value|value}}', VALUE: '{{Hodnota|hodnota}}',
DEVICE_DATA: 'Dáta zariadenia',
SENSOR_DATA: 'Dáta snímača',
DEVICES: 'Zariadenia', DEVICES: 'Zariadenia',
SENSORS: 'Snímače', SENSORS: 'Snímače',
RUN_COMMAND: 'Volať príkaz', RUN_COMMAND: 'Volať príkaz',
@@ -80,34 +77,33 @@ const sk: Translation = {
ACTIVE_DEVICES: 'Aktívne zariadenia a snímače', ACTIVE_DEVICES: 'Aktívne zariadenia a snímače',
EMS_DEVICE: 'EMS zariadenie', EMS_DEVICE: 'EMS zariadenie',
SUCCESS: 'ÚSPEŠNÉ', SUCCESS: 'ÚSPEŠNÉ',
FAIL: 'ZLYHANIE', FAIL: 'ZLÝHANIE',
QUALITY: 'KVALITA', QUALITY: 'KVALITA',
SCAN_DEVICES: 'Scan pre nové zariadenia', SCAN_DEVICES: 'Scan pre nové zariadenia',
EMS_BUS_STATUS_TITLE: 'EMS zbernica & stav aktivity',
SCAN: 'Scan', SCAN: 'Scan',
STATUS_NAMES: [ STATUS_NAMES: [
'EMS Telegramy prijaté (Rx)', 'EMS Telegramy prijaté (Rx)',
'EMS Čítania (Tx)', 'EMS Čítania (Tx)',
'EMS Zápisy (Tx)', 'EMS Zápisy (Tx)',
'Čítanie snímača teploty', 'Čítanie snímačov teploty',
'Analógové snímanie', 'Čítanie analógových snímačov',
'MQTT Publikovanie', 'MQTT Publikovanie',
'API volania', 'Externé API volania',
'Syslog správy' 'Syslog správy'
], ],
NUM_DEVICES: '{num} Zariadenia{{s}}', NUM_DEVICES: '{num} Zariaden{{í|ie|ia|ia|í|í}}',
NUM_TEMP_SENSORS: '{num} Teplotné snímače{{s}}', NUM_TEMP_SENSORS: '{num} Teplotn{{ých|ý|é|é|ých|ých}} sníma{{čov|č|če|če|čov|čov}}',
NUM_ANALOG_SENSORS: '{num} Analógové snímače{{s}}', NUM_ANALOG_SENSORS: '{num} Analogov{{ých|ý|é|é|ých|ých}} sníma{{čov|č|če|če|čov|čov}}',
NUM_DAYS: '{num} dní{{s}}', NUM_DAYS: '{num} d{{ní|eň|ní|ní|ní|ní}}',
NUM_SECONDS: '{num} sekúnd{{s}}', NUM_SECONDS: '{num} sek{{únd|unda|undy|undy|únd|únd}}',
NUM_HOURS: '{num} hodín{{s}}', NUM_HOURS: '{num} hod{{ín|ina|iny|iny|ín|ín}}',
NUM_MINUTES: '{num} minút{{s}}', NUM_MINUTES: '{num} minú{{t|ta|ty|ty|t|t}}',
APPLICATION_SETTINGS: 'Nastavenia aplikácie', APPLICATION_SETTINGS: 'Nastavenia aplikácie',
CUSTOMIZATIONS: 'Prispôsobenia', CUSTOMIZATIONS: 'Prispôsobenia',
APPLICATION_RESTARTING: 'EMS-ESP sa reštartuje', APPLICATION_RESTARTING: 'EMS-ESP sa reštartuje',
INTERFACE_BOARD_PROFILE: 'Profil boardu rozhrania', 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_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: 'Board profil', BOARD_PROFILE: 'Profil dosky',
CUSTOM: 'Vlastné', CUSTOM: 'Vlastné',
GPIO_OF: '{0} GPIO', GPIO_OF: '{0} GPIO',
BUTTON: 'Tlačidlo', BUTTON: 'Tlačidlo',
@@ -122,11 +118,12 @@ const sk: Translation = {
HIDE_LED: 'Skryť LED', HIDE_LED: 'Skryť LED',
ENABLE_TELNET: 'Povoliť Telnet konzolu', ENABLE_TELNET: 'Povoliť Telnet konzolu',
ENABLE_ANALOG: 'Povoliť analógové snímače', ENABLE_ANALOG: 'Povoliť analógové snímače',
CONVERT_FAHRENHEIT: 'Previesť hodnoty teploty na fahrenheity', CONVERT_FAHRENHEIT: 'Previesť hodnoty teploty na °F',
BYPASS_TOKEN: 'Vynechajte autorizáciu prístupového tokenu pri volaniach API', 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)', READONLY: 'Povoliť režim len na čítanie (blokuje všetky odchádzajúce príkazy EMS Tx Write)',
UNDERCLOCK_CPU: 'Podtaktovanie rýchlosti procesora', UNDERCLOCK_CPU: 'Podtaktovanie rýchlosti procesora',
HEATINGOFF: 'Spustite kotol s núteným vykurovaním', HEATINGOFF: 'Spustiť kotol s vynúteným vykurovaním',
ENABLE_SHOWER_TIMER: 'Povoliť časovač sprchovania', ENABLE_SHOWER_TIMER: 'Povoliť časovač sprchovania',
ENABLE_SHOWER_ALERT: 'Povoliť upozornenie na sprchu', ENABLE_SHOWER_ALERT: 'Povoliť upozornenie na sprchu',
TRIGGER_TIME: 'Čas spustenia', TRIGGER_TIME: 'Čas spustenia',
@@ -136,7 +133,7 @@ const sk: Translation = {
BOOLEAN_FORMAT_API: 'Boolean formát API/MQTT', BOOLEAN_FORMAT_API: 'Boolean formát API/MQTT',
ENUM_FORMAT: 'Enum formát API/MQTT', ENUM_FORMAT: 'Enum formát API/MQTT',
INDEX: 'Index', INDEX: 'Index',
ENABLE_PARASITE: 'Povolenie parazitného napájania', ENABLE_PARASITE: 'Povol parazité napájanie DS18B20',
LOGGING: 'Logovanie', LOGGING: 'Logovanie',
LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave', LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave',
ENABLE_SYSLOG: 'Povoliť Syslog', ENABLE_SYSLOG: 'Povoliť Syslog',
@@ -163,9 +160,7 @@ const sk: Translation = {
OPTIONS: 'Možnosti', OPTIONS: 'Možnosti',
NAME: 'Názov', 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?', 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?',
DEVICE_ENTITIES: 'Entity zariadenia', SUPPORT_INFORMATION: 'Informácie pre podporu',
SUPPORT_INFORMATION: 'Informácie o podpore',
CLICK_HERE: 'Kliknite tu',
HELP_INFORMATION_1: 'Navštívte online wiki, kde nájdete pokyny na konfiguráciu EMS-ESP', 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_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_3: 'Ak chcete požiadať o funkciu alebo nahlásiť chybu',
@@ -180,27 +175,25 @@ const sk: Translation = {
LOG_OF: '{0} Log', LOG_OF: '{0} Log',
STATUS_OF: '{0} Stav', STATUS_OF: '{0} Stav',
UPLOAD_DOWNLOAD: 'Nahrať/Stiahnuť', UPLOAD_DOWNLOAD: 'Nahrať/Stiahnuť',
VERSION_ON: 'Momentálne ste vo verzii', VERSION_ON: 'Momentálne nainštalovaná verzia: ',
SYSTEM_APPLY_FIRMWARE: 'na použitie nového firmvéru',
CLOSE: 'Zatvoriť', CLOSE: 'Zatvoriť',
USE: 'Použiť', USE: 'Použiť',
FACTORY_RESET: 'Továrenské nastavenia', FACTORY_RESET: 'Továrenské nastavenia',
SYSTEM_FACTORY_TEXT: 'Zariadenie bolo obnovené z výroby a teraz sa reštartuje', 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?', SYSTEM_FACTORY_TEXT_DIALOG: 'Naozaj chcete resetovať EMS-ESP na predvolené výrobné nastavenia?',
VERSION_CHECK: 'Kontrola verzie',
THE_LATEST: 'Posledná', THE_LATEST: 'Posledná',
OFFICIAL: 'officiálna', OFFICIAL: 'officiálna',
DEVELOPMENT: 'vývojárska', DEVELOPMENT: 'vývojárska',
RELEASE_IS: 'vydanie je', RELEASE_IS: 'verzia je',
RELEASE_NOTES: 'poznámky k vydaniu', RELEASE_NOTES: 'poznámky k verzii',
EMS_ESP_VER: 'EMS-ESP verzia', EMS_ESP_VER: 'EMS-ESP verzia',
UPTIME: 'Beh systému', UPTIME: 'Beh systému',
HEAP: 'Zásobník (voľné / max pridelenie)', HEAP: 'Zásobník (voľné / max pridelenie)',
PSRAM: 'PSRAM (Veľkosť / Voľné)', PSRAM: 'PSRAM (Veľkosť / Voľné)',
FLASH: 'Flash chip (Veľkosť / Rýchlosť)', FLASH: 'Flash chip (Veľkosť / Rýchlosť)',
APPSIZE: 'Applikácia (Priečka: Použité / Voľné)', APPSIZE: 'Applikácia (Oddiel: Použité / Voľné)',
FILESYSTEM: 'Súborový systém (Použité / Voľné)', FILESYSTEM: 'Súborový systém (Použité / Voľné)',
BUFFER_SIZE: 'Maximálna veľkosť vyrovnávacej pamäte', BUFFER_SIZE: 'Buffer-max.veľkosť',
COMPACT: 'Kompaktné', COMPACT: 'Kompaktné',
ENABLE_OTA: 'Povoliť OTA aktualizácie', ENABLE_OTA: 'Povoliť OTA aktualizácie',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Stiahnutie prispôsobení entity', DOWNLOAD_CUSTOMIZATION_TEXT: 'Stiahnutie prispôsobení entity',
@@ -208,7 +201,7 @@ const sk: Translation = {
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.', 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)', 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', UPLOADING: 'Nahrávanie',
UPLOAD_DROP_TEXT: 'Zahodiť súbor alebo kliknúť sem', UPLOAD_DROP_TEXT: 'Potiahnúť a pripnúť súbor alebo kliknúť sem',
ERROR: 'Neočakávaná chyba, prosím skúste to znova', ERROR: 'Neočakávaná chyba, prosím skúste to znova',
TIME_SET: 'Nastavený čas', TIME_SET: 'Nastavený čas',
MANAGE_USERS: 'Správa používateľov', MANAGE_USERS: 'Správa používateľov',
@@ -244,6 +237,7 @@ const sk: Translation = {
MQTT_INT_THERMOSTATS: 'Termostaty', MQTT_INT_THERMOSTATS: 'Termostaty',
MQTT_INT_SOLAR: 'Solárne moduly', MQTT_INT_SOLAR: 'Solárne moduly',
MQTT_INT_MIXER: 'Zmiešavacie moduley', MQTT_INT_MIXER: 'Zmiešavacie moduley',
MQTT_INT_WATER: 'Voda moduley',
MQTT_QUEUE: 'Fronta MQTT', MQTT_QUEUE: 'Fronta MQTT',
DEFAULT: 'Predvolené', DEFAULT: 'Predvolené',
MQTT_ENTITY_FORMAT: 'ID formát entity', MQTT_ENTITY_FORMAT: 'ID formát entity',
@@ -265,7 +259,7 @@ const sk: Translation = {
ACCESS_POINT: 'Prístupový bod', ACCESS_POINT: 'Prístupový bod',
AP_PROVIDE: 'Povoliť prístupový bod', AP_PROVIDE: 'Povoliť prístupový bod',
AP_PROVIDE_TEXT_1: 'vždy', AP_PROVIDE_TEXT_1: 'vždy',
AP_PROVIDE_TEXT_2: 'keď WiFi je odpojená', AP_PROVIDE_TEXT_2: 'keď je WiFi odpojená',
AP_PROVIDE_TEXT_3: 'nikdy', AP_PROVIDE_TEXT_3: 'nikdy',
AP_PREFERRED_CHANNEL: 'Preferovaný kanál', AP_PREFERRED_CHANNEL: 'Preferovaný kanál',
AP_HIDE_SSID: 'Skryť SSID', AP_HIDE_SSID: 'Skryť SSID',
@@ -293,11 +287,11 @@ const sk: Translation = {
NETWORK_GATEWAY: 'Brána', NETWORK_GATEWAY: 'Brána',
NETWORK_SUBNET: 'Maska podsiete', NETWORK_SUBNET: 'Maska podsiete',
NETWORK_DNS: 'DNS servery', NETWORK_DNS: 'DNS servery',
ADDRESS_OF: '{0} adries', ADDRESS_OF: '{0} adresa',
ADMIN: 'Admin', ADMIN: 'Admin',
GUEST: 'Hosť', GUEST: 'Hosť',
NEW: 'Nová', NEW: 'Nová',
NEW_NAME_OF: 'Nových {0} názvov', NEW_NAME_OF: 'Nový názov {0}',
ENTITY: 'entita', ENTITY: 'entita',
MIN: 'min', MIN: 'min',
MAX: 'max', MAX: 'max',
@@ -308,7 +302,7 @@ const sk: Translation = {
SCHEDULER: 'Plánovač', 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_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', SCHEDULER_HELP_2: 'Použite 00:00 na jednorazové spustenie pri štarte',
SCHEDULE: 'Plánovať', SCHEDULE: 'Plánovač',
TIME: 'Čas', TIME: 'Čas',
TIMER: 'Časovač', TIMER: 'Časovač',
SCHEDULE_UPDATED: 'Plánovanie aktualizované', SCHEDULE_UPDATED: 'Plánovanie aktualizované',
@@ -316,7 +310,7 @@ const sk: Translation = {
SCHEDULE_TIMER_2: 'každú minútu', SCHEDULE_TIMER_2: 'každú minútu',
SCHEDULE_TIMER_3: 'každú hodinu', SCHEDULE_TIMER_3: 'každú hodinu',
CUSTOM_ENTITIES: 'Vlastné entity', CUSTOM_ENTITIES: 'Vlastné entity',
ENTITIES_HELP_1: 'Získavanie vlastných entít zo zbernice EMS', // TODO translate ENTITIES_HELP_1: 'Získavanie vlastných entít zo zbernice EMS',
ENTITIES_UPDATED: 'Aktualizované entity', ENTITIES_UPDATED: 'Aktualizované entity',
WRITEABLE: 'Zapísateľný', WRITEABLE: 'Zapísateľný',
SHOWING: 'Zobrazenie', SHOWING: 'Zobrazenie',
@@ -329,7 +323,9 @@ const sk: Translation = {
ACTIVEHIGH: 'Aktívny Vysoký', ACTIVEHIGH: 'Aktívny Vysoký',
ACTIVELOW: 'Aktívny Nízky', ACTIVELOW: 'Aktívny Nízky',
UNCHANGED: 'Nezmenené', UNCHANGED: 'Nezmenené',
ALWAYS: 'Vždy' ALWAYS: 'Vždy',
ACTIVITY: 'Aktivita',
CONFIGURE: 'Konfiguracia {0}'
}; };
export default sk; 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,13 +175,11 @@ 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',
@@ -244,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',
@@ -329,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,13 +175,11 @@ 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',
@@ -244,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',
@@ -329,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">
@@ -680,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

@@ -13,17 +13,17 @@ import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import SettingsCustomEntitiesDialog from './SettingsCustomEntitiesDialog'; 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 SettingsCustomEntities: 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);
@@ -31,6 +31,8 @@ const SettingsCustomEntities: 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,
@@ -246,7 +248,7 @@ const SettingsCustomEntities: 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>
@@ -265,7 +267,7 @@ const SettingsCustomEntities: 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 SettingsCustomEntities: FC = () => {
); );
}; };
export default SettingsCustomEntities; export default CustomEntities;

View File

@@ -30,7 +30,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils'; import { numberValue, updateValue } from 'utils';
import { validate } from 'validators'; import { validate } from 'validators';
type SettingsCustomEntitiesDialogProps = { type CustomEntitiesDialogProps = {
open: boolean; open: boolean;
creating: boolean; creating: boolean;
onClose: () => void; onClose: () => void;
@@ -39,14 +39,14 @@ type SettingsCustomEntitiesDialogProps = {
validator: Schema; validator: Schema;
}; };
const SettingsCustomEntitiesDialog = ({ const CustomEntitiesDialog = ({
open, open,
creating, creating,
onClose, onClose,
onSave, onSave,
selectedItem, selectedItem,
validator validator
}: SettingsCustomEntitiesDialogProps) => { }: CustomEntitiesDialogProps) => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<EntityItem>(selectedItem); const [editItem, setEditItem] = useState<EntityItem>(selectedItem);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -281,4 +281,4 @@ const SettingsCustomEntitiesDialog = ({
); );
}; };
export default SettingsCustomEntitiesDialog; 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,9 +510,6 @@ 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 && (
@@ -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);
@@ -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,
@@ -281,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 });
} }
}; };
@@ -420,11 +423,8 @@ 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 && ( {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 }}>
@@ -523,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>
<IconButton onClick={customize}> {me.admin && (
<FormatListNumberedIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} /> <IconButton onClick={customize}>
</IconButton> <FormatListNumberedIcon sx={{ fontSize: 18, verticalAlign: 'middle' }} />
</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>
@@ -587,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 }} />
@@ -608,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()}
@@ -619,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>
@@ -636,4 +636,4 @@ const DashboardDevices: FC = () => {
); );
}; };
export default DashboardDevices; export default Devices;

View File

@@ -40,7 +40,7 @@ type DashboardDevicesDialogProps = {
progress: boolean; progress: boolean;
}; };
const DashboardDevicesDialog = ({ const DevicesDialog = ({
open, open,
onClose, onClose,
onSave, onSave,
@@ -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(0)} titleGutter> <SectionContent>
<List> <List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemButton component="a" href="https://emsesp.github.io/docs">
<MenuBookIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> <ListItemAvatar>
</ListItemAvatar> <Avatar sx={{ bgcolor: '#72caf9' }}>
<ListItemText> <MenuBookIcon />
{LL.HELP_INFORMATION_1()}&nbsp; </Avatar>
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> </ListItemAvatar>
&nbsp; <ListItemText primary={LL.HELP_INFORMATION_1()} />
<Link target="_blank" href="https://emsesp.github.io/docs" color="primary"> </ListItemButton>
{LL.CLICK_HERE()}
</Link>
</ListItemText>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemButton component="a" href="https://discord.gg/3J3GgnzpyT">
<CommentIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> <ListItemAvatar>
</ListItemAvatar> <Avatar sx={{ bgcolor: '#72caf9' }}>
<ListItemText> <CommentIcon />
{LL.HELP_INFORMATION_2()}&nbsp; </Avatar>
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> </ListItemAvatar>
&nbsp; <ListItemText primary={LL.HELP_INFORMATION_2()} />
<Link target="_blank" href="https://discord.gg/3J3GgnzpyT" color="primary"> </ListItemButton>
{LL.CLICK_HERE()}
</Link>
</ListItemText>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemButton component="a" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose">
<GitHubIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> <ListItemAvatar>
</ListItemAvatar> <Avatar sx={{ bgcolor: '#72caf9' }}>
<ListItemText> <GitHubIcon />
{LL.HELP_INFORMATION_3()}&nbsp; </Avatar>
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} /> </ListItemAvatar>
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" color="primary"> <ListItemText primary={LL.HELP_INFORMATION_3()} />
{LL.CLICK_HERE()} </ListItemButton>
</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>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => callAPI('system', 'info')}
>
{LL.SUPPORT_INFORMATION(0)}
</Button>
</Box> </Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => callAPI('system', 'info')}>
{LL.SUPPORT_INFORMATION(0)}
</Button>
<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>

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)) }}
@@ -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);
@@ -406,25 +407,20 @@ const DashboardSensors: FC = () => {
); );
return ( return (
<SectionContent title={LL.SENSOR_DATA()} titleGutter> <SectionContent>
{sensorData.ts.length > 0 && ( <Typography sx={{ pb: 1 }} variant="h6" color="secondary">
<> {LL.TEMP_SENSORS()}
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary"> </Typography>
{LL.TEMP_SENSORS()} <RenderTemperatureSensors />
</Typography> {selectedTemperatureSensor && (
<RenderTemperatureSensors /> <DashboardSensorsTemperatureDialog
{selectedTemperatureSensor && ( open={temperatureDialogOpen}
<DashboardSensorsTemperatureDialog onClose={onTemperatureDialogClose}
open={temperatureDialogOpen} onSave={onTemperatureDialogSave}
onClose={onTemperatureDialogClose} selectedItem={selectedTemperatureSensor}
onSave={onTemperatureDialogSave} validator={temperatureSensorItemValidation()}
selectedItem={selectedTemperatureSensor} />
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,
@@ -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 SettingsCustomEntities from './SettingsCustomEntities';
import SettingsCustomization from './SettingsCustomization';
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={<SettingsCustomEntities />} />
<Route path="*" element={<Navigate replace to="/settings/application" />} />
</Routes>
</>
);
};
export default Settings;

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,
@@ -25,7 +25,7 @@ export const readDeviceData = (id: number) =>
}); });
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

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[];
} }
@@ -360,6 +354,7 @@ export const enum DeviceType {
TEMPERATURESENSOR, TEMPERATURESENSOR,
ANALOGSENSOR, ANALOGSENSOR,
SCHEDULER, SCHEDULER,
CUSTOM,
BOILER, BOILER,
THERMOSTAT, THERMOSTAT,
MIXER, MIXER,
@@ -373,7 +368,9 @@ export const enum DeviceType {
EXTENSION, EXTENSION,
GENERIC, GENERIC,
HEATSOURCE, HEATSOURCE,
CUSTOM, VENTILATION,
WATER,
POOL,
UNKNOWN UNKNOWN
} }

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)] : [])
] ]
}); });

View File

@@ -10,14 +10,14 @@ export enum APNetworkStatus {
LINGERING = 2 LINGERING = 2
} }
export interface APStatus { export interface APStatusType {
status: APNetworkStatus; status: APNetworkStatus;
ip_address: string; ip_address: string;
mac_address: string; mac_address: string;
station_num: number; station_num: number;
} }
export interface APSettings { export interface APSettingsType {
provision_mode: APProvisionMode; provision_mode: APProvisionMode;
ssid: string; ssid: string;
password: string; password: string;

View File

@@ -9,7 +9,7 @@ export enum MqttDisconnectReason {
TCP_DISCONNECTED = 7 TCP_DISCONNECTED = 7
} }
export interface MqttStatus { export interface MqttStatusType {
enabled: boolean; enabled: boolean;
connected: boolean; connected: boolean;
client_id: string; client_id: string;
@@ -19,7 +19,7 @@ export interface MqttStatus {
connect_count: number; connect_count: number;
} }
export interface MqttSettings { export interface MqttSettingsType {
enabled: boolean; enabled: boolean;
host: string; host: string;
port: number; port: number;
@@ -36,6 +36,7 @@ export interface MqttSettings {
publish_time_thermostat: number; publish_time_thermostat: number;
publish_time_solar: number; publish_time_solar: number;
publish_time_mixer: number; publish_time_mixer: number;
publish_time_water: number;
publish_time_other: number; publish_time_other: number;
publish_time_sensor: number; publish_time_sensor: number;
publish_time_heartbeat: number; publish_time_heartbeat: number;

View File

@@ -20,7 +20,7 @@ export enum WiFiEncryptionType {
WIFI_AUTH_WPA2_WPA3_PSK = 7 WIFI_AUTH_WPA2_WPA3_PSK = 7
} }
export interface NetworkStatus { export interface NetworkStatusType {
status: NetworkConnectionStatus; status: NetworkConnectionStatus;
local_ip: string; local_ip: string;
local_ipv6: string; local_ipv6: string;
@@ -36,7 +36,7 @@ export interface NetworkStatus {
hostname: string; hostname: string;
} }
export interface NetworkSettings { export interface NetworkSettingsType {
ssid: string; ssid: string;
bssid: string; bssid: string;
password: string; password: string;

View File

@@ -4,14 +4,14 @@ export enum NTPSyncStatus {
NTP_ACTIVE = 2 NTP_ACTIVE = 2
} }
export interface NTPStatus { export interface NTPStatusType {
status: NTPSyncStatus; status: NTPSyncStatus;
utc_time: string; utc_time: string;
local_time: string; local_time: string;
server: string; server: string;
} }
export interface NTPSettings { export interface NTPSettingsType {
enabled: boolean; enabled: boolean;
server: string; server: string;
tz_label: string; tz_label: string;

View File

@@ -1,11 +1,11 @@
export interface User { export interface UserType {
username: string; username: string;
password: string; password: string;
admin: boolean; admin: boolean;
} }
export interface SecuritySettings { export interface SecuritySettingsType {
users: User[]; users: UserType[];
jwt_secret: string; jwt_secret: string;
} }

View File

@@ -1,4 +1,6 @@
export interface SystemStatus { import type { busConnectionStatus } from 'project/types';
export interface ESPSystemStatus {
emsesp_version: string; emsesp_version: string;
esp_platform: string; esp_platform: string;
max_alloc_heap: number; max_alloc_heap: number;
@@ -16,14 +18,29 @@ export interface SystemStatus {
app_free: number; app_free: number;
fs_used: number; fs_used: number;
fs_free: number; fs_free: number;
uptime: string;
free_mem: number; free_mem: number;
psram_size?: number; psram_size?: number;
free_psram?: number; free_psram?: number;
has_loader: boolean; has_loader: boolean;
} }
export interface OTASettings { export interface SystemStatus {
emsesp_version: string;
esp_platform: string;
status: busConnectionStatus;
uptime: number;
bus_uptime: number;
num_devices: number;
num_sensors: number;
num_analogs: number;
free_heap: number;
ntp_status: number;
ota_status: boolean;
mqtt_status: boolean;
ap_status: boolean;
}
export interface OTASettingsType {
enabled: boolean; enabled: boolean;
port: number; port: number;
password: string; password: string;

View File

@@ -1,9 +1,9 @@
import Schema from 'async-validator'; import Schema from 'async-validator';
import { IP_ADDRESS_VALIDATOR } from './shared'; import { IP_ADDRESS_VALIDATOR } from './shared';
import type { APSettings } from 'types'; import type { APSettingsType } from 'types';
import { isAPEnabled } from 'framework/ap/APSettingsForm'; import { isAPEnabled } from 'framework/ap/APSettings';
export const createAPSettingsValidator = (apSettings: APSettings) => export const createAPSettingsValidator = (apSettings: APSettingsType) =>
new Schema({ new Schema({
provision_mode: { required: true, message: 'Please provide a provision mode' }, provision_mode: { required: true, message: 'Please provide a provision mode' },
...(isAPEnabled(apSettings) && { ...(isAPEnabled(apSettings) && {

View File

@@ -1,8 +1,8 @@
import Schema from 'async-validator'; import Schema from 'async-validator';
import { IP_OR_HOSTNAME_VALIDATOR } from './shared'; import { IP_OR_HOSTNAME_VALIDATOR } from './shared';
import type { MqttSettings } from 'types'; import type { MqttSettingsType } from 'types';
export const createMqttSettingsValidator = (mqttSettings: MqttSettings) => export const createMqttSettingsValidator = (mqttSettings: MqttSettingsType) =>
new Schema({ new Schema({
...(mqttSettings.enabled && { ...(mqttSettings.enabled && {
host: [{ required: true, message: 'Host is required' }, IP_OR_HOSTNAME_VALIDATOR], host: [{ required: true, message: 'Host is required' }, IP_OR_HOSTNAME_VALIDATOR],

View File

@@ -1,8 +1,8 @@
import Schema from 'async-validator'; import Schema from 'async-validator';
import { HOSTNAME_VALIDATOR, IP_ADDRESS_VALIDATOR } from './shared'; import { HOSTNAME_VALIDATOR, IP_ADDRESS_VALIDATOR } from './shared';
import type { NetworkSettings } from 'types'; import type { NetworkSettingsType } from 'types';
export const createNetworkSettingsValidator = (networkSettings: NetworkSettings) => export const createNetworkSettingsValidator = (networkSettings: NetworkSettingsType) =>
new Schema({ new Schema({
ssid: [{ type: 'string', max: 32, message: 'SSID must be 32 characters or less' }], ssid: [{ type: 'string', max: 32, message: 'SSID must be 32 characters or less' }],
bssid: [{ type: 'string', max: 17, message: 'BSSID must be 17 characters or empty' }], bssid: [{ type: 'string', max: 17, message: 'BSSID must be 17 characters or empty' }],

View File

@@ -1,6 +1,6 @@
import Schema from 'async-validator'; import Schema from 'async-validator';
import type { InternalRuleItem } from 'async-validator'; import type { InternalRuleItem } from 'async-validator';
import type { User } from 'types'; import type { UserType } from 'types';
export const SECURITY_SETTINGS_VALIDATOR = new Schema({ export const SECURITY_SETTINGS_VALIDATOR = new Schema({
jwt_secret: [ jwt_secret: [
@@ -9,7 +9,7 @@ export const SECURITY_SETTINGS_VALIDATOR = new Schema({
] ]
}); });
export const createUniqueUsernameValidator = (users: User[]) => ({ export const createUniqueUsernameValidator = (users: UserType[]) => ({
validator(rule: InternalRuleItem, username: string, callback: (error?: string) => void) { validator(rule: InternalRuleItem, username: string, callback: (error?: string) => void) {
if (username && users.find((u) => u.username === username)) { if (username && users.find((u) => u.username === username)) {
callback('Username already in use'); callback('Username already in use');
@@ -19,7 +19,7 @@ export const createUniqueUsernameValidator = (users: User[]) => ({
} }
}); });
export const createUserValidator = (users: User[], creating: boolean) => export const createUserValidator = (users: UserType[], creating: boolean) =>
new Schema({ new Schema({
username: [ username: [
{ required: true, message: 'Username is required' }, { required: true, message: 'Username is required' },

View File

@@ -29,7 +29,7 @@ export default defineConfig(({ command, mode }) => {
}; };
} }
if (command === 'build' && mode === 'hosted') { if (mode === 'hosted') {
return { return {
plugins: [preact(), viteTsconfigPaths()], plugins: [preact(), viteTsconfigPaths()],
build: { build: {
@@ -38,97 +38,94 @@ export default defineConfig(({ command, mode }) => {
}; };
} }
// production build, both for hosted and building the firmware return {
if (command === 'build') { plugins: [
return { preact(),
plugins: [ viteTsconfigPaths(),
preact(), splitVendorChunkPlugin(),
viteTsconfigPaths(), {
splitVendorChunkPlugin(), ...viteImagemin({
{ verbose: false,
...viteImagemin({ gifsicle: {
verbose: false, optimizationLevel: 7,
gifsicle: { interlaced: false
optimizationLevel: 7,
interlaced: false
},
optipng: {
optimizationLevel: 7
},
mozjpeg: {
quality: 20
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: {
plugins: [
{
name: 'removeViewBox'
},
{
name: 'removeEmptyAttrs',
active: false
}
]
}
}),
enforce: 'pre'
},
visualizer({
template: 'treemap', // or sunburst
open: false,
gzipSize: true,
brotliSize: true,
filename: 'analyse.html' // will be saved in project's root
})
],
build: {
// target: 'es2022',
chunkSizeWarningLimit: 1024,
minify: 'terser',
terserOptions: {
compress: {
passes: 4,
arrows: true,
drop_console: true,
drop_debugger: true,
sequences: true
}, },
mangle: { optipng: {
// toplevel: true optimizationLevel: 7
// module: true
// properties: {
// regex: /^_/
// }
}, },
ecma: 5, mozjpeg: {
enclose: false, quality: 20
keep_classnames: false, },
keep_fnames: false, pngquant: {
ie8: false, quality: [0.8, 0.9],
module: false, speed: 4
nameCache: null, },
safari10: false, svgo: {
toplevel: false plugins: [
}, {
name: 'removeViewBox'
rollupOptions: { },
output: { {
manualChunks(id: string) { name: 'removeEmptyAttrs',
if (id.includes('node_modules')) { active: false
// creating a chunk to react routes deps. Reducing the vendor chunk size
if (id.includes('react-router-dom') || id.includes('@remix-run') || id.includes('react-router')) {
return '@react-router';
}
return 'vendor';
} }
]
}
}),
enforce: 'pre'
},
visualizer({
template: 'treemap', // or sunburst
open: false,
gzipSize: true,
brotliSize: true,
filename: '../analyse.html' // will be saved in project's root
})
],
build: {
// target: 'es2022',
chunkSizeWarningLimit: 1024,
minify: 'terser',
terserOptions: {
compress: {
passes: 4,
arrows: true,
drop_console: true,
drop_debugger: true,
sequences: true
},
mangle: {
// toplevel: true
// module: true
// properties: {
// regex: /^_/
// }
},
ecma: 5,
enclose: false,
keep_classnames: false,
keep_fnames: false,
ie8: false,
module: false,
nameCache: null,
safari10: false,
toplevel: false
},
rollupOptions: {
output: {
manualChunks(id: string) {
if (id.includes('node_modules')) {
// creating a chunk to react routes deps. Reducing the vendor chunk size
if (id.includes('react-router-dom') || id.includes('@remix-run') || id.includes('react-router')) {
return '@react-router';
}
return 'vendor';
} }
} }
} }
} }
}; }
} };
}); });

View File

@@ -1459,7 +1459,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react@npm:*, @types/react@npm:^18.2.69": "@types/react@npm:*":
version: 18.2.69 version: 18.2.69
resolution: "@types/react@npm:18.2.69" resolution: "@types/react@npm:18.2.69"
dependencies: dependencies:
@@ -1470,6 +1470,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react@npm:^18.2.72":
version: 18.2.72
resolution: "@types/react@npm:18.2.72"
dependencies:
"@types/prop-types": "npm:*"
csstype: "npm:^3.0.2"
checksum: 10/0c15461eeb8cd5153b48446d4aae681ae1d4f45fa5828fc53c12aaac9dfe426a64259a284737f6abfc3bea36df9507c129d7065ebb390b21349ad30128385ac1
languageName: node
linkType: hard
"@types/responselike@npm:^1.0.0": "@types/responselike@npm:^1.0.0":
version: 1.0.3 version: 1.0.3
resolution: "@types/responselike@npm:1.0.3" resolution: "@types/responselike@npm:1.0.3"
@@ -1502,15 +1512,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/eslint-plugin@npm:^7.3.1": "@typescript-eslint/eslint-plugin@npm:^7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/eslint-plugin@npm:7.3.1" resolution: "@typescript-eslint/eslint-plugin@npm:7.4.0"
dependencies: dependencies:
"@eslint-community/regexpp": "npm:^4.5.1" "@eslint-community/regexpp": "npm:^4.5.1"
"@typescript-eslint/scope-manager": "npm:7.3.1" "@typescript-eslint/scope-manager": "npm:7.4.0"
"@typescript-eslint/type-utils": "npm:7.3.1" "@typescript-eslint/type-utils": "npm:7.4.0"
"@typescript-eslint/utils": "npm:7.3.1" "@typescript-eslint/utils": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.3.1" "@typescript-eslint/visitor-keys": "npm:7.4.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
graphemer: "npm:^1.4.0" graphemer: "npm:^1.4.0"
ignore: "npm:^5.2.4" ignore: "npm:^5.2.4"
@@ -1523,44 +1533,44 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/8ed276113a714d93ab3ababb1179e4785bd9378e6d97726519ea1d2ac502a94475e0be988c2ec427dcfc1e6950329d58da6e64131ee87028fce63493461cc51a checksum: 10/9bd8852c7e4e9608c3fded94f7c60506cc7d2b6d8a8c1cad6d48969a7363751b20282874e55ccdf180635cf204cb10b3e1e5c3d1cff34d4fcd07762be3fc138e
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/parser@npm:^7.3.1": "@typescript-eslint/parser@npm:^7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/parser@npm:7.3.1" resolution: "@typescript-eslint/parser@npm:7.4.0"
dependencies: dependencies:
"@typescript-eslint/scope-manager": "npm:7.3.1" "@typescript-eslint/scope-manager": "npm:7.4.0"
"@typescript-eslint/types": "npm:7.3.1" "@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/typescript-estree": "npm:7.3.1" "@typescript-eslint/typescript-estree": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.3.1" "@typescript-eslint/visitor-keys": "npm:7.4.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/018326010fec1dcefd75809ccac5102a475bf1e052d824b898d707e7c0bf3e51e101164b410d1b2a513628985c96eb412538644d2005e26b99a22db6eb9402df checksum: 10/142a9e1187d305ed43b4fef659c36fa4e28359467198c986f0955c70b4067c9799f4c85d9881fbf099c55dfb265e30666e28b3ef290520e242b45ca7cb8e4ca9
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/scope-manager@npm:7.3.1": "@typescript-eslint/scope-manager@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/scope-manager@npm:7.3.1" resolution: "@typescript-eslint/scope-manager@npm:7.4.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:7.3.1" "@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.3.1" "@typescript-eslint/visitor-keys": "npm:7.4.0"
checksum: 10/7384d1f46d7f3678a1135a1ac0bd8b6dfa2f01e93b19e2510c7082766cf6983a1bf80b4ccf498651199a81d9f2bdb65101fd7a19226a723260514204d0c30b34 checksum: 10/8cf9292444f9731017a707cac34bef5ae0eb33b5cd42ed07fcd046e981d97889d9201d48e02f470f2315123f53771435e10b1dc81642af28a11df5352a8e8be2
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/type-utils@npm:7.3.1": "@typescript-eslint/type-utils@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/type-utils@npm:7.3.1" resolution: "@typescript-eslint/type-utils@npm:7.4.0"
dependencies: dependencies:
"@typescript-eslint/typescript-estree": "npm:7.3.1" "@typescript-eslint/typescript-estree": "npm:7.4.0"
"@typescript-eslint/utils": "npm:7.3.1" "@typescript-eslint/utils": "npm:7.4.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.0.1" ts-api-utils: "npm:^1.0.1"
peerDependencies: peerDependencies:
@@ -1568,23 +1578,23 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/fae9003a76a8f2a2a4bb88dc0f82c0a1ca0688633183fac391920e7124a12807aac84bb287a21f61e99523c15223d1c08e7680685ebf21d07429604cba6c420b checksum: 10/a8bd0929d8237679b2b8a7817f070a4b9658ee976882fba8ff37e4a70dd33f87793e1b157771104111fe8054eaa8ad437a010b6aa465072fbdb932647125db2d
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/types@npm:7.3.1": "@typescript-eslint/types@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/types@npm:7.3.1" resolution: "@typescript-eslint/types@npm:7.4.0"
checksum: 10/c9c8eae1cf937cececd99a253bd65eb71b40206e79cf917ad9c3b3ab80cc7ce5fefb2804f9fd2a70e7438951f0d1e63df3031fc61e3a08dfef5fde208a12e0ed checksum: 10/2782c5bf65cd3dfa9cd32bc3023676bbca22144987c3f6c6b67fd96c73d4a60b85a57458c49fd11b9971ac6531824bb3ae0664491e7a6de25d80c523c9be92b7
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/typescript-estree@npm:7.3.1": "@typescript-eslint/typescript-estree@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/typescript-estree@npm:7.3.1" resolution: "@typescript-eslint/typescript-estree@npm:7.4.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:7.3.1" "@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.3.1" "@typescript-eslint/visitor-keys": "npm:7.4.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
globby: "npm:^11.1.0" globby: "npm:^11.1.0"
is-glob: "npm:^4.0.3" is-glob: "npm:^4.0.3"
@@ -1594,34 +1604,34 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/363ad9864b56394b4000dff7c2b77d0ea52042c3c20e3b86c0f3c66044915632d9890255527c6f3a5ef056886dec72e38fbcfce49d4ad092c160440f54128230 checksum: 10/162ec9d7582f45588342e1be36fdb60e41f50bbdfbc3035c91b517ff5d45244f776921c88d88e543e1c7d0f1e6ada5474a8316b78f1b0e6d2233b101bc45b166
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/utils@npm:7.3.1": "@typescript-eslint/utils@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/utils@npm:7.3.1" resolution: "@typescript-eslint/utils@npm:7.4.0"
dependencies: dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0" "@eslint-community/eslint-utils": "npm:^4.4.0"
"@types/json-schema": "npm:^7.0.12" "@types/json-schema": "npm:^7.0.12"
"@types/semver": "npm:^7.5.0" "@types/semver": "npm:^7.5.0"
"@typescript-eslint/scope-manager": "npm:7.3.1" "@typescript-eslint/scope-manager": "npm:7.4.0"
"@typescript-eslint/types": "npm:7.3.1" "@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/typescript-estree": "npm:7.3.1" "@typescript-eslint/typescript-estree": "npm:7.4.0"
semver: "npm:^7.5.4" semver: "npm:^7.5.4"
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
checksum: 10/234d9d65fe5d0f4a31345bd8f5a6f2879a578b3a531a14c2b3edaa7fb587c71d26249f86c41857382c0405384dc104955c02b588b3cee6fc2734f1ae40aef07b checksum: 10/ffed27e770c486cd000ff892d9049b0afe8b9d6318452a5355b78a37436cbb414bceacae413a2ac813f3e584684825d5e0baa2e6376b7ad6013a108ac91bc19d
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/visitor-keys@npm:7.3.1": "@typescript-eslint/visitor-keys@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/visitor-keys@npm:7.3.1" resolution: "@typescript-eslint/visitor-keys@npm:7.4.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:7.3.1" "@typescript-eslint/types": "npm:7.4.0"
eslint-visitor-keys: "npm:^3.4.1" eslint-visitor-keys: "npm:^3.4.1"
checksum: 10/163a93597c1d696920a19b3c1627d02368bdd52059f811c0fadd680c38034bb6418ebefe99d8ce26e0dd44ae184f18fab186af775de1a8771256be1a7905c174 checksum: 10/70dc99f2ad116c6e2d9e55af249e4453e06bba2ceea515adef2d2e86e97e557865bb1b1d467667462443eb0d624baba36f7442fd1082f3874339bbc381c26e93
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1648,11 +1658,11 @@ __metadata:
"@types/imagemin": "npm:^8.0.5" "@types/imagemin": "npm:^8.0.5"
"@types/lodash-es": "npm:^4.17.12" "@types/lodash-es": "npm:^4.17.12"
"@types/node": "npm:^20.11.30" "@types/node": "npm:^20.11.30"
"@types/react": "npm:^18.2.69" "@types/react": "npm:^18.2.72"
"@types/react-dom": "npm:^18.2.22" "@types/react-dom": "npm:^18.2.22"
"@types/react-router-dom": "npm:^5.3.3" "@types/react-router-dom": "npm:^5.3.3"
"@typescript-eslint/eslint-plugin": "npm:^7.3.1" "@typescript-eslint/eslint-plugin": "npm:^7.4.0"
"@typescript-eslint/parser": "npm:^7.3.1" "@typescript-eslint/parser": "npm:^7.4.0"
alova: "npm:^2.18.0" alova: "npm:^2.18.0"
async-validator: "npm:^4.2.5" async-validator: "npm:^4.2.5"
concurrently: "npm:^8.2.2" concurrently: "npm:^8.2.2"
@@ -1682,7 +1692,7 @@ __metadata:
terser: "npm:^5.29.2" terser: "npm:^5.29.2"
typesafe-i18n: "npm:^5.26.2" typesafe-i18n: "npm:^5.26.2"
typescript: "npm:^5.4.3" typescript: "npm:^5.4.3"
vite: "npm:^5.2.4" vite: "npm:^5.2.6"
vite-plugin-imagemin: "npm:^0.6.1" vite-plugin-imagemin: "npm:^0.6.1"
vite-tsconfig-paths: "npm:^4.3.2" vite-tsconfig-paths: "npm:^4.3.2"
languageName: unknown languageName: unknown
@@ -8342,9 +8352,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"vite@npm:^5.2.4": "vite@npm:^5.2.6":
version: 5.2.4 version: 5.2.6
resolution: "vite@npm:5.2.4" resolution: "vite@npm:5.2.6"
dependencies: dependencies:
esbuild: "npm:^0.20.1" esbuild: "npm:^0.20.1"
fsevents: "npm:~2.3.3" fsevents: "npm:~2.3.3"
@@ -8378,7 +8388,7 @@ __metadata:
optional: true optional: true
bin: bin:
vite: bin/vite.js vite: bin/vite.js
checksum: 10/ed3d4fa2023642dd578e90eb02876e01729fda0af196304bb1c3a7f037b457f1a505bfa36c10f28d0466945b9da7d7972219716554d43ff3c025f26ed3d89e11 checksum: 10/0409acd4bbad1bca42b2015ac5d0f710bbc84b86f6b518add9a9c13adf1aab02fd40fcca854dc08ff2a2226c1df77d5d5b4a958c6c4c04ca27a6bfb0b4f60615
languageName: node languageName: node
linkType: hard linkType: hard

View File

@@ -190,7 +190,7 @@ void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage)
//length() is not thread-safe, thus acquiring the lock before this call.. //length() is not thread-safe, thus acquiring the lock before this call..
_lockmq.lock(); _lockmq.lock();
if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){ if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str()); // ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
delete dataMessage; delete dataMessage;
} else { } else {
_messageQueue.add(dataMessage); _messageQueue.add(dataMessage);

View File

@@ -460,7 +460,7 @@ void AsyncWebSocketClient::_queueMessage(std::shared_ptr<std::vector<uint8_t>> b
if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES)
{ {
l.unlock(); l.unlock();
ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued, closing connection\n"); //ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued, closing connection\n");
_status = WS_DISCONNECTED; _status = WS_DISCONNECTED;
if (_client) _client->close(true); if (_client) _client->close(true);
return; return;
@@ -1272,9 +1272,9 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket
(String&)key += WS_STR_UUID; (String&)key += WS_STR_UUID;
mbedtls_sha1_context ctx; mbedtls_sha1_context ctx;
mbedtls_sha1_init(&ctx); mbedtls_sha1_init(&ctx);
mbedtls_sha1_starts_ret(&ctx); mbedtls_sha1_starts(&ctx);
mbedtls_sha1_update_ret(&ctx, (const unsigned char*)key.c_str(), key.length()); mbedtls_sha1_update(&ctx, (const unsigned char*)key.c_str(), key.length());
mbedtls_sha1_finish_ret(&ctx, hash); mbedtls_sha1_finish(&ctx, hash);
mbedtls_sha1_free(&ctx); mbedtls_sha1_free(&ctx);
#endif #endif
base64_encodestate _state; base64_encodestate _state;

View File

@@ -76,10 +76,17 @@ static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or mo
return false; return false;
memset(_buf, 0x00, 16); memset(_buf, 0x00, 16);
#ifdef ESP32 #ifdef ESP32
#if ESP_ARDUINO_VERSION_MAJOR < 3
mbedtls_md5_init(&_ctx); mbedtls_md5_init(&_ctx);
mbedtls_md5_starts_ret(&_ctx); mbedtls_md5_starts_ret(&_ctx);
mbedtls_md5_update_ret(&_ctx, data, len); mbedtls_md5_update_ret(&_ctx, data, len);
mbedtls_md5_finish_ret(&_ctx, _buf); mbedtls_md5_finish_ret(&_ctx, _buf);
#else
mbedtls_md5_init(&_ctx);
mbedtls_md5_starts(&_ctx);
mbedtls_md5_update(&_ctx, data, len);
mbedtls_md5_finish(&_ctx, _buf);
#endif
#else #else
MD5Init(&_ctx); MD5Init(&_ctx);
MD5Update(&_ctx, data, len); MD5Update(&_ctx, data, len);

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