Compare commits
721 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9233f0dfcc | ||
|
|
292f743b14 | ||
|
|
dd6dfffd57 | ||
|
|
ec705a5307 | ||
|
|
f45f071710 | ||
|
|
f3858546de | ||
|
|
de40cb8920 | ||
|
|
8ddf1315eb | ||
|
|
00d5b16de7 | ||
|
|
0b02bb417a | ||
|
|
d68260411d | ||
|
|
7227937660 | ||
|
|
e66e2c40a6 | ||
|
|
39bc0d5fb2 | ||
|
|
50e73e2a04 | ||
|
|
efa9875744 | ||
|
|
8182b0e95a | ||
|
|
6091621858 | ||
|
|
ba568581ff | ||
|
|
c78190c3a0 | ||
|
|
84c968e053 | ||
|
|
6b7da4068c | ||
|
|
e3e14e7a66 | ||
|
|
997ced3938 | ||
|
|
b18da9064b | ||
|
|
b3ee5f4d9a | ||
|
|
5398abb074 | ||
|
|
65397d3e1e | ||
|
|
887d53528b | ||
|
|
1603eafbb2 | ||
|
|
33bcb54aaf | ||
|
|
d36f87707a | ||
|
|
226c1fd6c5 | ||
|
|
74b961ab29 | ||
|
|
e4358ba489 | ||
|
|
26522cb061 | ||
|
|
be128da9e0 | ||
|
|
381afcbb6d | ||
|
|
1dc008855e | ||
|
|
bbf4431b5f | ||
|
|
c38ad8e382 | ||
|
|
1416bad9cc | ||
|
|
0e0e9eccf1 | ||
|
|
032a631b61 | ||
|
|
5d0f6b665b | ||
|
|
4bbf096350 | ||
|
|
e1a950ec21 | ||
|
|
ede8e7dfce | ||
|
|
9d155d6a0e | ||
|
|
1fa92eec57 | ||
|
|
7fc28fd1bb | ||
|
|
dd338f5f4b | ||
|
|
486bc5ac28 | ||
|
|
68215f37f5 | ||
|
|
4449d3fce8 | ||
|
|
cbb20439ed | ||
|
|
ec357b71f1 | ||
|
|
5acdb4dc31 | ||
|
|
dd959a7316 | ||
|
|
0b6dbe8bfa | ||
|
|
78e6263a2e | ||
|
|
3b2a4d1eb4 | ||
|
|
485195e035 | ||
|
|
d735f397d8 | ||
|
|
5e41481e27 | ||
|
|
d52c54d6fb | ||
|
|
36c85eed5e | ||
|
|
b09deb1494 | ||
|
|
cb4cce119b | ||
|
|
7e174a1b7d | ||
|
|
1f08940e47 | ||
|
|
8db5724b77 | ||
|
|
a21352ae4f | ||
|
|
a38f4978fa | ||
|
|
4f05ddab93 | ||
|
|
bbbeb155f0 | ||
|
|
f6238cd6ab | ||
|
|
d658b67e93 | ||
|
|
e2c675d9a5 | ||
|
|
3e3a600a60 | ||
|
|
1f0d2b147b | ||
|
|
285b0dc01d | ||
|
|
172153b840 | ||
|
|
ce71b8b5f6 | ||
|
|
d72d2b33bd | ||
|
|
d15fbe7801 | ||
|
|
caab1cae39 | ||
|
|
dced355a29 | ||
|
|
f133fdfb1c | ||
|
|
80ea1e95ee | ||
|
|
4098cef279 | ||
|
|
d0aac18b88 | ||
|
|
7bb35812ff | ||
|
|
ad49267b29 | ||
|
|
88404bcb56 | ||
|
|
33af6ec57e | ||
|
|
4b3a0bf4e2 | ||
|
|
508dd399be | ||
|
|
8b2466dcde | ||
|
|
c754d19015 | ||
|
|
ad680de897 | ||
|
|
78ae4d7a7d | ||
|
|
74acf2fe0f | ||
|
|
bab9fd8ec4 | ||
|
|
cfce36c212 | ||
|
|
5c6d6da4f9 | ||
|
|
5005d06507 | ||
|
|
9a09062a84 | ||
|
|
6dc993a276 | ||
|
|
c45ceec33b | ||
|
|
c28de99907 | ||
|
|
b587c08768 | ||
|
|
71043963e7 | ||
|
|
04b17a15bb | ||
|
|
4aa9d11574 | ||
|
|
56b5739cfc | ||
|
|
bb1704ed7a | ||
|
|
9163fc74d4 | ||
|
|
49113edeff | ||
|
|
718ede8439 | ||
|
|
5ee49d8dad | ||
|
|
c19885d730 | ||
|
|
d2e64468d5 | ||
|
|
e3b6a3308f | ||
|
|
c3f88ae0c8 | ||
|
|
31a5868864 | ||
|
|
4cb50a1dff | ||
|
|
2062703565 | ||
|
|
5e00f07fd8 | ||
|
|
3bf2110df1 | ||
|
|
c3ff60dced | ||
|
|
7cf3b8f1a9 | ||
|
|
4536d60e3e | ||
|
|
2f1ea4da67 | ||
|
|
34f6b412f6 | ||
|
|
b16a16d100 | ||
|
|
55750844ea | ||
|
|
6c09134552 | ||
|
|
8fa74c63c9 | ||
|
|
275ac351fe | ||
|
|
b728827324 | ||
|
|
2db99a3e32 | ||
|
|
263c011a7b | ||
|
|
8eeebb0cef | ||
|
|
f055e53987 | ||
|
|
6dce5f5931 | ||
|
|
14cfbf78bd | ||
|
|
6de577839b | ||
|
|
bfcdf3ef98 | ||
|
|
ffa7ddebb8 | ||
|
|
29838a433a | ||
|
|
1f1422bedd | ||
|
|
d41e634611 | ||
|
|
22cc890cab | ||
|
|
15e940bd2d | ||
|
|
c788f53bb9 | ||
|
|
10f1c5781e | ||
|
|
75b0869a77 | ||
|
|
c4c341922b | ||
|
|
43b4adc618 | ||
|
|
bad982dbdd | ||
|
|
5d45064c2d | ||
|
|
6390f4aa48 | ||
|
|
bb94d56bd0 | ||
|
|
14f3d9ab12 | ||
|
|
d9ecf0efb8 | ||
|
|
9b66b02e46 | ||
|
|
feca878fdd | ||
|
|
f53fd74873 | ||
|
|
bbaf892523 | ||
|
|
451b3abddf | ||
|
|
ba03add3d3 | ||
|
|
a41de7ed1c | ||
|
|
28de5bb097 | ||
|
|
d08a1224e2 | ||
|
|
327cf7ec75 | ||
|
|
83438129a2 | ||
|
|
9b3b7fc8ff | ||
|
|
8000497302 | ||
|
|
84589a4b40 | ||
|
|
067129f5a9 | ||
|
|
ffcf98b06b | ||
|
|
b2f001e416 | ||
|
|
44c31c9c61 | ||
|
|
8aa659eee2 | ||
|
|
51bf163149 | ||
|
|
d5c02f6b94 | ||
|
|
55fa968afb | ||
|
|
a9aac33a46 | ||
|
|
1f45506b37 | ||
|
|
9d9d88b171 | ||
|
|
291da3f7fe | ||
|
|
70cfbc3715 | ||
|
|
9d80c2cea7 | ||
|
|
a7f7959f91 | ||
|
|
082268d9fc | ||
|
|
945ef2f1b0 | ||
|
|
1f1feed3ae | ||
|
|
290890f6e7 | ||
|
|
b7bfd803eb | ||
|
|
2151905d46 | ||
|
|
fdde118af1 | ||
|
|
5f9ba2e04b | ||
|
|
ca871f654b | ||
|
|
3c91ac27dc | ||
|
|
94b4cb0baf | ||
|
|
1b956c6ad7 | ||
|
|
b9b79bbd9a | ||
|
|
9802581301 | ||
|
|
c1f39fbf57 | ||
|
|
f66e7712c3 | ||
|
|
2bb6d985cc | ||
|
|
d300ed38ea | ||
|
|
9cbb810fe4 | ||
|
|
cfa486d8cc | ||
|
|
ab1924d266 | ||
|
|
d6de0f6fa8 | ||
|
|
d1afea104e | ||
|
|
fae8cf83cd | ||
|
|
5a69ac074f | ||
|
|
15f0560005 | ||
|
|
6f7fa6abd9 | ||
|
|
cbd55b0366 | ||
|
|
f1dbd3018d | ||
|
|
502613b433 | ||
|
|
2bdc0d59cf | ||
|
|
ce0ee49ebf | ||
|
|
bc3bdb1221 | ||
|
|
3bea7576b5 | ||
|
|
d70b5d7dc0 | ||
|
|
eb30c1e5c7 | ||
|
|
17cbd2623f | ||
|
|
dc27a44c0c | ||
|
|
f72b02ab0b | ||
|
|
c0946f1e0c | ||
|
|
575fdcb8cd | ||
|
|
783ea7901c | ||
|
|
44e6bb79a2 | ||
|
|
ab3b9f13b5 | ||
|
|
dc6e7f7b1b | ||
|
|
94b75dda24 | ||
|
|
a529879889 | ||
|
|
4b7bbb3d50 | ||
|
|
88c98efd94 | ||
|
|
147be12583 | ||
|
|
137e047205 | ||
|
|
4bd6db31c0 | ||
|
|
1350638fb3 | ||
|
|
b6de431a56 | ||
|
|
1aef27da33 | ||
|
|
1e78979ed0 | ||
|
|
ba90ebda4c | ||
|
|
7c1bade54d | ||
|
|
a46b394714 | ||
|
|
6dff1136c5 | ||
|
|
ccbb56d403 | ||
|
|
4f3a7e5451 | ||
|
|
e4cda4087e | ||
|
|
6eeb8de02c | ||
|
|
29c4fec90a | ||
|
|
5c9ba8de43 | ||
|
|
2358f6a9c9 | ||
|
|
7097279dca | ||
|
|
c3eb553425 | ||
|
|
24f64fac6b | ||
|
|
4cdd5e9f20 | ||
|
|
de9e261807 | ||
|
|
c26793c68d | ||
|
|
11fd833cdb | ||
|
|
2b21a0c31f | ||
|
|
7e888f6408 | ||
|
|
bba70ce852 | ||
|
|
e96b5af0c8 | ||
|
|
5f5e786c0e | ||
|
|
8d66e43117 | ||
|
|
ccbc809ecf | ||
|
|
e67fde24f1 | ||
|
|
923494fdce | ||
|
|
73bff2cabe | ||
|
|
c20fc5a345 | ||
|
|
f71c62f167 | ||
|
|
bdaf9e6dc6 | ||
|
|
a2730fb17c | ||
|
|
5061ddf38e | ||
|
|
1735c036cc | ||
|
|
fa703db41e | ||
|
|
9665f4136a | ||
|
|
abeef2be4a | ||
|
|
995420354e | ||
|
|
1af1a1863a | ||
|
|
fd04f8be5a | ||
|
|
ccc9e6dcfb | ||
|
|
9e23710c6d | ||
|
|
3878a3ee0b | ||
|
|
0c9d0a4d15 | ||
|
|
739c007c95 | ||
|
|
6f27253441 | ||
|
|
b900932574 | ||
|
|
0903b31fcf | ||
|
|
d080f5db0f | ||
|
|
5029a1625e | ||
|
|
2950f167aa | ||
|
|
afdf9a3dfb | ||
|
|
132ca9d106 | ||
|
|
990d75d42a | ||
|
|
1c48aa8444 | ||
|
|
67e07813cb | ||
|
|
7fbeed88a9 | ||
|
|
9053e7ac88 | ||
|
|
e3c94cc1f7 | ||
|
|
e8d6c4d451 | ||
|
|
ba1813c767 | ||
|
|
bcd8992abc | ||
|
|
557b532f74 | ||
|
|
d450464a1a | ||
|
|
5d10b28433 | ||
|
|
eafe358deb | ||
|
|
048bd877da | ||
|
|
7f18dd942f | ||
|
|
424234ec48 | ||
|
|
4168a08276 | ||
|
|
996063d76d | ||
|
|
085f5ff22f | ||
|
|
5b5dc6a8cc | ||
|
|
4103bad8de | ||
|
|
d6a8563cc7 | ||
|
|
c9ef0bcd7b | ||
|
|
6b978759ca | ||
|
|
bc31d54028 | ||
|
|
7bad0e04b1 | ||
|
|
7144c746c6 | ||
|
|
d5592e5662 | ||
|
|
12b027110e | ||
|
|
644896faff | ||
|
|
f41f7c0769 | ||
|
|
68f1a891ca | ||
|
|
b186311968 | ||
|
|
c21b594cf0 | ||
|
|
21ec46843a | ||
|
|
4da827501a | ||
|
|
5d0c7bfd32 | ||
|
|
d583409af4 | ||
|
|
559e607601 | ||
|
|
276b27d8a6 | ||
|
|
2134f42cfd | ||
|
|
88135d2750 | ||
|
|
1b1a4bcad4 | ||
|
|
af8e82a860 | ||
|
|
c8d5e37b44 | ||
|
|
781fe03b5d | ||
|
|
702103aa66 | ||
|
|
d2503431c6 | ||
|
|
dccdf18226 | ||
|
|
a92f287256 | ||
|
|
a193d67f11 | ||
|
|
6873e113bb | ||
|
|
77860d9d05 | ||
|
|
de97010cfb | ||
|
|
89245c7af7 | ||
|
|
d51745774f | ||
|
|
deda1daa04 | ||
|
|
8594c4a7eb | ||
|
|
d0d73aa5e2 | ||
|
|
1f43bb201c | ||
|
|
6e92d31f5d | ||
|
|
ae4070b7f2 | ||
|
|
ee2ac18b69 | ||
|
|
85ce697dad | ||
|
|
82eb79ce40 | ||
|
|
c76b4b9ede | ||
|
|
9c2e814e16 | ||
|
|
2e9499ea90 | ||
|
|
52296bfed1 | ||
|
|
a53d54a1d6 | ||
|
|
81cf08b723 | ||
|
|
f192d7dffc | ||
|
|
5425896988 | ||
|
|
9e2be00b5c | ||
|
|
06004ce478 | ||
|
|
5bda018d25 | ||
|
|
b12729a874 | ||
|
|
d3a84e02e4 | ||
|
|
9865c84df5 | ||
|
|
33d2cb9a49 | ||
|
|
37c121e8de | ||
|
|
9dd0bf01e2 | ||
|
|
92ac601072 | ||
|
|
dfd7647838 | ||
|
|
058246e2ce | ||
|
|
8ed789892b | ||
|
|
57775af24b | ||
|
|
845681b6dc | ||
|
|
ea2c9cbc9a | ||
|
|
96bb3013a3 | ||
|
|
aeee37fdae | ||
|
|
551497bfeb | ||
|
|
57057cac0e | ||
|
|
dccd9f09e7 | ||
|
|
b91e474343 | ||
|
|
f25262c191 | ||
|
|
723662a6bc | ||
|
|
757fcd3ea4 | ||
|
|
ec3e28436d | ||
|
|
6a291ef1e2 | ||
|
|
1e529a9e19 | ||
|
|
05d393e7fe | ||
|
|
c757ace2b4 | ||
|
|
5ab066de5f | ||
|
|
b0ae22d493 | ||
|
|
03a6abcfc6 | ||
|
|
e58491ed97 | ||
|
|
1e92ae05c0 | ||
|
|
513b6181a4 | ||
|
|
05b54bc6f5 | ||
|
|
1d4634a76c | ||
|
|
342cf12ae7 | ||
|
|
6883dbbce1 | ||
|
|
a3d3706b89 | ||
|
|
da911e374a | ||
|
|
26904f4e0e | ||
|
|
ec5601f3ca | ||
|
|
a2ee2a5e6b | ||
|
|
78d5f8b76d | ||
|
|
dd0cc00004 | ||
|
|
825836c447 | ||
|
|
4996eb9e3c | ||
|
|
0bc3a3c34e | ||
|
|
ca5bc313ee | ||
|
|
069df92dbf | ||
|
|
bdce4ee9f9 | ||
|
|
756a136124 | ||
|
|
4809ef3537 | ||
|
|
2c056d6807 | ||
|
|
90af466a2f | ||
|
|
20cbbcd9f4 | ||
|
|
036953044e | ||
|
|
499456fa77 | ||
|
|
22d9705412 | ||
|
|
663c853aff | ||
|
|
103ffa4761 | ||
|
|
3baedf01d1 | ||
|
|
bfad6d34b5 | ||
|
|
2aa2564078 | ||
|
|
6561bb5a6c | ||
|
|
ac75176292 | ||
|
|
1005079f71 | ||
|
|
e0e07a9deb | ||
|
|
c65005e5a6 | ||
|
|
64f2f82e0c | ||
|
|
98495c8114 | ||
|
|
d0ac0b7804 | ||
|
|
e45c31345e | ||
|
|
23cd677133 | ||
|
|
805c1298fb | ||
|
|
5199edff1e | ||
|
|
f3adc13c6d | ||
|
|
40f5f7026d | ||
|
|
b8b96763cf | ||
|
|
80b94fcd00 | ||
|
|
8d09d3c654 | ||
|
|
03564b3a82 | ||
|
|
a7569256d0 | ||
|
|
b0d4a094c1 | ||
|
|
1b232adc72 | ||
|
|
ed81e095ee | ||
|
|
76734b77f1 | ||
|
|
db6f8eaba1 | ||
|
|
eb7ad7163f | ||
|
|
6a478eec5e | ||
|
|
1dc08d6399 | ||
|
|
c3cfed5ac3 | ||
|
|
aba1dc93d9 | ||
|
|
d9bbf35afc | ||
|
|
dec3abfab9 | ||
|
|
bb98f13b19 | ||
|
|
8b10970e03 | ||
|
|
b859ab9d78 | ||
|
|
26208103fc | ||
|
|
d150b017e3 | ||
|
|
49414adfd2 | ||
|
|
5c675c7ce7 | ||
|
|
f16aaf7874 | ||
|
|
d80831e708 | ||
|
|
3e3e7156ec | ||
|
|
dbb2a365cb | ||
|
|
52a8c7288d | ||
|
|
42f6bf6182 | ||
|
|
9d4d3738ff | ||
|
|
1647a6d0a7 | ||
|
|
e5e058672d | ||
|
|
f5cd8e2523 | ||
|
|
fd2cc8aff1 | ||
|
|
f244fb837b | ||
|
|
b4848ab7f9 | ||
|
|
0fa60f0502 | ||
|
|
8d290317d7 | ||
|
|
cb5c8f3a85 | ||
|
|
5a0f4c1462 | ||
|
|
e4445413fd | ||
|
|
9be13eb0b9 | ||
|
|
2c7eeeca7b | ||
|
|
b59a552288 | ||
|
|
def67899ed | ||
|
|
ea0870c180 | ||
|
|
379d57ca8e | ||
|
|
761df2b4cc | ||
|
|
56e1f02f69 | ||
|
|
f921ef4708 | ||
|
|
f11dc9bc25 | ||
|
|
0ebfcd7238 | ||
|
|
5fb9cd142f | ||
|
|
7d5e112efb | ||
|
|
e82dd816de | ||
|
|
887a245d82 | ||
|
|
2f706b33fa | ||
|
|
dcbdb04009 | ||
|
|
008903cbbd | ||
|
|
b67113fc1f | ||
|
|
3afbe832cc | ||
|
|
9adfa0ecfc | ||
|
|
f081d7fd3c | ||
|
|
394a3253aa | ||
|
|
13890d2835 | ||
|
|
6fd3e567cd | ||
|
|
b6d8e55b00 | ||
|
|
e6d3d347ab | ||
|
|
c159ce7eb9 | ||
|
|
65c9bf22dc | ||
|
|
87dfffeddb | ||
|
|
8d6c676fed | ||
|
|
324d27896b | ||
|
|
fd5fcf356f | ||
|
|
0dde5a9d2b | ||
|
|
73f7603c1d | ||
|
|
5faffc3886 | ||
|
|
d09d5a7dbe | ||
|
|
b906fecdff | ||
|
|
a6e4122e44 | ||
|
|
e663ecb458 | ||
|
|
95876e28bf | ||
|
|
348932d929 | ||
|
|
bd28516324 | ||
|
|
d1fc050bed | ||
|
|
bace01e4f7 | ||
|
|
0a56ee7dbb | ||
|
|
c90be99216 | ||
|
|
a0a431e0e2 | ||
|
|
4489e7149c | ||
|
|
ec81420894 | ||
|
|
619dd0c99d | ||
|
|
9f5a5108fb | ||
|
|
974510b2c7 | ||
|
|
f2f10f0c79 | ||
|
|
39cbcd4d6f | ||
|
|
1accdfcafb | ||
|
|
e37bbe420c | ||
|
|
58c4455076 | ||
|
|
a413ffb9d2 | ||
|
|
1f96622e74 | ||
|
|
9527cf6abf | ||
|
|
13f0bc3296 | ||
|
|
58a0ec9cca | ||
|
|
fe385de342 | ||
|
|
ae5fb26387 | ||
|
|
cd67ab03ff | ||
|
|
94f134f3fe | ||
|
|
ca95e44a81 | ||
|
|
ecc045b2a8 | ||
|
|
9c205a07c5 | ||
|
|
c414354708 | ||
|
|
ddacd2d9d7 | ||
|
|
987fcb4a5a | ||
|
|
4c70da28e6 | ||
|
|
246684f28c | ||
|
|
e450a0e096 | ||
|
|
90d2144588 | ||
|
|
4302d314cf | ||
|
|
22322a55ed | ||
|
|
d09c0436e0 | ||
|
|
9b619216cb | ||
|
|
fe0a855618 | ||
|
|
3bca7c9a13 | ||
|
|
87887494a5 | ||
|
|
614a8cb14b | ||
|
|
da6e64e89f | ||
|
|
87d0db0b5c | ||
|
|
c756df90fc | ||
|
|
d3053d8ce2 | ||
|
|
4c728cf777 | ||
|
|
17b90af972 | ||
|
|
a0339f5ae2 | ||
|
|
732aaffbb6 | ||
|
|
d50606ce13 | ||
|
|
777c9db0f6 | ||
|
|
815397dba6 | ||
|
|
01f361e7cd | ||
|
|
1278776297 | ||
|
|
a3a8e515bf | ||
|
|
96af9afc83 | ||
|
|
c841c8c284 | ||
|
|
d6ee8ccb2d | ||
|
|
a7930d8403 | ||
|
|
eeeb889ba7 | ||
|
|
f1f4147628 | ||
|
|
42118e0169 | ||
|
|
b67d69a5c8 | ||
|
|
d8144c901d | ||
|
|
e9d9cf7e48 | ||
|
|
0867d7fe0e | ||
|
|
b646331adc | ||
|
|
30109a54ce | ||
|
|
e85ca7dcd3 | ||
|
|
faf05ceb72 | ||
|
|
049e37ba82 | ||
|
|
d187ee23ea | ||
|
|
20b0c9653d | ||
|
|
022b667858 | ||
|
|
1b4af09185 | ||
|
|
58ca4efd42 | ||
|
|
f1bb183017 | ||
|
|
081c11c503 | ||
|
|
d54843635a | ||
|
|
12638275fa | ||
|
|
25c50435ca | ||
|
|
e1c61cfadb | ||
|
|
06fd860964 | ||
|
|
ab4e1f63c5 | ||
|
|
fb998a7e6a | ||
|
|
dbbbfc1170 | ||
|
|
eb432d9acb | ||
|
|
23f65b9eb2 | ||
|
|
c11ea4fe0d | ||
|
|
6b1cfffb6f | ||
|
|
918c0315eb | ||
|
|
106517fc8b | ||
|
|
96070d6b42 | ||
|
|
b22473d0d0 | ||
|
|
ef842b6c52 | ||
|
|
e9415c1708 | ||
|
|
0114efc66f | ||
|
|
84e7847c16 | ||
|
|
3f935942ea | ||
|
|
8f45322b7b | ||
|
|
6e095c1085 | ||
|
|
acaceefc89 | ||
|
|
21c3a4bee8 | ||
|
|
a959af80b5 | ||
|
|
e0d7e7698e | ||
|
|
dbf9912c5a | ||
|
|
f9038b9180 | ||
|
|
506aacbd83 | ||
|
|
63e31d672b | ||
|
|
0d5d353e99 | ||
|
|
766ec6a6e4 | ||
|
|
872effb297 | ||
|
|
fb13b79a76 | ||
|
|
706daeb678 | ||
|
|
9d6af82f9c | ||
|
|
2bc37027dd | ||
|
|
e70b6b210e | ||
|
|
df8a36c695 | ||
|
|
702af4b1c8 | ||
|
|
cd15e11ce3 | ||
|
|
ca8b236ccb | ||
|
|
06b057f4a2 | ||
|
|
0158c9b434 | ||
|
|
368728d12b | ||
|
|
c1cab103aa | ||
|
|
d9da85638c | ||
|
|
a25989d213 | ||
|
|
6bfe4fe258 | ||
|
|
e7cbb8cc77 | ||
|
|
06354efa76 | ||
|
|
345f05b23f | ||
|
|
f0489cb918 | ||
|
|
d0b265985b | ||
|
|
82f0902fa4 | ||
|
|
1dcb44f81a | ||
|
|
9bbc421407 | ||
|
|
54f335946e | ||
|
|
d95d8278de | ||
|
|
c813339945 | ||
|
|
cc44bc9d7f | ||
|
|
4cd655fb36 | ||
|
|
5845c37672 | ||
|
|
9ae81779ff | ||
|
|
df4aa64883 | ||
|
|
70f94322ee | ||
|
|
c2ccb4edda | ||
|
|
f42fafb25f | ||
|
|
0bccf9d358 | ||
|
|
d3f105d9b9 | ||
|
|
5c240316ee | ||
|
|
2fde0c6d8c | ||
|
|
76d78f34ee | ||
|
|
3efb3a99e9 | ||
|
|
65dfc3b878 | ||
|
|
b820aad3fc | ||
|
|
09d4cae6f4 | ||
|
|
85308e52a7 | ||
|
|
4f42ae7efc | ||
|
|
7cc2eac79d | ||
|
|
e3487b4845 | ||
|
|
354b2facf7 | ||
|
|
0d381fd5e9 | ||
|
|
65b7d64e92 | ||
|
|
e79115d719 | ||
|
|
be4f49e96d | ||
|
|
6b24a71ad7 | ||
|
|
1ccacdc600 | ||
|
|
3ea71e1dfb | ||
|
|
d8e324a005 | ||
|
|
7122e878a5 | ||
|
|
c8848a9e76 | ||
|
|
84499dab35 | ||
|
|
e9c695f76a | ||
|
|
029ade8dd6 | ||
|
|
0ebb509205 | ||
|
|
9fa59310d2 | ||
|
|
1f8e6cc82b | ||
|
|
1a4ce643fc |
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,35 +1,50 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
name: Problem Report
|
||||
about: Create a Report to help us improve
|
||||
---
|
||||
|
||||
*Before creating a new issue please check that you have:*
|
||||
<!-- Thanks for reporting a problem for this project. READ THIS FIRST:
|
||||
|
||||
* *searched the existing [issues](https://github.com/emsesp/EMS-ESP32/issues) (both open and closed)*
|
||||
* *searched the [documentation help section](https://emsesp.github.io/docs)*
|
||||
Please DO NOT OPEN AN ISSUE if your EMS-ESP version is not the latest from the dev branch, please update your device before submitting your issue. Your problem might already be solved. The latest precompiled binaries of EMS-ESP can be downloaded from https://github.com/emsesp/EMS-ESP32/releases/tag/latest
|
||||
|
||||
*Completing this template will help developers and contributors to address the issue. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.*
|
||||
Please take a few minutes to complete the requested information below.
|
||||
|
||||
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.*
|
||||
-->
|
||||
|
||||
**Bug description**
|
||||
*A clear and concise description of what the bug is. Mention which EMS-ESP version you're using.*
|
||||
### PROBLEM DESCRIPTION
|
||||
|
||||
**Steps to reproduce**
|
||||
*Steps to reproduce the behavior.*
|
||||
_A clear and concise description of what the problem is._
|
||||
|
||||
**Expected behavior**
|
||||
*A clear and concise description of what you expected to happen.*
|
||||
### REQUESTED INFORMATION
|
||||
|
||||
**Screenshots**
|
||||
*If applicable, add screenshots to help explain your problem.*
|
||||
_Make sure your have performed every step and checked the applicable boxes before submitting your issue. Thank you!_
|
||||
|
||||
**Device information**
|
||||
*Copy-paste here the information as it is outputted by the device. You can get this information by from http://ems-esp.local/api/system*
|
||||
- [ ] Searched the problem in [issues](https://github.com/emsesp/EMS-ESP32/issues)
|
||||
- [ ] Searched the problem in [discussions](https://github.com/emsesp/EMS-ESP32/discussions)
|
||||
- [ ] Searched the problem in the [docs](https://emsesp.github.io/docs/Troubleshooting/)
|
||||
- [ ] Searched the problem in the [chat](https://discord.gg/3J3GgnzpyT)
|
||||
- [ ] Provide the output of http://ems-esp.local/api/system :
|
||||
|
||||
**Additional context**
|
||||
*Add any other context about the problem here.*
|
||||
```lua
|
||||
System information output here:
|
||||
|
||||
|
||||
```
|
||||
|
||||
### TO REPRODUCE
|
||||
|
||||
_Steps to reproduce the behavior:_
|
||||
|
||||
### EXPECTED BEHAVIOUR
|
||||
|
||||
_A clear and concise description of what you expected to happen._
|
||||
|
||||
### SCREENSHOTS
|
||||
|
||||
_If applicable, add screenshots to help explain your problem._
|
||||
|
||||
### ADDITIONAL CONTEXT
|
||||
|
||||
_Add any other context about the problem here._
|
||||
|
||||
**(Please, remember to close the issue when the problem has been addressed)**
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: EMS-ESP Docs
|
||||
url: https://emsesp.github.io/docs/
|
||||
about: All the information related to EMS-ESP.
|
||||
- name: EMS-ESP Discussions and Support
|
||||
url: https://github.com/emsesp/EMS-ESP32/discussions
|
||||
about: EMS-ESP usage Questions, Feature Requests and Projects.
|
||||
- name: EMS-ESP Users Chat
|
||||
url: https://discord.gg/3J3GgnzpyT
|
||||
about: Chat for feedback, questions and troubleshooting.
|
||||
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
*Before creating a new feature request please check that you have searched the existing [issues](https://github.com/emsesp/EMS-ESP32/issues) (both open and closed)*
|
||||
|
||||
*Completing this template will help developers and contributors evaluating the feature. If the information provided is not enough the issue will likely be closed.*
|
||||
|
||||
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the request then you can delete them.*
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
*A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]*
|
||||
|
||||
**Describe the solution you'd like**
|
||||
*A clear and concise description of what you want to happen.*
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
*A clear and concise description of any alternative solutions or features you've considered.*
|
||||
|
||||
**Additional context**
|
||||
*Add any other context or screenshots about the feature request here.*
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
name: Questions & Troubleshooting
|
||||
about: Anything not a bug or feature request
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
*Before creating a new issue please check that you have:*
|
||||
|
||||
* *searched the existing [issues](https://github.com/emsesp/EMS-ESP32/issues) (both open and closed)*
|
||||
* *searched the [documentation help section](https://emsesp.github.io/docs)*
|
||||
|
||||
*Completing this template will help developers and contributors help you. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.*
|
||||
|
||||
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.*
|
||||
|
||||
**Question**
|
||||
*A clear and concise description of what the problem/doubt is.*
|
||||
|
||||
**Screenshots**
|
||||
*If applicable, add screenshots to help explain your problem.*
|
||||
|
||||
**Device information**
|
||||
*Copy-paste here the information as it is outputted by the device. You can get this information from http://ems-esp.local/api/system*
|
||||
|
||||
**Additional context**
|
||||
*Add any other context about the problem here.*
|
||||
14
.github/workflows/pre_release.yml
vendored
@@ -14,9 +14,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
@@ -24,19 +24,19 @@ jobs:
|
||||
id: build_info
|
||||
run: |
|
||||
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/version.h | awk -F'"' '{print $2}'`
|
||||
echo "::set-output name=version::$version"
|
||||
echo "VERSION=$version" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
platformio upgrade
|
||||
platformio update
|
||||
|
||||
- name: Build WebUI
|
||||
run: |
|
||||
cd interface
|
||||
npm ci
|
||||
npx typesafe-i18n --no-watch
|
||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||
npm run build
|
||||
|
||||
- name: Build firmware
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
title: Development Build v${{steps.build_info.outputs.version}}
|
||||
title: Development Build v${{steps.build_info.outputs.VERSION}}
|
||||
automatic_release_tag: "latest"
|
||||
prerelease: true
|
||||
files: |
|
||||
|
||||
4
.github/workflows/tagged_release.yml
vendored
@@ -24,12 +24,14 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
platformio upgrade
|
||||
platformio update
|
||||
pio pkg update
|
||||
|
||||
- name: Build WebUI
|
||||
run: |
|
||||
cd interface
|
||||
npm ci
|
||||
npx typesafe-i18n --no-watch
|
||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||
npm run build
|
||||
|
||||
- name: Build firmware
|
||||
|
||||
12
.gitignore
vendored
@@ -13,7 +13,6 @@ debug.log
|
||||
# platformio
|
||||
.pio
|
||||
pio_local.ini
|
||||
/.VSCodeCounter
|
||||
|
||||
# OS specific
|
||||
.DS_Store
|
||||
@@ -31,7 +30,18 @@ test.sh
|
||||
scripts/__pycache__
|
||||
.temp
|
||||
|
||||
# i18n generated files
|
||||
interface/src/i18n/i18n-react.tsx
|
||||
interface/src/i18n/i18n-types.ts
|
||||
interface/src/i18n/i18n-util.ts
|
||||
interface/src/i18n/i18n-util.sync.ts
|
||||
interface/src/i18n/i18n-util.async.ts
|
||||
|
||||
# sonar
|
||||
.scannerwork/
|
||||
sonar/
|
||||
build_wrapper_output_directory/
|
||||
|
||||
# other build files
|
||||
dump_entities.csv
|
||||
dump_entities.xls*
|
||||
|
||||
81
CHANGELOG.md
@@ -5,19 +5,89 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
# [3.4.4]
|
||||
# [3.5.1] March 11 2023
|
||||
|
||||
## Added
|
||||
|
||||
- Detect old Tado thermostat, device-id 0x19, no entities
|
||||
- Some more HM200 entities [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
|
||||
- Add entity to force heating off (for systems without thermostat) [#951](https://github.com/emsesp/EMS-ESP32/issues/951)
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fix for new installations with filesystem not initializing
|
||||
- HA-discovery for analog sensor commands [#1035](https://github.com/emsesp/EMS-ESP32/issues/1035)
|
||||
|
||||
# [3.4.3]
|
||||
## Changed
|
||||
|
||||
- Use byte 0 for detection RC30 active heatingcircuit [#786](https://github.com/emsesp/EMS-ESP32/issues/786)
|
||||
- Write repeated selflowtemp if tx-queue is empty without verify [#954](https://github.com/emsesp/EMS-ESP32/issues/954)
|
||||
- HA discovery recreate after disconnect by device [#1067](https://github.com/emsesp/EMS-ESP32/issues/1067)
|
||||
- File upload: check flash size (overflow) instead of filesize
|
||||
|
||||
|
||||
# [3.5.0] February 6 2023
|
||||
|
||||
## **IMPORTANT! BREAKING CHANGES**
|
||||
|
||||
- When upgrading to v3.5 for the first time from v3.4 on a BBQKees Gateway board you will need to use the [EMS-EPS Flasher](https://github.com/emsesp/EMS-ESP-Flasher/releases) to correctly re-partition the flash. Make sure you backup the settings and customizations from the WebUI (System->Upload/Download) and restore after the upgrade.
|
||||
- Support for multiple EMS-ESPs [#759] has been added as an optional setting for MQTT. When enabled, which is now the default, all MQTT Discovery Entity IDs will include the MQTT base name and the shortname of the EMS-ESP device entity. For example what was previously `sensor.boiler_actual_boiler_temperature` will now become `sensor.ems_esp_boiler_boiltemp`. If you still want to use the old format and retain the history and script compatibility in Home Assistant then set this back to the old format.
|
||||
|
||||
## Added
|
||||
|
||||
- Translations in Web UI and all device entity names (DE, NL, SV, PL, NO, FR) [#22](https://github.com/emsesp/EMS-ESP32/issues/22)
|
||||
- Add support for Lolin C3 mini [#620](https://github.com/emsesp/EMS-ESP32/pull/620)
|
||||
- Add support for ESP32-S2 [#667](https://github.com/emsesp/EMS-ESP32/pull/667)
|
||||
- Add devices: Greenstar 30Ri boiler, Junkers FW500 thermostat, Buderus BC30 controller
|
||||
- Add program memory info
|
||||
- Add mqtt queue and connection infos
|
||||
- Adapt min/max if ems-value is not in this range
|
||||
- Add heat pump settings for inputs and limits [#600](https://github.com/emsesp/EMS-ESP32/issues/600)
|
||||
- Add hybrid heatpump [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
|
||||
- Add translated tags
|
||||
- Add min/max to customization table [#686](https://github.com/emsesp/EMS-ESP32/issues/686)
|
||||
- Add MD5 check [#637](https://github.com/emsesp/EMS-ESP32/issues/637)
|
||||
- Add more bus-ids [#673](https://github.com/emsesp/EMS-ESP32/issues/673)
|
||||
- Use HA connectivity device class for Status, added boot time [#751](https://github.com/emsesp/EMS-ESP32/issues/751)
|
||||
- Add commands for analog sensors outputs
|
||||
- Support for multiple EMS-ESPs with MQTT and HA [[#759](https://github.com/emsesp/EMS-ESP32/issues/759)]
|
||||
- Settings for heatpump silent mode and additional heater [[#802](https://github.com/emsesp/EMS-ESP32/issues/802)] [[#803](https://github.com/emsesp/EMS-ESP32/issues/803)]
|
||||
- Zone module MZ100 [#826](https://github.com/emsesp/EMS-ESP32/issues/826)
|
||||
- Default MQTT hostname is blank [#829](https://github.com/emsesp/EMS-ESP32/issues/829)
|
||||
- wwCurFlow for ems+ devices [#829](https://github.com/emsesp/EMS-ESP32/issues/829)
|
||||
- Add Rego 3000, TR120RF thermostats [#917](https://github.com/emsesp/EMS-ESP32/issues/917)
|
||||
- Add config for ESP32-S3
|
||||
- Add heatpump silent mode and other entities [#896](https://github.com/emsesp/EMS-ESP32/issues/896)
|
||||
- Allow reboot to other partition (factory or asymetric OTA)
|
||||
- Blacklist entities to remove from memory [#891](https://github.com/emsesp/EMS-ESP32/issues/891)
|
||||
- Add boiler pump operating mode [#944](https://github.com/emsesp/EMS-ESP32/issues/944)
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fix for new installations with filesystem not initializing
|
||||
- Factory Reset not working [#628](https://github.com/emsesp/EMS-ESP32/issues/628)
|
||||
- Valid 4 byte values [#820](https://github.com/emsesp/EMS-ESP32/issues/820)
|
||||
- Commands for multiple thermostats [#826](https://github.com/emsesp/EMS-ESP32/issues/826)
|
||||
- API queries for multiple devices [#865](https://github.com/emsesp/EMS-ESP32/issues/865)
|
||||
- Console crash when using call with command `hcx` only. [#841](https://github.com/emsesp/EMS-ESP32/issues/841)
|
||||
- `heatingPump2Mod` was wrong, changed to absBurnPow [[#908](https://github.com/emsesp/EMS-ESP32/issues/908)
|
||||
- Rounding of web input values
|
||||
- Analog sensor with single gpio number [#915](https://github.com/emsesp/EMS-ESP32/issues/915)
|
||||
- HA dallas and analog configs: remove/rebuild on change [#888](https://github.com/emsesp/EMS-ESP32/issues/888)
|
||||
- Modes and set seltemp for RC30 and RC20 [#932](https://github.com/emsesp/EMS-ESP32/issues/932)
|
||||
|
||||
# [3.4.2]
|
||||
## Changed
|
||||
|
||||
- Discovery in HomeAssistant don't work with custom base topic. [#596](https://github.com/emsesp/EMS-ESP32/issues/596) Base topic containing `/` are changed to `_`
|
||||
- RF room temperature sensor are shown as thermostat
|
||||
- Render mqtt float json values with trailing zero
|
||||
- Removed flash strings, to increase available heap memory
|
||||
- Reload page after restart button is pressed
|
||||
- Analog/dallas values command as list like ems-devices
|
||||
- Analog/dallas HA-entities based on id
|
||||
- MQTT Base is a mandatory field. Removed MQTT topic length from settings
|
||||
- HA duration class for time entities [[#822](https://github.com/emsesp/EMS-ESP32/issues/822)
|
||||
- AM200 alternative heatsource as class heatsource [[#857](https://github.com/emsesp/EMS-ESP32/issues/857)
|
||||
|
||||
# [3.4.2] September 18 2022
|
||||
|
||||
## Added
|
||||
|
||||
@@ -41,7 +111,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## Changed
|
||||
|
||||
- Shorten "friendly names" in Home Assistant [#555](https://github.com/emsesp/EMS-ESP32/issues/555)
|
||||
|
||||
- platformio 2.3.0 (IDF 4, Arduino 2)
|
||||
- remove master-thermostat, support multiple thermostats
|
||||
- merge up- and download in webui [#577](https://github.com/emsesp/EMS-ESP32/issues/577)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
19
Makefile
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# GNUMakefile for EMS-ESP
|
||||
# (c) 2020 Paul Derbyshire
|
||||
#
|
||||
|
||||
NUMJOBS=${NUMJOBS:-" -j4 "}
|
||||
MAKEFLAGS+="j "
|
||||
#----------------------------------------------------------------------
|
||||
@@ -17,23 +17,30 @@ MAKEFLAGS+="j "
|
||||
#TARGET := $(notdir $(CURDIR))
|
||||
TARGET := emsesp
|
||||
BUILD := build
|
||||
SOURCES := src src/* lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src lib/PButton
|
||||
INCLUDES := src lib_standalone lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/* src/devices
|
||||
SOURCES := src src/* lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src lib/PButton lib/semver
|
||||
INCLUDES := src lib_standalone lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/semver lib/* src/devices
|
||||
LIBRARIES :=
|
||||
|
||||
CPPCHECK = cppcheck
|
||||
# CHECKFLAGS = -q --force --std=c++17
|
||||
CHECKFLAGS = -q --force --std=c++11
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Languages Standard
|
||||
#----------------------------------------------------------------------
|
||||
# C_STANDARD := -std=c17
|
||||
# CXX_STANDARD := -std=c++17
|
||||
C_STANDARD := -std=c11
|
||||
CXX_STANDARD := -std=c++11
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Defined Symbols
|
||||
#----------------------------------------------------------------------
|
||||
DEFINES += -DFACTORY_WIFI_HOSTNAME=\"ems-esp\" -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0 -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_USE_SERIAL -DEMSESP_DEFAULT_BOARD_PROFILE=\"LOLIN\"
|
||||
DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0
|
||||
DEFINES += -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_USE_SERIAL
|
||||
DEFINES += $(ARGS)
|
||||
|
||||
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.5.0b11\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Sources & Files
|
||||
@@ -66,7 +73,7 @@ CXX := /usr/bin/g++
|
||||
# CXXFLAGS C++ Compiler Flags
|
||||
# LDFLAGS Linker Flags
|
||||
#----------------------------------------------------------------------
|
||||
CPPFLAGS += $(DEFINES) $(INCLUDE)
|
||||
CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
|
||||
CPPFLAGS += -ggdb
|
||||
CPPFLAGS += -g3
|
||||
CPPFLAGS += -Os
|
||||
@@ -114,6 +121,8 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
|
||||
# Targets
|
||||
#----------------------------------------------------------------------
|
||||
.PHONY: all
|
||||
.SILENT: $(OUTPUT)
|
||||
|
||||
all: $(OUTPUT)
|
||||
|
||||
$(OUTPUT): $(OBJS)
|
||||
|
||||
131
README.md
@@ -1,17 +1,5 @@
|
||||
# 
|
||||
|
||||
**EMS-ESP** is an open-source firmware for the Espressif ESP8266 and ESP32 microcontroller that communicates with **EMS** (Energy Management System) based equipment from manufacturers like Bosch, Buderus, Nefit, Junkers, Worcester and Sieger.
|
||||
|
||||
This project is the specifically for the ESP32. Compared with the previous ESP8266 (version 2) release it has the following enhancements:
|
||||
|
||||
- Ethernet Support
|
||||
- Pre-configured circuit board layouts
|
||||
- Supports writing EMS values directly from within Web UI
|
||||
- Mock API server for faster offline development and testing
|
||||
- Improved API and MQTT commands
|
||||
- Improvements to Dallas temperature sensors
|
||||
- Embedded log tracing in the Web UI
|
||||
|
||||
[](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md)
|
||||
[](https://github.com/emsesp/EMS-ESP32/commits/main)
|
||||
[](LICENSE)
|
||||
@@ -20,34 +8,54 @@ This project is the specifically for the ESP32. Compared with the previous ESP82
|
||||
[](https://github.com/emsesp/EMS-ESP32/releases)
|
||||
[](https://discord.gg/3J3GgnzpyT)
|
||||
|
||||
If you like **EMS-ESP**, please give it a star, or fork it and contribute!
|
||||
|
||||
[](https://github.com/emsesp/EMS-ESP32/stargazers)
|
||||
[](https://github.com/emsesp/EMS-ES32P/network)
|
||||
[](https://www.paypal.com/paypalme/prderbyshire/2)
|
||||
|
||||
Note, EMS-ESP requires a small hardware circuit that can convert the EMS bus data to be read by the microcontroller. These can be ordered at <https://bbqkees-electronics.nl> or contact the contributors that can provide the schematic and designs.
|
||||
**EMS-ESP** is an open-source firmware for the Espressif ESP32 microcontroller that communicates with **EMS** (Energy Management System) based equipment from manufacturers like Bosch, Buderus, Nefit, Junkers, Worcester and Sieger. It requires a small gateway circuit to interface with the EMS bus which can be purchased from <https://bbqkees-electronics.nl> or custom built.
|
||||
|
||||
<img src="media/gateway-integration.jpg" width=40%>
|
||||
## **Features**
|
||||
|
||||
---
|
||||
|
||||
# **Features**
|
||||
|
||||
- A multi-user secure web interface to change settings and monitor incoming data
|
||||
- A multi-user, multi-language secure web interface to change settings and monitor incoming data
|
||||
- A console, accessible via Serial and Telnet for more advanced monitoring
|
||||
- Native support for Home Assistant and Domoticz via [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/)
|
||||
- Native support for Home Assistant, Domoticz and openHAB via [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/)
|
||||
- Can run standalone as an independent WiFi Access Point or join an existing WiFi network
|
||||
- Easy first-time configuration via a web Captive Portal
|
||||
- Support for more than [100 EMS devices](https://emsesp.github.io/docs/#/Supported-EMS-Devices) (boilers, thermostats, solar modules, mixer modules, heat pumps, gateways)
|
||||
- Support for more than [110 EMS devices](https://emsesp.github.io/docs/#/Supported-EMS-Devices) (boilers, thermostats, solar modules, mixer modules, heat pumps, gateways, switches, heat sources)
|
||||
|
||||
## **Documentation**
|
||||
|
||||
For the complete documentation on how to install, configure and get support visit the [EMS-ESP Wiki](https://emsesp.github.io/docs).
|
||||
|
||||
## **Support**
|
||||
|
||||
To chat with the community reach out on our [Discord Server](https://discord.gg/3J3GgnzpyT).
|
||||
|
||||
If you like **EMS-ESP**, please give it a star, or fork it and contribute or offer a small donation!
|
||||
|
||||
## **Demo**
|
||||
|
||||
See a demo [here](https://ems-esp.derbyshire.nl). Log in with any username/password.
|
||||
For a live demo of the Web UI click [here](https://ems-esp.derbyshire.nl) and log in with any username/password.
|
||||
|
||||
# **Screenshots**
|
||||
## **Contributors ✨**
|
||||
|
||||
## Web Interface
|
||||
EMS-ESP is a project owned and maintained by [proddy](https://github.com/proddy) and [MichaelDvP](https://github.com/MichaelDvP).
|
||||
|
||||
## **Libraries used**
|
||||
|
||||
- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the framework that provides the core of the Web UI
|
||||
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries
|
||||
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON
|
||||
- [AsyncMqttClient](https://github.com/marvinroger/async-mqtt-client) for the MQTT client, with custom modifications from @bertmelis and @proddy
|
||||
- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance
|
||||
|
||||
## **License**
|
||||
|
||||
This program is licensed under GPL-3.0
|
||||
|
||||
## **Screenshots**
|
||||
|
||||
### Web Interface
|
||||
|
||||
| | |
|
||||
| ---------------------------------- | -------------------------------- |
|
||||
@@ -55,75 +63,10 @@ See a demo [here](https://ems-esp.derbyshire.nl). Log in with any username/passw
|
||||
| <img src="media/web_devices.png"> | <img src="media/web_mqtt.png"> |
|
||||
| <img src="media/web_edit.png"> | <img src="media/web_log.png"> |
|
||||
|
||||
## Telnet Console
|
||||
### Telnet Console
|
||||
|
||||
<img src="media/console.png" width=80% height=80%>
|
||||
<img src="media/console0.png" width=80% height=80%>
|
||||
|
||||
## In Home Assistant
|
||||
### In Home Assistant
|
||||
|
||||
<img src="media/ha_lovelace.png" width=80% height=80%>
|
||||
|
||||
# **Installing**
|
||||
|
||||
Refer to the [official documentation](https://emsesp.github.io/docs) to how to install the firmware and configure it. The documentation is being constantly updated as new features and settings are added.
|
||||
|
||||
You can choose to use an pre-built firmware image or compile the code yourself:
|
||||
|
||||
- [Uploading a pre-built firmware build](https://emsesp.github.io/docs/#/Uploading-firmware)
|
||||
- [Building the firmware from source code and flashing manually](https://emsesp.github.io/docs/#/Building-firmware)
|
||||
|
||||
# **Support Information**
|
||||
|
||||
If you're looking for support on **EMS-ESP** there are some options available:
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Official EMS-ESP Documentation](https://emsesp.github.io/docs): For information on how to build and upload the firmware
|
||||
- [FAQ and Troubleshooting](https://emsesp.github.io/docs/#/Troubleshooting): For information on common problems and solutions. See also [BBQKees's wiki](https://bbqkees-electronics.nl/wiki/gateway/troubleshooting.html)
|
||||
|
||||
## Support Community
|
||||
|
||||
- [Discord Server](https://discord.gg/3J3GgnzpyT): For support, troubleshooting and general questions. You have better chances to get fast answers from members of the community
|
||||
- [Search in Issues](https://github.com/emsesp/EMS-ESP32/issues): You might find an answer to your question by searching current or closed issues
|
||||
|
||||
## Developer's Community
|
||||
|
||||
- [Bug Report](https://github.com/emsesp/EMS-ESP32/issues/new?template=bug_report.md): For reporting Bugs
|
||||
- [Feature Request](https://github.com/emsesp/EMS-ESP32/issues/new?template=feature_request.md): For requesting features/functions
|
||||
- [Troubleshooting](https://github.com/emsesp/EMS-ESP32/issues/new?template=questions---troubleshooting.md): As a last resort, you can open new _Troubleshooting & Question_ issue on GitHub if the solution could not be found using the other channels. Just remember: the more info you provide the more chances you'll have to get an accurate answer
|
||||
|
||||
# **Contributors ✨**
|
||||
|
||||
EMS-ESP is a project originally created and owned by [proddy](https://github.com/proddy). Key contributors are:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/MichaelDvP"><img src="https://avatars.githubusercontent.com/u/59284019?v=3?s=100" width="100px;" alt=""/><br /><sub><b>MichaelDvP</b></sub></a><br /></a> <a href="https://github.com/emsesp/EMS-ESP/commits?author=MichaelDvP" title="v2 Commits">v2</a>
|
||||
<a href="https://github.com/emsesp/EMS-ESP32/commits?author=MichaelDvP" title="v3 Commits">v3</a>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
You can also contribute to EMS-ESP by
|
||||
|
||||
- providing Pull Requests (Features, Fixes, suggestions)
|
||||
- testing new released features and report issues on your EMS equipment
|
||||
- contributing to missing [Documentation](https://emsesp.github.io/docs)
|
||||
|
||||
# **Libraries used**
|
||||
|
||||
- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the framework that provides the core of the Web UI
|
||||
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries
|
||||
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for JSON
|
||||
- [AsyncMqttClient](https://github.com/marvinroger/async-mqtt-client) for the MQTT client, with custom modifications from @bertmelis and @proddy
|
||||
- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance
|
||||
|
||||
# **License**
|
||||
|
||||
This program is licensed under GPL-3.0
|
||||
|
||||
6
esp32_partition_16M.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, , 0x2000,
|
||||
app0, app, ota_0, , 0x7F0000,
|
||||
app1, app, ota_1, , 0x7F0000,
|
||||
spiffs, data, spiffs, , 64K,
|
||||
|
6
esp32_partition_4M.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, , 0x2000,
|
||||
app0, app, ota_0, , 0x1F0000,
|
||||
app1, app, ota_1, , 0x1F0000,
|
||||
spiffs, data, spiffs, , 64K,
|
||||
|
@@ -1,6 +0,0 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xE000, 0x2000,
|
||||
app0, app, ota_0, 0x10000, 0x1F0000,
|
||||
app1, app, ota_1, 0x200000, 0x1F0000,
|
||||
spiffs, data, spiffs, 0x3F0000, 0x10000,
|
||||
|
@@ -7,8 +7,8 @@ build_flags =
|
||||
|
||||
; Access point settings
|
||||
-D FACTORY_AP_PROVISION_MODE=AP_MODE_DISCONNECTED
|
||||
-D FACTORY_AP_SSID=\"ems-esp\" ; 1-64 characters
|
||||
-D FACTORY_AP_PASSWORD=\"ems-esp-neo\" ; 8-64 characters
|
||||
-D FACTORY_AP_SSID=\"ems-esp\"
|
||||
-D FACTORY_AP_PASSWORD=\"ems-esp-neo\"
|
||||
-D FACTORY_AP_LOCAL_IP=\"192.168.4.1\"
|
||||
-D FACTORY_AP_GATEWAY_IP=\"192.168.4.1\"
|
||||
-D FACTORY_AP_SUBNET_MASK=\"255.255.255.0\"
|
||||
@@ -28,11 +28,11 @@ build_flags =
|
||||
; OTA settings
|
||||
-D FACTORY_OTA_PORT=8266
|
||||
-D FACTORY_OTA_PASSWORD=\"ems-esp-neo\"
|
||||
-D FACTORY_OTA_ENABLED=true
|
||||
-D FACTORY_OTA_ENABLED=false
|
||||
|
||||
; MQTT settings
|
||||
-D FACTORY_MQTT_ENABLED=false
|
||||
-D FACTORY_MQTT_HOST=\"test.mosquitto.org\"
|
||||
-D FACTORY_MQTT_HOST=\"\"
|
||||
-D FACTORY_MQTT_PORT=1883
|
||||
-D FACTORY_MQTT_USERNAME=\"\"
|
||||
-D FACTORY_MQTT_PASSWORD=\"\"
|
||||
|
||||
5
interface/.typesafe-i18n.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"adapter": "react",
|
||||
"baseLocale": "pl",
|
||||
"$schema": "https://unpkg.com/typesafe-i18n@5.24.2/schema/typesafe-i18n.json"
|
||||
}
|
||||
16018
interface/package-lock.json
generated
@@ -1,36 +1,35 @@
|
||||
{
|
||||
"name": "EMS-ESP",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"private": true,
|
||||
"proxy": "http://localhost:3080",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.4",
|
||||
"@emotion/styled": "^11.10.4",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@msgpack/msgpack": "^2.8.0",
|
||||
"@mui/icons-material": "^5.10.3",
|
||||
"@mui/material": "^5.10.5",
|
||||
"@table-library/react-table-library": "4.0.18",
|
||||
"@types/lodash": "^4.14.185",
|
||||
"@types/node": "^18.7.18",
|
||||
"@types/react": "^18.0.20",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@mui/icons-material": "^5.11.0",
|
||||
"@mui/material": "^5.11.7",
|
||||
"@table-library/react-table-library": "4.0.24",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^18.11.19",
|
||||
"@types/react": "^18.0.27",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"async-validator": "^4.2.5",
|
||||
"axios": "^0.27.2",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"axios": "^1.3.2",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"notistack": "^2.0.5",
|
||||
"parse-ms": "^3.0.0",
|
||||
"notistack": "^2.0.8",
|
||||
"react": "^18.2.0",
|
||||
"react-app-rewired": "^2.2.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.2",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-router-dom": "^6.4.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"sockette": "^2.0.6",
|
||||
"typescript": "^4.8.3"
|
||||
"typesafe-i18n": "^5.24.0",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-app-rewired start",
|
||||
@@ -41,8 +40,9 @@
|
||||
"build-hosted": "env-cmd -f .env.hosted npm run build",
|
||||
"build-localhost": "PUBLIC_URL=/ react-app-rewired build",
|
||||
"mock-api": "nodemon --watch ../mock-api ../mock-api/server.js",
|
||||
"standalone": "npm-run-all -p start mock-api",
|
||||
"lint": "eslint . --ext .ts,.tsx"
|
||||
"standalone": "npm-run-all -p start typesafe-i18n mock-api",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
"typesafe-i18n": "typesafe-i18n"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -78,7 +78,7 @@
|
||||
"max-len": [
|
||||
1,
|
||||
{
|
||||
"code": 200
|
||||
"code": 220
|
||||
}
|
||||
],
|
||||
"arrow-parens": 1
|
||||
@@ -98,6 +98,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.20",
|
||||
"npm-run-all": "^4.1.5"
|
||||
"npm-run-all": "^4.1.5",
|
||||
"http-proxy-middleware": "^2.0.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
|
||||
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+0131, U+0141-0144, U+0152-0153, U+015A-015B, U+0179-017C,
|
||||
U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
|
||||
U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -19,6 +20,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/md.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
|
||||
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+0131, U+0141-0144, U+0152-0153, U+015A-015B, U+0179-017C,
|
||||
U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
|
||||
U+FFFD;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, createRef, createContext, useContext, RefObject } from 'react';
|
||||
import { FC, createRef, createContext, useContext, useEffect, useState, RefObject } from 'react';
|
||||
import { SnackbarProvider } from 'notistack';
|
||||
|
||||
import { IconButton } from '@mui/material';
|
||||
@@ -9,6 +9,13 @@ import { FeaturesLoader } from './contexts/features';
|
||||
import CustomTheme from './CustomTheme';
|
||||
import AppRouting from './AppRouting';
|
||||
|
||||
import { localStorageDetector } from 'typesafe-i18n/detectors';
|
||||
import TypesafeI18n from './i18n/i18n-react';
|
||||
import { detectLocale } from './i18n/i18n-util';
|
||||
import { loadLocaleAsync } from './i18n/i18n-util.async';
|
||||
|
||||
const detectedLocale = detectLocale(localStorageDetector);
|
||||
|
||||
const App: FC = () => {
|
||||
const notistackRef: RefObject<any> = createRef();
|
||||
|
||||
@@ -20,8 +27,17 @@ const App: FC = () => {
|
||||
|
||||
const colorMode = useContext(ColorModeContext);
|
||||
|
||||
const [wasLoaded, setWasLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true));
|
||||
}, []);
|
||||
|
||||
if (!wasLoaded) return null;
|
||||
|
||||
return (
|
||||
<ColorModeContext.Provider value={colorMode}>
|
||||
<TypesafeI18n locale={detectedLocale}>
|
||||
<CustomTheme>
|
||||
<SnackbarProvider
|
||||
maxSnack={3}
|
||||
@@ -38,6 +54,7 @@ const App: FC = () => {
|
||||
</FeaturesLoader>
|
||||
</SnackbarProvider>
|
||||
</CustomTheme>
|
||||
</TypesafeI18n>
|
||||
</ColorModeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@ import { FC, useContext, useEffect } from 'react';
|
||||
import { Navigate, Routes, Route, useLocation } from 'react-router-dom';
|
||||
import { useSnackbar, VariantType } from 'notistack';
|
||||
|
||||
import { useI18nContext } from './i18n/i18n-react';
|
||||
|
||||
import { Authentication, AuthenticationContext } from './contexts/authentication';
|
||||
import { FeaturesContext } from './contexts/features';
|
||||
import { RequireAuthenticated, RequireUnauthenticated } from './components';
|
||||
@@ -41,13 +43,14 @@ export const RemoveTrailingSlashes = () => {
|
||||
|
||||
const AppRouting: FC = () => {
|
||||
const { features } = useContext(FeaturesContext);
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
<Authentication>
|
||||
<RemoveTrailingSlashes />
|
||||
<Routes>
|
||||
<Route path="/unauthorized" element={<RootRedirect message="Please sign in to continue" signOut />} />
|
||||
<Route path="/fileUpdated" element={<RootRedirect message="Upload successful" variant="success" />} />
|
||||
<Route path="/unauthorized" element={<RootRedirect message={LL.PLEASE_SIGNIN()} signOut />} />
|
||||
<Route path="/fileUpdated" element={<RootRedirect message={LL.UPLOAD_SUCCESSFUL()} variant="success" />} />
|
||||
{features.security && (
|
||||
<Route
|
||||
path="/"
|
||||
|
||||
@@ -2,20 +2,30 @@ import { FC, useContext, useState } from 'react';
|
||||
import { ValidateFieldsError } from 'async-validator';
|
||||
import { useSnackbar } from 'notistack';
|
||||
|
||||
import { Box, Fab, Paper, Typography } from '@mui/material';
|
||||
import { Box, Fab, Paper, Typography, Button } from '@mui/material';
|
||||
import ForwardIcon from '@mui/icons-material/Forward';
|
||||
|
||||
import * as AuthenticationApi from './api/authentication';
|
||||
import { PROJECT_NAME } from './api/env';
|
||||
import { AuthenticationContext } from './contexts/authentication';
|
||||
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
import { extractErrorMessage, onEnterCallback, updateValue } from './utils';
|
||||
import { SignInRequest } from './types';
|
||||
import { ValidatedTextField } from './components';
|
||||
import { SIGN_IN_REQUEST_VALIDATOR, validate } from './validators';
|
||||
|
||||
import { I18nContext } from './i18n/i18n-react';
|
||||
import type { Locales } from './i18n/i18n-types';
|
||||
import { loadLocaleAsync } from './i18n/i18n-util.async';
|
||||
|
||||
import { ReactComponent as NLflag } from './i18n/NL.svg';
|
||||
import { ReactComponent as DEflag } from './i18n/DE.svg';
|
||||
import { ReactComponent as GBflag } from './i18n/GB.svg';
|
||||
import { ReactComponent as SVflag } from './i18n/SV.svg';
|
||||
import { ReactComponent as PLflag } from './i18n/PL.svg';
|
||||
import { ReactComponent as NOflag } from './i18n/NO.svg';
|
||||
import { ReactComponent as FRflag } from './i18n/FR.svg';
|
||||
|
||||
const SignIn: FC = () => {
|
||||
const authenticationContext = useContext(AuthenticationContext);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
@@ -31,6 +41,9 @@ const SignIn: FC = () => {
|
||||
|
||||
const validateAndSignIn = async () => {
|
||||
setProcessing(true);
|
||||
SIGN_IN_REQUEST_VALIDATOR.messages({
|
||||
required: LL.IS_REQUIRED('%s')
|
||||
});
|
||||
try {
|
||||
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
|
||||
signIn();
|
||||
@@ -44,13 +57,13 @@ const SignIn: FC = () => {
|
||||
try {
|
||||
const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest);
|
||||
authenticationContext.signIn(loginResponse.access_token);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
if (error.response?.status === 401) {
|
||||
enqueueSnackbar('Invalid login details', { variant: 'warning' });
|
||||
enqueueSnackbar(LL.INVALID_LOGIN(), { variant: 'warning' });
|
||||
}
|
||||
} else {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Unexpected error, please try again'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.ERROR()), { variant: 'error' });
|
||||
}
|
||||
setProcessing(false);
|
||||
}
|
||||
@@ -58,6 +71,14 @@ const SignIn: FC = () => {
|
||||
|
||||
const submitOnEnter = onEnterCallback(signIn);
|
||||
|
||||
const { LL, setLocale, locale } = useContext(I18nContext);
|
||||
|
||||
const selectLocale = async (loc: Locales) => {
|
||||
localStorage.setItem('lang', loc);
|
||||
await loadLocaleAsync(loc);
|
||||
setLocale(loc);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
@@ -81,11 +102,49 @@ const SignIn: FC = () => {
|
||||
})}
|
||||
>
|
||||
<Typography variant="h4">{PROJECT_NAME}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
'& button, & a, & .MuiCard-root': {
|
||||
mt: 0.5,
|
||||
mx: 0.5
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button size="small" variant={locale === 'en' ? 'contained' : 'outlined'} onClick={() => selectLocale('en')}>
|
||||
<GBflag style={{ width: 24 }} />
|
||||
EN
|
||||
</Button>
|
||||
<Button size="small" variant={locale === 'de' ? 'contained' : 'outlined'} onClick={() => selectLocale('de')}>
|
||||
<DEflag style={{ width: 24 }} />
|
||||
DE
|
||||
</Button>
|
||||
<Button size="small" variant={locale === 'fr' ? 'contained' : 'outlined'} onClick={() => selectLocale('fr')}>
|
||||
<FRflag style={{ width: 24 }} />
|
||||
FR
|
||||
</Button>
|
||||
<Button size="small" variant={locale === 'nl' ? 'contained' : 'outlined'} onClick={() => selectLocale('nl')}>
|
||||
<NLflag style={{ width: 24 }} />
|
||||
NL
|
||||
</Button>
|
||||
<Button size="small" variant={locale === 'no' ? 'contained' : 'outlined'} onClick={() => selectLocale('no')}>
|
||||
<NOflag style={{ width: 24 }} />
|
||||
NO
|
||||
</Button>
|
||||
<Button size="small" variant={locale === 'pl' ? 'contained' : 'outlined'} onClick={() => selectLocale('pl')}>
|
||||
<PLflag style={{ width: 24 }} />
|
||||
PL
|
||||
</Button>
|
||||
<Button size="small" variant={locale === 'sv' ? 'contained' : 'outlined'} onClick={() => selectLocale('sv')}>
|
||||
<SVflag style={{ width: 24 }} />
|
||||
SV
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
disabled={processing}
|
||||
name="username"
|
||||
label="Username"
|
||||
label={LL.USERNAME(0)}
|
||||
value={signInRequest.username}
|
||||
onChange={updateLoginRequestValue}
|
||||
margin="normal"
|
||||
@@ -97,7 +156,7 @@ const SignIn: FC = () => {
|
||||
disabled={processing}
|
||||
type="password"
|
||||
name="password"
|
||||
label="Password"
|
||||
label={LL.PASSWORD()}
|
||||
value={signInRequest.password}
|
||||
onChange={updateLoginRequestValue}
|
||||
onKeyDown={submitOnEnter}
|
||||
@@ -107,7 +166,7 @@ const SignIn: FC = () => {
|
||||
/>
|
||||
<Fab variant="extended" color="primary" sx={{ mt: 2 }} onClick={validateAndSignIn} disabled={processing}>
|
||||
<ForwardIcon sx={{ mr: 1 }} />
|
||||
Sign In
|
||||
{LL.SIGN_IN()}
|
||||
</Fab>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import axios, { AxiosPromise, CancelToken } from 'axios';
|
||||
import axios, { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios';
|
||||
|
||||
import { decode } from '@msgpack/msgpack';
|
||||
|
||||
@@ -89,7 +89,7 @@ function calculateEventSourceRoot(endpointPath: string) {
|
||||
|
||||
export interface FileUploadConfig {
|
||||
cancelToken?: CancelToken;
|
||||
onUploadProgress?: (progressEvent: ProgressEvent) => void;
|
||||
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
|
||||
}
|
||||
|
||||
export const startUploadFile = (url: string, file: File, config?: FileUploadConfig): AxiosPromise<void> => {
|
||||
|
||||
@@ -11,6 +11,6 @@ export function readMqttSettings(): AxiosPromise<MqttSettings> {
|
||||
return AXIOS.get('/mqttSettings');
|
||||
}
|
||||
|
||||
export function updateMqttSettings(ntpSettings: MqttSettings): AxiosPromise<MqttSettings> {
|
||||
return AXIOS.post('/mqttSettings', ntpSettings);
|
||||
export function updateMqttSettings(mqttSettings: MqttSettings): AxiosPromise<MqttSettings> {
|
||||
return AXIOS.post('/mqttSettings', mqttSettings);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ export function restart(): AxiosPromise<void> {
|
||||
return AXIOS.post('/restart');
|
||||
}
|
||||
|
||||
export function partition(): AxiosPromise<void> {
|
||||
return AXIOS.post('/partition');
|
||||
}
|
||||
|
||||
export function factoryReset(): AxiosPromise<void> {
|
||||
return AXIOS.post('/factoryReset');
|
||||
}
|
||||
@@ -38,4 +42,3 @@ export function updateLogSettings(logSettings: LogSettings): AxiosPromise<LogSet
|
||||
export function readLogEntries(): AxiosPromise<LogEntries> {
|
||||
return AXIOS_BIN.get('/fetchLog');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,36 @@
|
||||
import { FC, useState, useContext } from 'react';
|
||||
import { FC, useState, useContext, ChangeEventHandler } from 'react';
|
||||
|
||||
import { Box, Button, Divider, IconButton, Popover, Typography, Avatar, styled, TypographyProps } from '@mui/material';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
IconButton,
|
||||
Popover,
|
||||
Typography,
|
||||
Avatar,
|
||||
styled,
|
||||
TypographyProps,
|
||||
MenuItem,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
||||
|
||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import { I18nContext } from '../../i18n/i18n-react';
|
||||
import type { Locales } from '../../i18n/i18n-types';
|
||||
import { loadLocaleAsync } from '../../i18n/i18n-util.async';
|
||||
|
||||
import { ReactComponent as NLflag } from '../../i18n/NL.svg';
|
||||
import { ReactComponent as DEflag } from '../../i18n/DE.svg';
|
||||
import { ReactComponent as GBflag } from '../../i18n/GB.svg';
|
||||
import { ReactComponent as SVflag } from '../../i18n/SV.svg';
|
||||
import { ReactComponent as PLflag } from '../../i18n/PL.svg';
|
||||
import { ReactComponent as NOflag } from '../../i18n/NO.svg';
|
||||
import { ReactComponent as FRflag } from '../../i18n/FR.svg';
|
||||
|
||||
const ItemTypography = styled(Typography)<TypographyProps>({
|
||||
maxWidth: '250px',
|
||||
whiteSpace: 'nowrap',
|
||||
@@ -23,6 +47,15 @@ const LayoutAuthMenu: FC = () => {
|
||||
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);
|
||||
};
|
||||
@@ -32,7 +65,53 @@ const LayoutAuthMenu: FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton id="open-auth-menu" sx={{ padding: 0 }} aria-describedby={id} color="inherit" onClick={handleClick}>
|
||||
<TextField
|
||||
name="locale"
|
||||
InputProps={{ style: { fontSize: 10 } }}
|
||||
variant="outlined"
|
||||
value={locale}
|
||||
onChange={onLocaleSelected}
|
||||
size="small"
|
||||
select
|
||||
>
|
||||
<MenuItem key="en" value="en">
|
||||
<GBflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
EN
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem key="de" value="de">
|
||||
<DEflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
DE
|
||||
</MenuItem>
|
||||
<MenuItem key="fr" value="fr">
|
||||
<FRflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
FR
|
||||
</MenuItem>
|
||||
<MenuItem key="nl" value="nl">
|
||||
<NLflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
NL
|
||||
</MenuItem>
|
||||
<MenuItem key="no" value="no">
|
||||
<NOflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
NO
|
||||
</MenuItem>
|
||||
<MenuItem key="pl" value="pl">
|
||||
<PLflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
PL
|
||||
</MenuItem>
|
||||
<MenuItem key="sv" value="sv">
|
||||
<SVflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
SV
|
||||
</MenuItem>
|
||||
</TextField>
|
||||
|
||||
<IconButton
|
||||
id="open-auth-menu"
|
||||
sx={{ ml: 1, padding: 0 }}
|
||||
aria-describedby={id}
|
||||
color="inherit"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<AccountCircleIcon />
|
||||
</IconButton>
|
||||
<Popover
|
||||
@@ -56,13 +135,15 @@ const LayoutAuthMenu: FC = () => {
|
||||
</Avatar>
|
||||
<Box pl={2}>
|
||||
<ItemTypography variant="h6">{me.username}</ItemTypography>
|
||||
<ItemTypography variant="body1">{me.admin ? 'Admin User' : 'Guest User'}</ItemTypography>
|
||||
<ItemTypography variant="body1">
|
||||
{me.admin ? LL.ADMIN() : LL.GUEST()} {LL.USER(2)}
|
||||
</ItemTypography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box p={1.5}>
|
||||
<Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}>
|
||||
Sign Out
|
||||
{LL.SIGN_OUT()}
|
||||
</Button>
|
||||
</Box>
|
||||
</Popover>
|
||||
|
||||
@@ -15,9 +15,12 @@ import ProjectMenu from '../../project/ProjectMenu';
|
||||
import LayoutMenuItem from './LayoutMenuItem';
|
||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const LayoutMenu: FC = () => {
|
||||
const { features } = useContext(FeaturesContext);
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -28,12 +31,17 @@ const LayoutMenu: FC = () => {
|
||||
</List>
|
||||
)}
|
||||
<List disablePadding component="nav">
|
||||
<LayoutMenuItem icon={SettingsEthernetIcon} label="Network Connection" to="/network" />
|
||||
<LayoutMenuItem icon={SettingsInputAntennaIcon} label="Access Point" to="/ap" />
|
||||
{features.ntp && <LayoutMenuItem icon={AccessTimeIcon} label="Network Time" to="/ntp" />}
|
||||
<LayoutMenuItem icon={SettingsEthernetIcon} label={LL.NETWORK(0)} to="/network" />
|
||||
<LayoutMenuItem icon={SettingsInputAntennaIcon} label={LL.ACCESS_POINT(0)} to="/ap" />
|
||||
{features.ntp && <LayoutMenuItem icon={AccessTimeIcon} label="NTP" to="/ntp" />}
|
||||
{features.mqtt && <LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" />}
|
||||
<LayoutMenuItem icon={LockIcon} label="Security" to="/security" disabled={!authenticatedContext.me.admin} />
|
||||
<LayoutMenuItem icon={SettingsIcon} label="System" to="/system" />
|
||||
<LayoutMenuItem
|
||||
icon={LockIcon}
|
||||
label={LL.SECURITY(0)}
|
||||
to="/security"
|
||||
disabled={!authenticatedContext.me.admin}
|
||||
/>
|
||||
<LayoutMenuItem icon={SettingsIcon} label={LL.SYSTEM(0)} to="/system" />
|
||||
</List>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,8 @@ import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
|
||||
import { MessageBox } from '..';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface FormLoaderProps {
|
||||
message?: string;
|
||||
errorMessage?: string;
|
||||
@@ -12,12 +14,14 @@ interface FormLoaderProps {
|
||||
}
|
||||
|
||||
const FormLoader: FC<FormLoaderProps> = ({ errorMessage, onRetry, message = 'Loading…' }) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
if (errorMessage) {
|
||||
return (
|
||||
<MessageBox my={2} level="error" message={errorMessage}>
|
||||
{onRetry && (
|
||||
<Button startIcon={<RefreshIcon />} variant="contained" color="error" onClick={onRetry}>
|
||||
Retry
|
||||
{LL.RETRY()}
|
||||
</Button>
|
||||
)}
|
||||
</MessageBox>
|
||||
|
||||
@@ -2,11 +2,16 @@ import { FC } from 'react';
|
||||
|
||||
import { CircularProgress, Box, Typography, Theme } from '@mui/material';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface LoadingSpinnerProps {
|
||||
height?: number | string;
|
||||
}
|
||||
|
||||
const LoadingSpinner: FC<LoadingSpinnerProps> = ({ height = '100%' }) => (
|
||||
const LoadingSpinner: FC<LoadingSpinnerProps> = ({ height = '100%' }) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column" padding={2} height={height}>
|
||||
<CircularProgress
|
||||
sx={(theme: Theme) => ({
|
||||
@@ -16,9 +21,10 @@ const LoadingSpinner: FC<LoadingSpinnerProps> = ({ height = '100%' }) => (
|
||||
size={100}
|
||||
/>
|
||||
<Typography variant="h4" color="textSecondary">
|
||||
Loading…
|
||||
{LL.LOADING()}…
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingSpinner;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { FC, Fragment } from 'react';
|
||||
import { useDropzone, DropzoneState } from 'react-dropzone';
|
||||
|
||||
import { AxiosProgressEvent } from 'axios';
|
||||
|
||||
import { Box, Button, LinearProgress, Theme, Typography, useTheme } from '@mui/material';
|
||||
|
||||
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
|
||||
const progressPercentage = (progress: ProgressEvent) => Math.round((progress.loaded * 100) / progress.total);
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const getBorderColor = (theme: Theme, props: DropzoneState) => {
|
||||
if (props.isDragAccept) {
|
||||
@@ -25,7 +27,7 @@ export interface SingleUploadProps {
|
||||
onDrop: (acceptedFiles: File[]) => void;
|
||||
onCancel: () => void;
|
||||
uploading: boolean;
|
||||
progress?: ProgressEvent;
|
||||
progress?: AxiosProgressEvent;
|
||||
}
|
||||
|
||||
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, progress }) => {
|
||||
@@ -33,7 +35,8 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
||||
onDrop,
|
||||
accept: {
|
||||
'application/octet-stream': ['.bin'],
|
||||
'application/json': ['.json']
|
||||
'application/json': ['.json'],
|
||||
'text/plain': ['.md5']
|
||||
},
|
||||
disabled: uploading,
|
||||
multiple: false
|
||||
@@ -41,14 +44,16 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
||||
const { getRootProps, getInputProps } = dropzoneState;
|
||||
const theme = useTheme();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const progressText = () => {
|
||||
if (uploading) {
|
||||
if (progress?.lengthComputable) {
|
||||
return `Uploading: ${progressPercentage(progress)}%`;
|
||||
if (progress?.total) {
|
||||
return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%';
|
||||
}
|
||||
return 'Uploading\u2026';
|
||||
return LL.UPLOADING() + `\u2026`;
|
||||
}
|
||||
return 'Drop file or click here';
|
||||
return LL.UPLOAD_DROP_TEXT();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -60,7 +65,7 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
||||
borderWidth: 2,
|
||||
borderRadius: 2,
|
||||
borderStyle: 'dashed',
|
||||
color: theme.palette.grey[700],
|
||||
color: theme.palette.grey[400],
|
||||
transition: 'border .24s ease-in-out',
|
||||
width: '100%',
|
||||
cursor: uploading ? 'default' : 'pointer',
|
||||
@@ -76,12 +81,12 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
||||
<Fragment>
|
||||
<Box width="100%" p={2}>
|
||||
<LinearProgress
|
||||
variant={!progress || progress.lengthComputable ? 'determinate' : 'indeterminate'}
|
||||
value={!progress ? 0 : progress.lengthComputable ? progressPercentage(progress) : 0}
|
||||
variant={!progress || progress.total ? 'determinate' : 'indeterminate'}
|
||||
value={!progress ? 0 : progress.total ? Math.round((progress.loaded * 100) / progress.total) : 0}
|
||||
/>
|
||||
</Box>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import axios, { AxiosPromise, CancelTokenSource } from 'axios';
|
||||
import axios, { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios';
|
||||
import { useSnackbar } from 'notistack';
|
||||
|
||||
import { extractErrorMessage } from '../../utils';
|
||||
import { FileUploadConfig } from '../../api/endpoints';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface MediaUploadOptions {
|
||||
upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
||||
}
|
||||
|
||||
const useFileUpload = ({ upload }: MediaUploadOptions) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [uploading, setUploading] = useState<boolean>(false);
|
||||
const [uploadProgress, setUploadProgress] = useState<ProgressEvent>();
|
||||
const [md5, setMd5] = useState<string>('');
|
||||
const [uploadProgress, setUploadProgress] = useState<AxiosProgressEvent>();
|
||||
const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>();
|
||||
|
||||
const resetUploadingStates = () => {
|
||||
setUploading(false);
|
||||
setUploadProgress(undefined);
|
||||
setUploadCancelToken(undefined);
|
||||
setMd5('');
|
||||
};
|
||||
|
||||
const cancelUpload = useCallback(() => {
|
||||
@@ -37,23 +43,28 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
|
||||
const cancelToken = axios.CancelToken.source();
|
||||
setUploadCancelToken(cancelToken);
|
||||
setUploading(true);
|
||||
await upload(images[0], {
|
||||
const response = await upload(images[0], {
|
||||
onUploadProgress: setUploadProgress,
|
||||
cancelToken: cancelToken.token
|
||||
});
|
||||
resetUploadingStates();
|
||||
enqueueSnackbar('File uploaded', { variant: 'success' });
|
||||
} catch (error: unknown) {
|
||||
if (response.status === 200) {
|
||||
enqueueSnackbar(LL.UPLOAD() + ' ' + LL.SUCCESSFUL(), { variant: 'success' });
|
||||
} else if (response.status === 201) {
|
||||
setMd5(String(response.data));
|
||||
enqueueSnackbar(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL(), { variant: 'success' });
|
||||
}
|
||||
} catch (error) {
|
||||
if (axios.isCancel(error)) {
|
||||
enqueueSnackbar('Upload aborted', { variant: 'warning' });
|
||||
enqueueSnackbar(LL.UPLOAD() + ' ' + LL.ABORTED(), { variant: 'warning' });
|
||||
} else {
|
||||
resetUploadingStates();
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Upload failed'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.UPLOAD() + ' ' + LL.FAILED()), { variant: 'error' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return [uploadFile, cancelUpload, uploading, uploadProgress] as const;
|
||||
return [uploadFile, cancelUpload, uploading, uploadProgress, md5] as const;
|
||||
};
|
||||
|
||||
export default useFileUpload;
|
||||
|
||||
@@ -2,6 +2,8 @@ import { FC, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
import * as AuthenticationApi from '../../api/authentication';
|
||||
import { ACCESS_TOKEN } from '../../api/endpoints';
|
||||
import { RequiredChildrenProps } from '../../utils';
|
||||
@@ -12,6 +14,8 @@ import { AuthenticationContext } from './context';
|
||||
|
||||
const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
const { features } = useContext(FeaturesContext);
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
@@ -23,8 +27,8 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
AuthenticationApi.getStorage().setItem(ACCESS_TOKEN, accessToken);
|
||||
const decodedMe = AuthenticationApi.decodeMeJWT(accessToken);
|
||||
setMe(decodedMe);
|
||||
enqueueSnackbar(`Logged in as ${decodedMe.username}`, { variant: 'success' });
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(LL.LOGGED_IN({ name: decodedMe.username }), { variant: 'success' });
|
||||
} catch (error) {
|
||||
setMe(undefined);
|
||||
throw new Error('Failed to parse JWT');
|
||||
}
|
||||
@@ -50,7 +54,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
await AuthenticationApi.verifyAuthorization();
|
||||
setMe(AuthenticationApi.decodeMeJWT(accessToken));
|
||||
setInitialized(true);
|
||||
} catch (error: unknown) {
|
||||
} catch (error) {
|
||||
setMe(undefined);
|
||||
setInitialized(true);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
|
||||
try {
|
||||
const response = await FeaturesApi.readFeatures();
|
||||
setFeatures(response.data);
|
||||
} catch (error: unknown) {
|
||||
} catch (error) {
|
||||
setErrorMessage(extractErrorMessage(error, 'Failed to fetch application details.'));
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -19,6 +19,8 @@ import { APProvisionMode, APSettings } from '../../types';
|
||||
import { numberValue, updateValue, useRest } from '../../utils';
|
||||
import * as APApi from '../../api/ap';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const isAPEnabled = ({ provision_mode }: APSettings) => {
|
||||
return provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
|
||||
};
|
||||
@@ -29,6 +31,8 @@ const APSettingsForm: FC = () => {
|
||||
update: APApi.updateAPSettings
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
const updateFormValue = updateValue(setData);
|
||||
@@ -53,7 +57,7 @@ const APSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="provision_mode"
|
||||
label="Provide Access Point…"
|
||||
label={LL.AP_PROVIDE() + '...'}
|
||||
value={data.provision_mode}
|
||||
fullWidth
|
||||
select
|
||||
@@ -61,16 +65,16 @@ const APSettingsForm: FC = () => {
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>Always</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>When WiFi Disconnected</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_NEVER}>Never</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>{LL.AP_PROVIDE_TEXT_1()}</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>{LL.AP_PROVIDE_TEXT_2()}</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_NEVER}>{LL.AP_PROVIDE_TEXT_3()}</MenuItem>
|
||||
</ValidatedTextField>
|
||||
{isAPEnabled(data) && (
|
||||
<>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="ssid"
|
||||
label="Access Point SSID"
|
||||
label={LL.ACCESS_POINT(2) + ' SSID'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.ssid}
|
||||
@@ -80,7 +84,7 @@ const APSettingsForm: FC = () => {
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
name="password"
|
||||
label="Access Point Password"
|
||||
label={LL.ACCESS_POINT(2) + ' ' + LL.PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.password}
|
||||
@@ -90,7 +94,7 @@ const APSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="channel"
|
||||
label="Preferred Channel"
|
||||
label={LL.AP_PREFERRED_CHANNEL()}
|
||||
value={numberValue(data.channel)}
|
||||
fullWidth
|
||||
select
|
||||
@@ -107,12 +111,12 @@ const APSettingsForm: FC = () => {
|
||||
</ValidatedTextField>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="ssid_hidden" checked={data.ssid_hidden} onChange={updateFormValue} />}
|
||||
label="Hide SSID"
|
||||
label={LL.AP_HIDE_SSID()}
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="max_clients"
|
||||
label="Max Clients"
|
||||
label={LL.AP_MAX_CLIENTS()}
|
||||
value={numberValue(data.max_clients)}
|
||||
fullWidth
|
||||
select
|
||||
@@ -130,7 +134,7 @@ const APSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="local_ip"
|
||||
label="Local IP"
|
||||
label={LL.AP_LOCAL_IP()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.local_ip}
|
||||
@@ -140,7 +144,7 @@ const APSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="gateway_ip"
|
||||
label="Gateway"
|
||||
label={LL.NETWORK_GATEWAY()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.gateway_ip}
|
||||
@@ -150,7 +154,7 @@ const APSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="subnet_mask"
|
||||
label="Subnet"
|
||||
label={LL.NETWORK_SUBNET()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.subnet_mask}
|
||||
@@ -168,7 +172,7 @@ const APSettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -176,7 +180,7 @@ const APSettingsForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Access Point Settings" titleGutter>
|
||||
<SectionContent title={LL.SETTINGS_OF(LL.ACCESS_POINT(1))} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -11,6 +11,8 @@ import { APNetworkStatus, APStatus } from '../../types';
|
||||
import { ButtonRow, FormLoader, SectionContent } from '../../components';
|
||||
import { useRest } from '../../utils';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
|
||||
switch (status) {
|
||||
case APNetworkStatus.ACTIVE:
|
||||
@@ -24,24 +26,26 @@ export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const apStatus = ({ status }: APStatus) => {
|
||||
switch (status) {
|
||||
case APNetworkStatus.ACTIVE:
|
||||
return 'Active';
|
||||
case APNetworkStatus.INACTIVE:
|
||||
return 'Inactive';
|
||||
case APNetworkStatus.LINGERING:
|
||||
return 'Lingering until idle';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const APStatusForm: FC = () => {
|
||||
const { loadData, data, errorMessage } = useRest<APStatus>({ read: APApi.readAPStatus });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const apStatus = ({ status }: APStatus) => {
|
||||
switch (status) {
|
||||
case APNetworkStatus.ACTIVE:
|
||||
return LL.ACTIVE();
|
||||
case APNetworkStatus.INACTIVE:
|
||||
return LL.INACTIVE(0);
|
||||
case APNetworkStatus.LINGERING:
|
||||
return 'Lingering until idle';
|
||||
default:
|
||||
return LL.UNKNOWN();
|
||||
}
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
@@ -56,14 +60,14 @@ const APStatusForm: FC = () => {
|
||||
<SettingsInputAntennaIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Status" secondary={apStatus(data)} />
|
||||
<ListItemText primary={LL.STATUS_OF('')} secondary={apStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>IP</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="IP Address" secondary={data.ip_address} />
|
||||
<ListItemText primary={LL.ADDRESS_OF('IP')} secondary={data.ip_address} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -72,7 +76,7 @@ const APStatusForm: FC = () => {
|
||||
<DeviceHubIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="MAC Address" secondary={data.mac_address} />
|
||||
<ListItemText primary={LL.ADDRESS_OF('MAC')} secondary={data.mac_address} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -81,13 +85,13 @@ const APStatusForm: FC = () => {
|
||||
<ComputerIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="AP Clients" secondary={data.station_num} />
|
||||
<ListItemText primary={LL.AP_CLIENTS()} secondary={data.station_num} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</List>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -95,7 +99,7 @@ const APStatusForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Access Point Status" titleGutter>
|
||||
<SectionContent title={LL.STATUS_OF(LL.ACCESS_POINT(1))} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -8,8 +8,12 @@ import APStatusForm from './APStatusForm';
|
||||
import APSettingsForm from './APSettingsForm';
|
||||
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from '../../components';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const AccessPoint: FC = () => {
|
||||
useLayoutTitle('Access Point');
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
useLayoutTitle(LL.ACCESS_POINT(0));
|
||||
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
|
||||
@@ -18,8 +22,8 @@ const AccessPoint: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label="Access Point Status" />
|
||||
<Tab value="settings" label="Access Point Settings" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="status" label={LL.STATUS_OF(LL.ACCESS_POINT(1))} />
|
||||
<Tab value="settings" label={LL.SETTINGS_OF(LL.ACCESS_POINT(1))} disabled={!authenticatedContext.me.admin} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<APStatusForm />} />
|
||||
|
||||
@@ -9,7 +9,11 @@ import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
import MqttStatusForm from './MqttStatusForm';
|
||||
import MqttSettingsForm from './MqttSettingsForm';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const Mqtt: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
useLayoutTitle('MQTT');
|
||||
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
@@ -18,8 +22,8 @@ const Mqtt: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label="MQTT Status" />
|
||||
<Tab value="settings" label="MQTT Settings" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="status" label={LL.STATUS_OF('MQTT')} />
|
||||
<Tab value="settings" label={LL.SETTINGS_OF('MQTT')} disabled={!authenticatedContext.me.admin} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<MqttStatusForm />} />
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { FC, useState } from 'react';
|
||||
import { ValidateFieldsError } from 'async-validator';
|
||||
|
||||
import { Button, Checkbox, MenuItem, Grid, Typography } from '@mui/material';
|
||||
import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment } from '@mui/material';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
|
||||
import { MQTT_SETTINGS_VALIDATOR, validate } from '../../validators';
|
||||
import { createMqttSettingsValidator, validate } from '../../validators';
|
||||
import {
|
||||
BlockFormControlLabel,
|
||||
ButtonRow,
|
||||
@@ -17,12 +17,16 @@ import { MqttSettings } from '../../types';
|
||||
import { numberValue, updateValue, useRest } from '../../utils';
|
||||
import * as MqttApi from '../../api/mqtt';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const MqttSettingsForm: FC = () => {
|
||||
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<MqttSettings>({
|
||||
read: MqttApi.readMqttSettings,
|
||||
update: MqttApi.updateMqttSettings
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
const updateFormValue = updateValue(setData);
|
||||
@@ -35,7 +39,7 @@ const MqttSettingsForm: FC = () => {
|
||||
const validateAndSubmit = async () => {
|
||||
try {
|
||||
setFieldErrors(undefined);
|
||||
await validate(MQTT_SETTINGS_VALIDATOR, data);
|
||||
await validate(createMqttSettingsValidator(data), data);
|
||||
saveData();
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
@@ -46,14 +50,14 @@ const MqttSettingsForm: FC = () => {
|
||||
<>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
|
||||
label="Enable MQTT"
|
||||
label={LL.ENABLE_MQTT()}
|
||||
/>
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item xs={6}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="host"
|
||||
label="Host"
|
||||
label={LL.ADDRESS_OF(LL.BROKER())}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.host}
|
||||
@@ -80,7 +84,7 @@ const MqttSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="base"
|
||||
label="Base"
|
||||
label={LL.BASE_TOPIC()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.base}
|
||||
@@ -91,7 +95,7 @@ const MqttSettingsForm: FC = () => {
|
||||
<Grid item xs={6}>
|
||||
<ValidatedTextField
|
||||
name="client_id"
|
||||
label="Client ID (optional)"
|
||||
label={LL.ID_OF(LL.CLIENT()) + ' (' + LL.OPTIONAL() + ')'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.client_id}
|
||||
@@ -104,7 +108,7 @@ const MqttSettingsForm: FC = () => {
|
||||
<Grid item xs={6}>
|
||||
<ValidatedTextField
|
||||
name="username"
|
||||
label="Username"
|
||||
label={LL.USERNAME(0)}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.username}
|
||||
@@ -115,7 +119,7 @@ const MqttSettingsForm: FC = () => {
|
||||
<Grid item xs={6}>
|
||||
<ValidatedPasswordField
|
||||
name="password"
|
||||
label="Password"
|
||||
label={LL.PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.password}
|
||||
@@ -129,7 +133,10 @@ const MqttSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="keep_alive"
|
||||
label="Keep Alive (seconds)"
|
||||
label="Keep Alive"
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.keep_alive)}
|
||||
@@ -149,7 +156,7 @@ const MqttSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>0 (default)</MenuItem>
|
||||
<MenuItem value={0}>0</MenuItem>
|
||||
<MenuItem value={1}>1</MenuItem>
|
||||
<MenuItem value={2}>2</MenuItem>
|
||||
</ValidatedTextField>
|
||||
@@ -157,18 +164,19 @@ const MqttSettingsForm: FC = () => {
|
||||
</Grid>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="clean_session" checked={data.clean_session} onChange={updateFormValue} />}
|
||||
label="Set Clean Session"
|
||||
label={LL.MQTT_CLEAN_SESSION()}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="mqtt_retain" checked={data.mqtt_retain} onChange={updateFormValue} />}
|
||||
label="Always use Retain Flag"
|
||||
label={LL.MQTT_RETAIN_FLAG()}
|
||||
/>
|
||||
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Formatting
|
||||
{LL.FORMATTING()}
|
||||
</Typography>
|
||||
<ValidatedTextField
|
||||
name="nested_format"
|
||||
label="Topic/Payload Format"
|
||||
label={LL.MQTT_FORMAT()}
|
||||
value={data.nested_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -176,19 +184,19 @@ const MqttSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={1}>Nested in a single topic</MenuItem>
|
||||
<MenuItem value={2}>As individual topics</MenuItem>
|
||||
<MenuItem value={1}>{LL.MQTT_NEST_1()}</MenuItem>
|
||||
<MenuItem value={2}>{LL.MQTT_NEST_2()}</MenuItem>
|
||||
</ValidatedTextField>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />}
|
||||
label="Publish command output to a 'response' topic"
|
||||
label={LL.MQTT_RESPONSE()}
|
||||
/>
|
||||
{!data.ha_enabled && (
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="publish_single" checked={data.publish_single} onChange={updateFormValue} />}
|
||||
label="Publish single value topics on change"
|
||||
label={LL.MQTT_PUBLISH_TEXT_1()}
|
||||
/>
|
||||
</Grid>
|
||||
{data.publish_single && (
|
||||
@@ -197,7 +205,7 @@ const MqttSettingsForm: FC = () => {
|
||||
control={
|
||||
<Checkbox name="publish_single2cmd" checked={data.publish_single2cmd} onChange={updateFormValue} />
|
||||
}
|
||||
label="Publish to command topics (ioBroker)"
|
||||
label={LL.MQTT_PUBLISH_TEXT_2()}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
@@ -207,15 +215,18 @@ const MqttSettingsForm: FC = () => {
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item>
|
||||
<BlockFormControlLabel
|
||||
sx={{ pb: 1 }}
|
||||
control={<Checkbox name="ha_enabled" checked={data.ha_enabled} onChange={updateFormValue} />}
|
||||
label="Enable MQTT Discovery (Home Assistant, Domoticz)"
|
||||
label={LL.MQTT_PUBLISH_TEXT_3()}
|
||||
/>
|
||||
</Grid>
|
||||
{data.ha_enabled && (
|
||||
<Grid item xs={6}>
|
||||
<>
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="discovery_prefix"
|
||||
label="Prefix for the Discovery topics"
|
||||
label={LL.MQTT_PUBLISH_TEXT_4()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.discovery_prefix}
|
||||
@@ -223,18 +234,55 @@ const MqttSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="entity_format"
|
||||
label={LL.MQTT_ENTITY_FORMAT()}
|
||||
value={data.entity_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
|
||||
<MenuItem value={1}>{LL.MQTT_ENTITY_FORMAT_1()}</MenuItem>
|
||||
<MenuItem value={2}>{LL.MQTT_ENTITY_FORMAT_2()}</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Publish Intervals (in seconds, 0=automatic)
|
||||
{LL.MQTT_PUBLISH_INTERVALS()} (0=auto)
|
||||
</Typography>
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_heartbeat"
|
||||
label={LL.MQTT_INT_HEARTBEAT()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_heartbeat)}
|
||||
type="number"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_boiler"
|
||||
label="Boilers and Heat Pumps"
|
||||
label={LL.MQTT_INT_BOILER()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_boiler)}
|
||||
@@ -243,11 +291,14 @@ const MqttSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_thermostat"
|
||||
label="Thermostats"
|
||||
label={LL.MQTT_INT_THERMOSTATS()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_thermostat)}
|
||||
@@ -256,11 +307,14 @@ const MqttSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_solar"
|
||||
label="Solar Modules"
|
||||
label={LL.MQTT_INT_SOLAR()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_solar)}
|
||||
@@ -269,11 +323,14 @@ const MqttSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_mixer"
|
||||
label="Mixer Modules"
|
||||
label={LL.MQTT_INT_MIXER()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_mixer)}
|
||||
@@ -282,11 +339,14 @@ const MqttSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_sensor"
|
||||
label="Temperature Sensors"
|
||||
label={LL.TEMP_SENSORS()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_sensor)}
|
||||
@@ -295,11 +355,14 @@ const MqttSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_other"
|
||||
label="Default"
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
label={LL.DEFAULT(0)}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_other)}
|
||||
@@ -318,7 +381,7 @@ const MqttSettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -326,7 +389,7 @@ const MqttSettingsForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="MQTT Settings" titleGutter>
|
||||
<SectionContent title={LL.SETTINGS_OF('MQTT')} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -5,12 +5,15 @@ import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import ReportIcon from '@mui/icons-material/Report';
|
||||
import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff';
|
||||
import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
|
||||
|
||||
import { ButtonRow, FormLoader, SectionContent } from '../../components';
|
||||
import { MqttStatus, MqttDisconnectReason } from '../../types';
|
||||
import * as MqttApi from '../../api/mqtt';
|
||||
import { useRest } from '../../utils';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => {
|
||||
if (!enabled) {
|
||||
return theme.palette.info.main;
|
||||
@@ -29,17 +32,30 @@ export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatus, theme: Theme) =
|
||||
return theme.palette.error.main;
|
||||
};
|
||||
|
||||
export const mqttStatus = ({ enabled, connected }: MqttStatus) => {
|
||||
if (!enabled) {
|
||||
return 'Not enabled';
|
||||
}
|
||||
if (connected) {
|
||||
return 'Connected';
|
||||
}
|
||||
return 'Disconnected';
|
||||
export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatus, theme: Theme) => {
|
||||
if (mqtt_queued <= 1) return theme.palette.success.main;
|
||||
|
||||
return theme.palette.warning.main;
|
||||
};
|
||||
|
||||
export const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
|
||||
const MqttStatusForm: FC = () => {
|
||||
const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const mqttStatus = ({ enabled, connected, connect_count }: MqttStatus) => {
|
||||
if (!enabled) {
|
||||
return LL.NOT_ENABLED();
|
||||
}
|
||||
if (connected) {
|
||||
return LL.CONNECTED(0) + (connect_count > 1 ? ' (' + connect_count + ')' : '');
|
||||
}
|
||||
return LL.DISCONNECTED() + (connect_count > 1 ? ' (' + connect_count + ')' : '');
|
||||
};
|
||||
|
||||
const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
|
||||
switch (disconnect_reason) {
|
||||
case MqttDisconnectReason.TCP_DISCONNECTED:
|
||||
return 'TCP disconnected';
|
||||
@@ -62,25 +78,41 @@ export const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
|
||||
}
|
||||
};
|
||||
|
||||
const MqttStatusForm: FC = () => {
|
||||
const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus });
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
}
|
||||
|
||||
const renderConnectionStatus = () => {
|
||||
if (data.connected) {
|
||||
return (
|
||||
<>
|
||||
{!data.connected && (
|
||||
<>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<ReportIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={LL.DISCONNECT_REASON()} secondary={disconnectReason(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</>
|
||||
)}
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>#</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Client ID" secondary={data.client_id} />
|
||||
<ListItemText primary={LL.ID_OF(LL.CLIENT())} secondary={data.client_id} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar sx={{ bgcolor: mqttQueueHighlight(data, theme) }}>
|
||||
<AutoAwesomeMotionIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={LL.MQTT_QUEUE()} secondary={data.mqtt_queued} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -89,20 +121,7 @@ const MqttStatusForm: FC = () => {
|
||||
<SpeakerNotesOffIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="MQTT Publish Errors" secondary={data.mqtt_fails} />
|
||||
</ListItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<ReportIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Disconnect Reason" secondary={disconnectReason(data)} />
|
||||
<ListItemText primary={LL.ERRORS_OF('MQTT')} secondary={data.mqtt_fails} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</>
|
||||
@@ -118,14 +137,14 @@ const MqttStatusForm: FC = () => {
|
||||
<DeviceHubIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Status" secondary={mqttStatus(data)} />
|
||||
<ListItemText primary={LL.STATUS_OF('')} secondary={mqttStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
{data.enabled && renderConnectionStatus()}
|
||||
</List>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -133,7 +152,7 @@ const MqttStatusForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="MQTT Status" titleGutter>
|
||||
<SectionContent title={LL.STATUS_OF('MQTT')} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -11,12 +11,16 @@ import NetworkStatusForm from './NetworkStatusForm';
|
||||
import WiFiNetworkScanner from './WiFiNetworkScanner';
|
||||
import NetworkSettingsForm from './NetworkSettingsForm';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const NetworkConnection: FC = () => {
|
||||
useLayoutTitle('Network Connection');
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.NETWORK(0));
|
||||
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
const navigate = useNavigate();
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
const [selectedNetwork, setSelectedNetwork] = useState<WiFiNetwork>();
|
||||
|
||||
@@ -41,9 +45,9 @@ const NetworkConnection: FC = () => {
|
||||
}}
|
||||
>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label="Network Status" />
|
||||
<Tab value="scan" label="Scan WiFi Networks" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="settings" label="Network Settings" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="status" label={LL.STATUS_OF(LL.NETWORK(1))} />
|
||||
<Tab value="scan" label={LL.NETWORK_SCAN()} disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="settings" label={LL.SETTINGS_OF(LL.NETWORK(1))} disabled={!authenticatedContext.me.admin} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<NetworkStatusForm />} />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FC, useContext, useEffect, useState } from 'react';
|
||||
import { useSnackbar } from 'notistack';
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
@@ -10,13 +11,15 @@ import {
|
||||
ListItemAvatar,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
Typography
|
||||
Typography,
|
||||
InputAdornment
|
||||
} from '@mui/material';
|
||||
|
||||
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
|
||||
import {
|
||||
BlockFormControlLabel,
|
||||
@@ -24,11 +27,13 @@ import {
|
||||
FormLoader,
|
||||
SectionContent,
|
||||
ValidatedPasswordField,
|
||||
ValidatedTextField
|
||||
ValidatedTextField,
|
||||
MessageBox
|
||||
} from '../../components';
|
||||
import { NetworkSettings } from '../../types';
|
||||
import * as NetworkApi from '../../api/network';
|
||||
import { numberValue, updateValue, useRest } from '../../utils';
|
||||
import * as EMSESP from '../../project/api';
|
||||
|
||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
|
||||
@@ -36,11 +41,18 @@ import { ValidateFieldsError } from 'async-validator';
|
||||
import { validate } from '../../validators';
|
||||
import { createNetworkSettingsValidator } from '../../validators/network';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
import RestartMonitor from '../system/RestartMonitor';
|
||||
|
||||
const WiFiSettingsForm: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext);
|
||||
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<NetworkSettings>({
|
||||
const [restarting, setRestarting] = useState(false);
|
||||
const { loadData, saving, data, setData, saveData, errorMessage, restartNeeded } = useRest<NetworkSettings>({
|
||||
read: NetworkApi.readNetworkSettings,
|
||||
update: NetworkApi.updateNetworkSettings
|
||||
});
|
||||
@@ -57,7 +69,9 @@ const WiFiSettingsForm: FC = () => {
|
||||
bandwidth20: false,
|
||||
tx_power: 20,
|
||||
nosleep: false,
|
||||
enableMDNS: true
|
||||
enableMDNS: true,
|
||||
enableCORS: false,
|
||||
CORSOrigin: '*'
|
||||
});
|
||||
}
|
||||
setInitialized(true);
|
||||
@@ -85,6 +99,15 @@ const WiFiSettingsForm: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const restart = async () => {
|
||||
try {
|
||||
await EMSESP.restart();
|
||||
setRestarting(true);
|
||||
} catch (error) {
|
||||
enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
@@ -111,7 +134,7 @@ const WiFiSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="ssid"
|
||||
label="SSID (leave blank to disable WiFi)"
|
||||
label={'SSID (' + LL.NETWORK_BLANK_SSID() + ')'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.ssid}
|
||||
@@ -123,7 +146,7 @@ const WiFiSettingsForm: FC = () => {
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
name="password"
|
||||
label="Password"
|
||||
label={LL.PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.password}
|
||||
@@ -135,7 +158,10 @@ const WiFiSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="tx_power"
|
||||
label="WiFi Tx Power (dBm)"
|
||||
label={LL.TX_POWER()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">dBm</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.tx_power)}
|
||||
@@ -146,27 +172,22 @@ const WiFiSettingsForm: FC = () => {
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />}
|
||||
label="Disable WiFi Sleep Mode"
|
||||
label={LL.NETWORK_DISABLE_SLEEP()}
|
||||
/>
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="bandwidth20" checked={data.bandwidth20} onChange={updateFormValue} />}
|
||||
label="Use Lower WiFi Bandwidth"
|
||||
/>
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enableMDNS" checked={data.enableMDNS} onChange={updateFormValue} />}
|
||||
label="Enable mDNS Service"
|
||||
label={LL.NETWORK_LOW_BAND()}
|
||||
/>
|
||||
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
General
|
||||
{LL.GENERAL_OPTIONS()}
|
||||
</Typography>
|
||||
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="hostname"
|
||||
label="Hostname"
|
||||
label={LL.HOSTNAME()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.hostname}
|
||||
@@ -174,21 +195,43 @@ const WiFiSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enableMDNS" checked={data.enableMDNS} onChange={updateFormValue} />}
|
||||
label={LL.NETWORK_USE_DNS()}
|
||||
/>
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enableCORS" checked={data.enableCORS} onChange={updateFormValue} />}
|
||||
label={LL.NETWORK_ENABLE_CORS()}
|
||||
/>
|
||||
{data.enableCORS && (
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="CORSOrigin"
|
||||
label={LL.NETWORK_CORS_ORIGIN()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.CORSOrigin}
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
/>
|
||||
)}
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enableIPv6" checked={data.enableIPv6} onChange={updateFormValue} />}
|
||||
label="Enable IPv6 support"
|
||||
label={LL.NETWORK_ENABLE_IPV6()}
|
||||
/>
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="static_ip_config" checked={data.static_ip_config} onChange={updateFormValue} />}
|
||||
label="Use Fixed IP address"
|
||||
label={LL.NETWORK_FIXED_IP()}
|
||||
/>
|
||||
{data.static_ip_config && (
|
||||
<>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="local_ip"
|
||||
label="Local IP"
|
||||
label={LL.AP_LOCAL_IP()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.local_ip}
|
||||
@@ -198,7 +241,7 @@ const WiFiSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="gateway_ip"
|
||||
label="Gateway"
|
||||
label={LL.NETWORK_GATEWAY()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.gateway_ip}
|
||||
@@ -208,7 +251,7 @@ const WiFiSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="subnet_mask"
|
||||
label="Subnet"
|
||||
label={LL.NETWORK_SUBNET()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.subnet_mask}
|
||||
@@ -218,7 +261,7 @@ const WiFiSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="dns_ip_1"
|
||||
label="DNS IP #1"
|
||||
label="DNS #1"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.dns_ip_1}
|
||||
@@ -228,7 +271,7 @@ const WiFiSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="dns_ip_2"
|
||||
label="DNS IP #2"
|
||||
label="DNS #2"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.dns_ip_2}
|
||||
@@ -237,6 +280,14 @@ const WiFiSettingsForm: FC = () => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{restartNeeded && (
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
</MessageBox>
|
||||
)}
|
||||
{!restartNeeded && (
|
||||
<ButtonRow>
|
||||
<Button
|
||||
startIcon={<SaveIcon />}
|
||||
@@ -246,16 +297,17 @@ const WiFiSettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Network Settings" titleGutter>
|
||||
{content()}
|
||||
<SectionContent title={LL.SETTINGS_OF(LL.NETWORK(1))} titleGutter>
|
||||
{restarting ? <RestartMonitor /> : content()}
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,6 +14,8 @@ import { NetworkConnectionStatus, NetworkStatus } from '../../types';
|
||||
import * as NetworkApi from '../../api/network';
|
||||
import { useRest } from '../../utils';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const isConnected = ({ status }: NetworkStatus) =>
|
||||
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
|
||||
status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED;
|
||||
@@ -35,29 +37,6 @@ const networkStatusHighlight = ({ status }: NetworkStatus, theme: Theme) => {
|
||||
}
|
||||
};
|
||||
|
||||
const networkStatus = ({ status }: NetworkStatus) => {
|
||||
switch (status) {
|
||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
|
||||
return 'Inactive';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
|
||||
return 'Idle';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
|
||||
return 'No SSID Available';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
|
||||
return 'Connected (WiFi)';
|
||||
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
|
||||
return 'Connected (Ethernet)';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
|
||||
return 'Connection Failed';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
|
||||
return 'Connection Lost';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
|
||||
return 'Disconnected';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
export const isWiFi = ({ status }: NetworkStatus) => status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED;
|
||||
export const isEthernet = ({ status }: NetworkStatus) => status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED;
|
||||
|
||||
@@ -65,7 +44,7 @@ const dnsServers = ({ dns_ip_1, dns_ip_2 }: NetworkStatus) => {
|
||||
if (!dns_ip_1) {
|
||||
return 'none';
|
||||
}
|
||||
return dns_ip_1 + (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) => {
|
||||
@@ -81,8 +60,33 @@ const IPs = (status: NetworkStatus) => {
|
||||
const NetworkStatusForm: FC = () => {
|
||||
const { loadData, data, errorMessage } = useRest<NetworkStatus>({ read: NetworkApi.readNetworkStatus });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const networkStatus = ({ status }: NetworkStatus) => {
|
||||
switch (status) {
|
||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
|
||||
return LL.INACTIVE(1);
|
||||
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
|
||||
return LL.IDLE();
|
||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
|
||||
return 'No SSID Available';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
|
||||
return LL.CONNECTED(0) + ' (WiFi)';
|
||||
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
|
||||
return LL.CONNECTED(0) + ' (Ethernet)';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
|
||||
return LL.CONNECTED(1) + ' ' + LL.FAILED();
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
|
||||
return LL.CONNECTED(1) + ' ' + LL.LOST();
|
||||
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
|
||||
return LL.DISCONNECTED();
|
||||
default:
|
||||
return LL.UNKNOWN();
|
||||
}
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
@@ -120,7 +124,7 @@ const NetworkStatusForm: FC = () => {
|
||||
<ListItemAvatar>
|
||||
<Avatar>IP</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="IP Address" secondary={IPs(data)} />
|
||||
<ListItemText primary={LL.ADDRESS_OF('IP')} secondary={IPs(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -129,14 +133,14 @@ const NetworkStatusForm: FC = () => {
|
||||
<DeviceHubIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="MAC Address" secondary={data.mac_address} />
|
||||
<ListItemText primary={LL.ADDRESS_OF('MAC')} secondary={data.mac_address} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>#</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Subnet Mask" secondary={data.subnet_mask} />
|
||||
<ListItemText primary={LL.NETWORK_SUBNET()} secondary={data.subnet_mask} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -145,7 +149,7 @@ const NetworkStatusForm: FC = () => {
|
||||
<SettingsInputComponentIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Gateway IP" secondary={data.gateway_ip || 'none'} />
|
||||
<ListItemText primary={LL.NETWORK_GATEWAY()} secondary={data.gateway_ip || 'none'} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -154,7 +158,7 @@ const NetworkStatusForm: FC = () => {
|
||||
<DnsIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="DNS Server IP" secondary={dnsServers(data)} />
|
||||
<ListItemText primary={LL.NETWORK_DNS()} secondary={dnsServers(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</>
|
||||
@@ -162,7 +166,7 @@ const NetworkStatusForm: FC = () => {
|
||||
</List>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -170,7 +174,7 @@ const NetworkStatusForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Network Status" titleGutter>
|
||||
<SectionContent title={LL.STATUS_OF(LL.NETWORK(1))} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useEffect, FC, useState, useCallback, useRef } from 'react';
|
||||
import { useSnackbar } from 'notistack';
|
||||
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
import { Button } from '@mui/material';
|
||||
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
||||
|
||||
@@ -12,6 +10,8 @@ import { ButtonRow, FormLoader, SectionContent } from '../../components';
|
||||
|
||||
import WiFiNetworkSelector from './WiFiNetworkSelector';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const NUM_POLLS = 10;
|
||||
const POLLING_FREQUENCY = 500;
|
||||
|
||||
@@ -22,6 +22,8 @@ const compareNetworks = (network1: WiFiNetwork, network2: WiFiNetwork) => {
|
||||
};
|
||||
|
||||
const WiFiNetworkScanner: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const pollCount = useRef(0);
|
||||
@@ -46,21 +48,21 @@ const WiFiNetworkScanner: FC = () => {
|
||||
pollCount.current = completedPollCount;
|
||||
setTimeout(pollNetworkList, POLLING_FREQUENCY);
|
||||
} else {
|
||||
finishedWithError('Device did not return network list in timely manner');
|
||||
finishedWithError(LL.PROBLEM_LOADING());
|
||||
}
|
||||
} else {
|
||||
const newNetworkList = response.data;
|
||||
newNetworkList.networks.sort(compareNetworks);
|
||||
setNetworkList(newNetworkList);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
finishedWithError('Problem listing WiFi networks ' + error.response?.data.message);
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
|
||||
} else {
|
||||
finishedWithError('Problem listing WiFi networks');
|
||||
finishedWithError(LL.PROBLEM_LOADING());
|
||||
}
|
||||
}
|
||||
}, [finishedWithError]);
|
||||
}, [finishedWithError, LL]);
|
||||
|
||||
const startNetworkScan = useCallback(async () => {
|
||||
pollCount.current = 0;
|
||||
@@ -69,14 +71,14 @@ const WiFiNetworkScanner: FC = () => {
|
||||
try {
|
||||
await NetworkApi.scanNetworks();
|
||||
setTimeout(pollNetworkList, POLLING_FREQUENCY);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
finishedWithError('Problem scanning for WiFi networks ' + error.response?.data.message);
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
|
||||
} else {
|
||||
finishedWithError('Problem scanning for WiFi networks');
|
||||
finishedWithError(LL.PROBLEM_LOADING());
|
||||
}
|
||||
}
|
||||
}, [finishedWithError, pollNetworkList]);
|
||||
}, [finishedWithError, pollNetworkList, LL]);
|
||||
|
||||
useEffect(() => {
|
||||
startNetworkScan();
|
||||
@@ -84,13 +86,13 @@ const WiFiNetworkScanner: FC = () => {
|
||||
|
||||
const renderNetworkScanner = () => {
|
||||
if (!networkList) {
|
||||
return <FormLoader message="Scanning…" errorMessage={errorMessage} />;
|
||||
return <FormLoader message={LL.SCANNING() + '...'} errorMessage={errorMessage} />;
|
||||
}
|
||||
return <WiFiNetworkSelector networkList={networkList} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Network Scanner">
|
||||
<SectionContent title={LL.NETWORK_SCANNER()}>
|
||||
{renderNetworkScanner()}
|
||||
<ButtonRow>
|
||||
<Button
|
||||
@@ -100,7 +102,7 @@ const WiFiNetworkScanner: FC = () => {
|
||||
onClick={startNetworkScan}
|
||||
disabled={!errorMessage && !networkList}
|
||||
>
|
||||
Scan again…
|
||||
{LL.SCAN_AGAIN()}…
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</SectionContent>
|
||||
|
||||
@@ -12,6 +12,8 @@ import { WiFiEncryptionType, WiFiNetwork, WiFiNetworkList } from '../../types';
|
||||
|
||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface WiFiNetworkSelectorProps {
|
||||
networkList: WiFiNetworkList;
|
||||
}
|
||||
@@ -39,6 +41,8 @@ export const networkSecurityMode = ({ encryption_type }: WiFiNetwork) => {
|
||||
};
|
||||
|
||||
const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const wifiConnectionContext = useContext(WiFiConnectionContext);
|
||||
|
||||
const renderNetwork = (network: WiFiNetwork) => {
|
||||
@@ -61,7 +65,7 @@ const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
|
||||
};
|
||||
|
||||
if (networkList.networks.length === 0) {
|
||||
return <MessageBox mt={2} mb={1} message="No WiFi networks found" level="info" />;
|
||||
return <MessageBox mt={2} mb={1} message={LL.NETWORK_NO_WIFI()} level="info" />;
|
||||
}
|
||||
|
||||
return <List>{networkList.networks.map(renderNetwork)}</List>;
|
||||
|
||||
@@ -12,12 +12,16 @@ import * as NTPApi from '../../api/ntp';
|
||||
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
|
||||
import { NTP_SETTINGS_VALIDATOR } from '../../validators/ntp';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const NTPSettingsForm: FC = () => {
|
||||
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<NTPSettings>({
|
||||
read: NTPApi.readNTPSettings,
|
||||
update: NTPApi.updateNTPSettings
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const updateFormValue = updateValue(setData);
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
@@ -49,12 +53,12 @@ const NTPSettingsForm: FC = () => {
|
||||
<>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
|
||||
label="Enable NTP"
|
||||
label={LL.ENABLE_NTP()}
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="server"
|
||||
label="Server"
|
||||
label={LL.NTP_SERVER()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.server}
|
||||
@@ -64,7 +68,7 @@ const NTPSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="tz_label"
|
||||
label="Time zone"
|
||||
label={LL.TIME_ZONE()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={selectedTimeZone(data.tz_label, data.tz_format)}
|
||||
@@ -72,7 +76,7 @@ const NTPSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem disabled>Time zone...</MenuItem>
|
||||
<MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
|
||||
{timeZoneSelectItems()}
|
||||
</ValidatedTextField>
|
||||
<ButtonRow>
|
||||
@@ -84,7 +88,7 @@ const NTPSettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -92,7 +96,7 @@ const NTPSettingsForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="NTP Settings" titleGutter>
|
||||
<SectionContent title={LL.SETTINGS_OF('NTP')} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
ListItemText,
|
||||
TextField,
|
||||
Theme,
|
||||
useTheme
|
||||
useTheme,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
@@ -31,6 +32,8 @@ import { ButtonRow, FormLoader, SectionContent } from '../../components';
|
||||
import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from '../../utils';
|
||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE;
|
||||
export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED;
|
||||
|
||||
@@ -47,19 +50,6 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const ntpStatus = ({ status }: NTPStatus) => {
|
||||
switch (status) {
|
||||
case NTPSyncStatus.NTP_DISABLED:
|
||||
return 'Disabled';
|
||||
case NTPSyncStatus.NTP_INACTIVE:
|
||||
return 'Inactive';
|
||||
case NTPSyncStatus.NTP_ACTIVE:
|
||||
return 'Active';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const NTPStatusForm: FC = () => {
|
||||
const { loadData, data, errorMessage } = useRest<NTPStatus>({ read: NTPApi.readNTPStatus });
|
||||
const [localTime, setLocalTime] = useState<string>('');
|
||||
@@ -68,6 +58,8 @@ const NTPStatusForm: FC = () => {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value);
|
||||
|
||||
const openSetTime = () => {
|
||||
@@ -77,35 +69,48 @@ const NTPStatusForm: FC = () => {
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const ntpStatus = ({ status }: NTPStatus) => {
|
||||
switch (status) {
|
||||
case NTPSyncStatus.NTP_DISABLED:
|
||||
return LL.DISABLED(0);
|
||||
case NTPSyncStatus.NTP_INACTIVE:
|
||||
return LL.INACTIVE(0);
|
||||
case NTPSyncStatus.NTP_ACTIVE:
|
||||
return LL.ACTIVE();
|
||||
default:
|
||||
return LL.UNKNOWN();
|
||||
}
|
||||
};
|
||||
|
||||
const configureTime = async () => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
await NTPApi.updateTime({
|
||||
local_time: formatLocalDateTime(new Date(localTime))
|
||||
});
|
||||
enqueueSnackbar('Time set', { variant: 'success' });
|
||||
enqueueSnackbar(LL.TIME_SET(), { variant: 'success' });
|
||||
setSettingTime(false);
|
||||
loadData();
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem updating time'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderSetTimeDialog = () => {
|
||||
return (
|
||||
const renderSetTimeDialog = () => (
|
||||
<Dialog open={settingTime} onClose={() => setSettingTime(false)}>
|
||||
<DialogTitle>Set Time</DialogTitle>
|
||||
<DialogTitle>{LL.SET_TIME(1)}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box mb={2}>Enter local date and time below to set the device's time.</Box>
|
||||
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
|
||||
<Typography variant="body2">{LL.SET_TIME_TEXT()}</Typography>
|
||||
</Box>
|
||||
<TextField
|
||||
label="Local Time"
|
||||
label={LL.LOCAL_TIME()}
|
||||
type="datetime-local"
|
||||
value={localTime}
|
||||
onChange={updateLocalTime}
|
||||
disabled={processing}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
InputLabelProps={{
|
||||
shrink: true
|
||||
@@ -114,7 +119,7 @@ const NTPStatusForm: FC = () => {
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setSettingTime(false)} color="secondary">
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<AccessTimeIcon />}
|
||||
@@ -124,12 +129,11 @@ const NTPStatusForm: FC = () => {
|
||||
color="primary"
|
||||
autoFocus
|
||||
>
|
||||
Set Time
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
@@ -145,7 +149,7 @@ const NTPStatusForm: FC = () => {
|
||||
<UpdateIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Status" secondary={ntpStatus(data)} />
|
||||
<ListItemText primary={LL.STATUS_OF('')} secondary={ntpStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
{isNtpEnabled(data) && (
|
||||
@@ -156,7 +160,7 @@ const NTPStatusForm: FC = () => {
|
||||
<DnsIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="NTP Server" secondary={data.server} />
|
||||
<ListItemText primary={LL.NTP_SERVER()} secondary={data.server} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</>
|
||||
@@ -167,7 +171,7 @@ const NTPStatusForm: FC = () => {
|
||||
<AccessTimeIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Local Time" secondary={formatDateTime(data.local_time)} />
|
||||
<ListItemText primary={LL.LOCAL_TIME()} secondary={formatDateTime(data.local_time)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -176,7 +180,7 @@ const NTPStatusForm: FC = () => {
|
||||
<SwapVerticalCircleIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="UTC Time" secondary={formatDateTime(data.utc_time)} />
|
||||
<ListItemText primary={LL.UTC_TIME()} secondary={formatDateTime(data.utc_time)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</List>
|
||||
@@ -184,7 +188,7 @@ const NTPStatusForm: FC = () => {
|
||||
<Box flexGrow={1}>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -192,7 +196,7 @@ const NTPStatusForm: FC = () => {
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
<ButtonRow>
|
||||
<Button onClick={openSetTime} variant="outlined" color="primary" startIcon={<AccessTimeIcon />}>
|
||||
Set Time
|
||||
{LL.SET_TIME(0)}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -204,7 +208,7 @@ const NTPStatusForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="NTP Status" titleGutter>
|
||||
<SectionContent title={LL.STATUS_OF('NTP')} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -9,8 +9,11 @@ import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
import NTPStatusForm from './NTPStatusForm';
|
||||
import NTPSettingsForm from './NTPSettingsForm';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const NetworkTime: FC = () => {
|
||||
useLayoutTitle('Network Time');
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle('NTP');
|
||||
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
const { routerTab } = useRouterTab();
|
||||
@@ -18,8 +21,8 @@ const NetworkTime: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label="NTP Status" />
|
||||
<Tab value="settings" label="NTP Settings" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="status" label={LL.STATUS_OF('NTP')} />
|
||||
<Tab value="settings" label={LL.SETTINGS_OF('NTP')} disabled={!authenticatedContext.me.admin} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<NTPStatusForm />} />
|
||||
|
||||
@@ -19,6 +19,8 @@ import { MessageBox } from '../../components';
|
||||
import * as SecurityApi from '../../api/security';
|
||||
import { Token } from '../../types';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface GenerateTokenProps {
|
||||
username?: string;
|
||||
onClose: () => void;
|
||||
@@ -28,15 +30,17 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
||||
const [token, setToken] = useState<Token>();
|
||||
const open = !!username;
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const getToken = useCallback(async () => {
|
||||
try {
|
||||
setToken((await SecurityApi.generateToken(username)).data);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem generating token'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
}
|
||||
}, [username, enqueueSnackbar]);
|
||||
}, [username, enqueueSnackbar, LL]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@@ -46,16 +50,11 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} aria-labelledby="generate-token-dialog-title" open={!!username} fullWidth maxWidth="sm">
|
||||
<DialogTitle id="generate-token-dialog-title">Access Token for {username}</DialogTitle>
|
||||
<DialogTitle id="generate-token-dialog-title">{LL.ACCESS_TOKEN_FOR() + ' ' + username}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{token ? (
|
||||
<>
|
||||
<MessageBox
|
||||
message="The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the
|
||||
'Authorization' header or in the 'access_token' URL query parameter."
|
||||
level="info"
|
||||
my={2}
|
||||
/>
|
||||
<MessageBox message={LL.ACCESS_TOKEN_TEXT()} level="info" my={2} />
|
||||
<Box mt={2} mb={2}>
|
||||
<TextField label="Token" multiline value={token.token} fullWidth contentEditable={false} />
|
||||
</Box>
|
||||
@@ -63,13 +62,13 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
||||
) : (
|
||||
<Box m={4} textAlign="center">
|
||||
<LinearProgress />
|
||||
<Typography variant="h6">Generating token…</Typography>
|
||||
<Typography variant="h6">{LL.GENERATING_TOKEN()}…</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<CloseIcon />} variant="outlined" onClick={onClose} color="secondary">
|
||||
Close
|
||||
{LL.CLOSE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
@@ -20,6 +20,8 @@ import { createUserValidator } from '../../validators';
|
||||
import { useRest } from '../../utils';
|
||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
import GenerateToken from './GenerateToken';
|
||||
import UserForm from './UserForm';
|
||||
|
||||
@@ -34,9 +36,11 @@ const ManageUsersForm: FC = () => {
|
||||
const [generatingToken, setGeneratingToken] = useState<string>();
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const table_theme = useTheme({
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 120px;
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) minmax(120px, max-content) 120px;
|
||||
`,
|
||||
BaseRow: `
|
||||
font-size: 14px;
|
||||
@@ -136,8 +140,8 @@ const ManageUsersForm: FC = () => {
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell resize>USERNAME</HeaderCell>
|
||||
<HeaderCell stiff>IS ADMIN</HeaderCell>
|
||||
<HeaderCell resize>{LL.USERNAME(1)}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.IS_ADMIN(0)}</HeaderCell>
|
||||
<HeaderCell stiff />
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
@@ -169,9 +173,7 @@ const ManageUsersForm: FC = () => {
|
||||
)}
|
||||
</Table>
|
||||
|
||||
{noAdminConfigured() && (
|
||||
<MessageBox level="warning" message="You must have at least one admin user configured" my={2} />
|
||||
)}
|
||||
{noAdminConfigured() && <MessageBox level="warning" message={LL.USER_WARNING()} my={2} />}
|
||||
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
|
||||
@@ -183,14 +185,14 @@ const ManageUsersForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
<ButtonRow>
|
||||
<Button startIcon={<PersonAddIcon />} variant="outlined" color="secondary" onClick={createUser}>
|
||||
Add
|
||||
{LL.ADD(0)}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -210,7 +212,7 @@ const ManageUsersForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Manage Users" titleGutter>
|
||||
<SectionContent title={LL.MANAGE_USERS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -8,16 +8,19 @@ import { RouterTabs, useRouterTab, useLayoutTitle } from '../../components';
|
||||
import SecuritySettingsForm from './SecuritySettingsForm';
|
||||
import ManageUsersForm from './ManageUsersForm';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const Security: FC = () => {
|
||||
useLayoutTitle('Security');
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.SECURITY(0));
|
||||
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="users" label="Manage Users" />
|
||||
<Tab value="settings" label="Security Settings" />
|
||||
<Tab value="users" label={LL.MANAGE_USERS()} />
|
||||
<Tab value="settings" label={LL.SETTINGS_OF(LL.SECURITY(1))} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="users" element={<ManageUsersForm />} />
|
||||
|
||||
@@ -11,7 +11,11 @@ import { SECURITY_SETTINGS_VALIDATOR, validate } from '../../validators';
|
||||
import { updateValue, useRest } from '../../utils';
|
||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const SecuritySettingsForm: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<SecuritySettings>({
|
||||
read: SecurityApi.readSecuritySettings,
|
||||
@@ -42,18 +46,14 @@ const SecuritySettingsForm: FC = () => {
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
name="jwt_secret"
|
||||
label="su Password"
|
||||
label={LL.SU_PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.jwt_secret}
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
/>
|
||||
<MessageBox
|
||||
level="info"
|
||||
message="The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console."
|
||||
mt={1}
|
||||
/>
|
||||
<MessageBox level="info" message={LL.SU_TEXT()} mt={1} />
|
||||
<ButtonRow>
|
||||
<Button
|
||||
startIcon={<SaveIcon />}
|
||||
@@ -63,7 +63,7 @@ const SecuritySettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -71,7 +71,7 @@ const SecuritySettingsForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Security Settings" titleGutter>
|
||||
<SectionContent title={LL.SETTINGS_OF(LL.SECURITY(1))} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import Schema, { ValidateFieldsError } from 'async-validator';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
|
||||
import { Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
|
||||
|
||||
@@ -11,6 +12,8 @@ import { User } from '../../types';
|
||||
import { updateValue } from '../../utils';
|
||||
import { validate } from '../../validators';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface UserFormProps {
|
||||
creating: boolean;
|
||||
validator: Schema;
|
||||
@@ -23,6 +26,8 @@ interface UserFormProps {
|
||||
}
|
||||
|
||||
const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDoneEditing, onCancelEditing }) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const updateFormValue = updateValue(setUser);
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
const open = !!user;
|
||||
@@ -49,12 +54,14 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
|
||||
<Dialog onClose={onCancelEditing} open={!!user} fullWidth maxWidth="sm">
|
||||
{user && (
|
||||
<>
|
||||
<DialogTitle id="user-form-dialog-title">{creating ? 'Add' : 'Modify'} User</DialogTitle>
|
||||
<DialogTitle id="user-form-dialog-title">
|
||||
{creating ? LL.ADD(1) : LL.MODIFY()} {LL.USER(1)}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="username"
|
||||
label="Username"
|
||||
label={LL.USERNAME(1)}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={user.username}
|
||||
@@ -65,7 +72,7 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
name="password"
|
||||
label="Password"
|
||||
label={LL.PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={user.password}
|
||||
@@ -74,21 +81,21 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="admin" checked={user.admin} onChange={updateFormValue} />}
|
||||
label="is Admin?"
|
||||
label={LL.IS_ADMIN(1)}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={onCancelEditing} color="secondary">
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<PersonAddIcon />}
|
||||
startIcon={creating ? <PersonAddIcon /> : <SaveIcon />}
|
||||
variant="outlined"
|
||||
onClick={validateAndDone}
|
||||
color="primary"
|
||||
autoFocus
|
||||
>
|
||||
Add
|
||||
{creating ? LL.ADD(0) : LL.SAVE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { AxiosPromise } from 'axios';
|
||||
import { Typography, Button, Box } from '@mui/material';
|
||||
|
||||
import { FileUploadConfig } from '../../api/endpoints';
|
||||
|
||||
import { SingleUpload, useFileUpload } from '../../components';
|
||||
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
@@ -14,15 +15,19 @@ import { extractErrorMessage } from '../../utils';
|
||||
|
||||
import * as EMSESP from '../../project/api';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface UploadFileProps {
|
||||
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
||||
}
|
||||
|
||||
const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
||||
const [uploadFile, cancelUpload, uploading, uploadProgress] = useFileUpload({ upload: uploadGeneralFile });
|
||||
const [uploadFile, cancelUpload, uploading, uploadProgress, md5] = useFileUpload({ upload: uploadGeneralFile });
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const saveFile = (json: any, endpoint: string) => {
|
||||
const a = document.createElement('a');
|
||||
const filename = 'emsesp_' + endpoint + '.json';
|
||||
@@ -35,19 +40,19 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
enqueueSnackbar('File downloaded', { variant: 'info' });
|
||||
enqueueSnackbar(LL.DOWNLOAD_SUCCESSFUL(), { variant: 'info' });
|
||||
};
|
||||
|
||||
const downloadSettings = async () => {
|
||||
try {
|
||||
const response = await EMSESP.getSettings();
|
||||
if (response.status !== 200) {
|
||||
enqueueSnackbar('Unable to get settings', { variant: 'error' });
|
||||
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
|
||||
} else {
|
||||
saveFile(response.data, 'settings');
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,47 +60,50 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
||||
try {
|
||||
const response = await EMSESP.getCustomizations();
|
||||
if (response.status !== 200) {
|
||||
enqueueSnackbar('Unable to get customizations', { variant: 'error' });
|
||||
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
|
||||
} else {
|
||||
saveFile(response.data, 'customizations');
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
Upload
|
||||
</Typography>
|
||||
{!uploading && (
|
||||
<Box mb={2} color="warning.main">
|
||||
<Typography variant="body2">
|
||||
Upload a new firmware (.bin) file, settings or customizations (.json) file below.
|
||||
<>
|
||||
<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()} </Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{md5 !== '' && (
|
||||
<Box mb={2}>
|
||||
<Typography variant="body2">{'MD5: ' + md5}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} />
|
||||
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
Download
|
||||
</Typography>
|
||||
{!uploading && (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
{LL.DOWNLOAD(0)}
|
||||
</Typography>
|
||||
<Box color="warning.main">
|
||||
<Typography mb={1} variant="body2">
|
||||
Download the application settings. Be careful when sharing your settings as this file contains passwords
|
||||
and other sensitive system information.
|
||||
{LL.DOWNLOAD_SETTINGS_TEXT()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => downloadSettings()}>
|
||||
settings
|
||||
{LL.SETTINGS_OF('')}
|
||||
</Button>
|
||||
|
||||
<Box color="warning.main">
|
||||
<Typography mt={2} mb={1} variant="body2">
|
||||
Download the entity customizations.
|
||||
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
@@ -104,7 +112,7 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
||||
color="primary"
|
||||
onClick={() => downloadCustomizations()}
|
||||
>
|
||||
customizations
|
||||
{LL.CUSTOMIZATION()}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ValidatedPasswordField,
|
||||
ValidatedTextField
|
||||
} from '../../components';
|
||||
|
||||
import { OTASettings } from '../../types';
|
||||
import { numberValue, updateValue, useRest } from '../../utils';
|
||||
|
||||
@@ -19,12 +20,16 @@ import { ValidateFieldsError } from 'async-validator';
|
||||
import { validate } from '../../validators';
|
||||
import { OTA_SETTINGS_VALIDATOR } from '../../validators/system';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const OTASettingsForm: FC = () => {
|
||||
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<OTASettings>({
|
||||
read: SystemApi.readOTASettings,
|
||||
update: SystemApi.updateOTASettings
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const updateFormValue = updateValue(setData);
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
@@ -48,7 +53,7 @@ const OTASettingsForm: FC = () => {
|
||||
<>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
|
||||
label="Enable OTA Updates"
|
||||
label={LL.ENABLE_OTA()}
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
@@ -64,7 +69,7 @@ const OTASettingsForm: FC = () => {
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
name="password"
|
||||
label="Password"
|
||||
label={LL.PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.password}
|
||||
@@ -80,7 +85,7 @@ const OTASettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -88,7 +93,7 @@ const OTASettingsForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="OTA Settings" titleGutter>
|
||||
<SectionContent title={LL.SETTINGS_OF('OTA')} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,8 @@ import { FC, useRef, useState } from 'react';
|
||||
import * as SystemApi from '../../api/system';
|
||||
import { FormLoader } from '../../components';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const RESTART_TIMEOUT = 2 * 60 * 1000;
|
||||
const POLL_TIMEOUT = 2000;
|
||||
const POLL_INTERVAL = 5000;
|
||||
@@ -12,12 +14,14 @@ const RestartMonitor: FC = () => {
|
||||
const [failed, setFailed] = useState<boolean>(false);
|
||||
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
|
||||
const poll = useRef(async () => {
|
||||
try {
|
||||
await SystemApi.readSystemStatus(POLL_TIMEOUT);
|
||||
document.location.href = '/fileUpdated';
|
||||
} catch (error: unknown) {
|
||||
} catch (error) {
|
||||
if (new Date().getTime() < timeoutAt.current) {
|
||||
setTimeoutId(setTimeout(poll.current, POLL_INTERVAL));
|
||||
} else {
|
||||
@@ -32,12 +36,7 @@ const RestartMonitor: FC = () => {
|
||||
|
||||
useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]);
|
||||
|
||||
return (
|
||||
<FormLoader
|
||||
message="EMS-ESP is restarting, please wait…"
|
||||
errorMessage={failed ? 'Timed out waiting for device to restart.' : undefined}
|
||||
/>
|
||||
);
|
||||
return <FormLoader message={LL.APPLICATION_RESTARTING() + '...'} errorMessage={failed ? 'Timed out' : undefined} />;
|
||||
};
|
||||
|
||||
export default RestartMonitor;
|
||||
|
||||
@@ -12,8 +12,12 @@ import OTASettingsForm from './OTASettingsForm';
|
||||
|
||||
import SystemLog from './SystemLog';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const System: FC = () => {
|
||||
useLayoutTitle('System');
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
useLayoutTitle(LL.SYSTEM(0));
|
||||
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
const { features } = useContext(FeaturesContext);
|
||||
@@ -22,11 +26,11 @@ const System: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label="System Status" />
|
||||
<Tab value="log" label="System Log" />
|
||||
<Tab value="status" label={LL.STATUS_OF(LL.SYSTEM(1))} />
|
||||
<Tab value="log" label={LL.LOG_OF(LL.SYSTEM(2))} />
|
||||
|
||||
{features.ota && <Tab value="ota" label="OTA Settings" disabled={!me.admin} />}
|
||||
{features.upload_firmware && <Tab value="upload" label="Upload/Download" disabled={!me.admin} />}
|
||||
{features.ota && <Tab value="ota" label={LL.SETTINGS_OF('OTA')} disabled={!me.admin} />}
|
||||
{features.upload_firmware && <Tab value="upload" label={LL.UPLOAD_DOWNLOAD()} disabled={!me.admin} />}
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<SystemStatusForm />} />
|
||||
|
||||
@@ -15,6 +15,9 @@ import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import { useSnackbar } from 'notistack';
|
||||
|
||||
import { EVENT_SOURCE_ROOT } from '../../api/endpoints';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
|
||||
|
||||
const useWindowSize = () => {
|
||||
@@ -63,12 +66,13 @@ const levelLabel = (level: LogLevel) => {
|
||||
const SystemLog: FC = () => {
|
||||
useWindowSize();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { loadData, data, setData } = useRest<LogSettings>({
|
||||
read: SystemApi.readLogSettings
|
||||
});
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const [reconnectTimeout, setReconnectTimeout] = useState<NodeJS.Timeout>();
|
||||
const [logEntries, setLogEntries] = useState<LogEntries>({ events: [] });
|
||||
const [lastIndex, setLastIndex] = useState<number>(0);
|
||||
|
||||
@@ -104,10 +108,10 @@ const SystemLog: FC = () => {
|
||||
compact: data.compact
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
enqueueSnackbar('Problem applying log settings', { variant: 'error' });
|
||||
enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem applying log settings'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -157,11 +161,11 @@ const SystemLog: FC = () => {
|
||||
|
||||
const fetchLog = useCallback(async () => {
|
||||
try {
|
||||
setLogEntries((await SystemApi.readLogEntries()).data);
|
||||
} catch (error: unknown) {
|
||||
setErrorMessage(extractErrorMessage(error, 'Failed to fetch log'));
|
||||
await SystemApi.readLogEntries();
|
||||
} catch (error) {
|
||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
}, []);
|
||||
}, [LL]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchLog();
|
||||
@@ -171,20 +175,14 @@ const SystemLog: FC = () => {
|
||||
const es = new EventSource(addAccessTokenParameter(LOG_EVENTSOURCE_URL));
|
||||
es.onmessage = onMessage;
|
||||
es.onerror = () => {
|
||||
if (reconnectTimeout) {
|
||||
es.close();
|
||||
setReconnectTimeout(setTimeout(reloadPage, 1000));
|
||||
}
|
||||
reloadPage();
|
||||
};
|
||||
|
||||
return () => {
|
||||
es.close();
|
||||
if (reconnectTimeout) {
|
||||
clearTimeout(reconnectTimeout);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line
|
||||
}, [reconnectTimeout]);
|
||||
}, []);
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
@@ -197,7 +195,7 @@ const SystemLog: FC = () => {
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="level"
|
||||
label="Log Level"
|
||||
label={LL.LOG_LEVEL()}
|
||||
value={data.level}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -205,6 +203,7 @@ const SystemLog: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={-1}>OFF</MenuItem>
|
||||
<MenuItem value={3}>ERROR</MenuItem>
|
||||
<MenuItem value={4}>WARNING</MenuItem>
|
||||
<MenuItem value={5}>NOTICE</MenuItem>
|
||||
@@ -214,7 +213,7 @@ const SystemLog: FC = () => {
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<FormLabel>Buffer size</FormLabel>
|
||||
<FormLabel>{LL.BUFFER_SIZE()}</FormLabel>
|
||||
<Slider
|
||||
value={data.max_messages}
|
||||
valueLabelDisplay="auto"
|
||||
@@ -235,12 +234,12 @@ const SystemLog: FC = () => {
|
||||
<Grid item>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.compact} onChange={updateFormValue} name="compact" />}
|
||||
label="Compact"
|
||||
label={LL.COMPACT()}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="secondary" onClick={onDownload}>
|
||||
Export
|
||||
{LL.EXPORT()}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -273,7 +272,7 @@ const SystemLog: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="System Log" titleGutter id="log-window">
|
||||
<SectionContent title={LL.LOG_OF(LL.SYSTEM(2))} titleGutter id="log-window">
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -22,6 +22,7 @@ import ShowChartIcon from '@mui/icons-material/ShowChart';
|
||||
import MemoryIcon from '@mui/icons-material/Memory';
|
||||
import AppsIcon from '@mui/icons-material/Apps';
|
||||
import SdStorageIcon from '@mui/icons-material/SdStorage';
|
||||
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
@@ -31,13 +32,16 @@ import TimerIcon from '@mui/icons-material/Timer';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
|
||||
import { ButtonRow, FormLoader, SectionContent, MessageBox } from '../../components';
|
||||
import { EspPlatform, SystemStatus, Version } from '../../types';
|
||||
import { SystemStatus, Version } from '../../types';
|
||||
import * as SystemApi from '../../api/system';
|
||||
import { extractErrorMessage, useRest } from '../../utils';
|
||||
|
||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import axios from 'axios';
|
||||
import RestartMonitor from './RestartMonitor';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest';
|
||||
export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest';
|
||||
@@ -48,6 +52,9 @@ function formatNumber(num: number) {
|
||||
}
|
||||
|
||||
const SystemStatusForm: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
|
||||
const { loadData, data, errorMessage } = useRest<SystemStatus>({ read: SystemApi.readSystemStatus });
|
||||
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
@@ -64,7 +71,7 @@ const SystemStatusForm: FC = () => {
|
||||
setLatestVersion({
|
||||
version: response.data.name,
|
||||
url: response.data.assets[1].browser_download_url,
|
||||
changelog: response.data.html_url
|
||||
changelog: response.data.assets[0].browser_download_url
|
||||
});
|
||||
});
|
||||
axios.get(VERSIONCHECK_DEV_ENDPOINT).then((response) => {
|
||||
@@ -79,10 +86,25 @@ const SystemStatusForm: FC = () => {
|
||||
const restart = async () => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
await SystemApi.restart();
|
||||
enqueueSnackbar('EMS-ESP is restarting...', { variant: 'info' });
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem restarting device'), { variant: 'error' });
|
||||
const response = await SystemApi.restart();
|
||||
if (response.status === 200) {
|
||||
setRestarting(true);
|
||||
}
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
} finally {
|
||||
setConfirmRestart(false);
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const partition = async () => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
await SystemApi.partition();
|
||||
setRestarting(true);
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
} finally {
|
||||
setConfirmRestart(false);
|
||||
setProcessing(false);
|
||||
@@ -91,16 +113,17 @@ const SystemStatusForm: FC = () => {
|
||||
|
||||
const renderRestartDialog = () => (
|
||||
<Dialog open={confirmRestart} onClose={() => setConfirmRestart(false)}>
|
||||
<DialogTitle>Restart</DialogTitle>
|
||||
<DialogContent dividers>Are you sure you want to restart EMS-ESP?</DialogContent>
|
||||
<DialogTitle>{LL.RESTART()}</DialogTitle>
|
||||
<DialogContent dividers>{LL.RESTART_CONFIRM()}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
onClick={() => setConfirmRestart(false)}
|
||||
disabled={processing}
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
@@ -110,8 +133,19 @@ const SystemStatusForm: FC = () => {
|
||||
color="primary"
|
||||
autoFocus
|
||||
>
|
||||
Restart
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
{data?.has_loader && (
|
||||
<Button
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
variant="outlined"
|
||||
onClick={partition}
|
||||
disabled={processing}
|
||||
color="primary"
|
||||
>
|
||||
EMS-ESP-Loader
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -119,22 +153,19 @@ const SystemStatusForm: FC = () => {
|
||||
const renderVersionDialog = () => {
|
||||
return (
|
||||
<Dialog open={showingVersion} onClose={() => setShowingVersion(false)}>
|
||||
<DialogTitle>Version Check</DialogTitle>
|
||||
<DialogTitle>{LL.VERSION_CHECK(1)}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<MessageBox
|
||||
my={0}
|
||||
level="info"
|
||||
message={'You are currently running EMS-ESP version ' + data?.emsesp_version}
|
||||
/>
|
||||
<MessageBox my={0} level="info" message={LL.SYSTEM_VERSION_RUNNING() + ' ' + data?.emsesp_version} />
|
||||
{latestVersion && (
|
||||
<Box mt={2} mb={2}>
|
||||
The latest <u>official</u> version is <b>{latestVersion.version}</b> (
|
||||
{LL.THE_LATEST()} <u>{LL.OFFICIAL()}</u> {LL.VERSION_IS()} <b>{latestVersion.version}</b>
|
||||
(
|
||||
<Link target="_blank" href={latestVersion.changelog} color="primary">
|
||||
{'release notes'}
|
||||
{LL.RELEASE_NOTES()}
|
||||
</Link>
|
||||
) (
|
||||
<Link target="_blank" href={latestVersion.url} color="primary">
|
||||
{'download'}
|
||||
{LL.DOWNLOAD(1)}
|
||||
</Link>
|
||||
)
|
||||
</Box>
|
||||
@@ -142,14 +173,15 @@ const SystemStatusForm: FC = () => {
|
||||
|
||||
{latestDevVersion && (
|
||||
<Box mt={2} mb={2}>
|
||||
The latest <u>development</u> version is <b>{latestDevVersion.version}</b>
|
||||
{LL.THE_LATEST()} <u>{LL.DEVELOPMENT()}</u> {LL.VERSION_IS()}
|
||||
<b>{latestDevVersion.version}</b>
|
||||
(
|
||||
<Link target="_blank" href={latestDevVersion.changelog} color="primary">
|
||||
{'release notes'}
|
||||
{LL.RELEASE_NOTES()}
|
||||
</Link>
|
||||
) (
|
||||
<Link target="_blank" href={latestDevVersion.url} color="primary">
|
||||
{'download'}
|
||||
{LL.DOWNLOAD(1)}
|
||||
</Link>
|
||||
)
|
||||
</Box>
|
||||
@@ -157,17 +189,17 @@ const SystemStatusForm: FC = () => {
|
||||
|
||||
<Box color="warning.main" p={0} pl={0} pr={0} mt={4} mb={0}>
|
||||
<Typography variant="body2">
|
||||
Use
|
||||
<Link target="_blank" href={uploadURL} color="primary">
|
||||
{'UPLOAD'}
|
||||
{LL.USE()}
|
||||
<Link href={uploadURL} color="primary">
|
||||
{LL.UPLOAD()}
|
||||
</Link>
|
||||
to apply the new firmware
|
||||
{LL.SYSTEM_APPLY_FIRMWARE()}
|
||||
</Typography>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="outlined" onClick={() => setShowingVersion(false)} color="secondary">
|
||||
Close
|
||||
{LL.CLOSE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -178,9 +210,9 @@ const SystemStatusForm: FC = () => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
await SystemApi.factoryReset();
|
||||
enqueueSnackbar('Device has been factory reset and will now restart', { variant: 'info' });
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem factory resetting the device'), { variant: 'error' });
|
||||
setRestarting(true);
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setConfirmFactoryReset(false);
|
||||
setProcessing(false);
|
||||
@@ -189,16 +221,17 @@ const SystemStatusForm: FC = () => {
|
||||
|
||||
const renderFactoryResetDialog = () => (
|
||||
<Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}>
|
||||
<DialogTitle>Factory Reset</DialogTitle>
|
||||
<DialogContent dividers>Are you sure you want to reset the device to its factory defaults?</DialogContent>
|
||||
<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"
|
||||
>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
@@ -208,7 +241,7 @@ const SystemStatusForm: FC = () => {
|
||||
autoFocus
|
||||
color="error"
|
||||
>
|
||||
Factory Reset
|
||||
{LL.FACTORY_RESET()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -228,10 +261,10 @@ const SystemStatusForm: FC = () => {
|
||||
<BuildIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="EMS-ESP Version" secondary={'v' + data.emsesp_version} />
|
||||
<ListItemText primary={LL.EMS_ESP_VER()} secondary={'v' + data.emsesp_version} />
|
||||
{latestVersion && (
|
||||
<Button color="primary" onClick={() => setShowingVersion(true)}>
|
||||
Version Check
|
||||
{LL.VERSION_CHECK(0)}
|
||||
</Button>
|
||||
)}
|
||||
</ListItem>
|
||||
@@ -242,7 +275,7 @@ const SystemStatusForm: FC = () => {
|
||||
<DevicesIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Device (Platform / SDK)" secondary={data.esp_platform + ' / ' + data.sdk_version} />
|
||||
<ListItemText primary={LL.PLATFORM()} secondary={data.esp_platform + ' / ' + data.sdk_version} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -251,7 +284,7 @@ const SystemStatusForm: FC = () => {
|
||||
<TimerIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="System Uptime" secondary={data.uptime} />
|
||||
<ListItemText primary={LL.UPTIME()} secondary={data.uptime} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -260,7 +293,7 @@ const SystemStatusForm: FC = () => {
|
||||
<ShowChartIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="CPU Frequency" secondary={data.cpu_freq_mhz + ' MHz'} />
|
||||
<ListItemText primary={LL.CPU_FREQ()} secondary={data.cpu_freq_mhz + ' MHz'} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -270,17 +303,11 @@ const SystemStatusForm: FC = () => {
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="Heap (Free / Max Alloc)"
|
||||
secondary={
|
||||
formatNumber(data.free_heap) +
|
||||
' / ' +
|
||||
formatNumber(data.max_alloc_heap) +
|
||||
' bytes ' +
|
||||
(data.esp_platform === EspPlatform.ESP8266 ? '(' + data.heap_fragmentation + '% fragmentation)' : '')
|
||||
}
|
||||
primary={LL.HEAP()}
|
||||
secondary={formatNumber(data.free_heap) + ' KB / ' + formatNumber(data.max_alloc_heap) + ' KB '}
|
||||
/>
|
||||
</ListItem>
|
||||
{data.esp_platform === EspPlatform.ESP32 && data.psram_size > 0 && (
|
||||
{data.psram_size !== undefined && data.free_psram !== undefined && (
|
||||
<>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -290,8 +317,8 @@ const SystemStatusForm: FC = () => {
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="PSRAM (Size / Free)"
|
||||
secondary={formatNumber(data.psram_size) + ' / ' + formatNumber(data.free_psram) + ' bytes'}
|
||||
primary={LL.PSRAM()}
|
||||
secondary={formatNumber(data.psram_size) + ' KB / ' + formatNumber(data.free_psram) + ' KB'}
|
||||
/>
|
||||
</ListItem>
|
||||
</>
|
||||
@@ -304,13 +331,25 @@ const SystemStatusForm: FC = () => {
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="Flash Chip (Size / Speed)"
|
||||
primary={LL.FLASH()}
|
||||
secondary={
|
||||
formatNumber(data.flash_chip_size) + ' bytes / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz'
|
||||
formatNumber(data.flash_chip_size) + ' KB / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz'
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<SdCardAlertIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={LL.APPSIZE()}
|
||||
secondary={formatNumber(data.app_used) + ' KB / ' + formatNumber(data.app_free) + ' KB'}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
@@ -318,15 +357,8 @@ const SystemStatusForm: FC = () => {
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="File System (Used / Total)"
|
||||
secondary={
|
||||
formatNumber(data.fs_used) +
|
||||
' / ' +
|
||||
formatNumber(data.fs_total) +
|
||||
' bytes (' +
|
||||
formatNumber(data.fs_total - data.fs_used) +
|
||||
'\xa0bytes free)'
|
||||
}
|
||||
primary={LL.FILESYSTEM()}
|
||||
secondary={formatNumber(data.fs_used) + ' KB / ' + formatNumber(data.fs_free) + ' KB'}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
@@ -335,7 +367,7 @@ const SystemStatusForm: FC = () => {
|
||||
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -348,7 +380,7 @@ const SystemStatusForm: FC = () => {
|
||||
color="primary"
|
||||
onClick={() => setConfirmRestart(true)}
|
||||
>
|
||||
Restart
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
@@ -356,7 +388,7 @@ const SystemStatusForm: FC = () => {
|
||||
onClick={() => setConfirmFactoryReset(true)}
|
||||
color="error"
|
||||
>
|
||||
Factory reset
|
||||
{LL.FACTORY_RESET()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -370,8 +402,8 @@ const SystemStatusForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="System Status" titleGutter>
|
||||
{content()}
|
||||
<SectionContent title={LL.STATUS_OF(LL.SYSTEM(1))} titleGutter>
|
||||
{restarting ? <RestartMonitor /> : content()}
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,17 +7,23 @@ import { FileUploadConfig } from '../../api/endpoints';
|
||||
import GeneralFileUpload from './GeneralFileUpload';
|
||||
import RestartMonitor from './RestartMonitor';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const UploadFileForm: FC = () => {
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => {
|
||||
const response = await SystemApi.uploadFile(file, config);
|
||||
if (response.status === 200) {
|
||||
setRestarting(true);
|
||||
}
|
||||
return response;
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionContent title="Upload/Download" titleGutter>
|
||||
<SectionContent title={LL.UPLOAD_DOWNLOAD()} titleGutter>
|
||||
{restarting ? <RestartMonitor /> : <GeneralFileUpload uploadGeneralFile={uploadFile.current} />}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
1
interface/src/i18n/DE.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#D80027" d="M0 85.331h512v341.337H0z"/><path d="M0 85.331h512v113.775H0z"/><path fill="#FFDA44" d="M0 312.882h512v113.775H0z"/></svg>
|
||||
|
After Width: | Height: | Size: 216 B |
1
interface/src/i18n/FR.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#FFF" d="M0 85.331h512v341.337H0z"/><path fill="#0052B4" d="M0 85.331h170.663v341.337H0z"/><path fill="#D80027" d="M341.337 85.331H512v341.337H341.337z"/></svg>
|
||||
|
After Width: | Height: | Size: 243 B |
1
interface/src/i18n/GB.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#FFF" d="M0 85.333h512V426.67H0z"/><path fill="#D80027" d="M288 85.33h-64v138.666H0v64h224v138.666h64V287.996h224v-64H288z"/><g fill="#0052B4"><path d="M393.785 315.358 512 381.034v-65.676zM311.652 315.358 512 426.662v-31.474l-143.693-79.83zM458.634 426.662l-146.982-81.664v81.664z"/></g><path fill="#FFF" d="M311.652 315.358 512 426.662v-31.474l-143.693-79.83z"/><path fill="#D80027" d="M311.652 315.358 512 426.662v-31.474l-143.693-79.83z"/><g fill="#0052B4"><path d="M90.341 315.356 0 365.546v-50.19zM200.348 329.51v97.151H25.491z"/></g><path fill="#D80027" d="M143.693 315.358 0 395.188v31.474l200.348-111.304z"/><g fill="#0052B4"><path d="M118.215 196.634 0 130.958v65.676zM200.348 196.634 0 85.33v31.474l143.693 79.83zM53.366 85.33l146.982 81.664V85.33z"/></g><path fill="#FFF" d="M200.348 196.634 0 85.33v31.474l143.693 79.83z"/><path fill="#D80027" d="M200.348 196.634 0 85.33v31.474l143.693 79.83z"/><g fill="#0052B4"><path d="M421.659 196.636 512 146.446v50.19zM311.652 182.482V85.331h174.857z"/></g><path fill="#D80027" d="M368.307 196.634 512 116.804V85.33L311.652 196.634z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
interface/src/i18n/NL.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.5 513 342"><path fill="#FFF" d="M0 85.5h513v342H0z"/><path fill="#cd1f2a" d="M0 85.5h513v114H0z"/><path fill="#1d4185" d="M0 312h513v114H0z"/></svg>
|
||||
|
After Width: | Height: | Size: 202 B |
1
interface/src/i18n/NO.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#D80027" d="M0 85.334h512v341.337H0z"/><path fill="#FFF" d="M512 295.883H202.195v130.783H122.435V295.883H0V216.111h122.435V85.329H202.195v130.782H512V277.329z"/><path fill="#2E52B2" d="M512 234.666v42.663H183.652v149.337h-42.674V277.329H0v-42.663h140.978V85.329h42.674v149.337z"/></svg>
|
||||
|
After Width: | Height: | Size: 369 B |
1
interface/src/i18n/PL.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><g fill="#FFF"><path d="M0 85.337h512v341.326H0z"/><path d="M0 85.337h512V256H0z"/></g><path fill="#D80027" d="M0 256h512v170.663H0z"/></svg>
|
||||
|
After Width: | Height: | Size: 212 B |
1
interface/src/i18n/SV.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#0052B4" d="M0 85.333h512V426.67H0z"/><path fill="#FFDA44" d="M192 85.33h-64v138.666H0v64h128v138.666h64V287.996h320v-64H192z"/></svg>
|
||||
|
After Width: | Height: | Size: 217 B |
310
interface/src/i18n/de/index.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import type { Translation } from '../i18n-types';
|
||||
/* prettier-ignore */
|
||||
/* eslint-disable */
|
||||
|
||||
const de: Translation = {
|
||||
LANGUAGE: 'Sprache',
|
||||
RETRY: 'Neuer Versuch',
|
||||
LOADING: 'Laden',
|
||||
IS_REQUIRED: '{0} ist erforderlich',
|
||||
SIGN_IN: 'Einloggen',
|
||||
SIGN_OUT: 'Ausloggen',
|
||||
USERNAME: 'Nutzername',
|
||||
PASSWORD: 'Passwort',
|
||||
SU_PASSWORD: 'su Passwort',
|
||||
DASHBOARD: 'Kontrollzentrum',
|
||||
SETTINGS_OF: '{0} Einstellungen',
|
||||
SAVED: 'gespeichert',
|
||||
HELP_OF: '{0} Hilfe',
|
||||
LOGGED_IN: 'Eingeloggt als {name}',
|
||||
PLEASE_SIGNIN: 'Bitte einloggen, um fortzufahren',
|
||||
UPLOAD_SUCCESSFUL: 'Hochladen erfolgreich',
|
||||
DOWNLOAD_SUCCESSFUL: 'Herunterladen erfolgreich',
|
||||
INVALID_LOGIN: 'Ungültige Login Daten',
|
||||
NETWORK: 'Netzwerk',
|
||||
SECURITY: 'Sicherheit',
|
||||
ONOFF_CAP: 'AN/AUS',
|
||||
ONOFF: 'an/aus',
|
||||
TYPE: 'Typ',
|
||||
DESCRIPTION: 'Bezeichnung',
|
||||
ENTITIES: 'Entitäten',
|
||||
REFRESH: 'Aktualisieren',
|
||||
EXPORT: 'Exportieren',
|
||||
DEVICE_DETAILS: 'Geräte Details',
|
||||
BRAND: 'Marke',
|
||||
ID_OF: '{0} ID',
|
||||
DEVICE: 'Geräte',
|
||||
PRODUCT: 'Produkt',
|
||||
VERSION: 'Version',
|
||||
ENTITY_NAME: 'Entitätsname',
|
||||
VALUE: '{{Wert|wert}}',
|
||||
SHOW_FAV: 'nur Favoriten anzeigen',
|
||||
DEVICE_SENSOR_DATA: 'Geräte- und Sensordaten',
|
||||
DEVICES_SENSORS: 'Geräte & Sensoren',
|
||||
ATTACHED_SENSORS: 'Angeschlossene EMS-ESP Sensoren',
|
||||
RUN_COMMAND: 'Befehl ausführen',
|
||||
CHANGE_VALUE: 'Wert ändern',
|
||||
CANCEL: 'Abbrechen',
|
||||
RESET: 'Zurücksetzen',
|
||||
SEND: 'Senden',
|
||||
SAVE: 'Speichern',
|
||||
REMOVE: 'Entfernen',
|
||||
PROBLEM_UPDATING: 'Problem beim Aktualisieren',
|
||||
PROBLEM_LOADING: 'Problem beim Laden',
|
||||
ACCESS_DENIED: 'Zugriff abgelehnt',
|
||||
ANALOG_SENSOR: 'Analogsensor',
|
||||
ANALOG_SENSORS: 'Analogsensoren',
|
||||
UPDATED_OF: '{0} Aktualisiert',
|
||||
UPDATE_OF: '{0} Aktualisieren',
|
||||
REMOVED_OF: '{0} Entfernt',
|
||||
DELETION_OF: '{0} Löschung',
|
||||
OFFSET: 'Addition',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frequenz',
|
||||
DUTY_CYCLE: 'Duty Cycle',
|
||||
UNIT: 'UoM',
|
||||
STARTVALUE: 'Startwert',
|
||||
WARN_GPIO: 'Warnung: Vorsicht bei der korrekten Wahl des GPIO!',
|
||||
EDIT: 'Editiere',
|
||||
SENSOR: 'Sensor',
|
||||
TEMP_SENSOR: 'Temperatursensor',
|
||||
TEMP_SENSORS: 'Temperatursensoren',
|
||||
WRITE_CMD_SENT: 'Befehl schreiben wurde gesendet',
|
||||
WRITE_CMD_FAILED: 'Befehl schreiben failed', // TODO translate
|
||||
EMS_BUS_WARNING: 'EMS-Bus getrennt. Wenn diese Warnung nach einigen Sekunden immer noch besteht, überprüfen Sie bitte die Einstellungen und das Board-Profil',
|
||||
EMS_BUS_SCANNING: 'Suche nach EMS Geräten...',
|
||||
CONNECTED: 'Verbunden',
|
||||
TX_ISSUES: 'Tx-Probleme - versuchen Sie einen anderen Tx-Modus',
|
||||
DISCONNECTED: 'Getrennt',
|
||||
EMS_SCAN: 'Möchten Sie wirklich eine vollständige Gerätesuche des EMS-Busses starten?',
|
||||
EMS_BUS_STATUS: 'EMS-Busstatus',
|
||||
ACTIVE_DEVICES: 'Aktive Geräte und Sensoren',
|
||||
EMS_DEVICE: 'EMS Gerät',
|
||||
SUCCESS: 'ERFOLG',
|
||||
FAIL: 'FEHLER',
|
||||
QUALITY: 'QUALITÄT',
|
||||
SCAN_DEVICES: 'Nach neuen Geräten suchen',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS-Bus- und Aktivitätsstatus',
|
||||
SCAN: 'Suche',
|
||||
STATUS_NAMES: [
|
||||
'EMS-Telegramme empfangen (Rx)',
|
||||
'EMS-Telegramme gelesen (Tx)',
|
||||
'EMS-Telegramme geschrieben (Tx)',
|
||||
'Temperatursensoren gelesen',
|
||||
'Analogsensoren gelesen',
|
||||
'MQTT-Nachrichten gesendet',
|
||||
'API-Aufrufe',
|
||||
'Syslog-Mitteilungen'
|
||||
],
|
||||
NUM_DEVICES: '{num} Gerät{{e}}',
|
||||
NUM_TEMP_SENSORS: '{num} Temperatursensor{{en}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analogsensor{{en}}',
|
||||
NUM_DAYS: '{num} Tag{{e}}',
|
||||
NUM_SECONDS: '{num} Sekunde{{n}}',
|
||||
NUM_HOURS: '{num} Stunde{{n}}',
|
||||
NUM_MINUTES: '{num} Minute{{n}}',
|
||||
APPLICATION_SETTINGS: 'Anwendungseinstellungen',
|
||||
CUSTOMIZATION: 'Anpassungen',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP startet neu',
|
||||
INTERFACE_BOARD_PROFILE: 'Interface Platinenprofil',
|
||||
BOARD_PROFILE_TEXT: 'Wählen Sie ein vorkonfiguriertes Platinenprofil aus der Liste unten aus oder wählen Sie "Custom", um Ihre eigenen Hardwareeinstellungen zu konfigurieren',
|
||||
BOARD_PROFILE: 'Platinenprofil',
|
||||
CUSTOM: 'Custom',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
BUTTON: 'Taste',
|
||||
TEMPERATURE: 'Temperatur',
|
||||
PHY_TYPE: 'Eth PHY Typ',
|
||||
DISABLED: 'deaktiviert',
|
||||
TX_MODE: 'Tx Modus',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
GENERAL_OPTIONS: 'Allgemeine Optionen',
|
||||
LANGUAGE_ENTITIES: 'Sprache (für Geräteentitäten)',
|
||||
HIDE_LED: 'LED ausblenden',
|
||||
ENABLE_TELNET: 'Aktiviere Telnet Konsole',
|
||||
ENABLE_ANALOG: 'Aktiviere Analogsensoren',
|
||||
CONVERT_FAHRENHEIT: 'Konvertiere Temperaturwerte in Fahrenheit',
|
||||
BYPASS_TOKEN: 'Zugriffstoken-Autorisierung bei API-Aufrufen umgehen',
|
||||
READONLY: 'Nur-Lese-Modus aktivieren (blockiert alle ausgehenden EMS Tx Write-Befehle)',
|
||||
UNDERCLOCK_CPU: 'CPU-Geschwindigkeit untertakten',
|
||||
HEATINGOFF: 'Boiler Start mit Heizung ausgeschaltet',
|
||||
ENABLE_SHOWER_TIMER: 'Duschtimer aktivieren',
|
||||
ENABLE_SHOWER_ALERT: 'Duschalarm aktivieren',
|
||||
TRIGGER_TIME: 'Auslösezeit',
|
||||
COLD_SHOT_DURATION: 'Kaltschussdauer',
|
||||
FORMATTING_OPTIONS: 'Formatierungsoptionen',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolsches Format für Web',
|
||||
BOOLEAN_FORMAT_API: 'Boolesches Format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Parasitäre Stomversorgung',
|
||||
LOGGING: 'Protokollierung',
|
||||
LOG_HEX: 'EMS-Telegramme hexadezimal protokollieren',
|
||||
ENABLE_SYSLOG: 'Syslog aktivieren',
|
||||
LOG_LEVEL: 'Log Level',
|
||||
MARK_INTERVAL: 'Intervallmarke',
|
||||
SECONDS: 'Sekunden',
|
||||
MINUTES: 'Minuten',
|
||||
HOURS: 'Stunden',
|
||||
RESTART: 'Neu starten',
|
||||
RESTART_TEXT: 'EMS-ESP muss neu gestartet werden, um geänderte Systemeinstellungen zu übernehmen',
|
||||
RESTART_CONFIRM: 'EMS-ESP wirklich neu starten?',
|
||||
COMMAND: 'Befehl',
|
||||
CUSTOMIZATIONS_RESTART: 'Alle Anpassungen wurden entfernt. Neustart...',
|
||||
CUSTOMIZATIONS_FULL: 'Ausgewählte Entitäten haben das Limit überschritten. Bitte stapelweise speichern',
|
||||
CUSTOMIZATIONS_SAVED: 'Anpassungen gespeichert',
|
||||
CUSTOMIZATIONS_HELP_1: 'Wählen Sie ein Gerät aus und passen Sie die Entitäten mithilfe der Optionen an',
|
||||
CUSTOMIZATIONS_HELP_2: 'als Favorit markieren',
|
||||
CUSTOMIZATIONS_HELP_3: 'Schreibaktion deaktivieren',
|
||||
CUSTOMIZATIONS_HELP_4: 'von MQTT und API ausschließen',
|
||||
CUSTOMIZATIONS_HELP_5: 'Aus dem Kontrollzentrum ausblenden',
|
||||
CUSTOMIZATIONS_HELP_6: 'Aus dem Speicher löschen',
|
||||
SELECT_DEVICE: 'Wählen Sie ein Gerät aus',
|
||||
SET_ALL: 'setzen Sie alle',
|
||||
OPTIONS: 'Optionen',
|
||||
NAME: 'Name',
|
||||
CUSTOMIZATIONS_RESET: 'Möchten Sie wirklich alle Anpassungen entfernen, einschließlich der benutzerdefinierten Einstellungen der Temperatur- und Analogsensoren?',
|
||||
DEVICE_ENTITIES: 'Geräteentitäten',
|
||||
USER_CUSTOMIZATION: 'Benutzeranpassung',
|
||||
SUPPORT_INFORMATION: 'Unterstützende Informationen',
|
||||
CLICK_HERE: 'Hier klicken',
|
||||
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_3: 'Um neue Funktionen anzufragen oder Fehler zu melden, eröffnen Sie ein Issue auf Github',
|
||||
HELP_INFORMATION_4: 'Bitte laden Sie die System-Details und hängen Sie sie an das Support-Issue an. ',
|
||||
HELP_INFORMATION_5: 'EMS-ESP ist ein freies Open-Source Projekt. Bitte unterstützen Sie die zukünftige Entwicklung mit einem "Star" auf Github!',
|
||||
SUPPORT_INFO: 'Support Info',
|
||||
UPLOAD_OF: '{0} Hochladen',
|
||||
UPLOAD: 'Hochladen',
|
||||
DOWNLOAD: 'Herunterladen',
|
||||
ABORTED: 'abgebrochen',
|
||||
FAILED: 'gescheitert',
|
||||
SUCCESSFUL: 'erfolgreich',
|
||||
SYSTEM: 'System',
|
||||
LOG_OF: '{0} Log',
|
||||
STATUS_OF: '{0} Status',
|
||||
UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen',
|
||||
SYSTEM_VERSION_RUNNING: 'Sie verwenden die Version',
|
||||
SYSTEM_APPLY_FIRMWARE: 'um die neue Firmware anzuwenden',
|
||||
CLOSE: 'Schließen',
|
||||
USE: 'Verwenden Sie',
|
||||
FACTORY_RESET: 'Werkseinstellung',
|
||||
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?',
|
||||
VERSION_CHECK: 'Versionsprüfung',
|
||||
THE_LATEST: 'Die neueste',
|
||||
OFFICIAL: 'offizielle',
|
||||
DEVELOPMENT: 'Entwicklungs',
|
||||
VERSION_IS: 'Version ist',
|
||||
RELEASE_NOTES: 'Versionshinweise',
|
||||
EMS_ESP_VER: 'EMS-ESP Version',
|
||||
PLATFORM: 'Platform (Platform / SDK)',
|
||||
UPTIME: 'System Betriebszeit',
|
||||
CPU_FREQ: 'CPU Frequenz',
|
||||
HEAP: 'freier RAM Speicher (Gesamt / max. Block)',
|
||||
PSRAM: 'PSRAM (Größe / Frei)',
|
||||
FLASH: 'Flash Speicher (Größe / Geschwindigkeit)',
|
||||
APPSIZE: 'Programm (Genutzt / Frei)',
|
||||
FILESYSTEM: 'Dateisystem (Genutzt / Frei)',
|
||||
BUFFER_SIZE: 'max. Puffergröße',
|
||||
COMPACT: 'Kompakte Darstellung',
|
||||
ENABLE_OTA: 'OTA Updates verwenden',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Herunterladen der individuellen Entitätsanpassungen',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Herunterladen der Anwendungseinstellungen. Vorsicht beim Teilen der Einstellungen, da sie Passwörter und andere sensitive Einstellungen enthalten',
|
||||
UPLOAD_TEXT: 'Hochladen von neuer Firmware (.bin), Geräte- oder Entitätseinstellungen (.json), zur optionalen Validitätsprüfung zuerst die (.md5) Datei hochladen',
|
||||
UPLOADING: 'Hochladen',
|
||||
UPLOAD_DROP_TEXT: 'Klicken Sie hier, oder ziehen eine Datei hierher',
|
||||
ERROR: 'Unerwarteter Fehler, bitter versuchen Sie es erneut',
|
||||
TIME_SET: 'Zeit gesetzt',
|
||||
MANAGE_USERS: 'Nutzerverwaltung',
|
||||
IS_ADMIN: 'ist Admin',
|
||||
USER_WARNING: 'Sie müssen mindestens einen Admin-Nutzer konfigurieren',
|
||||
ADD: 'Hinzufügen',
|
||||
ACCESS_TOKEN_FOR: 'Zugangs-Token für',
|
||||
ACCESS_TOKEN_TEXT: 'Dieses Token ist für REST API Aufrufe bestimmt, die eine Authentifizierung benötigen. Es kann entweder als Bearer Token im `Authorization-Header` oder in der Access_Token URL verwendet werden.',
|
||||
GENERATING_TOKEN: 'Erzeuge Token',
|
||||
USER: 'Nutzer',
|
||||
MODIFY: 'Ändern',
|
||||
SU_TEXT: 'Das su (super user) Passwort wird zum Signieren der Authentifikations-Tokens verwendet und ermöglicht Admin-Berechtigung in der Konsole.',
|
||||
NOT_ENABLED: 'Nicht aktiviert',
|
||||
ERRORS_OF: '{0} Fehler',
|
||||
DISCONNECT_REASON: 'Grund der Verbindungsunterbrechung',
|
||||
ENABLE_MQTT: 'MQTT aktivieren',
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Optional',
|
||||
FORMATTING: 'Formattierung',
|
||||
MQTT_FORMAT: 'Topic/Payload Format',
|
||||
MQTT_NEST_1: 'Eingebettet in einem Gesamttopic',
|
||||
MQTT_NEST_2: 'Als einzelne Topics',
|
||||
MQTT_RESPONSE: 'Veröffentliche die Kommandoantwort als `response` Topic',
|
||||
MQTT_PUBLISH_TEXT_1: 'Veröffentliche einzelne Werte bei Veränderung als eigene Topics',
|
||||
MQTT_PUBLISH_TEXT_2: 'Veröffentliche als Kommando-Topic (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Aktiviere `MQTT Discovery` (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix für die `Discovery`-Topics',
|
||||
MQTT_PUBLISH_INTERVALS: 'Veröffentlichungs-Intervalle',
|
||||
MQTT_INT_BOILER: 'Boiler und Wärmepumpen',
|
||||
MQTT_INT_THERMOSTATS: 'Thermostate',
|
||||
MQTT_INT_SOLAR: 'Solarmodule',
|
||||
MQTT_INT_MIXER: 'Mischermodule',
|
||||
MQTT_INT_HEARTBEAT: 'Heartbeat',
|
||||
MQTT_QUEUE: 'MQTT Queue',
|
||||
DEFAULT: 'Standard',
|
||||
MQTT_ENTITY_FORMAT: 'Entitäts-ID Format',
|
||||
MQTT_ENTITY_FORMAT_0: 'Einzelinstanz, Langname (v3.4)',
|
||||
MQTT_ENTITY_FORMAT_1: 'Einzelinstanz, MQTT-Namen',
|
||||
MQTT_ENTITY_FORMAT_2: 'Mehrfachinstanzen, MQTT-Namen',
|
||||
MQTT_CLEAN_SESSION: 'Setze `Clean Session`',
|
||||
MQTT_RETAIN_FLAG: 'Setze `Retain flag` immer',
|
||||
INACTIVE: 'Inaktiv',
|
||||
ACTIVE: 'Aktiv',
|
||||
UNKNOWN: 'Unbekannt',
|
||||
SET_TIME: 'Zeiteinstellung',
|
||||
SET_TIME_TEXT: 'Geben Sie das lokale Datum und die Zeit ein',
|
||||
LOCAL_TIME: 'Lokalzeit',
|
||||
UTC_TIME: 'UTC Zeit',
|
||||
ENABLE_NTP: 'Aktiviere NTP',
|
||||
NTP_SERVER: 'NTP Server',
|
||||
TIME_ZONE: 'Zeitzone',
|
||||
ACCESS_POINT: 'Zugangspunkt',
|
||||
AP_PROVIDE: 'Aktiviere Zugangspunkt',
|
||||
AP_PROVIDE_TEXT_1: 'Immer',
|
||||
AP_PROVIDE_TEXT_2: 'Wenn WiFi nicht verbunden',
|
||||
AP_PROVIDE_TEXT_3: 'Niemals',
|
||||
AP_PREFERRED_CHANNEL: 'Bevorzugter Kanal',
|
||||
AP_HIDE_SSID: 'Verstecke SSID',
|
||||
AP_CLIENTS: 'AP-Klienten',
|
||||
AP_MAX_CLIENTS: 'Max Anzahl AP-Klienten',
|
||||
AP_LOCAL_IP: 'Lokale IP',
|
||||
NETWORK_SCAN: 'Suche nach WiFi Netzwerken',
|
||||
IDLE: 'Leerlauf',
|
||||
LOST: 'Verloren',
|
||||
SCANNING: 'Suche',
|
||||
SCAN_AGAIN: 'Erneute Suche',
|
||||
NETWORK_SCANNER: 'Netzwerk Suche',
|
||||
NETWORK_NO_WIFI: 'Keine WiFi Netzwerke gefunden',
|
||||
NETWORK_BLANK_SSID: 'Freilassen um WiFi zu deaktivieren',
|
||||
TX_POWER: 'Tx Leistung',
|
||||
HOSTNAME: 'Hostname',
|
||||
NETWORK_DISABLE_SLEEP: 'Deaktiviere WiFi Schlafmodus',
|
||||
NETWORK_LOW_BAND: 'Verwende niedrige WiFi Bandbreite',
|
||||
NETWORK_USE_DNS: 'Aktiviere mDNS Service',
|
||||
NETWORK_ENABLE_CORS: 'Aktiviere CORS',
|
||||
NETWORK_CORS_ORIGIN: 'CORS origin',
|
||||
NETWORK_ENABLE_IPV6: 'Aktiviere IPv6 Unterstützung',
|
||||
NETWORK_FIXED_IP: 'Feste IP Adresse',
|
||||
NETWORK_GATEWAY: 'Gateway',
|
||||
NETWORK_SUBNET: 'Subnetz Maske',
|
||||
NETWORK_DNS: 'DNS Server',
|
||||
ADDRESS_OF: '{0} Adresse',
|
||||
ADMIN: 'Administrator',
|
||||
GUEST: 'Gast',
|
||||
NEW: 'Neuer',
|
||||
NEW_NAME_OF: 'Ändere {0}',
|
||||
ENTITY: 'Entität',
|
||||
MIN: 'min',
|
||||
MAX: 'max'
|
||||
};
|
||||
|
||||
export default de;
|
||||
310
interface/src/i18n/en/index.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import type { Translation } from '../i18n-types';
|
||||
/* prettier-ignore */
|
||||
/* eslint-disable */
|
||||
|
||||
const en: Translation = {
|
||||
LANGUAGE: 'Language',
|
||||
RETRY: 'Retry',
|
||||
LOADING: 'Loading',
|
||||
IS_REQUIRED: '{0} is required',
|
||||
SIGN_IN: 'Sign In',
|
||||
SIGN_OUT: 'Sign Out',
|
||||
USERNAME: 'Username',
|
||||
PASSWORD: 'Password',
|
||||
SU_PASSWORD: 'su Password',
|
||||
DASHBOARD: 'Dashboard',
|
||||
SETTINGS_OF: '{0} Settings',
|
||||
SAVED: 'saved',
|
||||
HELP_OF: '{0} Help',
|
||||
LOGGED_IN: 'Logged in as {name}',
|
||||
PLEASE_SIGNIN: 'Please sign in to continue',
|
||||
UPLOAD_SUCCESSFUL: 'Upload finished',
|
||||
DOWNLOAD_SUCCESSFUL: 'Download finished',
|
||||
INVALID_LOGIN: 'Invalid login details',
|
||||
NETWORK: 'Network',
|
||||
SECURITY: 'Security',
|
||||
ONOFF_CAP: 'ON/OFF',
|
||||
ONOFF: 'on/off',
|
||||
TYPE: 'Type',
|
||||
DESCRIPTION: 'Description',
|
||||
ENTITIES: 'Entities',
|
||||
REFRESH: 'Refresh',
|
||||
EXPORT: 'Export',
|
||||
DEVICE_DETAILS: 'Device Details',
|
||||
ID_OF: '{0} ID',
|
||||
DEVICE: 'Device',
|
||||
PRODUCT: 'Product',
|
||||
VERSION: 'Version',
|
||||
BRAND: 'Brand',
|
||||
ENTITY_NAME: 'Entity Name',
|
||||
VALUE: '{{Value|value}}',
|
||||
SHOW_FAV: 'only show favorites',
|
||||
DEVICE_SENSOR_DATA: 'Device and Sensor Data',
|
||||
DEVICES_SENSORS: 'Devices & Sensors',
|
||||
ATTACHED_SENSORS: 'Attached EMS-ESP Sensors',
|
||||
RUN_COMMAND: 'Call Command',
|
||||
CHANGE_VALUE: 'Change Value',
|
||||
CANCEL: 'Cancel',
|
||||
RESET: 'Reset',
|
||||
SEND: 'Send',
|
||||
SAVE: 'Save',
|
||||
REMOVE: 'Remove',
|
||||
PROBLEM_UPDATING: 'Problem updating',
|
||||
PROBLEM_LOADING: 'Problem loading',
|
||||
ACCESS_DENIED: 'Access Denied',
|
||||
ANALOG_SENSOR: 'Analog Sensor',
|
||||
ANALOG_SENSORS: 'Analog Sensors',
|
||||
UPDATED_OF: '{0} Updated',
|
||||
UPDATE_OF: '{0} Update',
|
||||
REMOVED_OF: '{0} Removed',
|
||||
DELETION_OF: '{0} Deletion',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Factor',
|
||||
FREQ: 'Frequency',
|
||||
DUTY_CYCLE: 'Duty Cycle',
|
||||
UNIT: 'UoM',
|
||||
STARTVALUE: 'Start value',
|
||||
WARN_GPIO: 'Warning: be careful when assigning a GPIO!',
|
||||
EDIT: 'Edit',
|
||||
SENSOR: 'Sensor',
|
||||
TEMP_SENSOR: 'Temperature Sensor',
|
||||
TEMP_SENSORS: 'Temperature Sensors',
|
||||
WRITE_CMD_SENT: 'Write command has been sent',
|
||||
WRITE_CMD_FAILED: 'Write command failed',
|
||||
EMS_BUS_WARNING: 'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile',
|
||||
EMS_BUS_SCANNING: 'Scanning for EMS devices...',
|
||||
CONNECTED: 'Connected',
|
||||
TX_ISSUES: 'Tx issues - try a different Tx Mode',
|
||||
DISCONNECTED: 'Disconnected',
|
||||
EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?',
|
||||
EMS_BUS_STATUS: 'EMS Bus Status',
|
||||
ACTIVE_DEVICES: 'Active Devices & Sensors',
|
||||
EMS_DEVICE: 'EMS Device',
|
||||
SUCCESS: 'SUCCESS',
|
||||
FAIL: 'FAIL',
|
||||
QUALITY: 'QUALITY',
|
||||
SCAN_DEVICES: 'Scan for new devices',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS Bus & Activity Status',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegrams Received (Rx)',
|
||||
'EMS Reads (Tx)',
|
||||
'EMS Writes (Tx)',
|
||||
'Temperature Sensor Reads',
|
||||
'Analog Sensor Reads',
|
||||
'MQTT Publishes',
|
||||
'API Calls',
|
||||
'Syslog Messages'
|
||||
],
|
||||
NUM_DEVICES: '{num} Device{{s}}',
|
||||
NUM_TEMP_SENSORS: '{num} Temperature Sensor{{s}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analog Sensor{{s}}',
|
||||
NUM_DAYS: '{num} day{{s}}',
|
||||
NUM_SECONDS: '{num} second{{s}}',
|
||||
NUM_HOURS: '{num} hour{{s}}',
|
||||
NUM_MINUTES: '{num} minute{{s}}',
|
||||
APPLICATION_SETTINGS: 'Application Settings',
|
||||
CUSTOMIZATION: 'Customization',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP is restarting',
|
||||
INTERFACE_BOARD_PROFILE: 'Interface Board Profile',
|
||||
BOARD_PROFILE_TEXT: 'Select a pre-configured interface board profile from the list below or choose Custom to configure your own hardware settings',
|
||||
BOARD_PROFILE: 'Board Profile',
|
||||
CUSTOM: 'Custom',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
BUTTON: 'Button',
|
||||
TEMPERATURE: 'Temperature',
|
||||
PHY_TYPE: 'Eth PHY Type',
|
||||
DISABLED: 'disabled',
|
||||
TX_MODE: 'Tx Mode',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
GENERAL_OPTIONS: 'General Options',
|
||||
LANGUAGE_ENTITIES: 'Language (for device entities)',
|
||||
HIDE_LED: 'Hide LED',
|
||||
ENABLE_TELNET: 'Enable Telnet Console',
|
||||
ENABLE_ANALOG: 'Enable Analog Sensors',
|
||||
CONVERT_FAHRENHEIT: 'Convert temperature values to Fahrenheit',
|
||||
BYPASS_TOKEN: 'Bypass Access Token authorization on API calls',
|
||||
READONLY: 'Enable read-only mode (blocks all outgoing EMS Tx Write commands)',
|
||||
UNDERCLOCK_CPU: 'Underclock CPU speed',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Enable Shower Timer',
|
||||
ENABLE_SHOWER_ALERT: 'Enable Shower Alert',
|
||||
TRIGGER_TIME: 'Trigger Time',
|
||||
COLD_SHOT_DURATION: 'Cold Shot Duration',
|
||||
FORMATTING_OPTIONS: 'Formatting Options',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolean Format Dashboard',
|
||||
BOOLEAN_FORMAT_API: 'Boolean Format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Enable parasite power',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Log EMS telegrams in hexadecimal',
|
||||
ENABLE_SYSLOG: 'Enable Syslog',
|
||||
LOG_LEVEL: 'Log Level',
|
||||
MARK_INTERVAL: 'Mark Interval',
|
||||
SECONDS: 'seconds',
|
||||
MINUTES: 'minutes',
|
||||
HOURS: 'hours',
|
||||
RESTART: 'Restart',
|
||||
RESTART_TEXT: 'EMS-ESP needs to be restarted to apply changed system settings',
|
||||
RESTART_CONFIRM: 'Are you sure you want to restart EMS-ESP?',
|
||||
COMMAND: 'Command',
|
||||
CUSTOMIZATIONS_RESTART: 'All customizations have been removed. Restarting...',
|
||||
CUSTOMIZATIONS_FULL: 'Selected entities exceeded limit. Please save in batches',
|
||||
CUSTOMIZATIONS_SAVED: 'Customizations saved',
|
||||
CUSTOMIZATIONS_HELP_1: 'Select a device and customize the entities options or click to rename',
|
||||
CUSTOMIZATIONS_HELP_2: 'mark as favorite',
|
||||
CUSTOMIZATIONS_HELP_3: 'disable write action',
|
||||
CUSTOMIZATIONS_HELP_4: 'exclude from MQTT and API',
|
||||
CUSTOMIZATIONS_HELP_5: 'hide from Dashboard',
|
||||
CUSTOMIZATIONS_HELP_6: 'remove from memory',
|
||||
SELECT_DEVICE: 'Select a device',
|
||||
SET_ALL: 'set all',
|
||||
OPTIONS: 'Options',
|
||||
NAME: 'Name',
|
||||
CUSTOMIZATIONS_RESET: 'Are you sure you want remove all customizations including the custom settings of the Temperature and Analog sensors?',
|
||||
DEVICE_ENTITIES: 'Device Entities',
|
||||
USER_CUSTOMIZATION: 'User Customization',
|
||||
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_2: 'For live community chat join our Discord server',
|
||||
HELP_INFORMATION_3: 'To request a feature or report a bug',
|
||||
HELP_INFORMATION_4: 'remember to download and attach your system 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!',
|
||||
SUPPORT_INFO: 'Support Info',
|
||||
UPLOAD_OF: '{0} Upload',
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: 'Download',
|
||||
ABORTED: 'aborted',
|
||||
FAILED: 'failed',
|
||||
SUCCESSFUL: 'successful',
|
||||
SYSTEM: 'System',
|
||||
LOG_OF: '{0} Log',
|
||||
STATUS_OF: '{0} Status',
|
||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||
SYSTEM_VERSION_RUNNING: 'You are currently running version',
|
||||
SYSTEM_APPLY_FIRMWARE: 'to apply the new firmware',
|
||||
CLOSE: 'Close',
|
||||
USE: 'Use',
|
||||
FACTORY_RESET: 'Factory Reset',
|
||||
SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset the device to its factory defaults?',
|
||||
VERSION_CHECK: 'Version Check',
|
||||
THE_LATEST: 'The latest',
|
||||
OFFICIAL: 'official',
|
||||
DEVELOPMENT: 'development',
|
||||
VERSION_IS: 'version is',
|
||||
RELEASE_NOTES: 'release notes',
|
||||
EMS_ESP_VER: 'EMS-ESP Version',
|
||||
PLATFORM: 'Device (Platform / SDK)',
|
||||
UPTIME: 'System Uptime',
|
||||
CPU_FREQ: 'CPU Frequency',
|
||||
HEAP: 'Heap (Free / Max Alloc)',
|
||||
PSRAM: 'PSRAM (Size / Free)',
|
||||
FLASH: 'Flash Chip (Size / Speed)',
|
||||
APPSIZE: 'Application (Used / Free)',
|
||||
FILESYSTEM: 'File System (Used / Free)',
|
||||
BUFFER_SIZE: 'Max Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
ENABLE_OTA: 'Enable OTA Updates',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download the entity customizations',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information',
|
||||
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below, for optional validation upload (.md5) first',
|
||||
UPLOADING: 'Uploading',
|
||||
UPLOAD_DROP_TEXT: 'Drop file or click here',
|
||||
ERROR: 'Unexpected Error, please try again',
|
||||
TIME_SET: 'Time set',
|
||||
MANAGE_USERS: 'Manage Users',
|
||||
IS_ADMIN: 'is Admin',
|
||||
USER_WARNING: 'You must have at least one admin user configured',
|
||||
ADD: 'Add',
|
||||
ACCESS_TOKEN_FOR: 'Access Token for',
|
||||
ACCESS_TOKEN_TEXT: 'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.',
|
||||
GENERATING_TOKEN: 'Generating token',
|
||||
USER: 'User',
|
||||
MODIFY: 'Modify',
|
||||
SU_TEXT: 'The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.',
|
||||
NOT_ENABLED: 'Not enabled',
|
||||
ERRORS_OF: '{0} Errors',
|
||||
DISCONNECT_REASON: 'Disconnect Reason',
|
||||
ENABLE_MQTT: 'Enable MQTT',
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Optional',
|
||||
FORMATTING: 'Formatting',
|
||||
MQTT_FORMAT: 'Topic/Payload Format',
|
||||
MQTT_NEST_1: 'Nested in a single topic',
|
||||
MQTT_NEST_2: 'As individual topics',
|
||||
MQTT_RESPONSE: 'Publish command output to a `response` topic',
|
||||
MQTT_PUBLISH_TEXT_1: 'Publish single value topics on change',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publish to command topics (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Enable MQTT Discovery (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix for the Discovery topics',
|
||||
MQTT_PUBLISH_INTERVALS: 'Publish Intervals',
|
||||
MQTT_INT_BOILER: 'Boilers and Heat Pumps',
|
||||
MQTT_INT_THERMOSTATS: 'Thermostats',
|
||||
MQTT_INT_SOLAR: 'Solar Modules',
|
||||
MQTT_INT_MIXER: 'Mixer Modules',
|
||||
MQTT_INT_HEARTBEAT: 'Heartbeat',
|
||||
MQTT_QUEUE: 'MQTT Queue',
|
||||
DEFAULT: 'Default',
|
||||
MQTT_ENTITY_FORMAT: 'Entity ID format',
|
||||
MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)',
|
||||
MQTT_ENTITY_FORMAT_1: 'Single instance, short name',
|
||||
MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name',
|
||||
MQTT_CLEAN_SESSION: 'Set Clean Session',
|
||||
MQTT_RETAIN_FLAG: 'Always set Retain flag',
|
||||
INACTIVE: 'Inactive',
|
||||
ACTIVE: 'Active',
|
||||
UNKNOWN: 'Unknown',
|
||||
SET_TIME: 'Set Time',
|
||||
SET_TIME_TEXT: 'Enter local date and time below to set the time',
|
||||
LOCAL_TIME: 'Local Time',
|
||||
UTC_TIME: 'UTC Time',
|
||||
ENABLE_NTP: 'Enable NTP',
|
||||
NTP_SERVER: 'NTP Server',
|
||||
TIME_ZONE: 'Time Zone',
|
||||
ACCESS_POINT: 'Access Point',
|
||||
AP_PROVIDE: 'Enable Access Point',
|
||||
AP_PROVIDE_TEXT_1: 'always',
|
||||
AP_PROVIDE_TEXT_2: 'when WiFi is disconnected',
|
||||
AP_PROVIDE_TEXT_3: 'never',
|
||||
AP_PREFERRED_CHANNEL: 'Preferred Channel',
|
||||
AP_HIDE_SSID: 'Hide SSID',
|
||||
AP_CLIENTS: 'AP Clients',
|
||||
AP_MAX_CLIENTS: 'Max Clients',
|
||||
AP_LOCAL_IP: 'Local IP',
|
||||
NETWORK_SCAN: 'Scan WiFi Networks',
|
||||
IDLE: 'Idle',
|
||||
LOST: 'Lost',
|
||||
SCANNING: 'Scanning',
|
||||
SCAN_AGAIN: 'Scan again',
|
||||
NETWORK_SCANNER: 'Network Scanner',
|
||||
NETWORK_NO_WIFI: 'No WiFi networks found',
|
||||
NETWORK_BLANK_SSID: 'leave blank to disable WiFi',
|
||||
TX_POWER: 'Tx Power',
|
||||
HOSTNAME: 'Hostname',
|
||||
NETWORK_DISABLE_SLEEP: 'Disable WiFi Sleep Mode',
|
||||
NETWORK_LOW_BAND: 'Use Lower WiFi Bandwidth',
|
||||
NETWORK_USE_DNS: 'Enable mDNS Service',
|
||||
NETWORK_ENABLE_CORS: 'Enable CORS',
|
||||
NETWORK_CORS_ORIGIN: 'CORS origin',
|
||||
NETWORK_ENABLE_IPV6: 'Enable IPv6 support',
|
||||
NETWORK_FIXED_IP: 'Use Fixed IP address',
|
||||
NETWORK_GATEWAY: 'Gateway',
|
||||
NETWORK_SUBNET: 'Subnet Mask',
|
||||
NETWORK_DNS: 'DNS Servers',
|
||||
ADDRESS_OF: '{0} Address',
|
||||
ADMIN: 'Admin',
|
||||
GUEST: 'Guest',
|
||||
NEW: 'New',
|
||||
NEW_NAME_OF: 'New {0} name',
|
||||
ENTITY: 'entity',
|
||||
MIN: 'min',
|
||||
MAX: 'max'
|
||||
};
|
||||
|
||||
export default en;
|
||||
10
interface/src/i18n/formatters.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { FormattersInitializer } from 'typesafe-i18n';
|
||||
import type { Locales, Formatters } from './i18n-types';
|
||||
|
||||
export const initFormatters: FormattersInitializer<Locales, Formatters> = (locale: Locales) => {
|
||||
const formatters: Formatters = {
|
||||
// add your formatter functions here
|
||||
};
|
||||
|
||||
return formatters;
|
||||
};
|
||||
310
interface/src/i18n/fr/index.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import type { Translation } from '../i18n-types';
|
||||
/* prettier-ignore */
|
||||
/* eslint-disable */
|
||||
|
||||
const fr: Translation = {
|
||||
LANGUAGE: 'Langue',
|
||||
RETRY: 'Réessayer',
|
||||
LOADING: 'Chargement',
|
||||
IS_REQUIRED: '{0} est requis',
|
||||
SIGN_IN: 'Se connecter',
|
||||
SIGN_OUT: 'Se déconnecter',
|
||||
USERNAME: 'Nom d\'utilisateur',
|
||||
PASSWORD: 'Mot de passe',
|
||||
SU_PASSWORD: 'Mot de passe su',
|
||||
DASHBOARD: 'Tableau de bord',
|
||||
SETTINGS_OF: 'Paramètres {0}',
|
||||
SAVED: 'sauvegardé',
|
||||
HELP_OF: 'Aide {0}',
|
||||
LOGGED_IN: 'Connecté en tant que {name}',
|
||||
PLEASE_SIGNIN: 'Veuillez vous connecter pour continuer',
|
||||
UPLOAD_SUCCESSFUL: 'Upload terminée',
|
||||
DOWNLOAD_SUCCESSFUL: 'Téléchargement terminé',
|
||||
INVALID_LOGIN: 'Informations de connexion invalides',
|
||||
NETWORK: 'Réseau',
|
||||
SECURITY: 'Sécurité',
|
||||
ONOFF_CAP: 'ON/OFF',
|
||||
ONOFF: 'on/off',
|
||||
TYPE: 'Type',
|
||||
DESCRIPTION: 'Description',
|
||||
ENTITIES: 'Entités',
|
||||
REFRESH: 'Rafraîchir',
|
||||
EXPORT: 'Exporter',
|
||||
DEVICE_DETAILS: 'Détails de l\'appareil',
|
||||
ID_OF: 'ID {0}',
|
||||
DEVICE: 'Appareil',
|
||||
PRODUCT: 'Produit',
|
||||
VERSION: 'Version',
|
||||
BRAND: 'Marque',
|
||||
ENTITY_NAME: 'Nom de l\'entité',
|
||||
VALUE: 'Valeur',
|
||||
SHOW_FAV: 'ne montrer que les favoris',
|
||||
DEVICE_SENSOR_DATA: 'Données des appareils et capteurs',
|
||||
DEVICES_SENSORS: 'Appareils et capteurs',
|
||||
ATTACHED_SENSORS: 'Capteurs EMS-ESP connectés',
|
||||
RUN_COMMAND: 'Lancer une commande',
|
||||
CHANGE_VALUE: 'Changer la valeur',
|
||||
CANCEL: 'Annuler',
|
||||
RESET: 'Réinitialiser',
|
||||
SEND: 'Envoyer',
|
||||
SAVE: 'Sauvegarder',
|
||||
REMOVE: 'Enlever',
|
||||
PROBLEM_UPDATING: 'Problème lors de la mise à jour',
|
||||
PROBLEM_LOADING: 'Problème lors du chargement',
|
||||
ACCESS_DENIED: 'Accès refusé',
|
||||
ANALOG_SENSOR: 'Capteur analogique',
|
||||
ANALOG_SENSORS: 'Capteurs analogiques',
|
||||
UPDATED_OF: '{0} mis à jour',
|
||||
UPDATE_OF: 'Mise à jour de {0}',
|
||||
REMOVED_OF: '{0} enlevé',
|
||||
DELETION_OF: '{0} supprimé',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Facteur',
|
||||
FREQ: 'Fréquence',
|
||||
DUTY_CYCLE: 'Cycle de fonctionnement',
|
||||
UNIT: 'Unité',
|
||||
STARTVALUE: 'Valeur de départ',
|
||||
WARN_GPIO: 'Attention: soyez vigilant en choisissant un GPIO!',
|
||||
EDIT: 'Éditer',
|
||||
SENSOR: 'Capteur',
|
||||
TEMP_SENSOR: 'Capteur de température',
|
||||
TEMP_SENSORS: 'Capteurs de température',
|
||||
WRITE_CMD_SENT: 'Envoyer la commande sent', // TODO translate
|
||||
WRITE_CMD_FAILED: 'Envoyer la commande failed', // TODO translate
|
||||
EMS_BUS_WARNING: 'Bus EMS déconnecté. Si ce message persiste après quelques secondes, vérifiez les paramètres et la configuration de la carte.',
|
||||
EMS_BUS_SCANNING: 'Scan des appareils EMS...',
|
||||
CONNECTED: 'Connecté',
|
||||
TX_ISSUES: 'Problèmes de transmission (Tx) - Essayez un autre mode Tx',
|
||||
DISCONNECTED: 'Déconnecté',
|
||||
EMS_SCAN: 'Etes-vous sûr de vouloir lancer un scan complet du bus EMS ?',
|
||||
EMS_BUS_STATUS: 'Statut du bus EMS',
|
||||
ACTIVE_DEVICES: 'Appareils et capteurs actifs',
|
||||
EMS_DEVICE: 'Appareils EMS',
|
||||
SUCCESS: 'SUCCÈS',
|
||||
FAIL: 'ÉCHEC',
|
||||
QUALITY: 'QUALITÉ',
|
||||
SCAN_DEVICES: 'Rechercher de nouveaux appareils',
|
||||
EMS_BUS_STATUS_TITLE: 'Statut du bus et de l\'activité EMS',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'Télégrammes EMS reçus (Rx)',
|
||||
'Lectures EMS (Tx)',
|
||||
'Écritures EMS (Tx)',
|
||||
'Lectures capteurs de température',
|
||||
'Lectures capteurs analogiques',
|
||||
'Publications MQTT',
|
||||
'Appels à l\'API',
|
||||
'Messages Syslog'
|
||||
],
|
||||
NUM_DEVICES: '{num} Appareil{{s}}',
|
||||
NUM_TEMP_SENSORS: '{num} Capteur{{s}} de température',
|
||||
NUM_ANALOG_SENSORS: '{num} Capteur{{s}} analogique{{s}}',
|
||||
NUM_DAYS: '{num} jour{{s}}',
|
||||
NUM_SECONDS: '{num} seconde{{s}}',
|
||||
NUM_HOURS: '{num} heure{{s}}',
|
||||
NUM_MINUTES: '{num} minute{{s}}',
|
||||
APPLICATION_SETTINGS: 'Paramètres de l\'application',
|
||||
CUSTOMIZATION: 'Personnalisation',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP redémarre',
|
||||
INTERFACE_BOARD_PROFILE: 'Profile de carte d\'interface',
|
||||
BOARD_PROFILE_TEXT: 'Sélectionnez un profil de carte d\'interface préconfiguré dans la liste ci-dessous ou choisissez Personnalisé pour configurer vos propres paramètres matériels',
|
||||
BOARD_PROFILE: 'Profil de carte',
|
||||
CUSTOM: 'Personnalisé',
|
||||
GPIO_OF: 'GPIO {0}',
|
||||
BUTTON: 'Bouton',
|
||||
TEMPERATURE: 'Température',
|
||||
PHY_TYPE: 'Eth PHY Type',
|
||||
DISABLED: 'désactivé',
|
||||
TX_MODE: 'Tx Mode',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
GENERAL_OPTIONS: 'Options générales',
|
||||
LANGUAGE_ENTITIES: 'Langue (pour les entités du matériel)',
|
||||
HIDE_LED: 'Cacher la LED',
|
||||
ENABLE_TELNET: 'Activer la console Telnet',
|
||||
ENABLE_ANALOG: 'Activer les capteurs analogiques',
|
||||
CONVERT_FAHRENHEIT: 'Convertir les températures en Fahrenheit',
|
||||
BYPASS_TOKEN: 'Contourner l\'autorisation du jeton d\'accès sur les appels API',
|
||||
READONLY: 'Activer le mode lecture uniquement (bloque toutes les commandes EMS sortantes en écriture Tx)',
|
||||
UNDERCLOCK_CPU: 'Underclock du CPU',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Activer la minuterie de la douche',
|
||||
ENABLE_SHOWER_ALERT: 'Activer les alertes de durée de douche',
|
||||
TRIGGER_TIME: 'Durée avant déclenchement',
|
||||
COLD_SHOT_DURATION: 'Durée du coup d\'eau froide',
|
||||
FORMATTING_OPTIONS: 'Options de mise en forme',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Tableau de bord du format booléen',
|
||||
BOOLEAN_FORMAT_API: 'Format booléen API/MQTT',
|
||||
ENUM_FORMAT: 'Format enum API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Activer la puissance parasite',
|
||||
LOGGING: 'Journal',
|
||||
LOG_HEX: 'Enregistrer les télégrammes EMS en hexadécimal',
|
||||
ENABLE_SYSLOG: 'Activer les logs système',
|
||||
LOG_LEVEL: 'Niveau de log',
|
||||
MARK_INTERVAL: 'Intervalle de marquage',
|
||||
SECONDS: 'secondes',
|
||||
MINUTES: 'minutes',
|
||||
HOURS: 'heures',
|
||||
RESTART: 'Redémarrer',
|
||||
RESTART_TEXT: 'EMS-ESP a besoin de redémarrer pour appliquer les changements de paramètres du système',
|
||||
RESTART_CONFIRM: 'Etes-vous sûr de vouloir redémarrer EMS-ESP ?',
|
||||
COMMAND: 'Commande',
|
||||
CUSTOMIZATIONS_RESTART: 'Toutes les personnalisations ont été supprimées. Redémarrage...',
|
||||
CUSTOMIZATIONS_FULL: 'Les entités sélectionnées ont dépassé la limite. Veuillez sauvegarder par lots',
|
||||
CUSTOMIZATIONS_SAVED: 'Personnalisations enregistrées',
|
||||
CUSTOMIZATIONS_HELP_1: 'Sélectionnez un appareil et personnalisez les options des entités ou cliquez pour renommer',
|
||||
CUSTOMIZATIONS_HELP_2: 'marquer comme favori',
|
||||
CUSTOMIZATIONS_HELP_3: 'désactiver l\'action d\'écriture',
|
||||
CUSTOMIZATIONS_HELP_4: 'exclure de MQTT et de l\'API',
|
||||
CUSTOMIZATIONS_HELP_5: 'cacher du Tableau de bord',
|
||||
CUSTOMIZATIONS_HELP_6: 'remove from memory',
|
||||
SELECT_DEVICE: 'Sélectionnez un appareil',
|
||||
SET_ALL: 'tout régler',
|
||||
OPTIONS: 'Options',
|
||||
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 ?',
|
||||
DEVICE_ENTITIES: 'Entités de l\'appareil',
|
||||
USER_CUSTOMIZATION: 'Personnalisation de l\'utilisateur',
|
||||
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_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_4: 'n\'oubliez pas de télécharger et de joindre les informations relatives à votre système pour obtenir une réponse plus rapide lorsque vous signalez un problème',
|
||||
HELP_INFORMATION_5: 'EMS-ESP est un projet libre et open-source. Merci de soutenir son développement futur en lui donnant une étoile sur Github !',
|
||||
SUPPORT_INFO: 'Information de support',
|
||||
UPLOAD_OF: 'Upload de {0}',
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: 'Download',
|
||||
ABORTED: 'annulé',
|
||||
FAILED: 'échoué',
|
||||
SUCCESSFUL: 'réussi',
|
||||
SYSTEM: 'Système',
|
||||
LOG_OF: '{0} Log',
|
||||
STATUS_OF: 'Statut {0}',
|
||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||
SYSTEM_VERSION_RUNNING: 'Vous utilisez actuellement la version',
|
||||
SYSTEM_APPLY_FIRMWARE: 'pour appliquer le nouveau firmware',
|
||||
CLOSE: 'Fermer',
|
||||
USE: 'Utiliser',
|
||||
FACTORY_RESET: 'Réinitialisation',
|
||||
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 ?',
|
||||
VERSION_CHECK: 'Vérification de la version',
|
||||
THE_LATEST: 'La dernière',
|
||||
OFFICIAL: 'officielle',
|
||||
DEVELOPMENT: 'développement',
|
||||
VERSION_IS: 'version est',
|
||||
RELEASE_NOTES: 'notes de version',
|
||||
EMS_ESP_VER: 'Version EMS-ESP',
|
||||
PLATFORM: 'Appareil (Plateforme / SDK)',
|
||||
UPTIME: 'Durée de fonctionnement du système',
|
||||
CPU_FREQ: 'Fréquence du CPU',
|
||||
HEAP: 'Heap (Libre / Max Allouée)',
|
||||
PSRAM: 'PSRAM (Taille / Libre)',
|
||||
FLASH: 'Flash Chip (Taille / Vitesse)',
|
||||
APPSIZE: 'Application (Utilisée / Libre)',
|
||||
FILESYSTEM: 'File System (Utilisée / Libre)',
|
||||
BUFFER_SIZE: 'Max taille du buffer',
|
||||
COMPACT: 'Compact',
|
||||
ENABLE_OTA: 'Activer les updates OTA',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Télécharger les personnalisations d\'entités',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Téléchargez les paramètres de l\'application. Soyez prudent lorsque vous partagez vos paramètres car ce fichier contient des mots de passe et d\'autres informations système sensibles.',
|
||||
UPLOAD_TEXT: 'Téléchargez un nouveau fichier de firmware (.bin), un fichier de paramètres ou de personnalisations (.json) ci-dessous, pour une validation optionnelle téléchargez d\'abord un fichier (.md5)',
|
||||
UPLOADING: 'Téléchargement',
|
||||
UPLOAD_DROP_TEXT: 'Déposer le fichier ou cliquer ici',
|
||||
ERROR: 'Erreur inattendue, veuillez réessayer',
|
||||
TIME_SET: 'Time set',
|
||||
MANAGE_USERS: 'Gérer les utilisateurs',
|
||||
IS_ADMIN: 'admin',
|
||||
USER_WARNING: 'Vous devez avoir au moins un utilisateur admin configuré',
|
||||
ADD: 'Ajouter',
|
||||
ACCESS_TOKEN_FOR: 'Jeton d\'accès pour',
|
||||
ACCESS_TOKEN_TEXT: 'Le jeton ci-dessous est utilisé avec les appels d\'API REST qui nécessitent une autorisation. Il peut être passé soit en tant que jeton Bearer dans l\'en-tête Authorization, soit dans le paramètre de requête URL access_token.',
|
||||
GENERATING_TOKEN: 'Génération de jeton',
|
||||
USER: 'Utilisateur',
|
||||
MODIFY: 'Modifier',
|
||||
SU_TEXT: 'Le mot de passe su (super utilisateur) est utilisé pour signer les jetons d\'authentification et activer les privilèges d\'administrateur dans la console.',
|
||||
NOT_ENABLED: 'Non activé',
|
||||
ERRORS_OF: 'Erreurs {0}',
|
||||
DISCONNECT_REASON: 'Raison de la déconnexion',
|
||||
ENABLE_MQTT: 'Activer le MQTT',
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Optionnel',
|
||||
FORMATTING: 'Mise en forme',
|
||||
MQTT_FORMAT: 'Format du Topic/Payload',
|
||||
MQTT_NEST_1: 'Englobé dans un topic unique',
|
||||
MQTT_NEST_2: 'En tant que topics individuels',
|
||||
MQTT_RESPONSE: 'Publier le résultat des commandes dans un topic `response`',
|
||||
MQTT_PUBLISH_TEXT_1: 'Publier des topics à valeur unique sur changement',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publier vers des topics de commande (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Activer la découverte MQTT (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Préfixe pour les topics découverte',
|
||||
MQTT_PUBLISH_INTERVALS: 'Intervalles de publication',
|
||||
MQTT_INT_BOILER: 'Chaudières et pompes à chaleur',
|
||||
MQTT_INT_THERMOSTATS: 'Thermostats',
|
||||
MQTT_INT_SOLAR: 'Modules solaires',
|
||||
MQTT_INT_MIXER: 'Modules mélangeurs',
|
||||
MQTT_INT_HEARTBEAT: 'Battements',
|
||||
MQTT_QUEUE: 'Queue MQTT',
|
||||
DEFAULT: 'Défaut',
|
||||
MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate
|
||||
MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)',// TODO translate
|
||||
MQTT_ENTITY_FORMAT_1: 'Single instance, short name', // TODO translate
|
||||
MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name', // TODO translate
|
||||
MQTT_CLEAN_SESSION: 'Flag Clean Session',
|
||||
MQTT_RETAIN_FLAG: 'Toujours activer le Retain Flag',
|
||||
INACTIVE: 'Inactif',
|
||||
ACTIVE: 'Actif',
|
||||
UNKNOWN: 'Inconnu',
|
||||
SET_TIME: 'Définir l\'heure',
|
||||
SET_TIME_TEXT: 'Entrer la date et l\'heure locale ci-dessous pour régler l\'heure',
|
||||
LOCAL_TIME: 'Heure locale',
|
||||
UTC_TIME: 'Heure UTC',
|
||||
ENABLE_NTP: 'Activer le NTP',
|
||||
NTP_SERVER: 'Serveur NTP',
|
||||
TIME_ZONE: 'Fuseau horaire',
|
||||
ACCESS_POINT: 'Point d\'accès',
|
||||
AP_PROVIDE: 'Activer le Point d\'Accès',
|
||||
AP_PROVIDE_TEXT_1: 'toujours',
|
||||
AP_PROVIDE_TEXT_2: 'quand le WiFi est déconnecté',
|
||||
AP_PROVIDE_TEXT_3: 'jamais',
|
||||
AP_PREFERRED_CHANNEL: 'Canal préféré',
|
||||
AP_HIDE_SSID: 'Cacher le SSID',
|
||||
AP_CLIENTS: 'AP Clients',
|
||||
AP_MAX_CLIENTS: 'Max Clients',
|
||||
AP_LOCAL_IP: 'IP locale',
|
||||
NETWORK_SCAN: 'Scanner les réseaux WiFi',
|
||||
IDLE: 'Inactif',
|
||||
LOST: 'Perdu',
|
||||
SCANNING: 'Scan en cours',
|
||||
SCAN_AGAIN: 'Rescanner',
|
||||
NETWORK_SCANNER: 'Scan réseau',
|
||||
NETWORK_NO_WIFI: 'Pas de réseau WiFi trouvé',
|
||||
NETWORK_BLANK_SSID: 'laisser vide pour désactiver le WiFi',
|
||||
TX_POWER: 'Puissance Tx',
|
||||
HOSTNAME: 'Nom d\'hôte',
|
||||
NETWORK_DISABLE_SLEEP: 'Désactiver le mode veille du WiFi',
|
||||
NETWORK_LOW_BAND: 'Utiliser une bande passante WiFi plus faible',
|
||||
NETWORK_USE_DNS: 'Activer le service mDNS',
|
||||
NETWORK_ENABLE_CORS: 'Activer CORS',
|
||||
NETWORK_CORS_ORIGIN: 'Origine CORS',
|
||||
NETWORK_ENABLE_IPV6: 'Activer le support de l\'IPv6',
|
||||
NETWORK_FIXED_IP: 'Utiliser une adresse IP fixe',
|
||||
NETWORK_GATEWAY: 'Passerelle',
|
||||
NETWORK_SUBNET: 'Masque de sous-réseau',
|
||||
NETWORK_DNS: 'Serveurs DNS',
|
||||
ADDRESS_OF: 'Adresse de {0}',
|
||||
ADMIN: 'Admin',
|
||||
GUEST: 'Invité',
|
||||
NEW: 'Nouveau',
|
||||
NEW_NAME_OF: 'Nouveau nom de {0}',
|
||||
ENTITY: 'entité',
|
||||
MIN: 'min',
|
||||
MAX: 'max'
|
||||
};
|
||||
|
||||
export default fr;
|
||||
310
interface/src/i18n/nl/index.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import type { Translation } from '../i18n-types';
|
||||
/* prettier-ignore */
|
||||
/* eslint-disable */
|
||||
|
||||
const nl: Translation = {
|
||||
LANGUAGE: 'Taal',
|
||||
RETRY: 'Opnieuw proberen',
|
||||
LOADING: 'Laden',
|
||||
IS_REQUIRED: '{0} is verplicht',
|
||||
SIGN_IN: 'Inloggen',
|
||||
SIGN_OUT: 'Uitloggen',
|
||||
USERNAME: 'Gebruikersnaam',
|
||||
PASSWORD: 'Wachtwoord',
|
||||
SU_PASSWORD: 'su Wachtwoord',
|
||||
DASHBOARD: 'Dashboard',
|
||||
SETTINGS_OF: '{0} Instellingen',
|
||||
SAVED: 'opgeslagen',
|
||||
HELP_OF: '{0} Help',
|
||||
LOGGED_IN: 'Ingelogd als {name}',
|
||||
PLEASE_SIGNIN: 'Log in om verder te gaan',
|
||||
UPLOAD_SUCCESSFUL: 'Upload successvol',
|
||||
DOWNLOAD_SUCCESSFUL: 'Download successvol',
|
||||
INVALID_LOGIN: 'Logingegevens fout',
|
||||
NETWORK: 'Netwerk',
|
||||
SECURITY: 'Beveiliging',
|
||||
ONOFF_CAP: 'AAN/UIT',
|
||||
ONOFF: 'aan/uit',
|
||||
TYPE: 'Type',
|
||||
DESCRIPTION: 'Beschrijving',
|
||||
ENTITIES: 'Entiteiten',
|
||||
REFRESH: 'Ververs',
|
||||
EXPORT: 'Export',
|
||||
DEVICE_DETAILS: 'Device Gegevens',
|
||||
ID_OF: '{0} ID',
|
||||
DEVICE: 'Apparaat',
|
||||
PRODUCT: 'Product',
|
||||
VERSION: 'Versie',
|
||||
BRAND: 'Merk',
|
||||
ENTITY_NAME: 'Entiteit',
|
||||
VALUE: '{{Waarde|waarde}}',
|
||||
SHOW_FAV: 'alleen favorieten weergeven',
|
||||
DEVICE_SENSOR_DATA: 'Apparaat en Sensor data',
|
||||
DEVICES_SENSORS: 'Apparaten & Sensoren',
|
||||
ATTACHED_SENSORS: 'Aangesloten EMS-ESP sensoren',
|
||||
RUN_COMMAND: 'Call commando',
|
||||
CHANGE_VALUE: 'Wijzig waarde',
|
||||
CANCEL: 'Annuleren',
|
||||
RESET: 'Reset',
|
||||
SEND: 'Verzenden',
|
||||
SAVE: 'Opslaan',
|
||||
REMOVE: 'Verwijderen',
|
||||
PROBLEM_UPDATING: 'Probleem met updaten',
|
||||
PROBLEM_LOADING: 'Probleem met laden',
|
||||
ACCESS_DENIED: 'Toegang geweigerd',
|
||||
ANALOG_SENSOR: 'Analoge sensor',
|
||||
ANALOG_SENSORS: 'Analoge Sensoren',
|
||||
UPDATED_OF: '{0} Bijgewerkt',
|
||||
UPDATE_OF: '{0} Bijwerken',
|
||||
REMOVED_OF: '{0} Verwijderd',
|
||||
DELETION_OF: '{0} Verwijder',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Factor',
|
||||
FREQ: 'Frequentie',
|
||||
DUTY_CYCLE: 'Duty Cycle',
|
||||
UNIT: 'UoM',
|
||||
STARTVALUE: 'Startwaarde',
|
||||
WARN_GPIO: 'Waarschuwing: let op met het koppelen van de juiste GPIO pin!',
|
||||
EDIT: 'Wijzigen',
|
||||
SENSOR: 'Sensor',
|
||||
TEMP_SENSOR: 'Temperatuur sensor',
|
||||
TEMP_SENSORS: 'Temperatuur Sensoren',
|
||||
WRITE_CMD_SENT: 'Schrijf commando sent', // TODO translate
|
||||
WRITE_CMD_FAILED: 'Schrijf commando failed', // TODO translate
|
||||
EMS_BUS_WARNING: 'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden dan loop de instellingen na en in het bijzonder het apparaat type profiel na.',
|
||||
EMS_BUS_SCANNING: 'Scannen naar EMS apparaten...',
|
||||
CONNECTED: 'Verbonden',
|
||||
TX_ISSUES: 'Tx bus probleem. Probeer een andere Tx verzendmodus',
|
||||
DISCONNECTED: 'Niet verbonden',
|
||||
EMS_SCAN: 'Weet je zeker dat je een volledige EMS bus scan uit wilt voeren?',
|
||||
EMS_BUS_STATUS: 'EMS busstatus',
|
||||
ACTIVE_DEVICES: 'Actieve Apparaten & Sensoren',
|
||||
EMS_DEVICE: 'EMS Apparaat',
|
||||
SUCCESS: 'SUCCESS',
|
||||
FAIL: 'MISLUKT',
|
||||
QUALITY: 'QUALITEIT',
|
||||
SCAN_DEVICES: 'Scannen naar nieuwe apparaten',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS Bus & Activiteitenstatus',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegrammen ontvangen (Rx)',
|
||||
'EMS Leesopdrachten (Tx)',
|
||||
'EMS Schrijfopdrachten (Tx)',
|
||||
'Temperatuursensoren uitgelezen',
|
||||
'Analoge sensoren uitgelezen',
|
||||
'MQTT publicaties',
|
||||
'API calls',
|
||||
'Syslog berichten'
|
||||
],
|
||||
NUM_DEVICES: '{num} Apparaat{{en}}',
|
||||
NUM_TEMP_SENSORS: '{num} Temperatuursensor{{en}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analoge sensor{{en}}',
|
||||
NUM_DAYS: '{num} dag{{en}}',
|
||||
NUM_SECONDS: '{num} second{{en}}',
|
||||
NUM_HOURS: '{num} {{uur|uren}}',
|
||||
NUM_MINUTES: '{num} {{minuut|minuten}}',
|
||||
APPLICATION_SETTINGS: 'Applicatieinstellingen',
|
||||
CUSTOMIZATION: 'Custom aanpassingen',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP herstarten',
|
||||
INTERFACE_BOARD_PROFILE: 'Interface Apparaatprofiel',
|
||||
BOARD_PROFILE_TEXT: 'Selecteer een vooraf ingesteld apparaat profiel uit de lijst of kies Eigen om zelf uw hardware te configureren',
|
||||
BOARD_PROFILE: 'Apparaatprofiel',
|
||||
CUSTOM: 'Custom',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
BUTTON: 'Toets',
|
||||
TEMPERATURE: 'Temperatuur',
|
||||
PHY_TYPE: 'Eth PHY Type',
|
||||
TX_MODE: 'Tx Mode',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
DISABLED: 'Uitgeschakeld',
|
||||
GENERAL_OPTIONS: 'Algemene Opties',
|
||||
LANGUAGE_ENTITIES: 'Taal (voor apparaat entiteiten)',
|
||||
HIDE_LED: 'Verberg LED',
|
||||
ENABLE_TELNET: 'Activeer Telnet console',
|
||||
ENABLE_ANALOG: 'Activeer analoge sensoren',
|
||||
CONVERT_FAHRENHEIT: 'Converteer temperatuurwaarden naar Fahrenheit',
|
||||
BYPASS_TOKEN: 'API Access Token authenticatie uitschakelen',
|
||||
READONLY: 'Activeer read-only modus (blokkeert alle outgaande EMS Tx schrijf commandos)',
|
||||
UNDERCLOCK_CPU: 'Underclock CPU snelheid',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)',
|
||||
ENABLE_SHOWER_ALERT: 'Activeer Douchemelding',
|
||||
TRIGGER_TIME: 'Trigger tijd',
|
||||
COLD_SHOT_DURATION: 'Tijd Shot koud water',
|
||||
FORMATTING_OPTIONS: 'Formatteringsopties',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolean formaat dashboard',
|
||||
BOOLEAN_FORMAT_API: 'Boolean formaat API/MQTT',
|
||||
ENUM_FORMAT: 'Enum formaat API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Activeer Dallas parasitaire modus',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Log EMS telegrammen in hexadecimaal',
|
||||
ENABLE_SYSLOG: 'Activeer Syslog',
|
||||
LOG_LEVEL: 'Log Level',
|
||||
MARK_INTERVAL: 'Markeringsinterval',
|
||||
SECONDS: 'seconden',
|
||||
MINUTES: 'minuten',
|
||||
HOURS: 'uren',
|
||||
RESTART: 'Herstarten',
|
||||
RESTART_TEXT: 'EMS-ESP dient opnieuw gestart te worden om de wijzingen toe te passen',
|
||||
RESTART_CONFIRM: 'Weet je zeker dat je EMS-ESP wilt herstarten?',
|
||||
COMMAND: 'Commando',
|
||||
CUSTOMIZATIONS_RESTART: 'Alle custom profielen worden verwijderd. Herstarten...',
|
||||
CUSTOMIZATIONS_FULL: 'Te veel entiteiten geselecteerd. Sla op in delen aub',
|
||||
CUSTOMIZATIONS_SAVED: 'Custom aanpassingen opgeslagen',
|
||||
CUSTOMIZATIONS_HELP_1: 'Selecteer een apparaat en pas de entiteiten aan door middel van de opties',
|
||||
CUSTOMIZATIONS_HELP_2: 'Markeer as favoriet',
|
||||
CUSTOMIZATIONS_HELP_3: 'Zet schrijfacties uit',
|
||||
CUSTOMIZATIONS_HELP_4: 'Uitsluiten van MQTT en API',
|
||||
CUSTOMIZATIONS_HELP_5: 'verberg van het Dashboard',
|
||||
CUSTOMIZATIONS_HELP_6: 'remove from memory',
|
||||
SELECT_DEVICE: 'Selecteer een apparaat',
|
||||
SET_ALL: 'Alles aanzetten',
|
||||
OPTIONS: 'Opties',
|
||||
NAME: 'Naam',
|
||||
CUSTOMIZATIONS_RESET: 'Weet je zeker dat je alle custom aanpassingen wilt verwijderen inclusief de custom instellingen voor analoge temperatuursensoren?',
|
||||
DEVICE_ENTITIES: 'Apparaat Entiteiten',
|
||||
USER_CUSTOMIZATION: 'Custom Instellingen',
|
||||
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_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_4: 'zorg dat je ook je systeem details zijn toevoeged voor een sneller antwoord',
|
||||
HELP_INFORMATION_5: 'EMS-ESP is een gratis en open source project. Steun ons met een Star op Github!',
|
||||
SUPPORT_INFO: 'Support Info',
|
||||
UPLOAD_OF: '{0} Upload',
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: 'Download',
|
||||
ABORTED: 'afgebroken',
|
||||
FAILED: 'mislukt',
|
||||
SUCCESSFUL: 'successvol',
|
||||
SYSTEM: 'Systeem',
|
||||
LOG_OF: '{0} Log',
|
||||
STATUS_OF: '{0} Status',
|
||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||
SYSTEM_VERSION_RUNNING: 'op dit moment draai je versie',
|
||||
SYSTEM_APPLY_FIRMWARE: 'om de nieuwe firmware te activeren',
|
||||
CLOSE: 'Sluiten',
|
||||
USE: 'Gebruik',
|
||||
FACTORY_RESET: '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?',
|
||||
VERSION_CHECK: 'Versie Check',
|
||||
THE_LATEST: 'De laatste',
|
||||
OFFICIAL: 'official',
|
||||
DEVELOPMENT: 'development',
|
||||
VERSION_IS: 'versie is',
|
||||
RELEASE_NOTES: 'release notes',
|
||||
EMS_ESP_VER: 'EMS-ESP Version',
|
||||
PLATFORM: 'Apparaat (Platform / SDK)',
|
||||
UPTIME: 'Systeem Uptime',
|
||||
CPU_FREQ: 'CPU Frequency',
|
||||
HEAP: 'Heap (Free / Max Alloc)',
|
||||
PSRAM: 'PSRAM (Size / Free)',
|
||||
FLASH: 'Flash Chip (Size / Speed)',
|
||||
APPSIZE: 'Application (Used / Free)',
|
||||
FILESYSTEM: 'File System (Used / Free)',
|
||||
BUFFER_SIZE: 'Max Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
ENABLE_OTA: 'Acitveer OTA Updates',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download alle custom instellingen',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Download de applicatie settings. Wees voorzichting met het delen van dit bestand want het bevat o.a. de wachtwoorden in plain text',
|
||||
UPLOAD_TEXT: 'Upload een nieuwe firmware (.bin) file, instellingen of custom instellingen (.json) bestand hieronder',
|
||||
UPLOADING: 'Uploading',
|
||||
UPLOAD_DROP_TEXT: 'Sleep bestand hierheen of klik hier',
|
||||
ERROR: 'Onverwachte fout, probeer opnieuw',
|
||||
TIME_SET: 'Tijd ingesteld',
|
||||
MANAGE_USERS: 'Beheer Gebruikers',
|
||||
IS_ADMIN: 'is Admin',
|
||||
USER_WARNING: 'U dient tenminste 1 admin gebruiker te configureren',
|
||||
ADD: 'Toevoegen',
|
||||
ACCESS_TOKEN_FOR: 'Access Token voor',
|
||||
ACCESS_TOKEN_TEXT: 'Het token hieronder wordt gebruikt voor de REST API calls die authorisatie nodig hebben. Het kan zowel als Bearer token in de Authorization header of in acccess_token URL query parameter gebruikt worden',
|
||||
GENERATING_TOKEN: 'Token aan het genereren',
|
||||
USER: 'Gebruiker',
|
||||
MODIFY: 'Aanpassen',
|
||||
SU_TEXT: 'Het su (super user) wachtwoord wordt gebruikt om authorisatie tokens te signeren en ook om admin privileges te activeren in de console.',
|
||||
NOT_ENABLED: 'Niet geactiveerd',
|
||||
ERRORS_OF: '{0} Foutmeldingen',
|
||||
DISCONNECT_REASON: 'Verbinding verbroken vanwege',
|
||||
ENABLE_MQTT: 'Activeer MQTT',
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Optioneel',
|
||||
FORMATTING: 'Formatteren',
|
||||
MQTT_FORMAT: 'Topic/Payload Formattering',
|
||||
MQTT_NEST_1: 'Genest in 1 topic',
|
||||
MQTT_NEST_2: 'Als individuele topics',
|
||||
MQTT_RESPONSE: 'Publiceer commando output naar een `response` topic',
|
||||
MQTT_PUBLISH_TEXT_1: 'Publiceer enkele waarde topics on change',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publiceer naar commando topics (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Activeer MQTT Discovery (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix voor de Discovery topics',
|
||||
MQTT_PUBLISH_INTERVALS: 'Publicatie intervallen',
|
||||
MQTT_INT_BOILER: 'CV ketels en warmtepompen',
|
||||
MQTT_INT_THERMOSTATS: 'Thermostaten',
|
||||
MQTT_INT_SOLAR: 'Solar Modules',
|
||||
MQTT_INT_MIXER: 'Mixer Modules',
|
||||
MQTT_INT_HEARTBEAT: 'Heartbeat',
|
||||
MQTT_QUEUE: 'MQTT Queue',
|
||||
DEFAULT: 'Default',
|
||||
MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate
|
||||
MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)', // TODO translate
|
||||
MQTT_ENTITY_FORMAT_1: 'Single instance, short name', // TODO translate
|
||||
MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name', // TODO translate
|
||||
MQTT_CLEAN_SESSION: 'Clean Session aan',
|
||||
MQTT_RETAIN_FLAG: 'Retain flag aan',
|
||||
INACTIVE: 'Inactief',
|
||||
ACTIVE: 'Actief',
|
||||
UNKNOWN: 'Onbekend',
|
||||
SET_TIME: 'Tijd instellen',
|
||||
SET_TIME_TEXT: 'Geef de locale datum en tijd in',
|
||||
LOCAL_TIME: 'Locale Tijd',
|
||||
UTC_TIME: 'UTC Tijd',
|
||||
ENABLE_NTP: 'Activeer NTP',
|
||||
NTP_SERVER: 'NTP Server',
|
||||
TIME_ZONE: 'Tijdzone',
|
||||
ACCESS_POINT: 'Access Point',
|
||||
AP_PROVIDE: 'Activeer Access Point',
|
||||
AP_PROVIDE_TEXT_1: 'altijd',
|
||||
AP_PROVIDE_TEXT_2: 'als WiFi niet is verbonden',
|
||||
AP_PROVIDE_TEXT_3: 'nooit',
|
||||
AP_PREFERRED_CHANNEL: 'Voorkeurskanaal',
|
||||
AP_HIDE_SSID: 'SSID verbergen',
|
||||
AP_CLIENTS: 'AP Clients',
|
||||
AP_MAX_CLIENTS: 'Max Clients',
|
||||
AP_LOCAL_IP: 'Local IP',
|
||||
NETWORK_SCAN: 'Scan WiFi Networken',
|
||||
IDLE: 'Idle',
|
||||
LOST: 'Verloren',
|
||||
SCANNING: 'Scannen',
|
||||
SCAN_AGAIN: 'Opnieuw scannen',
|
||||
NETWORK_SCANNER: 'Netwerk Scanner',
|
||||
NETWORK_NO_WIFI: 'Geen WiFi networken gevonden',
|
||||
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen',
|
||||
TX_POWER: 'Tx Vermogen',
|
||||
HOSTNAME: 'Hostname',
|
||||
NETWORK_DISABLE_SLEEP: 'WiFi Sleep Mode uitzetten',
|
||||
NETWORK_LOW_BAND: 'Lagere WiFi bandbreedte gebruiken',
|
||||
NETWORK_USE_DNS: 'Activeer mDNS Service',
|
||||
NETWORK_ENABLE_CORS: 'Activeer CORS',
|
||||
NETWORK_CORS_ORIGIN: 'CORS origin',
|
||||
NETWORK_ENABLE_IPV6: 'Activeer IPv6 support',
|
||||
NETWORK_FIXED_IP: 'Gebruik vast IP addres',
|
||||
NETWORK_GATEWAY: 'Gateway',
|
||||
NETWORK_SUBNET: 'Subnetmasker',
|
||||
NETWORK_DNS: 'DNS Servers',
|
||||
ADDRESS_OF: '{0} Address',
|
||||
ADMIN: 'Admin',
|
||||
GUEST: 'Gast',
|
||||
NEW: 'Nieuwe',
|
||||
NEW_NAME_OF: 'Hernoem {0}',
|
||||
ENTITY: 'Entiteit',
|
||||
MIN: 'min',
|
||||
MAX: 'max'
|
||||
};
|
||||
|
||||
export default nl;
|
||||
310
interface/src/i18n/no/index.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import type { Translation } from '../i18n-types';
|
||||
/* prettier-ignore */
|
||||
/* eslint-disable */
|
||||
|
||||
const no: Translation = {
|
||||
LANGUAGE: 'Språk',
|
||||
RETRY: 'Forsøk igjen',
|
||||
LOADING: 'Laster',
|
||||
IS_REQUIRED: '{0} er nødvendig',
|
||||
SIGN_IN: 'Logg inn',
|
||||
SIGN_OUT: 'Logg ut',
|
||||
USERNAME: 'Brukernavn',
|
||||
PASSWORD: 'Passord',
|
||||
SU_PASSWORD: 'su Passord',
|
||||
DASHBOARD: 'Dashboard',
|
||||
SETTINGS_OF: '{0} Innstillinger',
|
||||
SAVED: 'lagret',
|
||||
HELP_OF: '{0} Hjelp',
|
||||
LOGGED_IN: 'Logget in som {name}',
|
||||
PLEASE_SIGNIN: 'Venligst logge inn for å fortsetta',
|
||||
UPLOAD_SUCCESSFUL: 'Opplasting lykkes',
|
||||
DOWNLOAD_SUCCESSFUL: 'Nedlasting lykkes',
|
||||
INVALID_LOGIN: 'Ugyldig innlogging',
|
||||
NETWORK: 'Nettverk',
|
||||
SECURITY: 'Sikkerhet',
|
||||
ONOFF_CAP: 'PÅ/AV',
|
||||
ONOFF: 'på/av',
|
||||
TYPE: 'Type',
|
||||
DESCRIPTION: 'Beskrivelse',
|
||||
ENTITIES: 'Ojekter',
|
||||
REFRESH: 'Oppdater',
|
||||
EXPORT: 'Eksport',
|
||||
DEVICE_DETAILS: 'Enhetsdetaljer',
|
||||
ID_OF: '{0}-ID',
|
||||
DEVICE: 'Enhets',
|
||||
PRODUCT: 'Produkt',
|
||||
VERSION: 'Versjon',
|
||||
BRAND: 'Fabrikat',
|
||||
ENTITY_NAME: 'Objektsnavn',
|
||||
VALUE: '{{Verdi|verdi}}',
|
||||
SHOW_FAV: ' Vis kun favoritter',
|
||||
DEVICE_SENSOR_DATA: 'Enheter og Sensordata',
|
||||
DEVICES_SENSORS: 'Enheter og Sensorer',
|
||||
ATTACHED_SENSORS: 'Tilkoblede EMS-ESP Sensorer',
|
||||
RUN_COMMAND: 'Kjør kommando',
|
||||
CHANGE_VALUE: 'Endre Verdi',
|
||||
CANCEL: 'Avbryt',
|
||||
RESET: 'Nullstill',
|
||||
SEND: 'Send',
|
||||
SAVE: 'Lagre',
|
||||
REMOVE: 'Fjern',
|
||||
PROBLEM_UPDATING: 'Problem med oppdatering',
|
||||
PROBLEM_LOADING: 'Problem med opplasting',
|
||||
ACCESS_DENIED: 'Tilgang nektet',
|
||||
ANALOG_SENSOR: 'Analog Sensor',
|
||||
ANALOG_SENSORS: 'Analoge Sensorer',
|
||||
UPDATED_OF: '{0} Oppdatert',
|
||||
UPDATE_OF: '{0} Oppdater',
|
||||
REMOVED_OF: '{0} Slettet',
|
||||
DELETION_OF: '{0} Sletting',
|
||||
OFFSET: 'Kompensering',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frekvens',
|
||||
DUTY_CYCLE: 'Duty Cycle',
|
||||
UNIT: 'UoM',
|
||||
STARTVALUE: 'Startverdi',
|
||||
WARN_GPIO: 'Advarsel: vær forsiktig ved aktivering av GPIO!',
|
||||
EDIT: 'Endre',
|
||||
SENSOR: 'Sensor',
|
||||
TEMP_SENSOR: 'Temperatursensor',
|
||||
TEMP_SENSORS: 'Temperaturesensorer',
|
||||
WRITE_CMD_SENT: 'Skriv kommando sent', // TODO translate
|
||||
WRITE_CMD_FAILED: 'Skriv kommando failed', // TODO translate
|
||||
EMS_BUS_WARNING: 'EMS bussen koblet ned. Hvis denne advarselen fortsetter etter noen f¨sekunder sjekk instillinger og prosessorkort',
|
||||
EMS_BUS_SCANNING: 'Søker etter EMS enheter...',
|
||||
CONNECTED: 'Tilkoblet',
|
||||
TX_ISSUES: 'Tx problemer - prøv en annen Tx Modus',
|
||||
DISCONNECTED: 'Frakoblet',
|
||||
EMS_SCAN: 'Er du sikker på du vil starte full søking av EMS bussen?',
|
||||
EMS_BUS_STATUS: 'EMS Buss Status',
|
||||
ACTIVE_DEVICES: 'Aktive Enheter og Sensorer',
|
||||
EMS_DEVICE: 'EMS Enhet',
|
||||
SUCCESS: 'VELLYKKET',
|
||||
FAIL: 'MISLYKKET',
|
||||
QUALITY: 'KVALITET',
|
||||
SCAN_DEVICES: 'Søk etter nye enheter',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS Buss & Aktivitet Status',
|
||||
SCAN: 'Søk',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegrammer Mottatt (Rx)',
|
||||
'EMS Lest (Tx)',
|
||||
'EMS Skrevet (Tx)',
|
||||
'Temperatur Sensor Lest',
|
||||
'Analog Sensor Lest',
|
||||
'MQTT Publiseringer',
|
||||
'API Anrop',
|
||||
'Syslog Meldinger'
|
||||
],
|
||||
NUM_DEVICES: '{num} Enhet{{er}}',
|
||||
NUM_TEMP_SENSORS: '{num} Temperatursensor{{er}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analogsensor{{er}}',
|
||||
NUM_DAYS: '{num} sag{{er}}',
|
||||
NUM_SECONDS: '{num} sekund{{er}}',
|
||||
NUM_HOURS: '{num} time{{r}}',
|
||||
NUM_MINUTES: '{num} minutt{{er}}',
|
||||
APPLICATION_SETTINGS: 'Innstillinger',
|
||||
CUSTOMIZATION: 'Tilpasninger',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP restarter',
|
||||
INTERFACE_BOARD_PROFILE: 'Interface Prosessor Profil',
|
||||
BOARD_PROFILE_TEXT: 'Velg en pre-konfigurert prosessor profil fra listen under eller velg Tilpasset for å konfigurere dine egne innstillinger',
|
||||
BOARD_PROFILE: 'Prosessor Profil',
|
||||
CUSTOM: 'Custom',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
BUTTON: 'Knapp',
|
||||
TEMPERATURE: 'Temperatur',
|
||||
PHY_TYPE: 'Eth PHY Type',
|
||||
DISABLED: 'avslått',
|
||||
TX_MODE: 'Tx Mode',
|
||||
HARDWARE: 'Hardware',
|
||||
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||
GENERAL_OPTIONS: 'Generelle Innstillinger',
|
||||
LANGUAGE_ENTITIES: 'Språk (for objekter)',
|
||||
HIDE_LED: 'Skjul LED',
|
||||
ENABLE_TELNET: 'Aktiver Telnet',
|
||||
ENABLE_ANALOG: 'Aktiver Analoge Sensorer',
|
||||
CONVERT_FAHRENHEIT: 'Konverter temperatur til Fahrenheit',
|
||||
BYPASS_TOKEN: 'Utelat Aksess Token authorisering av API kall',
|
||||
READONLY: 'Aktiver read-only modus (blokker all EMS Tx Skriving)',
|
||||
UNDERCLOCK_CPU: 'Underklokking av prosessorhastighet',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Aktiver Dusjtimer',
|
||||
ENABLE_SHOWER_ALERT: 'Aktiver Dusj-varsling',
|
||||
TRIGGER_TIME: 'Aktiveringstid',
|
||||
COLD_SHOT_DURATION: 'Tid på kaldt vann',
|
||||
FORMATTING_OPTIONS: 'Formatteringsalternativs',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Bool Format Dashboard',
|
||||
BOOLEAN_FORMAT_API: 'Bool Format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Indeks',
|
||||
ENABLE_PARASITE: 'Aktiver parasitt strømforsyning',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Logg EMS telegrammer i hexadesimal',
|
||||
ENABLE_SYSLOG: 'Aktiver Syslog',
|
||||
LOG_LEVEL: 'Log Level',
|
||||
MARK_INTERVAL: 'Oppdateringsintervall',
|
||||
SECONDS: 'sekunder',
|
||||
MINUTES: 'minutter',
|
||||
HOURS: 'timer',
|
||||
RESTART: 'Omstart',
|
||||
RESTART_TEXT: 'EMS-ESP må omstartes for å iverksette endrede systeminstillinger',
|
||||
RESTART_CONFIRM: 'Er du sikker på at du vil omstarte EMS-ESP?',
|
||||
COMMAND: 'Kommando',
|
||||
CUSTOMIZATIONS_RESTART: 'Alle tilpasninger har blitt slettet. Restarter...',
|
||||
CUSTOMIZATIONS_FULL: 'Antall valgte objekter for høyt. Largre i mindre antall om gangen',
|
||||
CUSTOMIZATIONS_SAVED: 'Tilpasninger lagret',
|
||||
CUSTOMIZATIONS_HELP_1: 'Velg en enhet og tilpass underenheter med hjelp av alternativer eller velg å gi nytt navn',
|
||||
CUSTOMIZATIONS_HELP_2: 'merk som favoritt',
|
||||
CUSTOMIZATIONS_HELP_3: 'inaktiviser skriving',
|
||||
CUSTOMIZATIONS_HELP_4: 'ekskludere fra MQTT og API',
|
||||
CUSTOMIZATIONS_HELP_5: 'gjemme fra Dashboard',
|
||||
CUSTOMIZATIONS_HELP_6: 'remove from memory',
|
||||
SELECT_DEVICE: 'Velg en enhet',
|
||||
SET_ALL: 'sett alle',
|
||||
OPTIONS: 'Alternativ',
|
||||
NAME: 'Navn',
|
||||
CUSTOMIZATIONS_RESET: 'Er du sikker på att du vil fjerne tilpassninger inkludert innstillinger for Temperatur og Analoge sensorer?',
|
||||
DEVICE_ENTITIES: 'Enhets objekter',
|
||||
USER_CUSTOMIZATION: 'Brukertilpasninger',
|
||||
SUPPORT_INFORMATION: 'Supportinformasjon',
|
||||
CLICK_HERE: 'Klikk her',
|
||||
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_3: 'For å be om en ny funksjon eller melde feil',
|
||||
HELP_INFORMATION_4: 'husk å laste ned og legg ved din systeminformasjon for en raskere respons når du rapporterer et problem',
|
||||
HELP_INFORMATION_5: 'EMS-ESP er gratis og åpen kildekode. Bidra til utviklingen ved å gi oss en stjerne på GitHub!',
|
||||
SUPPORT_INFO: 'Supportinfo',
|
||||
UPLOAD_OF: '{0} Opplasning',
|
||||
UPLOAD: 'Opplasning',
|
||||
DOWNLOAD: 'Nedlasting',
|
||||
ABORTED: 'avbrutt',
|
||||
FAILED: 'feilet',
|
||||
SUCCESSFUL: 'vellykket',
|
||||
SYSTEM: 'System',
|
||||
LOG_OF: '{0} Logg',
|
||||
STATUS_OF: '{0} Status',
|
||||
UPLOAD_DOWNLOAD: 'Opp/Nedlasting',
|
||||
SYSTEM_VERSION_RUNNING: 'Du benytter versjon',
|
||||
SYSTEM_APPLY_FIRMWARE: 'for å aktivere ny firmware',
|
||||
CLOSE: 'Steng',
|
||||
USE: 'Bruk',
|
||||
FACTORY_RESET: 'Sett tilbake til fabrikkinstilling',
|
||||
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?',
|
||||
VERSION_CHECK: 'Versjonsjekk',
|
||||
THE_LATEST: 'Den nyeste',
|
||||
OFFICIAL: 'official',
|
||||
DEVELOPMENT: 'development',
|
||||
VERSION_IS: 'versjonen er',
|
||||
RELEASE_NOTES: 'release notes',
|
||||
EMS_ESP_VER: 'EMS-ESP Version',
|
||||
PLATFORM: 'Enhet (Platform / SDK)',
|
||||
UPTIME: 'System Oppetid',
|
||||
CPU_FREQ: 'CPU Frekvens',
|
||||
HEAP: 'Heap (Ledig / Max Allokert)',
|
||||
PSRAM: 'PSRAM (Størrelse / Ledig)',
|
||||
FLASH: 'Flash Chip (Størrelse / Hastighet)',
|
||||
APPSIZE: 'Applikasjon (Brukt / Ledig)',
|
||||
FILESYSTEM: 'File System (Brukt / Ledig)',
|
||||
BUFFER_SIZE: 'Max Buffer Størrelse',
|
||||
COMPACT: 'Komprimere',
|
||||
ENABLE_OTA: 'Aktiviser OTA oppdateringer',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Last ned objektstilpasninger',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Last ned applikasjonskonfigurasjon. Vær varsom med å dele fila da den inneholder passord og annen sensitiv system informasjon',
|
||||
UPLOAD_TEXT: 'Last opp en ny firmware (.bin) fil, innstillinger eller tilpassninger (.json) fil nedenfor',
|
||||
UPLOADING: 'Opplasting',
|
||||
UPLOAD_DROP_TEXT: 'Slipp fil eller klikk her',
|
||||
ERROR: 'Ukjent feil, prøv igjen',
|
||||
TIME_SET: 'Still in tid',
|
||||
MANAGE_USERS: 'Administrer Brukere',
|
||||
IS_ADMIN: 'er Admin',
|
||||
USER_WARNING: 'Du må ha minst en admin bruker konfigurert',
|
||||
ADD: 'Legg til',
|
||||
ACCESS_TOKEN_FOR: 'Aksess Token for',
|
||||
ACCESS_TOKEN_TEXT: 'Token nedenfor benyttes med REST API-kall som krever autorisering. Den kan sendes med enten som en Bearer token i Authorization-headern eller i access_token URL query-parameter.',
|
||||
GENERATING_TOKEN: 'Generer token',
|
||||
USER: 'Bruker',
|
||||
MODIFY: 'Endre',
|
||||
SU_TEXT: 'su brukeren (super user) passord benyttes for å signere autentiserings token samt å tillate admin privileger i konsoll modus.',
|
||||
NOT_ENABLED: 'Ikke aktiv',
|
||||
ERRORS_OF: '{0} Feil',
|
||||
DISCONNECT_REASON: 'Årsak til nedkobling',
|
||||
ENABLE_MQTT: 'Aktiver MQTT',
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Valgfritt',
|
||||
FORMATTING: 'Formatering',
|
||||
MQTT_FORMAT: 'Topic/Payload Format',
|
||||
MQTT_NEST_1: 'Nestet i en topic',
|
||||
MQTT_NEST_2: 'Som individuelle topics',
|
||||
MQTT_RESPONSE: 'Publiser kommandoer til en `response` topic',
|
||||
MQTT_PUBLISH_TEXT_1: 'Publiser singel verdi topics ved endringer',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publiser til kommando topics (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Aktiver MQTT Discovery (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefiks for Discovery topics',
|
||||
MQTT_PUBLISH_INTERVALS: 'Publiseringsintervall',
|
||||
MQTT_INT_BOILER: 'Fyr/Varmepumpe',
|
||||
MQTT_INT_THERMOSTATS: 'Termostat',
|
||||
MQTT_INT_SOLAR: 'Solpaneler',
|
||||
MQTT_INT_MIXER: 'Blandeventil',
|
||||
MQTT_INT_HEARTBEAT: 'Heartbeat',
|
||||
MQTT_QUEUE: 'MQTT Queue',
|
||||
DEFAULT: 'Standard',
|
||||
MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate
|
||||
MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)', // TODO translate
|
||||
MQTT_ENTITY_FORMAT_1: 'Single instance, short name', // TODO translate
|
||||
MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name', // TODO translate
|
||||
MQTT_CLEAN_SESSION: 'Benytt Clean Session',
|
||||
MQTT_RETAIN_FLAG: 'Alltid sett Retain flag',
|
||||
INACTIVE: 'Innaktiv',
|
||||
ACTIVE: 'Aktiv',
|
||||
UNKNOWN: 'Ukjent',
|
||||
SET_TIME: 'Sett Tid',
|
||||
SET_TIME_TEXT: 'Skriv inn dato og klokke nedenfor',
|
||||
LOCAL_TIME: 'Lokaltid',
|
||||
UTC_TIME: 'UTC Tid',
|
||||
ENABLE_NTP: 'Aktiver NTP',
|
||||
NTP_SERVER: 'NTP Server',
|
||||
TIME_ZONE: 'Tidssone',
|
||||
ACCESS_POINT: 'Aksesspunkt',
|
||||
AP_PROVIDE: 'Aktiver Aksesspunkt',
|
||||
AP_PROVIDE_TEXT_1: 'alltid',
|
||||
AP_PROVIDE_TEXT_2: 'når WiFi er utilgjengelig',
|
||||
AP_PROVIDE_TEXT_3: 'aldri',
|
||||
AP_PREFERRED_CHANNEL: 'Foretrukket kanal',
|
||||
AP_HIDE_SSID: 'Skjul SSID',
|
||||
AP_CLIENTS: 'AP Clients',
|
||||
AP_MAX_CLIENTS: 'Max Clients',
|
||||
AP_LOCAL_IP: 'Local IP',
|
||||
NETWORK_SCAN: 'Søk etter trådløst nettverk',
|
||||
IDLE: 'Klar',
|
||||
LOST: 'Mistet',
|
||||
SCANNING: 'Søker',
|
||||
SCAN_AGAIN: 'Søk igjen',
|
||||
NETWORK_SCANNER: 'Nettverk Scanner',
|
||||
NETWORK_NO_WIFI: 'Ingen trådløse nett funnet',
|
||||
NETWORK_BLANK_SSID: 'la feltet være blankt for å deaktivisere trådløst nettverk',
|
||||
TX_POWER: 'Tx Effekt',
|
||||
HOSTNAME: 'Hostname',
|
||||
NETWORK_DISABLE_SLEEP: 'Hindre at trådløst nettverk går i Sleep Mode',
|
||||
NETWORK_LOW_BAND: 'Benytt smalere båndbredde på trådløst nettverk',
|
||||
NETWORK_USE_DNS: 'Aktiviser mDNS Service',
|
||||
NETWORK_ENABLE_CORS: 'Aktiviser CORS',
|
||||
NETWORK_CORS_ORIGIN: 'CORS origin',
|
||||
NETWORK_ENABLE_IPV6: 'Aktiviser IPv6 støtte',
|
||||
NETWORK_FIXED_IP: 'Benytt statisk IP adresse',
|
||||
NETWORK_GATEWAY: 'Gateway',
|
||||
NETWORK_SUBNET: 'Nettverksmaske',
|
||||
NETWORK_DNS: 'DNS Servers',
|
||||
ADDRESS_OF: '{0} Address',
|
||||
ADMIN: 'Admin',
|
||||
GUEST: 'Gjest',
|
||||
NEW: 'Ny',
|
||||
NEW_NAME_OF: 'Bytt navn {0}',
|
||||
ENTITY: 'Entitet',
|
||||
MIN: 'min',
|
||||
MAX: 'max'
|
||||
};
|
||||
|
||||
export default no;
|
||||
310
interface/src/i18n/pl/index.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import type { BaseTranslation } from '../i18n-types';
|
||||
/* prettier-ignore */
|
||||
/* eslint-disable */
|
||||
|
||||
const pl: BaseTranslation = {
|
||||
LANGUAGE: 'Język',
|
||||
RETRY: 'Ponów',
|
||||
LOADING: 'Ładowanie',
|
||||
IS_REQUIRED: 'Pole {0} nie może być puste!',
|
||||
SIGN_IN: 'Zaloguj się',
|
||||
SIGN_OUT: 'Wyloguj się',
|
||||
USERNAME: '{{Użytkownik|Nazwa użytkownika|}}',
|
||||
PASSWORD: 'Hasło',
|
||||
SU_PASSWORD: 'Hasło "su"',
|
||||
DASHBOARD: 'Pulpit',
|
||||
SETTINGS_OF: 'Ustawienia {0}',
|
||||
SAVED: 'zostały zapisane.',
|
||||
HELP_OF: 'Pomoc {0}',
|
||||
LOGGED_IN: 'Zalogowano użytkownika {name}.',
|
||||
PLEASE_SIGNIN: 'Zaloguj się aby kontynuować.',
|
||||
UPLOAD_SUCCESSFUL: 'Wysyłanie zakończone.',
|
||||
DOWNLOAD_SUCCESSFUL: 'Pobieranie zakończone.',
|
||||
INVALID_LOGIN: 'Nieprawidłowy użytkownik lub hasło!',
|
||||
NETWORK: '{{Sieć|sieci|}}',
|
||||
SECURITY: '{{B|b|}}ezpieczeństw{{o|a|}}',
|
||||
ONOFF_CAP: 'wł./wył.',
|
||||
ONOFF: 'włączono/wyłączono',
|
||||
TYPE: 'Typ',
|
||||
DESCRIPTION: 'Opis',
|
||||
ENTITIES: 'Encje',
|
||||
REFRESH: 'Odśwież',
|
||||
EXPORT: 'Eksportuj',
|
||||
DEVICE_DETAILS: 'Szczegóły urządzenia',
|
||||
ID_OF: 'ID {0}',
|
||||
DEVICE: 'urządzenia',
|
||||
PRODUCT: 'produktu',
|
||||
BRAND: 'Marka',
|
||||
VERSION: 'Wersja',
|
||||
ENTITY_NAME: 'Nazwa encji',
|
||||
VALUE: '{{W|w|}}artość',
|
||||
SHOW_FAV: 'Pokaż tylko "ulubione"',
|
||||
DEVICE_SENSOR_DATA: 'Dane z urządzeń i czujników',
|
||||
DEVICES_SENSORS: 'Urządzenia i czujniki',
|
||||
ATTACHED_SENSORS: 'Urządzenia podłączone do EMS-ESP (czujniki temperatury/analogowe/cyfrowe, wyjścia cyfrowe)',
|
||||
RUN_COMMAND: 'Wykonaj komendę',
|
||||
CHANGE_VALUE: 'Zmień wartość',
|
||||
CANCEL: 'Anuluj',
|
||||
RESET: 'Reset{{uj|owanie|}}',
|
||||
SEND: 'Wyślij',
|
||||
SAVE: 'Zapisz',
|
||||
REMOVE: 'Usuń',
|
||||
PROBLEM_UPDATING: 'Problem z uaktualnieniem!',
|
||||
PROBLEM_LOADING: 'Problem z załadowaniem!',
|
||||
ACCESS_DENIED: 'Brak dostępu!',
|
||||
ANALOG_SENSOR: 'urządzenia podłączonego do EMS-ESP',
|
||||
ANALOG_SENSORS: 'Urządzenia podłączone do EMS-ESP',
|
||||
UPDATED_OF: 'Zaktualizowano ustawienia {0}.',
|
||||
UPDATE_OF: 'Aktualizacja {0}',
|
||||
REMOVED_OF: 'Usunięto ustawienia {0}.',
|
||||
DELETION_OF: 'Kasowanie {0}',
|
||||
OFFSET: 'Korekta ±',
|
||||
FACTOR: 'Mnożnik',
|
||||
FREQ: 'Częstotliwość',
|
||||
DUTY_CYCLE: 'Wypełnienie',
|
||||
UNIT: 'J.m.',
|
||||
STARTVALUE: 'Wartość początkowa',
|
||||
WARN_GPIO: 'Uwaga! Zachowaj ostrożność przypisując GPIO do urządzenia!',
|
||||
EDIT: 'Edycja',
|
||||
SENSOR: 'czujnika',
|
||||
TEMP_SENSOR: 'czujnika temperatury',
|
||||
TEMP_SENSORS: 'Czujniki temperatury 1-Wire®',
|
||||
WRITE_CMD_SENT: 'Komenda zapisu została wysłana.',
|
||||
WRITE_CMD_FAILED: 'Komenda zapisu nie powiodła się!',
|
||||
EMS_BUS_WARNING: 'Brak połączenia z magistralą EMS. Jeśli ten błąd występuje dłużej niż kilka sekund, sprawdź ustawienia oraz profil płytki interfejsu.',
|
||||
EMS_BUS_SCANNING: 'Trwa skanowanie urządzeń na magistrali EMS...',
|
||||
CONNECTED: '{{połączono|połączenie|}}',
|
||||
TX_ISSUES: 'problem z zapisem na magistralę EMS, spróbuj wybrać inny "Tryb transmisji (Tx)"',
|
||||
DISCONNECTED: 'brak połączenia',
|
||||
EMS_SCAN: 'Czy na pewno wykonać pełne skanowanie magistrali EMS?',
|
||||
EMS_BUS_STATUS: 'Status magistrali EMS',
|
||||
ACTIVE_DEVICES: 'Aktywne urządzenia i czujniki',
|
||||
EMS_DEVICE: 'Urządzenie EMS',
|
||||
SUCCESS: 'Udane',
|
||||
FAIL: 'Nieudane',
|
||||
QUALITY: 'Jakość',
|
||||
SCAN_DEVICES: 'Wyszukiwanie nowych urządzeń',
|
||||
EMS_BUS_STATUS_TITLE: 'Aktywność',
|
||||
SCAN: 'Skanuj',
|
||||
STATUS_NAMES: [
|
||||
'EMS, telegramy odebrane (Rx)',
|
||||
'EMS, wysłane telegramy "odczyt" (Tx)',
|
||||
'EMS, wysłane telegramy "zapis" (Tx)',
|
||||
'Odczyty czujników temperatury 1-Wire®',
|
||||
'Odczyty czujników analogowych i cyfrowych',
|
||||
'Publikacje MQTT',
|
||||
'Wywołania API',
|
||||
'Wpisy w SysLog'
|
||||
],
|
||||
NUM_DEVICES: '{num} urządze{{ń|nie|nia|nia|ń}} EMS',
|
||||
NUM_TEMP_SENSORS: '{num} czujni{{ków|k|ki|ki|ków}} temperatury',
|
||||
NUM_ANALOG_SENSORS: '{num} inn{{ych|e|e|e|ych}} urządze{{ń|nie|nia(two)|nia|ń}} podłączon{{ych|e|e|e|ych}} do EMS-ESP',
|
||||
NUM_DAYS: '{num} d{{ni|zień|ni|ni|ni}}',
|
||||
NUM_SECONDS: '{num} sekun{{d|da|dy|dy|d}}',
|
||||
NUM_HOURS: '{num} godzi{{n|na|ny|ny|n}}',
|
||||
NUM_MINUTES: '{num} minu{{t|ta|ty|ty|t}}',
|
||||
APPLICATION_SETTINGS: 'Ustawienia aplikacji',
|
||||
CUSTOMIZATION: 'Personalizacja',
|
||||
APPLICATION_RESTARTING: 'Trwa ponowne uruchamianie',
|
||||
INTERFACE_BOARD_PROFILE: 'Profil płytki interfejsu',
|
||||
BOARD_PROFILE_TEXT: 'Wybierz z listy gotowy profil płytki interfejsu lub "własny..." i samodzielnie skonfiguruj posiadany sprzęt.',
|
||||
BOARD_PROFILE: 'Profil płytki',
|
||||
CUSTOM: 'własny',
|
||||
GPIO_OF: 'GPIO {0}',
|
||||
BUTTON: 'przycisku',
|
||||
TEMPERATURE: '1-Wire®',
|
||||
PHY_TYPE: 'Typ układu ethernetowego (PHY)',
|
||||
DISABLED: '{{wyłączono|brak|}}',
|
||||
TX_MODE: 'Tryb transmisji (Tx)',
|
||||
EMS_BUS: '{{magistrali EMS|na magistrali|}}',
|
||||
HARDWARE: 'sprzętowy',
|
||||
GENERAL_OPTIONS: 'Opcje podstawowe',
|
||||
LANGUAGE_ENTITIES: 'Język encji',
|
||||
HIDE_LED: 'Wyłącz LED',
|
||||
ENABLE_TELNET: 'Aktywuj dostęp dla konsoli Telnet',
|
||||
ENABLE_ANALOG: 'Aktywuj urządzenia GPIO (czujniki analogowe i cyfrowe oraz wyjścia cyfrowe)',
|
||||
CONVERT_FAHRENHEIT: 'Konwertuj temperatury do skali Fahrenheita',
|
||||
BYPASS_TOKEN: 'Pomiń autoryzację tokenem w wywołaniach API',
|
||||
READONLY: 'Tryb pracy "tylko do odczytu" (blokuje wszystkie komendy zapisu na magistralę EMS)',
|
||||
UNDERCLOCK_CPU: 'Obniż taktowanie CPU',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Aktywuj minutnik prysznica',
|
||||
ENABLE_SHOWER_ALERT: 'Aktywuj alarm prysznica',
|
||||
TRIGGER_TIME: 'Wyzwalaj po czasie',
|
||||
COLD_SHOT_DURATION: 'Czas trwania tryśnięcia zimnej wody',
|
||||
FORMATTING_OPTIONS: 'Opcje formatowania',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Wartości dwustanowe na pulpicie',
|
||||
BOOLEAN_FORMAT_API: 'Wartości dwustanowe w API/MQTT',
|
||||
ENUM_FORMAT: 'Wartości z listy w API/MQTT',
|
||||
INDEX: 'indeks',
|
||||
ENABLE_PARASITE: 'Aktywuj zasilanie pasożytnicze',
|
||||
LOGGING: 'Logowanie',
|
||||
LOG_HEX: 'Loguj telegramy EMS w systemie szesnastkowym (hex)',
|
||||
ENABLE_SYSLOG: 'Aktywuj SysLog',
|
||||
LOG_LEVEL: 'Poziom logowania',
|
||||
MARK_INTERVAL: 'Znaczniki interwałów (0=brak)',
|
||||
SECONDS: 'sekund',
|
||||
MINUTES: 'minut',
|
||||
HOURS: 'godzin',
|
||||
RESTART: 'Restart',
|
||||
RESTART_TEXT: 'Aby zastosować wprowadzone zmiany interfejs EMS-ESP musi zostać zrestartowany.',
|
||||
RESTART_CONFIRM: 'Jesteś pewien, że chcesz zrestartować interfejs EMS-ESP?',
|
||||
COMMAND: 'KOMENDA',
|
||||
CUSTOMIZATIONS_RESTART: 'Wszystkie personalizacje zostały usunięte. Restartuję...',
|
||||
CUSTOMIZATIONS_FULL: 'Wybrano za dużo obiektów. Wprowadź zmiany w mniejszych partiach.',
|
||||
CUSTOMIZATIONS_SAVED: 'Personalizacje zostały zapisane.',
|
||||
CUSTOMIZATIONS_HELP_1: 'Wybierz urządzenie EMS, dostosuj opcje lub kliknij by zmienić nazwę encji.',
|
||||
CUSTOMIZATIONS_HELP_2: 'oznacz jako ulubioną',
|
||||
CUSTOMIZATIONS_HELP_3: 'zablokuj akcje zapisu',
|
||||
CUSTOMIZATIONS_HELP_4: 'wyklucz z MQTT i API',
|
||||
CUSTOMIZATIONS_HELP_5: 'ukryj na pulpicie',
|
||||
CUSTOMIZATIONS_HELP_6: 'usuń z pamięci',
|
||||
SELECT_DEVICE: 'wybierz urządzenie',
|
||||
SET_ALL: 'Ustaw wszystko jako',
|
||||
OPTIONS: 'Opcje',
|
||||
NAME: '{{Nazwa|nazwa|}}',
|
||||
CUSTOMIZATIONS_RESET: 'Czy jesteś pewien, że 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',
|
||||
USER_CUSTOMIZATION: 'Personalizacje użytkownika',
|
||||
SUPPORT_INFORMATION: 'Informacje dotyczące wsparcia',
|
||||
CLICK_HERE: 'Kliknij tu',
|
||||
HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP skorzystaj z wiki w internecie',
|
||||
HELP_INFORMATION_2: 'Aby dołączyć do naszego serwera Discord i komunikować się na żywo ze społecznością',
|
||||
HELP_INFORMATION_3: 'Aby zaproponować nową funkcjonalność lub zgłosić problem',
|
||||
HELP_INFORMATION_4: 'Zgłaszając problem, nie zapomnij dołączyć informacji o swoim systemie!',
|
||||
HELP_INFORMATION_5: 'EMS-ESP jest darmowym projektem typu open-source. Aby go wesprzeć, rozważ przyznanie nam gwiazdki na Github!',
|
||||
SUPPORT_INFO: 'Pobierz informacje',
|
||||
UPLOAD_OF: 'Wysyłanie {0}',
|
||||
UPLOAD: 'Wysyłanie',
|
||||
DOWNLOAD: '{{P|p||P}}obier{{anie|z||z}}',
|
||||
ABORTED: 'zostało przerwane!',
|
||||
FAILED: 'nie powiodło się!',
|
||||
SUCCESSFUL: 'powiodło się.',
|
||||
SYSTEM: '{{S|s||s}}yste{{m|mu||mowy}}',
|
||||
LOG_OF: 'Log {0}',
|
||||
STATUS_OF: 'Status {0}',
|
||||
UPLOAD_DOWNLOAD: 'Przesyłanie plików',
|
||||
SYSTEM_VERSION_RUNNING: 'Obecnie zainstalowana wersja to:',
|
||||
SYSTEM_APPLY_FIRMWARE: '',
|
||||
CLOSE: 'Zamknij',
|
||||
USE: 'Aby zaktualizować firmware skorzystaj z funkcji',
|
||||
FACTORY_RESET: 'Ustawienia fabryczne',
|
||||
SYSTEM_FACTORY_TEXT: 'Interfejs EMS-ESP został przywrócony do ustawień fabrycznych i zostanie teraz ponownie uruchomiony.',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Czy jesteś pewien, że chcesz przywrócić ustawienia fabryczne interfejsu EMS-ESP? ',
|
||||
VERSION_CHECK: 'Sprawd{{ź|zanie|}} wersj{{ę|i|}}',
|
||||
THE_LATEST: 'Najnowsza',
|
||||
OFFICIAL: 'oficjalna',
|
||||
DEVELOPMENT: 'testowa',
|
||||
VERSION_IS: 'wersja to',
|
||||
RELEASE_NOTES: 'lista zmian',
|
||||
EMS_ESP_VER: 'Wersja EMS-ESP',
|
||||
PLATFORM: 'Urządzenie (platforma / SDK)',
|
||||
UPTIME: 'Czas działania systemu',
|
||||
CPU_FREQ: 'Taktowanie CPU',
|
||||
HEAP: 'HEAP (wolne / maksymalny przydział)',
|
||||
PSRAM: 'PSRAM (rozmiar / wolne)',
|
||||
FLASH: 'FLASH (rozmiar / taktowanie)',
|
||||
APPSIZE: 'Aplikacja (wykorzystane / wolne)',
|
||||
FILESYSTEM: 'System plików (wykorzystane / wolne)',
|
||||
BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)',
|
||||
COMPACT: 'Kompaktowy',
|
||||
ENABLE_OTA: 'Aktywuj aktualizację OTA',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Pobierz personalizacje',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uważaj jeśli udostępniasz plik z ustawieniami, ponieważ zawiera on hasła oraz inne wrażliwe informacje!',
|
||||
UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji (.md5).',
|
||||
UPLOADING: 'Wysłano',
|
||||
UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij',
|
||||
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
|
||||
TIME_SET: 'Zegar został ustawiony.',
|
||||
MANAGE_USERS: 'Zarządzanie użytkownikami',
|
||||
IS_ADMIN: '{{Administrator|Uprawnienia administratora|}}',
|
||||
USER_WARNING: 'Przynajmniej jeden użytkownik musi mieć uprawnienia administratora!',
|
||||
ADD: 'Doda{{j|wanie|}}',
|
||||
ACCESS_TOKEN_FOR: 'Token dostępu dla użytkownika',
|
||||
ACCESS_TOKEN_TEXT: 'Token jest używany w wywołaniach REST API wymagających autoryzacji. Można go przekazywać bezpośrednio lub przez URL.',
|
||||
GENERATING_TOKEN: 'Generowanie tokenu',
|
||||
USER: '{{Użytkownik|użytkownika|}}',
|
||||
MODIFY: 'Edycja',
|
||||
SU_TEXT: 'Hasło "su" (super-użytkownika) służy do podpisywania tokenów autoryzujących oraz włączania uprawnień administratora w konsoli.',
|
||||
NOT_ENABLED: 'nie aktywowano',
|
||||
ERRORS_OF: 'Błędy {0}',
|
||||
DISCONNECT_REASON: 'Przyczyna braku połączenia',
|
||||
ENABLE_MQTT: 'Aktywuj MQTT',
|
||||
BROKER: 'brokera',
|
||||
CLIENT: 'klienta',
|
||||
BASE_TOPIC: 'Prefiks bazowy (unikalny!)',
|
||||
OPTIONAL: 'opcjonalny',
|
||||
FORMATTING: 'Formatowanie',
|
||||
MQTT_FORMAT: 'Sposób publikowania danych',
|
||||
MQTT_NEST_1: 'zagnieżdżone w jednym temacie',
|
||||
MQTT_NEST_2: 'jako oddzielne tematy',
|
||||
MQTT_RESPONSE: 'Rezultat wykonania komendy publikuj w temacie "response"',
|
||||
MQTT_PUBLISH_TEXT_1: 'Tematy z pojedynczą wartością publikuj po jej zmianie',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publikuj w tematach "command" (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Włącz opcję "MQTT discovery" (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefiks dla "MQTT discovery"',
|
||||
MQTT_PUBLISH_INTERVALS: 'Interwały publikowania',
|
||||
MQTT_INT_BOILER: 'Kotły i pompy ciepła',
|
||||
MQTT_INT_THERMOSTATS: 'Termostaty',
|
||||
MQTT_INT_SOLAR: 'Panele solarne',
|
||||
MQTT_INT_MIXER: 'Mieszacze',
|
||||
MQTT_INT_HEARTBEAT: '"Heartbeat" (aktywność)',
|
||||
MQTT_QUEUE: 'Kolejka MQTT',
|
||||
DEFAULT: '{{Pozostałe|Domyślna|}}',
|
||||
MQTT_ENTITY_FORMAT: 'Format "Entity ID"',
|
||||
MQTT_ENTITY_FORMAT_0: 'długa nazwa (jak w v3.4)',
|
||||
MQTT_ENTITY_FORMAT_1: 'krótka nazwa',
|
||||
MQTT_ENTITY_FORMAT_2: 'prefiks bazowy + krótka nazwa',
|
||||
MQTT_CLEAN_SESSION: 'Ustawiaj flagę "Clean session"',
|
||||
MQTT_RETAIN_FLAG: 'Ustawiaj flagę "Retain"',
|
||||
INACTIVE: 'nieaktywn{{y|a|}}',
|
||||
ACTIVE: 'aktywny',
|
||||
UNKNOWN: 'nieznany',
|
||||
SET_TIME: '{{Ustaw zegar|Ustawianie zegara|}}',
|
||||
SET_TIME_TEXT: 'Wprowadź aktualną datę i godzinę',
|
||||
LOCAL_TIME: 'Czas lokalny',
|
||||
UTC_TIME: 'Czas UTC',
|
||||
ENABLE_NTP: 'Aktywuj NTP (data i godzina będą automatycznie synchronizowane z poniższym serwerem czasu)',
|
||||
NTP_SERVER: 'Serwer NTP',
|
||||
TIME_ZONE: 'Strefa czasowa',
|
||||
ACCESS_POINT: '{{Punkt|punktu|}} {{dostępowy|dostępowego|}}',
|
||||
AP_PROVIDE: 'Punkt dostępowy',
|
||||
AP_PROVIDE_TEXT_1: 'zawsze aktywny',
|
||||
AP_PROVIDE_TEXT_2: 'aktywny jeśli brak połączenia z siecią',
|
||||
AP_PROVIDE_TEXT_3: 'nieaktywny',
|
||||
AP_PREFERRED_CHANNEL: 'Preferowany kanał',
|
||||
AP_HIDE_SSID: 'Ukryj SSID',
|
||||
AP_CLIENTS: 'Liczba klientów',
|
||||
AP_MAX_CLIENTS: 'Maksymalna liczba klientów',
|
||||
AP_LOCAL_IP: 'Lokalny adres IP',
|
||||
NETWORK_SCAN: 'Skanowanie sieci WiFi',
|
||||
IDLE: 'bezczynna',
|
||||
LOST: 'zostało utracone.',
|
||||
SCANNING: 'Skanuję',
|
||||
SCAN_AGAIN: 'Skanuj ponownie',
|
||||
NETWORK_SCANNER: 'Skaner sieci WiFi',
|
||||
NETWORK_NO_WIFI: 'Brak sieci WiFi w zasięgu',
|
||||
NETWORK_BLANK_SSID: 'pozostaw puste aby wyłączyć WiFi',
|
||||
TX_POWER: 'Moc nadawania',
|
||||
HOSTNAME: 'Nazwa w sieci',
|
||||
NETWORK_DISABLE_SLEEP: 'Wyłącz tryb usypiania WiFi',
|
||||
NETWORK_LOW_BAND: 'Używaj mniejszej szerokości pasma WiFi (20MHz)',
|
||||
NETWORK_USE_DNS: 'Włącz wsparcie dla mDNS',
|
||||
NETWORK_ENABLE_CORS: 'Włącz wsparcie dla CORS',
|
||||
NETWORK_CORS_ORIGIN: 'CORS origin',
|
||||
NETWORK_ENABLE_IPV6: 'Włącz wsparcie dla IPv6',
|
||||
NETWORK_FIXED_IP: 'Użyj stałego adresu IP',
|
||||
NETWORK_GATEWAY: 'Brama',
|
||||
NETWORK_SUBNET: 'Maska podsieci',
|
||||
NETWORK_DNS: 'Serwery DNS',
|
||||
ADDRESS_OF: 'Adres {0}',
|
||||
ADMIN: 'Użytkownik "administrator".',
|
||||
GUEST: 'Użytkownik "gość".',
|
||||
NEW: 'Nowy',
|
||||
NEW_NAME_OF: 'Nowa nazwa {0}',
|
||||
ENTITY: 'encji',
|
||||
MIN: 'Min.',
|
||||
MAX: 'Maks.'
|
||||
};
|
||||
|
||||
export default pl;
|
||||
310
interface/src/i18n/sv/index.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import type { Translation } from '../i18n-types';
|
||||
/* prettier-ignore */
|
||||
/* eslint-disable */
|
||||
|
||||
const sv: Translation = {
|
||||
LANGUAGE: 'Språk',
|
||||
RETRY: 'Försök igen',
|
||||
LOADING: 'Laddar',
|
||||
IS_REQUIRED: '{0} Krävs',
|
||||
SIGN_IN: 'Logga In',
|
||||
SIGN_OUT: 'Logga Ut',
|
||||
USERNAME: 'Användarnamn',
|
||||
PASSWORD: 'Lösenord',
|
||||
SU_PASSWORD: 'su Lösenord',
|
||||
DASHBOARD: 'Kontrollpanel',
|
||||
SETTINGS_OF: '{0} Inställningar',
|
||||
SAVED: 'Sparat',
|
||||
HELP_OF: '{0} Hjälp',
|
||||
LOGGED_IN: 'Inloggad som {name}',
|
||||
PLEASE_SIGNIN: 'Vänligen logga in för att fortsätta',
|
||||
UPLOAD_SUCCESSFUL: 'Uppladdning lyckades',
|
||||
DOWNLOAD_SUCCESSFUL: 'Nedladdning lyckades',
|
||||
INVALID_LOGIN: 'Ogiltig login',
|
||||
NETWORK: 'Nätverk',
|
||||
SECURITY: 'Säkerhet',
|
||||
ONOFF_CAP: 'PÅ/AV',
|
||||
ONOFF: 'på/av',
|
||||
TYPE: 'Typ',
|
||||
DESCRIPTION: 'Beskrivning',
|
||||
ENTITIES: 'Entiteter',
|
||||
REFRESH: 'Uppdatera',
|
||||
EXPORT: 'Exportera',
|
||||
DEVICE_DETAILS: 'Enhetsdetaljer',
|
||||
ID_OF: '{0}-ID',
|
||||
DEVICE: 'Enhets',
|
||||
PRODUCT: 'Produkt',
|
||||
VERSION: 'Version',
|
||||
BRAND: 'Fabrikat',
|
||||
ENTITY_NAME: 'Entitetsnamn',
|
||||
VALUE: '{{Värde|värde}}',
|
||||
SHOW_FAV: 'Visa enbart favoriter',
|
||||
DEVICE_SENSOR_DATA: 'Enhets och Sensor-data',
|
||||
DEVICES_SENSORS: 'Enheter & Sensorer',
|
||||
ATTACHED_SENSORS: 'Anslutna EMS-ESP Sensorer',
|
||||
RUN_COMMAND: 'Kör Kommando',
|
||||
CHANGE_VALUE: 'Ändra Värde',
|
||||
CANCEL: 'Avbryt',
|
||||
RESET: 'Nollställ',
|
||||
SEND: 'Skicka',
|
||||
SAVE: 'Spara',
|
||||
REMOVE: 'Ta bort',
|
||||
PROBLEM_UPDATING: 'Problem vid uppdatering',
|
||||
PROBLEM_LOADING: 'Problem vid hämtning',
|
||||
ACCESS_DENIED: 'Åtkomst Nekad',
|
||||
ANALOG_SENSOR: 'Analog Sensor',
|
||||
ANALOG_SENSORS: 'Analoga Sensorer',
|
||||
UPDATED_OF: '{0} Uppdaterad',
|
||||
UPDATE_OF: '{0} Uppdatera',
|
||||
REMOVED_OF: '{0} Raderad',
|
||||
DELETION_OF: '{0} Radering',
|
||||
OFFSET: 'Kompensering',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frekvens',
|
||||
DUTY_CYCLE: 'Duty Cycle',
|
||||
UNIT: 'UoM',
|
||||
STARTVALUE: 'Startvärde',
|
||||
WARN_GPIO: 'Varning: Var försiktig vid aktivering av GPIO!',
|
||||
EDIT: 'Ändra',
|
||||
SENSOR: 'Sensor',
|
||||
TEMP_SENSOR: 'Temperatursensor',
|
||||
TEMP_SENSORS: 'Temperatursensorer',
|
||||
WRITE_CMD_SENT: 'Skrivkommandon skickade',
|
||||
WRITE_CMD_FAILED: 'Skrivkommandon misslyckade',
|
||||
EMS_BUS_WARNING: 'EMS-buss nedkopplad. Om denna varning kvarstår efter några sekunder, kontrollera inställningar och enhets-profil.',
|
||||
EMS_BUS_SCANNING: 'Söker efter EMS-enheter...',
|
||||
CONNECTED: 'Ansluten',
|
||||
TX_ISSUES: 'Sändfel - Prova ett annat TX-läge',
|
||||
DISCONNECTED: 'Nedkopplad',
|
||||
EMS_SCAN: 'Är du säker att du vill initiera en full genomsökning av EMS-bussen?',
|
||||
EMS_BUS_STATUS: 'Status',
|
||||
ACTIVE_DEVICES: 'Aktiva Enheter',
|
||||
EMS_DEVICE: 'EMS Enhet',
|
||||
SUCCESS: 'Lyckades',
|
||||
FAIL: 'Misslyckades',
|
||||
QUALITY: 'Kvalitet',
|
||||
SCAN_DEVICES: 'Sök efter nya enheter',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS-buss & aktivitetsstatus',
|
||||
SCAN: 'Sök',
|
||||
STATUS_NAMES: [
|
||||
'EMS-telegram (Rx)',
|
||||
'EMS-läsningar (Tx)',
|
||||
'EMS-skrivningar (Tx)',
|
||||
'Temperatursensor-läsningar',
|
||||
'Analog Sensor-läsningar',
|
||||
'MQTT-publiceringar',
|
||||
'API-anrop',
|
||||
'Syslog-meddelanden'
|
||||
],
|
||||
NUM_DEVICES: '{num} Enhet{{er}}',
|
||||
NUM_TEMP_SENSORS: '{num} Temperatur-sensor{{er}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analoga Sensor{{er}}',
|
||||
NUM_DAYS: '{num} dag{{ar}}',
|
||||
NUM_SECONDS: '{num} sekund{{er}}',
|
||||
NUM_HOURS: '{num} timmar',
|
||||
NUM_MINUTES: '{num} minut{{er}}',
|
||||
APPLICATION_SETTINGS: 'Inställningar',
|
||||
CUSTOMIZATION: 'Anpassa',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP startar om',
|
||||
INTERFACE_BOARD_PROFILE: 'Interface Hårdvaruprofil',
|
||||
BOARD_PROFILE_TEXT: 'Välj en förkonfigurerad hårdvaruprofil från listan nedan eller välj Anpassad för att konfigurera dina egna hårdvaruinställningar',
|
||||
BOARD_PROFILE: 'Hårdvarutyp',
|
||||
CUSTOM: 'Anpassa',
|
||||
GPIO_OF: '{0} GPIO',
|
||||
BUTTON: 'Knapp',
|
||||
TEMPERATURE: 'Temperatur',
|
||||
PHY_TYPE: 'Eth PHY-typ',
|
||||
DISABLED: 'inaktiverad',
|
||||
TX_MODE: 'Tx-läge',
|
||||
HARDWARE: 'Hårdvara',
|
||||
EMS_BUS: '{{BUSS|EMS-BUSS}}',
|
||||
GENERAL_OPTIONS: 'Allmänna Inställningar',
|
||||
LANGUAGE_ENTITIES: 'Språk (för entiteter)',
|
||||
HIDE_LED: 'Inaktivera LED',
|
||||
ENABLE_TELNET: 'Aktivera Telnet',
|
||||
ENABLE_ANALOG: 'Aktivera Analoga Sensorer',
|
||||
CONVERT_FAHRENHEIT: 'Konvertera temperaturer till Fahrenheit',
|
||||
BYPASS_TOKEN: 'Inaktivera Token-autensiering för API-anrop',
|
||||
READONLY: 'Aktivera read-only (blockerar alla utgående skrivkommandon mot EMS-bussen)',
|
||||
UNDERCLOCK_CPU: 'Nedklocka Processorhastighet',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Aktivera Dusch-timer',
|
||||
ENABLE_SHOWER_ALERT: 'Aktivera Dusch-varning',
|
||||
TRIGGER_TIME: 'Aktiveringstid',
|
||||
COLD_SHOT_DURATION: 'Längd på kalldusch',
|
||||
FORMATTING_OPTIONS: 'Formatteringsalternativ',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Bool-format Kontrollpanel',
|
||||
BOOLEAN_FORMAT_API: 'Bool-format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum-format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Aktivera parasitström',
|
||||
LOGGING: 'Loggning',
|
||||
LOG_HEX: 'Logga EMS-telegram i hexadecimal',
|
||||
ENABLE_SYSLOG: 'Aktivera Syslog',
|
||||
LOG_LEVEL: 'Loggnivå',
|
||||
MARK_INTERVAL: 'Markerings-interval',
|
||||
SECONDS: 'sekunder',
|
||||
MINUTES: 'minuter',
|
||||
HOURS: 'timmar',
|
||||
RESTART: 'Starta om',
|
||||
RESTART_TEXT: 'EMS-ESP kräver en omstart för att applicera förändrade systeminställningar',
|
||||
RESTART_CONFIRM: 'Är du säker på att du vill starta om EMS-ESP?',
|
||||
COMMAND: 'Kommando',
|
||||
CUSTOMIZATIONS_RESTART: 'Alla anpassningr har raderats. Startar om...',
|
||||
CUSTOMIZATIONS_FULL: 'Antal valda enheter för högt. Vänligen spara i mindre antal åt gången.',
|
||||
CUSTOMIZATIONS_SAVED: 'Anpassningar sparade',
|
||||
CUSTOMIZATIONS_HELP_1: 'Välj en enhet och anpassa underenheter med hjälp av alternativen',
|
||||
CUSTOMIZATIONS_HELP_2: 'Favorit',
|
||||
CUSTOMIZATIONS_HELP_3: 'Inaktivera skrivningar',
|
||||
CUSTOMIZATIONS_HELP_4: 'Exkludera från MQTT & API',
|
||||
CUSTOMIZATIONS_HELP_5: 'Göm från Kontrollpanel',
|
||||
CUSTOMIZATIONS_HELP_6: 'remove from memory',
|
||||
SELECT_DEVICE: 'Välj en enhet',
|
||||
SET_ALL: 'ställ in alla',
|
||||
OPTIONS: 'Alternativ',
|
||||
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?',
|
||||
DEVICE_ENTITIES: 'Enhets-entiteter',
|
||||
USER_CUSTOMIZATION: 'Användaranpassningar',
|
||||
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_2: 'För community-support besök vår Discord-server',
|
||||
HELP_INFORMATION_3: 'Önska en ny funktion eller rapportera en bugg',
|
||||
HELP_INFORMATION_4: 'Bifoga din systeminformation för snabbare hantering när du rapporterar ett problem',
|
||||
HELP_INFORMATION_5: 'EMS-ESP är gratis och är öppen källkod. Bidra till utvecklingen genom att ge oss en stjärna på GitHub!',
|
||||
SUPPORT_INFO: 'Supportinfo',
|
||||
UPLOAD_OF: '{0} Uppladdning',
|
||||
UPLOAD: 'Uppladdning',
|
||||
DOWNLOAD: 'Nedladdning',
|
||||
ABORTED: 'Avbruten',
|
||||
FAILED: 'Misslyckades',
|
||||
SUCCESSFUL: 'Lyckades',
|
||||
SYSTEM: 'System',
|
||||
LOG_OF: '{0} Logg',
|
||||
STATUS_OF: '{0} Status',
|
||||
UPLOAD_DOWNLOAD: 'Upp/Nedladdning',
|
||||
SYSTEM_VERSION_RUNNING: 'Du använder version',
|
||||
SYSTEM_APPLY_FIRMWARE: 'för att aktivera ny firmware',
|
||||
CLOSE: 'Stäng',
|
||||
USE: 'Använd',
|
||||
FACTORY_RESET: 'Fabriksåterställning',
|
||||
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?',
|
||||
VERSION_CHECK: 'Senaste versioner',
|
||||
THE_LATEST: 'Den senaste',
|
||||
OFFICIAL: 'officiell',
|
||||
DEVELOPMENT: 'utveckling',
|
||||
VERSION_IS: 'version är',
|
||||
RELEASE_NOTES: 'release-logg',
|
||||
EMS_ESP_VER: 'EMS-ESP Version',
|
||||
PLATFORM: 'Enhet (Plattform / SDK)',
|
||||
UPTIME: 'Systemets Upptid',
|
||||
CPU_FREQ: 'CPU-frekvens',
|
||||
HEAP: 'Heap (Ledigt / Max allokerat)',
|
||||
PSRAM: 'PSRAM (Storlek / Ledigt)',
|
||||
FLASH: 'Flashminne (Storlek / Hastighet)',
|
||||
APPSIZE: 'Applikationer (Använt / Ledigt)',
|
||||
FILESYSTEM: 'Filsystem (Använt / Ledigt)',
|
||||
BUFFER_SIZE: 'Max Bufferstorlek',
|
||||
COMPACT: 'Komprimera',
|
||||
ENABLE_OTA: 'Aktivera OTA-uppdateringar',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Ladda ner entitetsanpassningar',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Ladda ner applikationsinställningar. Var försiktig om du delar dina iställlningar då de innehåller lösenord och annan känslig systeminformation',
|
||||
UPLOAD_TEXT: 'Ladda upp ett nytt firmware (.bin), inställningar eller anpassningar (.json) nedan',
|
||||
UPLOADING: 'Laddar upp',
|
||||
UPLOAD_DROP_TEXT: 'Släpp fil eller klicka här',
|
||||
ERROR: 'Okänt Fel, var god försök igen',
|
||||
TIME_SET: 'Ställ in tid',
|
||||
MANAGE_USERS: 'Användare',
|
||||
IS_ADMIN: 'Admin',
|
||||
USER_WARNING: 'Du måste ha minst en admin konfigurerad',
|
||||
ADD: 'Lägg till',
|
||||
ACCESS_TOKEN_FOR: 'Access Token för',
|
||||
ACCESS_TOKEN_TEXT: 'Nedan Token används med REST API-anrop som kräver auktorisering. Den kan skickas med antingen som en Bearer token i Authorization-headern eller i access_token URL query-parametern.',
|
||||
GENERATING_TOKEN: 'Genererar token',
|
||||
USER: 'Användare',
|
||||
MODIFY: 'Ändra',
|
||||
SU_TEXT: 'SU-användarens (super user) lösenord används för att signera autensierings-tokens samt för att aktivera administratörsprivilegier i Console-läge',
|
||||
NOT_ENABLED: 'Ej aktiv',
|
||||
ERRORS_OF: '{0} fel',
|
||||
DISCONNECT_REASON: 'Anledning till nedkoppling',
|
||||
ENABLE_MQTT: 'Aktivera MQTT',
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
OPTIONAL: 'Valfritt',
|
||||
FORMATTING: 'Formatering',
|
||||
MQTT_FORMAT: 'Topic/Payload Format',
|
||||
MQTT_NEST_1: 'Nestlat i en topic.',
|
||||
MQTT_NEST_2: 'Som individuella topics',
|
||||
MQTT_RESPONSE: 'Publish-kommando som ett `response` topic',
|
||||
MQTT_PUBLISH_TEXT_1: 'Publicera single value topics vid värdeförändring',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publicera till kommando-topics (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Aktivera MQTT Discovery (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix för Discovery topics',
|
||||
MQTT_PUBLISH_INTERVALS: 'Publiceringsintervall',
|
||||
MQTT_INT_BOILER: 'Värmepump/panna',
|
||||
MQTT_INT_THERMOSTATS: 'Termostater',
|
||||
MQTT_INT_SOLAR: 'Solpaneler',
|
||||
MQTT_INT_MIXER: 'Blandningsventiler',
|
||||
MQTT_INT_HEARTBEAT: 'Heartbeat',
|
||||
MQTT_QUEUE: 'MQTT-kö',
|
||||
DEFAULT: 'Standard',
|
||||
MQTT_ENTITY_FORMAT: 'Entitets-ID format',
|
||||
MQTT_ENTITY_FORMAT_0: 'Singel-instans, långt namn(v3.4)',
|
||||
MQTT_ENTITY_FORMAT_1: 'Singel-instans, kort name',
|
||||
MQTT_ENTITY_FORMAT_2: 'Multi-instans, kort name',
|
||||
MQTT_CLEAN_SESSION: 'Använd "Clean Session"-flaggan',
|
||||
MQTT_RETAIN_FLAG: 'Använd "Always Retain"-flaggan',
|
||||
INACTIVE: 'Inaktiv',
|
||||
ACTIVE: 'Aktiv',
|
||||
UNKNOWN: 'Okänt',
|
||||
SET_TIME: 'Ställ in klockan',
|
||||
SET_TIME_TEXT: 'Ange lokal datum och tid nedan för att ställa in klockan',
|
||||
LOCAL_TIME: 'Tid (lokal)',
|
||||
UTC_TIME: 'Tid (UTC)',
|
||||
ENABLE_NTP: 'Aktivera NTP',
|
||||
NTP_SERVER: 'NTP-server',
|
||||
TIME_ZONE: 'Tidszon',
|
||||
ACCESS_POINT: 'Accesspunkt',
|
||||
AP_PROVIDE: 'Aktivera Accesspunkt',
|
||||
AP_PROVIDE_TEXT_1: 'alltid',
|
||||
AP_PROVIDE_TEXT_2: 'när WiFi är nedkopplat',
|
||||
AP_PROVIDE_TEXT_3: 'aldrig',
|
||||
AP_PREFERRED_CHANNEL: 'Kanal',
|
||||
AP_HIDE_SSID: 'Göm SSID',
|
||||
AP_CLIENTS: 'AP-klienter',
|
||||
AP_MAX_CLIENTS: 'Max Klienter',
|
||||
AP_LOCAL_IP: 'Lokalt IP',
|
||||
NETWORK_SCAN: 'Sök efter WiFi-nätverk',
|
||||
IDLE: 'Vilande',
|
||||
LOST: 'Förlorad',
|
||||
SCANNING: 'Söker',
|
||||
SCAN_AGAIN: 'Sök igen',
|
||||
NETWORK_SCANNER: 'Hittade nätverk',
|
||||
NETWORK_NO_WIFI: 'Inga WiFi-nätverk hittades',
|
||||
NETWORK_BLANK_SSID: 'lämna blankt för att inaktivera WiFi',
|
||||
TX_POWER: 'Tx Effekt',
|
||||
HOSTNAME: 'Värdnamn',
|
||||
NETWORK_DISABLE_SLEEP: 'Inaktivera sömnläge',
|
||||
NETWORK_LOW_BAND: 'Använd lägre bandbredd',
|
||||
NETWORK_USE_DNS: 'Aktivera mDNS-tjänsten',
|
||||
NETWORK_ENABLE_CORS: 'Aktivera CORS',
|
||||
NETWORK_CORS_ORIGIN: 'CORS origin',
|
||||
NETWORK_ENABLE_IPV6: 'Aktivera IPv6-support',
|
||||
NETWORK_FIXED_IP: 'Använd statiskt IP',
|
||||
NETWORK_GATEWAY: 'Gateway',
|
||||
NETWORK_SUBNET: 'Subnätmask',
|
||||
NETWORK_DNS: 'DNS-Server',
|
||||
ADDRESS_OF: '{0} Adress',
|
||||
ADMIN: 'Admin',
|
||||
GUEST: 'Gäst',
|
||||
NEW: 'Ny',
|
||||
NEW_NAME_OF: 'Byt namn {0}',
|
||||
ENTITY: 'Entitet',
|
||||
MIN: 'min',
|
||||
MAX: 'max'
|
||||
};
|
||||
|
||||
export default sv;
|
||||
@@ -5,17 +5,21 @@ import { Tab } from '@mui/material';
|
||||
|
||||
import { RouterTabs, useRouterTab, useLayoutTitle } from '../components';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
import DashboardStatus from './DashboardStatus';
|
||||
import DashboardData from './DashboardData';
|
||||
|
||||
const Dashboard: FC = () => {
|
||||
useLayoutTitle('Dashboard');
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.DASHBOARD());
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="data" label="Devices & Sensors" />
|
||||
<Tab value="data" label={LL.DEVICES_SENSORS()} />
|
||||
<Tab value="status" label="Status" />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
|
||||
@@ -49,8 +49,6 @@ import DeviceIcon from './DeviceIcon';
|
||||
|
||||
import { IconContext } from 'react-icons';
|
||||
|
||||
import { formatDurationMin, pluralize } from '../utils';
|
||||
|
||||
import { AuthenticatedContext } from '../contexts/authentication';
|
||||
|
||||
import { ButtonRow, ValidatedTextField, SectionContent, MessageBox } from '../components';
|
||||
@@ -74,12 +72,23 @@ import {
|
||||
DeviceEntityMask
|
||||
} from './types';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
const DashboardData: FC = () => {
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [coreData, setCoreData] = useState<CoreData>({ connected: true, devices: [], active_sensors: 0, analog_enabled: false });
|
||||
const [coreData, setCoreData] = useState<CoreData>({
|
||||
connected: true,
|
||||
devices: [],
|
||||
s_n: '',
|
||||
active_sensors: 0,
|
||||
analog_enabled: false
|
||||
});
|
||||
|
||||
const [deviceData, setDeviceData] = useState<DeviceData>({ label: '', data: [] });
|
||||
const [sensorData, setSensorData] = useState<SensorData>({ sensors: [], analogs: [] });
|
||||
const [deviceValue, setDeviceValue] = useState<DeviceValue>();
|
||||
@@ -134,7 +143,7 @@ const DashboardData: FC = () => {
|
||||
common_theme,
|
||||
{
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: 40px 100px repeat(1, minmax(0, 1fr)) 80px 40px;
|
||||
--data-table-library_grid-template-columns: 40px 160px repeat(1, minmax(0, 1fr)) 100px 40px;
|
||||
`,
|
||||
BaseRow: `
|
||||
.td {
|
||||
@@ -162,7 +171,7 @@ const DashboardData: FC = () => {
|
||||
common_theme,
|
||||
{
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 140px 40px;
|
||||
--data-table-library_grid-template-columns: minmax(0, 1fr) 35% 40px;
|
||||
`,
|
||||
BaseRow: `
|
||||
.td {
|
||||
@@ -319,10 +328,10 @@ const DashboardData: FC = () => {
|
||||
|
||||
const handleDownloadCsv = () => {
|
||||
const columns = [
|
||||
{ accessor: (dv: any) => dv.id.slice(2), name: 'Entity' },
|
||||
{ accessor: (dv: any) => dv.id.slice(2), name: LL.ENTITY_NAME() },
|
||||
{
|
||||
accessor: (dv: any) => (typeof dv.v === 'number' ? new Intl.NumberFormat().format(dv.v) : dv.v),
|
||||
name: 'Value'
|
||||
name: LL.VALUE(0)
|
||||
},
|
||||
{ accessor: (dv: any) => DeviceValueUOM_s[dv.u], name: 'UoM' }
|
||||
];
|
||||
@@ -354,10 +363,10 @@ const DashboardData: FC = () => {
|
||||
const fetchCoreData = useCallback(async () => {
|
||||
try {
|
||||
setCoreData((await EMSESP.readCoreData()).data);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Failed to fetch core data'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
}, [enqueueSnackbar]);
|
||||
}, [enqueueSnackbar, LL]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCoreData();
|
||||
@@ -375,30 +384,50 @@ const DashboardData: FC = () => {
|
||||
const unique_id = parseInt(id);
|
||||
try {
|
||||
setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching device data'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSensorData = async () => {
|
||||
try {
|
||||
setSensorData((await EMSESP.readSensorData()).data);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching sensor data'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const isCmdOnly = (dv: DeviceValue) => dv.v === '' && dv.c;
|
||||
|
||||
const formatDurationMin = (duration_min: number) => {
|
||||
const days = Math.trunc((duration_min * 60000) / 86400000);
|
||||
const hours = Math.trunc((duration_min * 60000) / 3600000) % 24;
|
||||
const minutes = Math.trunc((duration_min * 60000) / 60000) % 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 });
|
||||
}
|
||||
return formatted;
|
||||
};
|
||||
|
||||
function formatValue(value: any, uom: number) {
|
||||
if (value === undefined) {
|
||||
return '';
|
||||
}
|
||||
switch (uom) {
|
||||
case DeviceValueUOM.HOURS:
|
||||
return value ? formatDurationMin(value * 60) : '0 hours';
|
||||
return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 });
|
||||
case DeviceValueUOM.MINUTES:
|
||||
return value ? formatDurationMin(value) : '0 minutes';
|
||||
return value ? formatDurationMin(value) : LL.NUM_MINUTES({ num: 0 });
|
||||
case DeviceValueUOM.SECONDS:
|
||||
return LL.NUM_SECONDS({ num: value });
|
||||
case DeviceValueUOM.NONE:
|
||||
if (typeof value === 'number') {
|
||||
return new Intl.NumberFormat().format(value);
|
||||
@@ -414,13 +443,24 @@ const DashboardData: FC = () => {
|
||||
' ' +
|
||||
DeviceValueUOM_s[uom]
|
||||
);
|
||||
case DeviceValueUOM.SECONDS:
|
||||
return pluralize(value, DeviceValueUOM_s[uom]);
|
||||
default:
|
||||
return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom];
|
||||
}
|
||||
}
|
||||
|
||||
const setUom = (uom: number) => {
|
||||
switch (uom) {
|
||||
case DeviceValueUOM.HOURS:
|
||||
return LL.HOURS();
|
||||
case DeviceValueUOM.MINUTES:
|
||||
return LL.MINUTES();
|
||||
case DeviceValueUOM.SECONDS:
|
||||
return LL.SECONDS();
|
||||
default:
|
||||
return DeviceValueUOM_s[uom];
|
||||
}
|
||||
};
|
||||
|
||||
const sendDeviceValue = async () => {
|
||||
if (deviceValue) {
|
||||
try {
|
||||
@@ -429,15 +469,15 @@ const DashboardData: FC = () => {
|
||||
devicevalue: deviceValue
|
||||
});
|
||||
if (response.status === 204) {
|
||||
enqueueSnackbar('Write command failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.WRITE_CMD_FAILED(), { variant: 'error' });
|
||||
} else if (response.status === 403) {
|
||||
enqueueSnackbar('Write access denied', { variant: 'error' });
|
||||
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
|
||||
} else {
|
||||
enqueueSnackbar('Write command sent', { variant: 'success' });
|
||||
enqueueSnackbar(LL.WRITE_CMD_SENT(), { variant: 'success' });
|
||||
}
|
||||
setDeviceValue(undefined);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem writing value'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
refreshData();
|
||||
setDeviceValue(undefined);
|
||||
@@ -449,7 +489,7 @@ const DashboardData: FC = () => {
|
||||
if (deviceValue) {
|
||||
return (
|
||||
<Dialog open={deviceValue !== undefined} onClose={() => setDeviceValue(undefined)}>
|
||||
<DialogTitle>{isCmdOnly(deviceValue) ? 'Run Command' : 'Change Value'}</DialogTitle>
|
||||
<DialogTitle>{isCmdOnly(deviceValue) ? LL.RUN_COMMAND() : LL.CHANGE_VALUE()}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{deviceValue.l && (
|
||||
<ValidatedTextField
|
||||
@@ -470,7 +510,7 @@ const DashboardData: FC = () => {
|
||||
<ValidatedTextField
|
||||
name="v"
|
||||
label={deviceValue.id.slice(2)}
|
||||
value={deviceValue.u ? numberValue(deviceValue.v) : deviceValue.v}
|
||||
value={typeof deviceValue.v === 'number' ? Math.round(deviceValue.v * 10) / 10 : deviceValue.v}
|
||||
autoFocus
|
||||
multiline={deviceValue.u ? false : true}
|
||||
sx={{ width: '30ch' }}
|
||||
@@ -478,7 +518,7 @@ const DashboardData: FC = () => {
|
||||
onChange={updateValue(setDeviceValue)}
|
||||
inputProps={deviceValue.u ? { min: deviceValue.m, max: deviceValue.x, step: deviceValue.s } : {}}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start">{DeviceValueUOM_s[deviceValue.u]}</InputAdornment>
|
||||
startAdornment: <InputAdornment position="start">{setUom(deviceValue.u)}</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -491,7 +531,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => setDeviceValue(undefined)}
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SendIcon />}
|
||||
@@ -500,7 +540,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => sendDeviceValue()}
|
||||
color="warning"
|
||||
>
|
||||
Send
|
||||
{LL.SEND()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -521,15 +561,15 @@ const DashboardData: FC = () => {
|
||||
offset: sensor.o
|
||||
});
|
||||
if (response.status === 204) {
|
||||
enqueueSnackbar('Sensor change failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.UPLOAD_OF(LL.SENSOR()) + ' ' + LL.FAILED(), { variant: 'error' });
|
||||
} else if (response.status === 403) {
|
||||
enqueueSnackbar('Access denied', { variant: 'error' });
|
||||
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
|
||||
} else {
|
||||
enqueueSnackbar('Sensor updated', { variant: 'success' });
|
||||
enqueueSnackbar(LL.UPDATED_OF(LL.SENSOR()), { variant: 'success' });
|
||||
}
|
||||
setSensor(undefined);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem updating sensor'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setSensor(undefined);
|
||||
fetchSensorData();
|
||||
@@ -541,16 +581,20 @@ const DashboardData: FC = () => {
|
||||
if (sensor) {
|
||||
return (
|
||||
<Dialog open={sensor !== undefined} onClose={() => setSensor(undefined)}>
|
||||
<DialogTitle>Edit Temperature Sensor</DialogTitle>
|
||||
<DialogTitle>
|
||||
{LL.EDIT()} {LL.TEMP_SENSOR()}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
|
||||
<Typography variant="body2">Sensor ID {sensor.id}</Typography>
|
||||
<Typography variant="body2">
|
||||
{LL.ID_OF(LL.SENSOR())}: {sensor.id}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="n"
|
||||
label="Name"
|
||||
label={LL.ENTITY_NAME()}
|
||||
value={sensor.n}
|
||||
autoFocus
|
||||
sx={{ width: '30ch' }}
|
||||
@@ -560,7 +604,7 @@ const DashboardData: FC = () => {
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="Offset"
|
||||
label={LL.OFFSET()}
|
||||
value={numberValue(sensor.o)}
|
||||
sx={{ width: '12ch' }}
|
||||
type="number"
|
||||
@@ -581,7 +625,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => setSensor(undefined)}
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SaveIcon />}
|
||||
@@ -590,7 +634,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => sendSensor()}
|
||||
color="warning"
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -602,35 +646,35 @@ const DashboardData: FC = () => {
|
||||
if (coreData && coreData.devices.length > 0 && deviceDialog !== -1) {
|
||||
return (
|
||||
<Dialog open={deviceDialog !== -1} onClose={() => setDeviceDialog(-1)}>
|
||||
<DialogTitle>Device Details</DialogTitle>
|
||||
<DialogTitle>{LL.DEVICE_DETAILS()}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<List dense={true}>
|
||||
<ListItem>
|
||||
<ListItemText primary="Type" secondary={coreData.devices[deviceDialog].t} />
|
||||
<ListItemText primary={LL.TYPE()} secondary={coreData.devices[deviceDialog].tn} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="Name" secondary={coreData.devices[deviceDialog].n} />
|
||||
<ListItemText primary={LL.NAME(0)} secondary={coreData.devices[deviceDialog].n} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="Brand" secondary={coreData.devices[deviceDialog].b} />
|
||||
<ListItemText primary={LL.BRAND()} secondary={coreData.devices[deviceDialog].b} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Device ID"
|
||||
primary={LL.ID_OF(LL.DEVICE())}
|
||||
secondary={'0x' + ('00' + coreData.devices[deviceDialog].d.toString(16).toUpperCase()).slice(-2)}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="Product ID" secondary={coreData.devices[deviceDialog].p} />
|
||||
<ListItemText primary={LL.ID_OF(LL.PRODUCT())} secondary={coreData.devices[deviceDialog].p} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="Version" secondary={coreData.devices[deviceDialog].v} />
|
||||
<ListItemText primary={LL.VERSION()} secondary={coreData.devices[deviceDialog].v} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="outlined" onClick={() => setDeviceDialog(-1)} color="secondary">
|
||||
Close
|
||||
{LL.CLOSE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -640,17 +684,20 @@ const DashboardData: FC = () => {
|
||||
|
||||
const renderCoreData = () => (
|
||||
<IconContext.Provider value={{ color: 'lightblue', size: '24', style: { verticalAlign: 'middle' } }}>
|
||||
{!coreData.connected && <MessageBox my={2} level="error" message="EMSbus disconnected, check settings and board profile" />}
|
||||
{coreData.connected && coreData.devices.length === 0 && <MessageBox my={2} level="warning" message="Scanning for EMS devices..." />}
|
||||
{!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()} />
|
||||
)}
|
||||
|
||||
<Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell stiff />
|
||||
<HeaderCell stiff>TYPE</HeaderCell>
|
||||
<HeaderCell resize>DESCRIPTION</HeaderCell>
|
||||
<HeaderCell stiff>ENTITIES</HeaderCell>
|
||||
<HeaderCell stiff>{LL.TYPE()}</HeaderCell>
|
||||
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.ENTITIES()}</HeaderCell>
|
||||
<HeaderCell stiff />
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
@@ -658,9 +705,9 @@ const DashboardData: FC = () => {
|
||||
{tableList.map((device: Device, index: number) => (
|
||||
<Row key={device.id} item={device}>
|
||||
<Cell stiff>
|
||||
<DeviceIcon type={device.t} />
|
||||
<DeviceIcon type_id={device.t} />
|
||||
</Cell>
|
||||
<Cell stiff>{device.t}</Cell>
|
||||
<Cell stiff>{device.tn}</Cell>
|
||||
<Cell>{device.n}</Cell>
|
||||
<Cell stiff>{device.e}</Cell>
|
||||
<Cell stiff>
|
||||
@@ -673,10 +720,10 @@ const DashboardData: FC = () => {
|
||||
{(coreData.active_sensors > 0 || coreData.analog_enabled) && (
|
||||
<Row key="sensor" item={{ id: 'sensor' }}>
|
||||
<Cell>
|
||||
<DeviceIcon type="Sensor" />
|
||||
<DeviceIcon type_id={1} />
|
||||
</Cell>
|
||||
<Cell>Sensors</Cell>
|
||||
<Cell>Attached EMS-ESP Sensors</Cell>
|
||||
<Cell>{coreData.s_n}</Cell>
|
||||
<Cell>{LL.ATTACHED_SENSORS()}</Cell>
|
||||
<Cell>{coreData.active_sensors}</Cell>
|
||||
<Cell>
|
||||
<IconButton size="small" onClick={() => addAnalogSensor()}>
|
||||
@@ -723,7 +770,7 @@ const DashboardData: FC = () => {
|
||||
control={<Checkbox size="small" name="onlyFav" checked={onlyFav} onChange={() => setOnlyFav(!onlyFav)} />}
|
||||
label={
|
||||
<span style={{ fontSize: '12px' }}>
|
||||
only show favorites
|
||||
{LL.SHOW_FAV()}
|
||||
<StarIcon color="primary" sx={{ fontSize: 12 }} />
|
||||
</span>
|
||||
}
|
||||
@@ -749,7 +796,7 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(dv_sort.state, 'NAME')}
|
||||
onClick={() => dv_sort.fns.onToggleSort({ sortKey: 'NAME' })}
|
||||
>
|
||||
ENTITY NAME
|
||||
{LL.ENTITY_NAME()}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell resize>
|
||||
@@ -759,7 +806,7 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(dv_sort.state, 'VALUE')}
|
||||
onClick={() => dv_sort.fns.onToggleSort({ sortKey: 'VALUE' })}
|
||||
>
|
||||
VALUE
|
||||
{LL.VALUE(0)}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell stiff />
|
||||
@@ -806,7 +853,7 @@ const DashboardData: FC = () => {
|
||||
const renderDallasData = () => (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
|
||||
Temperature Sensors
|
||||
{LL.TEMP_SENSORS()}
|
||||
</Typography>
|
||||
<Table
|
||||
data={{ nodes: sensorData.sensors }}
|
||||
@@ -825,7 +872,7 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(sensor_sort.state, 'NAME')}
|
||||
onClick={() => sensor_sort.fns.onToggleSort({ sortKey: 'NAME' })}
|
||||
>
|
||||
NAME
|
||||
{LL.ENTITY_NAME()}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell stiff>
|
||||
@@ -835,7 +882,7 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(sensor_sort.state, 'TEMPERATURE')}
|
||||
onClick={() => sensor_sort.fns.onToggleSort({ sortKey: 'TEMPERATURE' })}
|
||||
>
|
||||
TEMPERATURE
|
||||
{LL.VALUE(0)}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell stiff />
|
||||
@@ -865,7 +912,7 @@ const DashboardData: FC = () => {
|
||||
const renderAnalogData = () => (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
|
||||
Analog Sensors
|
||||
{LL.ANALOG_SENSORS()}
|
||||
</Typography>
|
||||
|
||||
<Table data={{ nodes: sensorData.analogs }} theme={analog_theme} sort={analog_sort} layout={{ custom: true }}>
|
||||
@@ -890,7 +937,7 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(analog_sort.state, 'NAME')}
|
||||
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'NAME' })}
|
||||
>
|
||||
NAME
|
||||
{LL.ENTITY_NAME()}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell stiff>
|
||||
@@ -900,10 +947,10 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(analog_sort.state, 'TYPE')}
|
||||
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'TYPE' })}
|
||||
>
|
||||
TYPE
|
||||
{LL.TYPE()}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell stiff>VALUE</HeaderCell>
|
||||
<HeaderCell stiff>{LL.VALUE(0)}</HeaderCell>
|
||||
<HeaderCell stiff />
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
@@ -943,14 +990,14 @@ const DashboardData: FC = () => {
|
||||
});
|
||||
|
||||
if (response.status === 204) {
|
||||
enqueueSnackbar('Analog deletion failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.DELETION_OF(LL.ANALOG_SENSOR()) + ' ' + LL.FAILED(), { variant: 'error' });
|
||||
} else if (response.status === 403) {
|
||||
enqueueSnackbar('Access denied', { variant: 'error' });
|
||||
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
|
||||
} else {
|
||||
enqueueSnackbar('Analog sensor removed', { variant: 'success' });
|
||||
enqueueSnackbar(LL.REMOVED_OF(LL.ANALOG_SENSOR()), { variant: 'success' });
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem updating analog sensor'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setAnalog(undefined);
|
||||
fetchSensorData();
|
||||
@@ -971,14 +1018,14 @@ const DashboardData: FC = () => {
|
||||
});
|
||||
|
||||
if (response.status === 204) {
|
||||
enqueueSnackbar('Analog sensor update failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.UPDATE_OF(LL.ANALOG_SENSOR()) + ' ' + LL.FAILED(), { variant: 'error' });
|
||||
} else if (response.status === 403) {
|
||||
enqueueSnackbar('Access denied', { variant: 'error' });
|
||||
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
|
||||
} else {
|
||||
enqueueSnackbar('Analog sensor updated', { variant: 'success' });
|
||||
enqueueSnackbar(LL.UPDATED_OF(LL.ANALOG_SENSOR()), { variant: 'success' });
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem updating analog'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setAnalog(undefined);
|
||||
fetchSensorData();
|
||||
@@ -990,32 +1037,42 @@ const DashboardData: FC = () => {
|
||||
if (analog) {
|
||||
return (
|
||||
<Dialog open={analog !== undefined} onClose={() => setAnalog(undefined)}>
|
||||
<DialogTitle>Edit Analog Sensor</DialogTitle>
|
||||
<DialogTitle>
|
||||
{LL.EDIT()} {LL.ANALOG_SENSOR()}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item>
|
||||
<Grid item xs={12}>
|
||||
<ValidatedTextField
|
||||
name="n"
|
||||
label={LL.ENTITY_NAME()}
|
||||
value={analog.n}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={updateValue(setAnalog)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="g"
|
||||
label="GPIO"
|
||||
value={analog.g}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
autoFocus
|
||||
onChange={updateValue(setAnalog)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid item xs={8}>
|
||||
<ValidatedTextField
|
||||
name="n"
|
||||
label="Name"
|
||||
value={analog.n}
|
||||
sx={{ width: '20ch' }}
|
||||
variant="outlined"
|
||||
name="t"
|
||||
label={LL.TYPE()}
|
||||
value={analog.t}
|
||||
fullWidth
|
||||
select
|
||||
onChange={updateValue(setAnalog)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ValidatedTextField name="t" label="Type" value={analog.t} select onChange={updateValue(setAnalog)}>
|
||||
>
|
||||
{AnalogTypeNames.map((val, i) => (
|
||||
<MenuItem key={i} value={i}>
|
||||
{val}
|
||||
@@ -1025,8 +1082,15 @@ const DashboardData: FC = () => {
|
||||
</Grid>
|
||||
{analog.t >= AnalogType.COUNTER && analog.t <= AnalogType.RATE && (
|
||||
<>
|
||||
<Grid item>
|
||||
<ValidatedTextField name="u" label="UoM" value={analog.u} select onChange={updateValue(setAnalog)}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="u"
|
||||
label={LL.UNIT()}
|
||||
value={analog.u}
|
||||
fullWidth
|
||||
select
|
||||
onChange={updateValue(setAnalog)}
|
||||
>
|
||||
{DeviceValueUOM_s.map((val, i) => (
|
||||
<MenuItem key={i} value={i}>
|
||||
{val}
|
||||
@@ -1035,12 +1099,12 @@ const DashboardData: FC = () => {
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
{analog.t === AnalogType.ADC && (
|
||||
<Grid item>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="Offset"
|
||||
label={LL.OFFSET()}
|
||||
value={numberValue(analog.o)}
|
||||
sx={{ width: '20ch' }}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateValue(setAnalog)}
|
||||
@@ -1052,41 +1116,41 @@ const DashboardData: FC = () => {
|
||||
</Grid>
|
||||
)}
|
||||
{analog.t === AnalogType.COUNTER && (
|
||||
<Grid item>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="Start Value"
|
||||
label={LL.STARTVALUE()}
|
||||
value={numberValue(analog.o)}
|
||||
sx={{ width: '20ch' }}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateValue(setAnalog)}
|
||||
inputProps={{ min: '0', step: '1' }}
|
||||
inputProps={{ step: '0.001' }}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="f"
|
||||
label="Factor"
|
||||
label={LL.FACTOR()}
|
||||
value={numberValue(analog.f)}
|
||||
sx={{ width: '20ch' }}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateValue(setAnalog)}
|
||||
inputProps={{ min: '-100', max: '100', step: '0.1' }}
|
||||
inputProps={{ step: '0.001' }}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{analog.t === AnalogType.DIGITAL_OUT && (analog.id === '25' || analog.id === '26') && (
|
||||
{analog.t === AnalogType.DIGITAL_OUT && (analog.g === 25 || analog.g === 26) && (
|
||||
<>
|
||||
<Grid item>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="DAC Value"
|
||||
label={LL.VALUE(0)}
|
||||
value={numberValue(analog.o)}
|
||||
sx={{ width: '20ch' }}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateValue(setAnalog)}
|
||||
@@ -1095,14 +1159,14 @@ const DashboardData: FC = () => {
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{analog.t === AnalogType.DIGITAL_OUT && analog.id !== '25' && analog.id !== '26' && (
|
||||
{analog.t === AnalogType.DIGITAL_OUT && analog.g !== 25 && analog.g !== 26 && (
|
||||
<>
|
||||
<Grid item>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="Value"
|
||||
label={LL.VALUE(0)}
|
||||
value={numberValue(analog.o)}
|
||||
sx={{ width: '20ch' }}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateValue(setAnalog)}
|
||||
@@ -1113,12 +1177,12 @@ const DashboardData: FC = () => {
|
||||
)}
|
||||
{analog.t >= AnalogType.PWM_0 && (
|
||||
<>
|
||||
<Grid item>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="f"
|
||||
label="Frequency"
|
||||
label={LL.FREQ()}
|
||||
value={numberValue(analog.f)}
|
||||
sx={{ width: '20ch' }}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateValue(setAnalog)}
|
||||
@@ -1128,12 +1192,12 @@ const DashboardData: FC = () => {
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="Dutycycle"
|
||||
label={LL.DUTY_CYCLE()}
|
||||
value={numberValue(analog.o)}
|
||||
sx={{ width: '20ch' }}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateValue(setAnalog)}
|
||||
@@ -1147,13 +1211,13 @@ const DashboardData: FC = () => {
|
||||
)}
|
||||
</Grid>
|
||||
<Box color="warning.main" mt={2}>
|
||||
<Typography variant="body2">Warning: be careful when assigning a GPIO!</Typography>
|
||||
<Typography variant="body2">{LL.WARN_GPIO()}</Typography>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Box flexGrow={1} sx={{ '& button': { mt: 0 } }}>
|
||||
<Button startIcon={<RemoveIcon />} variant="outlined" color="error" onClick={() => sendRemoveAnalog()}>
|
||||
Remove
|
||||
{LL.REMOVE()}
|
||||
</Button>
|
||||
</Box>
|
||||
<Button
|
||||
@@ -1162,7 +1226,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => setAnalog(undefined)}
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SaveIcon />}
|
||||
@@ -1171,7 +1235,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => sendAnalog()}
|
||||
color="warning"
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -1180,7 +1244,7 @@ const DashboardData: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Device and Sensor Data" titleGutter>
|
||||
<SectionContent title={LL.DEVICE_SENSOR_DATA()} titleGutter>
|
||||
{renderCoreData()}
|
||||
{renderDeviceData()}
|
||||
{renderDeviceDialog()}
|
||||
@@ -1191,11 +1255,11 @@ const DashboardData: FC = () => {
|
||||
{renderAnalogDialog()}
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={refreshData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
{device_select.state.id && device_select.state.id !== 'sensor' && (
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" onClick={handleDownloadCsv}>
|
||||
Export
|
||||
{LL.EXPORT()}
|
||||
</Button>
|
||||
)}
|
||||
</ButtonRow>
|
||||
|
||||
@@ -32,10 +32,13 @@ import { ButtonRow, FormLoader, SectionContent } from '../components';
|
||||
|
||||
import { Status, busConnectionStatus, Stat } from './types';
|
||||
|
||||
import { formatDurationSec, pluralize, extractErrorMessage, useRest } from '../utils';
|
||||
import { extractErrorMessage, useRest } from '../utils';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
|
||||
import type { Translation } from '../i18n/i18n-types';
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE;
|
||||
|
||||
const busStatusHighlight = ({ status }: Status, theme: Theme) => {
|
||||
@@ -51,19 +54,6 @@ const busStatusHighlight = ({ status }: Status, theme: Theme) => {
|
||||
}
|
||||
};
|
||||
|
||||
const busStatus = ({ status }: Status) => {
|
||||
switch (status) {
|
||||
case busConnectionStatus.BUS_STATUS_CONNECTED:
|
||||
return 'Connected';
|
||||
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
|
||||
return 'Tx issues - try a different Tx Mode';
|
||||
case busConnectionStatus.BUS_STATUS_OFFLINE:
|
||||
return 'Disconnected';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const showQuality = (stat: Stat) => {
|
||||
if (stat.q === 0 || stat.s + stat.f === 0) {
|
||||
return;
|
||||
@@ -81,12 +71,32 @@ const showQuality = (stat: Stat) => {
|
||||
const DashboardStatus: FC = () => {
|
||||
const { loadData, data, errorMessage } = useRest<Status>({ read: EMSESP.readStatus });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const theme = useTheme();
|
||||
const [confirmScan, setConfirmScan] = useState<boolean>(false);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
const showName = (id: any) => {
|
||||
let name: keyof Translation['STATUS_NAMES'] = id;
|
||||
return LL.STATUS_NAMES[name]();
|
||||
};
|
||||
|
||||
const busStatus = ({ status }: Status) => {
|
||||
switch (status) {
|
||||
case busConnectionStatus.BUS_STATUS_CONNECTED:
|
||||
return LL.CONNECTED(0);
|
||||
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
|
||||
return LL.TX_ISSUES();
|
||||
case busConnectionStatus.BUS_STATUS_OFFLINE:
|
||||
return LL.DISCONNECTED();
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const stats_theme = tableTheme({
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px;
|
||||
@@ -137,24 +147,44 @@ const DashboardStatus: FC = () => {
|
||||
const scan = async () => {
|
||||
try {
|
||||
await EMSESP.scanDevices();
|
||||
enqueueSnackbar('Scanning for devices...', { variant: 'info' });
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem initiating scan'), { variant: 'error' });
|
||||
enqueueSnackbar(LL.SCANNING() + '...', { variant: 'info' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setConfirmScan(false);
|
||||
}
|
||||
};
|
||||
|
||||
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 renderScanDialog = () => (
|
||||
<Dialog open={confirmScan} onClose={() => setConfirmScan(false)}>
|
||||
<DialogTitle>EMS Device Scan</DialogTitle>
|
||||
<DialogContent dividers>Are you sure you want to initiate a full device scan of the EMS bus?</DialogContent>
|
||||
<DialogTitle>{LL.SCAN_DEVICES()}</DialogTitle>
|
||||
<DialogContent dividers>{LL.EMS_SCAN()}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmScan(false)} color="secondary">
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button startIcon={<PermScanWifiIcon />} variant="outlined" onClick={scan} color="primary" autoFocus>
|
||||
Scan
|
||||
{LL.SCAN()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -174,7 +204,10 @@ const DashboardStatus: FC = () => {
|
||||
<DirectionsBusIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="EMS Bus Status" secondary={busStatus(data) + formatDurationSec(data.uptime)} />
|
||||
<ListItemText
|
||||
primary={LL.EMS_BUS_STATUS()}
|
||||
secondary={busStatus(data) + ' (' + formatDurationSec(data.uptime) + ')'}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
@@ -183,13 +216,13 @@ const DashboardStatus: FC = () => {
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="Active Devices & Sensors"
|
||||
primary={LL.ACTIVE_DEVICES()}
|
||||
secondary={
|
||||
pluralize(data.num_devices, 'EMS Device') +
|
||||
LL.NUM_DEVICES({ num: data.num_devices }) +
|
||||
', ' +
|
||||
pluralize(data.num_sensors, 'Temperature Sensor') +
|
||||
LL.NUM_TEMP_SENSORS({ num: data.num_sensors }) +
|
||||
', ' +
|
||||
pluralize(data.num_analogs, 'Analog Sensor')
|
||||
LL.NUM_ANALOG_SENSORS({ num: data.num_analogs })
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
@@ -200,15 +233,15 @@ const DashboardStatus: FC = () => {
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell resize></HeaderCell>
|
||||
<HeaderCell stiff>SUCCESS</HeaderCell>
|
||||
<HeaderCell stiff>FAIL</HeaderCell>
|
||||
<HeaderCell stiff>QUALITY</HeaderCell>
|
||||
<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>{stat.id}</Cell>
|
||||
<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>
|
||||
@@ -223,7 +256,7 @@ const DashboardStatus: FC = () => {
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</Box>
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
@@ -235,7 +268,7 @@ const DashboardStatus: FC = () => {
|
||||
disabled={!me.admin}
|
||||
onClick={() => setConfirmScan(true)}
|
||||
>
|
||||
Scan for new devices
|
||||
{LL.SCAN_DEVICES()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -245,7 +278,7 @@ const DashboardStatus: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="EMS Bus & Activity Status" titleGutter>
|
||||
<SectionContent title={LL.EMS_BUS_STATUS_TITLE()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -9,31 +9,61 @@ import { GiHeatHaze } from 'react-icons/gi';
|
||||
import { TiFlowSwitch } from 'react-icons/ti';
|
||||
import { VscVmConnect } from 'react-icons/vsc';
|
||||
import { AiOutlineGateway } from 'react-icons/ai';
|
||||
import { AiOutlineAlert } from 'react-icons/ai';
|
||||
import { AiOutlineChrome } from 'react-icons/ai';
|
||||
|
||||
interface DeviceIconProps {
|
||||
type: string;
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
const DeviceIcon: FC<DeviceIconProps> = ({ type }) => {
|
||||
switch (type) {
|
||||
case 'Boiler':
|
||||
return <CgSmartHomeBoiler />;
|
||||
case 'Sensor':
|
||||
// matches emsdevice.h DeviceType
|
||||
const enum DeviceType {
|
||||
SYSTEM = 0,
|
||||
DALLASSENSOR,
|
||||
ANALOGSENSOR,
|
||||
BOILER,
|
||||
THERMOSTAT,
|
||||
MIXER,
|
||||
SOLAR,
|
||||
HEATPUMP,
|
||||
GATEWAY,
|
||||
SWITCH,
|
||||
CONTROLLER,
|
||||
CONNECT,
|
||||
ALERT,
|
||||
PUMP,
|
||||
GENERIC,
|
||||
HEATSOURCE,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
|
||||
switch (type_id) {
|
||||
case DeviceType.DALLASSENSOR:
|
||||
case DeviceType.ANALOGSENSOR:
|
||||
return <MdOutlineSensors />;
|
||||
case 'Solar':
|
||||
return <FaSolarPanel />;
|
||||
case 'Thermostat':
|
||||
case DeviceType.BOILER:
|
||||
case DeviceType.HEATSOURCE:
|
||||
return <CgSmartHomeBoiler />;
|
||||
case DeviceType.THERMOSTAT:
|
||||
return <MdThermostatAuto />;
|
||||
case 'Mixer':
|
||||
case DeviceType.MIXER:
|
||||
return <AiOutlineControl />;
|
||||
case 'Heatpump':
|
||||
case DeviceType.SOLAR:
|
||||
return <FaSolarPanel />;
|
||||
case DeviceType.HEATPUMP:
|
||||
return <GiHeatHaze />;
|
||||
case 'Switch':
|
||||
return <TiFlowSwitch />;
|
||||
case 'Connect':
|
||||
return <VscVmConnect />;
|
||||
case 'Gateway':
|
||||
case DeviceType.GATEWAY:
|
||||
return <AiOutlineGateway />;
|
||||
case DeviceType.SWITCH:
|
||||
return <TiFlowSwitch />;
|
||||
case DeviceType.CONTROLLER:
|
||||
case DeviceType.CONNECT:
|
||||
return <VscVmConnect />;
|
||||
case DeviceType.ALERT:
|
||||
return <AiOutlineAlert />;
|
||||
case DeviceType.PUMP:
|
||||
return <AiOutlineChrome />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -5,16 +5,20 @@ import { Tab } from '@mui/material';
|
||||
|
||||
import { RouterTabs, useRouterTab, useLayoutTitle } from '../components';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
import HelpInformation from './HelpInformation';
|
||||
|
||||
const Help: FC = () => {
|
||||
useLayoutTitle('Help');
|
||||
const { LL } = useI18nContext();
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
useLayoutTitle(LL.HELP_OF(''));
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="information" label="EMS-ESP Help" />
|
||||
<Tab value="information" label={LL.HELP_OF('EMS-ESP')} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="information" element={<HelpInformation />} />
|
||||
|
||||
@@ -9,14 +9,18 @@ import { useSnackbar } from 'notistack';
|
||||
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
||||
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
|
||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import EastIcon from '@mui/icons-material/East';
|
||||
|
||||
import { extractErrorMessage } from '../utils';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
|
||||
const HelpInformation: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const saveFile = (json: any, endpoint: string) => {
|
||||
@@ -31,7 +35,7 @@ const HelpInformation: FC = () => {
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
enqueueSnackbar('System information downloaded', { variant: 'info' });
|
||||
enqueueSnackbar(LL.DOWNLOAD_SUCCESSFUL(), { variant: 'info' });
|
||||
};
|
||||
|
||||
const callAPI = async (endpoint: string) => {
|
||||
@@ -42,83 +46,84 @@ const HelpInformation: FC = () => {
|
||||
id: 0
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
enqueueSnackbar('API call failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
|
||||
} else {
|
||||
saveFile(response.data, endpoint);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Support Information" titleGutter>
|
||||
<SectionContent title={LL.SUPPORT_INFORMATION()} titleGutter>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<MenuBookIcon />
|
||||
<MenuBookIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText>
|
||||
Visit the online
|
||||
{LL.HELP_INFORMATION_1()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
|
||||
<Link target="_blank" href="https://emsesp.github.io/docs" color="primary">
|
||||
{'Wiki'}
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
to get instructions on how to
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://emsesp.github.io/docs/#/Configure-firmware?id=ems-esp-settings"
|
||||
color="primary"
|
||||
>
|
||||
{'configure'}
|
||||
</Link>
|
||||
EMS-ESP and access other information.
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<CommentIcon />
|
||||
<CommentIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText>
|
||||
For live community chat join our
|
||||
{LL.HELP_INFORMATION_2()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
|
||||
<Link target="_blank" href="https://discord.gg/3J3GgnzpyT" color="primary">
|
||||
{'Discord'}
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
server.
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<GitHubIcon />
|
||||
<GitHubIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
|
||||
<ListItemText>
|
||||
Submit a
|
||||
{LL.HELP_INFORMATION_3()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" color="primary">
|
||||
support issue
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
for requesting a new feature or reporting a bug.
|
||||
<br />
|
||||
Make sure you also
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => callAPI('info')}>
|
||||
download
|
||||
<i>({LL.HELP_INFORMATION_4()}</i>
|
||||
<Button
|
||||
startIcon={<DownloadIcon />}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => callAPI('info')}
|
||||
>
|
||||
{LL.SUPPORT_INFO()}
|
||||
</Button>
|
||||
and attach your system details for a faster response.
|
||||
)
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<Box border={1} p={1} mt={4}>
|
||||
<Typography align="center" variant="h6" color="orange">
|
||||
EMS-ESP will always be a free and open-source project
|
||||
<br></br>Please consider supporting it with a
|
||||
<StarIcon style={{ fontSize: 16, color: 'yellow', verticalAlign: 'middle' }} /> on
|
||||
<Link href="https://github.com/emsesp/EMS-ESP32" color="primary">
|
||||
{'GitHub'}
|
||||
<Box border={1} p={1} mt={4} color="orange">
|
||||
<Typography align="center" variant="subtitle1" color="orange">
|
||||
<b>{LL.HELP_INFORMATION_5()}</b>
|
||||
</Typography>
|
||||
<Typography align="center">
|
||||
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32" color="primary">
|
||||
{'github.com/emsesp/EMS-ESP32'}
|
||||
</Link>
|
||||
</Typography>
|
||||
<Typography align="center">@proddy @MichaelDvP</Typography>
|
||||
<Typography color="white" align="center">
|
||||
@proddy @MichaelDvP
|
||||
</Typography>
|
||||
</Box>
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -13,9 +13,13 @@ import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
|
||||
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
|
||||
|
||||
type OptionType = 'readonly' | 'web_exclude' | 'api_mqtt_exclude' | 'favorite';
|
||||
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||
|
||||
type OptionType = 'deleted' | 'readonly' | 'web_exclude' | 'api_mqtt_exclude' | 'favorite';
|
||||
|
||||
const OPTION_ICONS: { [type in OptionType]: [React.ComponentType<SvgIconProps>, React.ComponentType<SvgIconProps>] } = {
|
||||
deleted: [DeleteForeverIcon, DeleteOutlineIcon],
|
||||
readonly: [EditOffOutlinedIcon, EditOutlinedIcon],
|
||||
web_exclude: [VisibilityOffOutlinedIcon, VisibilityOutlinedIcon],
|
||||
api_mqtt_exclude: [CommentsDisabledOutlinedIcon, InsertCommentOutlinedIcon],
|
||||
|
||||
@@ -6,6 +6,8 @@ import { AuthenticatedContext } from '../contexts/authentication';
|
||||
|
||||
import { PROJECT_PATH } from '../api/env';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||
import LayoutMenuItem from '../components/layout/LayoutMenuItem';
|
||||
@@ -13,17 +15,18 @@ import InfoIcon from '@mui/icons-material/Info';
|
||||
|
||||
const ProjectMenu: FC = () => {
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
<List>
|
||||
<LayoutMenuItem icon={DashboardIcon} label="Dashboard" to={`/${PROJECT_PATH}/dashboard`} />
|
||||
<LayoutMenuItem icon={DashboardIcon} label={LL.DASHBOARD()} to={`/${PROJECT_PATH}/dashboard`} />
|
||||
<LayoutMenuItem
|
||||
icon={TuneIcon}
|
||||
label="Settings"
|
||||
label={LL.SETTINGS_OF('')}
|
||||
to={`/${PROJECT_PATH}/settings`}
|
||||
disabled={!authenticatedContext.me.admin}
|
||||
/>
|
||||
<LayoutMenuItem icon={InfoIcon} label="Help" to={`/${PROJECT_PATH}/help`} />
|
||||
<LayoutMenuItem icon={InfoIcon} label={LL.HELP_OF('')} to={`/${PROJECT_PATH}/help`} />
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,18 +5,22 @@ import { Tab } from '@mui/material';
|
||||
|
||||
import { RouterTabs, useRouterTab, useLayoutTitle } from '../components';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
import SettingsApplication from './SettingsApplication';
|
||||
import SettingsCustomization from './SettingsCustomization';
|
||||
|
||||
const Settings: FC = () => {
|
||||
useLayoutTitle('Settings');
|
||||
const { LL } = useI18nContext();
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
useLayoutTitle(LL.SETTINGS_OF(''));
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="application" label="Application Settings" />
|
||||
<Tab value="customization" label="Customization" />
|
||||
<Tab value="application" label={LL.APPLICATION_SETTINGS()} />
|
||||
<Tab value="customization" label={LL.CUSTOMIZATION()} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="application" element={<SettingsApplication />} />
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ValidateFieldsError } from 'async-validator';
|
||||
|
||||
import { useSnackbar } from 'notistack';
|
||||
|
||||
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider } from '@mui/material';
|
||||
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment } from '@mui/material';
|
||||
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
@@ -24,6 +24,9 @@ import { numberValue, extractErrorMessage, updateValue, useRest } from '../utils
|
||||
import * as EMSESP from './api';
|
||||
import { Settings, BOARD_PROFILES } from './types';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
import RestartMonitor from '../framework/system/RestartMonitor';
|
||||
|
||||
export function boardProfileSelectItems() {
|
||||
return Object.keys(BOARD_PROFILES).map((code) => (
|
||||
<MenuItem key={code} value={code}>
|
||||
@@ -37,6 +40,9 @@ const SettingsApplication: FC = () => {
|
||||
read: EMSESP.readSettings,
|
||||
update: EMSESP.writeSettings
|
||||
});
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
@@ -64,8 +70,8 @@ const SettingsApplication: FC = () => {
|
||||
eth_clock_mode: response.data.eth_clock_mode
|
||||
});
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching board profile'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setProcessingBoard(false);
|
||||
}
|
||||
@@ -102,28 +108,26 @@ const SettingsApplication: FC = () => {
|
||||
validateAndSubmit();
|
||||
try {
|
||||
await EMSESP.restart();
|
||||
enqueueSnackbar('EMS-ESP is restarting...', { variant: 'info' });
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem restarting device'), { variant: 'error' });
|
||||
setRestarting(true);
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Interface Board Profile
|
||||
{LL.INTERFACE_BOARD_PROFILE()}
|
||||
</Typography>
|
||||
<Box color="warning.main">
|
||||
<Typography variant="body2">
|
||||
Select a pre-configured interface board profile from the list below or choose "Custom" to configure your own
|
||||
hardware settings.
|
||||
</Typography>
|
||||
<Typography variant="body2">{LL.BOARD_PROFILE_TEXT()}</Typography>
|
||||
</Box>
|
||||
<ValidatedTextField
|
||||
name="board_profile"
|
||||
label="Board Profile"
|
||||
label={LL.BOARD_PROFILE()}
|
||||
value={data.board_profile}
|
||||
disabled={processingBoard}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={changeBoardProfile}
|
||||
margin="normal"
|
||||
@@ -132,17 +136,24 @@ const SettingsApplication: FC = () => {
|
||||
{boardProfileSelectItems()}
|
||||
<Divider />
|
||||
<MenuItem key={'CUSTOM'} value={'CUSTOM'}>
|
||||
Custom…
|
||||
{LL.CUSTOM()}…
|
||||
</MenuItem>
|
||||
</ValidatedTextField>
|
||||
{data.board_profile === 'CUSTOM' && (
|
||||
<>
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item xs={4}>
|
||||
<Grid
|
||||
container
|
||||
spacing={1}
|
||||
sx={{ pt: 1 }}
|
||||
direction="row"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="rx_gpio"
|
||||
label="Rx GPIO"
|
||||
label={LL.GPIO_OF('Rx')}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.rx_gpio)}
|
||||
@@ -152,11 +163,11 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="tx_gpio"
|
||||
label="Tx GPIO"
|
||||
label={LL.GPIO_OF('Tx')}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.tx_gpio)}
|
||||
@@ -166,11 +177,11 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="pbutton_gpio"
|
||||
label="Button GPIO"
|
||||
label={LL.GPIO_OF(LL.BUTTON())}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.pbutton_gpio)}
|
||||
@@ -180,11 +191,11 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="dallas_gpio"
|
||||
label="Temperature GPIO (0=disabled)"
|
||||
label={LL.GPIO_OF(LL.TEMPERATURE()) + ' (0=' + LL.DISABLED(1) + ')'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.dallas_gpio)}
|
||||
@@ -194,11 +205,11 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="led_gpio"
|
||||
label="LED GPIO (0=disabled)"
|
||||
label={LL.GPIO_OF('LED') + ' (0=' + LL.DISABLED(1) + ')'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.led_gpio)}
|
||||
@@ -208,11 +219,10 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
name="phy_type"
|
||||
label="Eth PHY Type"
|
||||
label={LL.PHY_TYPE()}
|
||||
disabled={saving}
|
||||
value={data.phy_type}
|
||||
fullWidth
|
||||
@@ -221,17 +231,25 @@ const SettingsApplication: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>No Ethernet Module</MenuItem>
|
||||
<MenuItem value={0}>{LL.DISABLED(1)}</MenuItem>
|
||||
<MenuItem value={1}>LAN8720</MenuItem>
|
||||
<MenuItem value={2}>TLK110</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{data.phy_type !== 0 && (
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item>
|
||||
<Grid
|
||||
container
|
||||
spacing={1}
|
||||
sx={{ pt: 1 }}
|
||||
direction="row"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-start"
|
||||
>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
name="eth_power"
|
||||
label="Eth Power GPIO (-1=disabled)"
|
||||
label={LL.GPIO_OF('PHY Power') + ' (-1=' + LL.DISABLED(1) + ')'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.eth_power)}
|
||||
@@ -241,10 +259,10 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
name="eth_phy_addr"
|
||||
label="Eth I²C-address"
|
||||
label={LL.ADDRESS_OF('PHY I²C')}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.eth_phy_addr)}
|
||||
@@ -254,10 +272,10 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
name="eth_clock_mode"
|
||||
label="Eth Clock Mode"
|
||||
label="PHY Clk"
|
||||
disabled={saving}
|
||||
value={data.eth_clock_mode}
|
||||
fullWidth
|
||||
@@ -276,14 +294,14 @@ const SettingsApplication: FC = () => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Typography variant="h6" color="primary">
|
||||
EMS Bus Settings
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
{LL.SETTINGS_OF(LL.EMS_BUS(0))}
|
||||
</Typography>
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item xs={6}>
|
||||
<ValidatedTextField
|
||||
name="tx_mode"
|
||||
label="Tx Mode"
|
||||
label={LL.TX_MODE()}
|
||||
disabled={saving}
|
||||
value={data.tx_mode}
|
||||
fullWidth
|
||||
@@ -295,13 +313,13 @@ const SettingsApplication: FC = () => {
|
||||
<MenuItem value={1}>EMS</MenuItem>
|
||||
<MenuItem value={2}>EMS+</MenuItem>
|
||||
<MenuItem value={3}>HT3</MenuItem>
|
||||
<MenuItem value={4}>Hardware</MenuItem>
|
||||
<MenuItem value={4}>{LL.HARDWARE()}</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<ValidatedTextField
|
||||
name="ems_bus_id"
|
||||
label="Bus ID"
|
||||
label={LL.ID_OF(LL.EMS_BUS(1))}
|
||||
disabled={saving}
|
||||
value={data.ems_bus_id}
|
||||
fullWidth
|
||||
@@ -310,102 +328,148 @@ const SettingsApplication: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0x0a}>Terminal (0x0A)</MenuItem>
|
||||
<MenuItem value={0x0b}>Service Key (0x0B)</MenuItem>
|
||||
<MenuItem value={0x0d}>Modem (0x0D)</MenuItem>
|
||||
<MenuItem value={0x0a}>Terminal (0x0A)</MenuItem>
|
||||
<MenuItem value={0x0e}>Converter (0x0E)</MenuItem>
|
||||
<MenuItem value={0x0f}>Time Module (0x0F)</MenuItem>
|
||||
<MenuItem value={0x12}>Alarm Module (0x12)</MenuItem>
|
||||
<MenuItem value={0x48}>Gateway 1 (0x48)</MenuItem>
|
||||
<MenuItem value={0x49}>Gateway 2 (0x49)</MenuItem>
|
||||
<MenuItem value={0x4a}>Gateway 3 (0x4A)</MenuItem>
|
||||
<MenuItem value={0x4b}>Gateway 4 (0x4B)</MenuItem>
|
||||
<MenuItem value={0x4c}>Gateway 5 (0x4C)</MenuItem>
|
||||
<MenuItem value={0x4d}>Gateway 7 (0x4D)</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
General Options
|
||||
{LL.GENERAL_OPTIONS()}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
'& .MuiTextField-root': { width: '25ch' }
|
||||
}}
|
||||
>
|
||||
<ValidatedTextField
|
||||
name="locale"
|
||||
label={LL.LANGUAGE_ENTITIES()}
|
||||
disabled={saving}
|
||||
value={data.locale}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
size="small"
|
||||
select
|
||||
>
|
||||
<MenuItem value="en">English (EN)</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem value="de">Deutsch (DE)</MenuItem>
|
||||
<MenuItem value="fr">Français (FR)</MenuItem>
|
||||
<MenuItem value="nl">Nederlands (NL)</MenuItem>
|
||||
<MenuItem value="no">Norsk (NO)</MenuItem>
|
||||
<MenuItem value="pl">Polski (PL)</MenuItem>
|
||||
<MenuItem value="sv">Svenska (SV)</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Box>
|
||||
{data.led_gpio !== 0 && (
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.hide_led} onChange={updateFormValue} name="hide_led" />}
|
||||
label="Hide LED"
|
||||
label={LL.HIDE_LED()}
|
||||
disabled={saving}
|
||||
/>
|
||||
)}
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.telnet_enabled} onChange={updateFormValue} name="telnet_enabled" />}
|
||||
label="Enable Telnet Console"
|
||||
label={LL.ENABLE_TELNET()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.analog_enabled} onChange={updateFormValue} name="analog_enabled" />}
|
||||
label="Enable Analog Sensors"
|
||||
label={LL.ENABLE_ANALOG()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.fahrenheit} onChange={updateFormValue} name="fahrenheit" />}
|
||||
label="Convert temperature values to Fahrenheit"
|
||||
label={LL.CONVERT_FAHRENHEIT()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.notoken_api} onChange={updateFormValue} name="notoken_api" />}
|
||||
label="Bypass Access Token authorization on API calls"
|
||||
label={LL.BYPASS_TOKEN()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.readonly_mode} onChange={updateFormValue} name="readonly_mode" />}
|
||||
label="Enable Read only mode (blocks all outgoing EMS Tx write commands)"
|
||||
label={LL.READONLY()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.low_clock} onChange={updateFormValue} name="low_clock" />}
|
||||
label="Underclock CPU speed"
|
||||
label={LL.UNDERCLOCK_CPU()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.boiler_heatingoff} onChange={updateFormValue} name="boiler_heatingoff" />}
|
||||
label={LL.HEATINGOFF()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<Grid container spacing={0} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.shower_timer} onChange={updateFormValue} name="shower_timer" />}
|
||||
label="Enable Shower Timer"
|
||||
label={LL.ENABLE_SHOWER_TIMER()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
sx={{ pb: 2 }}
|
||||
control={<Checkbox checked={data.shower_alert} onChange={updateFormValue} name="shower_alert" />}
|
||||
label="Enable Shower Alert"
|
||||
label={LL.ENABLE_SHOWER_ALERT()}
|
||||
disabled={!data.shower_timer}
|
||||
/>
|
||||
{data.shower_alert && (
|
||||
<>
|
||||
<Grid item xs={2}>
|
||||
<Grid item sx={{ pr: 1, pb: 2 }}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="shower_alert_trigger"
|
||||
label="Trigger Time (minutes)"
|
||||
label={LL.TRIGGER_TIME()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment>
|
||||
}}
|
||||
variant="outlined"
|
||||
value={data.shower_alert_trigger}
|
||||
type="number"
|
||||
onChange={updateFormValue}
|
||||
size="small"
|
||||
disabled={!data.shower_timer}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<Grid item sx={{ pb: 3 }}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="shower_alert_coldshot"
|
||||
label="Cold Shot Time (seconds)"
|
||||
label={LL.COLD_SHOT_DURATION()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
variant="outlined"
|
||||
value={data.shower_alert_coldshot}
|
||||
type="number"
|
||||
onChange={updateFormValue}
|
||||
size="small"
|
||||
disabled={!data.shower_timer}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Formatting Options
|
||||
<Typography variant="h6" color="primary">
|
||||
{LL.FORMATTING_OPTIONS()}
|
||||
</Typography>
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
name="bool_dashboard"
|
||||
label="Boolean Format Dashboard"
|
||||
label={LL.BOOLEAN_FORMAT_DASHBOARD()}
|
||||
value={data.bool_dashboard}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -413,16 +477,16 @@ const SettingsApplication: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={1}>on/off</MenuItem>
|
||||
<MenuItem value={2}>ON/OFF</MenuItem>
|
||||
<MenuItem value={1}>{LL.ONOFF()}</MenuItem>
|
||||
<MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
|
||||
<MenuItem value={3}>true/false</MenuItem>
|
||||
<MenuItem value={5}>1/0</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
name="bool_format"
|
||||
label="Boolean Format API/MQTT"
|
||||
label={LL.BOOLEAN_FORMAT_API()}
|
||||
value={data.bool_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -430,18 +494,18 @@ const SettingsApplication: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={1}>"on"/"off"</MenuItem>
|
||||
<MenuItem value={2}>"ON"/"OFF"</MenuItem>
|
||||
<MenuItem value={1}>{LL.ONOFF()}</MenuItem>
|
||||
<MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
|
||||
<MenuItem value={3}>"true"/"false"</MenuItem>
|
||||
<MenuItem value={4}>true/false</MenuItem>
|
||||
<MenuItem value={5}>"1"/"0"</MenuItem>
|
||||
<MenuItem value={6}>1/0</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} sm={4}>
|
||||
<ValidatedTextField
|
||||
name="enum_format"
|
||||
label="Enum Format API/MQTT"
|
||||
label={LL.ENUM_FORMAT()}
|
||||
value={data.enum_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -449,29 +513,29 @@ const SettingsApplication: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={1}>Value</MenuItem>
|
||||
<MenuItem value={2}>Index</MenuItem>
|
||||
<MenuItem value={1}>{LL.VALUE(1)}</MenuItem>
|
||||
<MenuItem value={2}>{LL.INDEX()}</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{data.dallas_gpio !== 0 && (
|
||||
<>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Temperature Sensors
|
||||
{LL.TEMP_SENSORS()}
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.dallas_parasite} onChange={updateFormValue} name="dallas_parasite" />}
|
||||
label="Enable parasite power"
|
||||
label={LL.ENABLE_PARASITE()}
|
||||
disabled={saving}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Logging
|
||||
{LL.LOGGING()}
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.trace_raw} onChange={updateFormValue} name="trace_raw" />}
|
||||
label="Log EMS telegrams in hexadecimal"
|
||||
label={LL.LOG_HEX()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
@@ -483,11 +547,11 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
}
|
||||
label="Enable Syslog"
|
||||
label={LL.ENABLE_SYSLOG()}
|
||||
/>
|
||||
{data.syslog_enabled && (
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item xs={5}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="syslog_host"
|
||||
@@ -500,7 +564,7 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="syslog_port"
|
||||
@@ -514,10 +578,10 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={5}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="syslog_level"
|
||||
label="Log Level"
|
||||
label={LL.LOG_LEVEL()}
|
||||
value={data.syslog_level}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -534,11 +598,14 @@ const SettingsApplication: FC = () => {
|
||||
<MenuItem value={9}>ALL</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="syslog_mark_interval"
|
||||
label="Mark Interval (seconds, 0=off)"
|
||||
label={LL.MARK_INTERVAL()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.syslog_mark_interval}
|
||||
@@ -551,9 +618,9 @@ const SettingsApplication: FC = () => {
|
||||
</Grid>
|
||||
)}
|
||||
{restartNeeded && (
|
||||
<MessageBox my={2} level="warning" message="EMS-ESP needs to be restarted to apply changed system settings">
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
||||
Restart
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
</MessageBox>
|
||||
)}
|
||||
@@ -567,7 +634,7 @@ const SettingsApplication: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
)}
|
||||
@@ -576,8 +643,8 @@ const SettingsApplication: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Application Settings" titleGutter>
|
||||
{content()}
|
||||
<SectionContent title={LL.APPLICATION_SETTINGS()} titleGutter>
|
||||
{restarting ? <RestartMonitor /> : content()}
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,9 +17,11 @@ import {
|
||||
Link
|
||||
} from '@mui/material';
|
||||
|
||||
import { MessageBox } from '../components';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
|
||||
import { Table } from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
|
||||
import { Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||
|
||||
import { useSnackbar } from 'notistack';
|
||||
@@ -28,9 +30,6 @@ import SaveIcon from '@mui/icons-material/Save';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
|
||||
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
||||
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
|
||||
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
|
||||
@@ -40,16 +39,25 @@ import { ButtonRow, FormLoader, ValidatedTextField, SectionContent } from '../co
|
||||
|
||||
import * as EMSESP from './api';
|
||||
|
||||
import { extractErrorMessage } from '../utils';
|
||||
import { extractErrorMessage, updateValue } from '../utils';
|
||||
|
||||
import { DeviceShort, Devices, DeviceEntity, DeviceEntityMask } from './types';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
import RestartMonitor from '../framework/system/RestartMonitor';
|
||||
|
||||
export const APIURL = window.location.origin + '/api/';
|
||||
|
||||
const SettingsCustomization: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const [restarting, setRestarting] = useState<boolean>(false);
|
||||
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>([{ id: '', v: 0, n: '', m: 0, w: false }]);
|
||||
const emptyDeviceEntity = { id: '', v: 0, n: '', cn: '', m: 0, w: false };
|
||||
|
||||
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>([emptyDeviceEntity]);
|
||||
const [devices, setDevices] = useState<Devices>();
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const [selectedDevice, setSelectedDevice] = useState<number>(-1);
|
||||
@@ -57,12 +65,14 @@ const SettingsCustomization: FC = () => {
|
||||
const [selectedFilters, setSelectedFilters] = useState<number>(0);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const [deviceEntity, setDeviceEntity] = useState<DeviceEntity>();
|
||||
|
||||
// eslint-disable-next-line
|
||||
const [masks, setMasks] = useState(() => ['']);
|
||||
|
||||
const entities_theme = useTheme({
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: 120px repeat(1, minmax(0, 1fr)) 120px;
|
||||
--data-table-library_grid-template-columns: 150px repeat(1, minmax(80px, 1fr)) 45px 45px 120px;
|
||||
`,
|
||||
BaseRow: `
|
||||
font-size: 14px;
|
||||
@@ -71,6 +81,12 @@ const SettingsCustomization: FC = () => {
|
||||
}
|
||||
`,
|
||||
BaseCell: `
|
||||
&:nth-of-type(3) {
|
||||
text-align: right;
|
||||
}
|
||||
&:nth-of-type(4) {
|
||||
text-align: right;
|
||||
}
|
||||
&:last-of-type {
|
||||
text-align: right;
|
||||
}
|
||||
@@ -92,6 +108,7 @@ const SettingsCustomization: FC = () => {
|
||||
Row: `
|
||||
background-color: #1e1e1e;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.td {
|
||||
border-top: 1px solid #565656;
|
||||
@@ -104,6 +121,11 @@ const SettingsCustomization: FC = () => {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
&:hover .td {
|
||||
border-top: 1px solid #177ac9;
|
||||
border-bottom: 1px solid #177ac9;
|
||||
}
|
||||
|
||||
&:nth-of-type(odd) .td {
|
||||
background-color: #303030;
|
||||
}
|
||||
@@ -112,56 +134,36 @@ const SettingsCustomization: FC = () => {
|
||||
&:nth-of-type(2) {
|
||||
padding: 8px;
|
||||
}
|
||||
&:nth-of-type(3) {
|
||||
padding-right: 4px;
|
||||
}
|
||||
&:nth-of-type(4) {
|
||||
padding-right: 4px;
|
||||
}
|
||||
&:last-of-type {
|
||||
padding-right: 8px;
|
||||
}
|
||||
`
|
||||
});
|
||||
|
||||
const getSortIcon = (state: any, sortKey: any) => {
|
||||
if (state.sortKey === sortKey && state.reverse) {
|
||||
return <KeyboardArrowDownOutlinedIcon />;
|
||||
}
|
||||
if (state.sortKey === sortKey && !state.reverse) {
|
||||
return <KeyboardArrowUpOutlinedIcon />;
|
||||
}
|
||||
return <UnfoldMoreOutlinedIcon />;
|
||||
};
|
||||
|
||||
const entity_sort = useSort(
|
||||
{ nodes: deviceEntities },
|
||||
{},
|
||||
{
|
||||
sortIcon: {
|
||||
iconDefault: <UnfoldMoreOutlinedIcon />,
|
||||
iconUp: <KeyboardArrowUpOutlinedIcon />,
|
||||
iconDown: <KeyboardArrowDownOutlinedIcon />
|
||||
},
|
||||
sortToggleType: SortToggleType.AlternateWithReset,
|
||||
sortFns: {
|
||||
NAME: (array) => array.sort((a, b) => a.id.localeCompare(b.id))
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const fetchDevices = useCallback(async () => {
|
||||
try {
|
||||
setDevices((await EMSESP.readDevices()).data);
|
||||
} catch (error: unknown) {
|
||||
setErrorMessage(extractErrorMessage(error, 'Failed to fetch device list'));
|
||||
} catch (error) {
|
||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
}, []);
|
||||
}, [LL]);
|
||||
|
||||
const setInitialMask = (data: DeviceEntity[]) => {
|
||||
setDeviceEntities(data.map((de) => ({ ...de, om: de.m })));
|
||||
setDeviceEntities(data.map((de) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })));
|
||||
};
|
||||
|
||||
const fetchDeviceEntities = async (unique_id: number) => {
|
||||
try {
|
||||
const data = (await EMSESP.readDeviceEntities({ id: unique_id })).data;
|
||||
setInitialMask(data);
|
||||
} catch (error: unknown) {
|
||||
setErrorMessage(extractErrorMessage(error, 'Problem fetching device entities'));
|
||||
const new_deviceEntities = (await EMSESP.readDeviceEntities({ id: unique_id })).data;
|
||||
setInitialMask(new_deviceEntities);
|
||||
} catch (error) {
|
||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -181,15 +183,10 @@ const SettingsCustomization: FC = () => {
|
||||
}
|
||||
|
||||
function formatName(de: DeviceEntity) {
|
||||
if (de.n === undefined || de.n === de.id) {
|
||||
return de.id;
|
||||
} else if (de.n === '') {
|
||||
return 'Command: ' + de.id;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{de.n} (
|
||||
<Link target="_blank" href={APIURL + devices?.devices[selectedDevice].t + '/' + de.id}>
|
||||
{de.n && (de.n[0] === '!' ? LL.COMMAND() + ': ' + de.n.slice(1) : de.cn && de.cn !== '' ? de.cn : de.n) + ' '}(
|
||||
<Link target="_blank" href={APIURL + devices?.devices[selectedDevice].tn + '/' + de.id}>
|
||||
{de.id}
|
||||
</Link>
|
||||
)
|
||||
@@ -219,6 +216,9 @@ const SettingsCustomization: FC = () => {
|
||||
if ((m & 8) === 8) {
|
||||
new_masks.push('8');
|
||||
}
|
||||
if ((m & 128) === 128) {
|
||||
new_masks.push('128');
|
||||
}
|
||||
return new_masks;
|
||||
};
|
||||
|
||||
@@ -244,43 +244,65 @@ const SettingsCustomization: FC = () => {
|
||||
const selected_device = parseInt(event.target.value, 10);
|
||||
setSelectedDevice(selected_device);
|
||||
fetchDeviceEntities(devices?.devices[selected_device].i);
|
||||
setRestartNeeded(false);
|
||||
}
|
||||
};
|
||||
|
||||
const resetCustomization = async () => {
|
||||
try {
|
||||
await EMSESP.resetCustomizations();
|
||||
enqueueSnackbar('All customizations have been removed. Restarting...', { variant: 'info' });
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem resetting customizations'), { variant: 'error' });
|
||||
enqueueSnackbar(LL.CUSTOMIZATIONS_RESTART(), { variant: 'info' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setConfirmReset(false);
|
||||
}
|
||||
};
|
||||
|
||||
const restart = async () => {
|
||||
try {
|
||||
await EMSESP.restart();
|
||||
setRestarting(true);
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const saveCustomization = async () => {
|
||||
if (devices && deviceEntities && selectedDevice !== -1) {
|
||||
const masked_entities = deviceEntities
|
||||
.filter((de) => de.m !== de.om)
|
||||
.map((new_de) => new_de.m.toString(16).padStart(2, '0') + new_de.id);
|
||||
.filter((de) => de.m !== de.o_m || de.cn !== de.o_cn || de.ma !== de.o_ma || de.mi !== de.o_mi)
|
||||
.map(
|
||||
(new_de) =>
|
||||
new_de.m.toString(16).padStart(2, '0') +
|
||||
new_de.id +
|
||||
(new_de.cn || new_de.mi || new_de.ma ? '|' : '') +
|
||||
(new_de.cn ? new_de.cn : '') +
|
||||
(new_de.mi ? '>' + new_de.mi : '') +
|
||||
(new_de.ma ? '<' + new_de.ma : '')
|
||||
);
|
||||
|
||||
if (masked_entities.length > 60) {
|
||||
enqueueSnackbar('Selected entities exceeded limit of 60. Please Save in batches', { variant: 'warning' });
|
||||
// check size in bytes to match buffer in CPP, which is 2048
|
||||
const bytes = new TextEncoder().encode(JSON.stringify(masked_entities)).length;
|
||||
if (bytes > 2000) {
|
||||
enqueueSnackbar(LL.CUSTOMIZATIONS_FULL(), { variant: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await EMSESP.writeMaskedEntities({
|
||||
const response = await EMSESP.writeCustomEntities({
|
||||
id: devices?.devices[selectedDevice].i,
|
||||
entity_ids: masked_entities
|
||||
});
|
||||
if (response.status === 200) {
|
||||
enqueueSnackbar('Customization saved', { variant: 'success' });
|
||||
enqueueSnackbar(LL.CUSTOMIZATIONS_SAVED(), { variant: 'success' });
|
||||
} else if (response.status === 201) {
|
||||
setRestartNeeded(true);
|
||||
} else {
|
||||
enqueueSnackbar('Customization save failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem sending entity list'), { variant: 'error' });
|
||||
} catch (error) {
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
}
|
||||
setInitialMask(deviceEntities);
|
||||
}
|
||||
@@ -294,21 +316,18 @@ const SettingsCustomization: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Box mb={2} color="warning.main">
|
||||
<Typography variant="body2">Select a device and customize each of its entities using the options:</Typography>
|
||||
<Typography variant="body2">{LL.CUSTOMIZATIONS_HELP_1()}</Typography>
|
||||
<Typography variant="body2">
|
||||
<OptionIcon type="favorite" isSet={true} />
|
||||
=mark as favorite
|
||||
<OptionIcon type="readonly" isSet={true} />
|
||||
=disable write action
|
||||
<OptionIcon type="api_mqtt_exclude" isSet={true} />
|
||||
=exclude from MQTT and API
|
||||
<OptionIcon type="web_exclude" isSet={true} />
|
||||
=hide from Dashboard
|
||||
<OptionIcon type="favorite" isSet={true} />={LL.CUSTOMIZATIONS_HELP_2()}
|
||||
<OptionIcon type="readonly" isSet={true} />={LL.CUSTOMIZATIONS_HELP_3()}
|
||||
<OptionIcon type="api_mqtt_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_4()}
|
||||
<OptionIcon type="web_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_5()}
|
||||
<OptionIcon type="deleted" isSet={true} />={LL.CUSTOMIZATIONS_HELP_6()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<ValidatedTextField
|
||||
name="device"
|
||||
label="EMS Device"
|
||||
label={LL.EMS_DEVICE()}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={selectedDevice}
|
||||
@@ -317,7 +336,7 @@ const SettingsCustomization: FC = () => {
|
||||
select
|
||||
>
|
||||
<MenuItem disabled key={0} value={-1}>
|
||||
Select a device...
|
||||
{LL.SELECT_DEVICE()}...
|
||||
</MenuItem>
|
||||
{devices.devices.map((device: DeviceShort, index) => (
|
||||
<MenuItem key={index} value={index}>
|
||||
@@ -329,6 +348,33 @@ const SettingsCustomization: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const editEntity = (de: DeviceEntity) => {
|
||||
if (de.n === undefined || (de.n && de.n[0] === '!')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (de.cn === undefined) {
|
||||
de.cn = '';
|
||||
}
|
||||
setDeviceEntity(de);
|
||||
};
|
||||
|
||||
const updateEntity = () => {
|
||||
if (deviceEntity) {
|
||||
setDeviceEntities((prevState) => {
|
||||
const newState = prevState.map((obj) => {
|
||||
if (obj.id === deviceEntity.id) {
|
||||
return { ...obj, cn: deviceEntity.cn, mi: deviceEntity.mi, ma: deviceEntity.ma };
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
|
||||
setDeviceEntity(undefined);
|
||||
};
|
||||
|
||||
const renderDeviceData = () => {
|
||||
if (devices?.devices.length === 0 || deviceEntities[0].id === '') {
|
||||
return;
|
||||
@@ -389,6 +435,9 @@ const SettingsCustomization: FC = () => {
|
||||
<ToggleButton value="1">
|
||||
<OptionIcon type="web_exclude" isSet={true} />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="128">
|
||||
<OptionIcon type="deleted" isSet={true} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Grid>
|
||||
|
||||
@@ -401,7 +450,7 @@ const SettingsCustomization: FC = () => {
|
||||
color="inherit"
|
||||
onClick={() => maskDisabled(false)}
|
||||
>
|
||||
set all
|
||||
{LL.SET_ALL()}
|
||||
<OptionIcon type="api_mqtt_exclude" isSet={false} />
|
||||
<OptionIcon type="web_exclude" isSet={false} />
|
||||
</Button>
|
||||
@@ -416,36 +465,34 @@ const SettingsCustomization: FC = () => {
|
||||
color="inherit"
|
||||
onClick={() => maskDisabled(true)}
|
||||
>
|
||||
set all
|
||||
{LL.SET_ALL()}
|
||||
<OptionIcon type="api_mqtt_exclude" isSet={true} />
|
||||
<OptionIcon type="web_exclude" isSet={true} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Table data={{ nodes: shown_data }} theme={entities_theme} sort={entity_sort} layout={{ custom: true }}>
|
||||
<Table data={{ nodes: shown_data }} theme={entities_theme} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell stiff>OPTIONS</HeaderCell>
|
||||
<HeaderCell stiff>{LL.OPTIONS()}</HeaderCell>
|
||||
<HeaderCell resize>
|
||||
<Button
|
||||
fullWidth
|
||||
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
|
||||
endIcon={getSortIcon(entity_sort.state, 'NAME')}
|
||||
onClick={() => entity_sort.fns.onToggleSort({ sortKey: 'NAME' })}
|
||||
>
|
||||
NAME
|
||||
<Button fullWidth style={{ fontSize: '14px', justifyContent: 'flex-start' }}>
|
||||
{LL.NAME(1)}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell resize>VALUE</HeaderCell>
|
||||
<HeaderCell stiff>{LL.MIN()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.MAX()}</HeaderCell>
|
||||
<HeaderCell resize>{LL.VALUE(0)}</HeaderCell>
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
<Body>
|
||||
{tableList.map((de: DeviceEntity) => (
|
||||
<Row key={de.id} item={de}>
|
||||
<Row key={de.id} item={de} onClick={() => editEntity(de)}>
|
||||
<Cell stiff>
|
||||
{!deviceEntity && (
|
||||
<ToggleButtonGroup
|
||||
size="small"
|
||||
color="secondary"
|
||||
@@ -461,19 +508,19 @@ const SettingsCustomization: FC = () => {
|
||||
setMasks(['']);
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="8" disabled={(de.m & 1) !== 0 || de.n === undefined}>
|
||||
<ToggleButton value="8" disabled={(de.m & 0x81) !== 0 || de.n === undefined}>
|
||||
<OptionIcon
|
||||
type="favorite"
|
||||
isSet={(de.m & DeviceEntityMask.DV_FAVORITE) === DeviceEntityMask.DV_FAVORITE}
|
||||
/>
|
||||
</ToggleButton>
|
||||
<ToggleButton value="4" disabled={!de.w || (de.m & 3) === 3}>
|
||||
<ToggleButton value="4" disabled={!de.w || (de.m & 0x83) >= 3}>
|
||||
<OptionIcon
|
||||
type="readonly"
|
||||
isSet={(de.m & DeviceEntityMask.DV_READONLY) === DeviceEntityMask.DV_READONLY}
|
||||
/>
|
||||
</ToggleButton>
|
||||
<ToggleButton value="2" disabled={de.n === ''}>
|
||||
<ToggleButton value="2" disabled={de.n === '' || (de.m & 0x80) !== 0}>
|
||||
<OptionIcon
|
||||
type="api_mqtt_exclude"
|
||||
isSet={
|
||||
@@ -481,16 +528,25 @@ const SettingsCustomization: FC = () => {
|
||||
}
|
||||
/>
|
||||
</ToggleButton>
|
||||
<ToggleButton value="1" disabled={de.n === undefined}>
|
||||
<ToggleButton value="1" disabled={de.n === undefined || (de.m & 0x80) !== 0}>
|
||||
<OptionIcon
|
||||
type="web_exclude"
|
||||
isSet={(de.m & DeviceEntityMask.DV_WEB_EXCLUDE) === DeviceEntityMask.DV_WEB_EXCLUDE}
|
||||
/>
|
||||
</ToggleButton>
|
||||
<ToggleButton value="128">
|
||||
<OptionIcon
|
||||
type="deleted"
|
||||
isSet={(de.m & DeviceEntityMask.DV_DELETED) === DeviceEntityMask.DV_DELETED}
|
||||
/>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
)}
|
||||
</Cell>
|
||||
<Cell>{formatName(de)}</Cell>
|
||||
<Cell>{formatValue(de.v)}</Cell>
|
||||
<Cell>{!deviceEntity && formatName(de)}</Cell>
|
||||
<Cell>{!deviceEntity && !(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.mi)}</Cell>
|
||||
<Cell>{!deviceEntity && !(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.ma)}</Cell>
|
||||
<Cell>{!deviceEntity && formatValue(de.v)}</Cell>
|
||||
</Row>
|
||||
))}
|
||||
</Body>
|
||||
@@ -503,14 +559,11 @@ const SettingsCustomization: FC = () => {
|
||||
|
||||
const renderResetDialog = () => (
|
||||
<Dialog open={confirmReset} onClose={() => setConfirmReset(false)}>
|
||||
<DialogTitle>Reset</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
Are you sure you want remove all customizations including the custom settings of the Temperature and Analog
|
||||
sensors?
|
||||
</DialogContent>
|
||||
<DialogTitle>{LL.RESET(1)}</DialogTitle>
|
||||
<DialogContent dividers>{LL.CUSTOMIZATIONS_RESET()}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmReset(false)} color="secondary">
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
@@ -519,25 +572,32 @@ const SettingsCustomization: FC = () => {
|
||||
autoFocus
|
||||
color="error"
|
||||
>
|
||||
Reset
|
||||
{LL.RESET(0)}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
const content = () => {
|
||||
return (
|
||||
const renderContent = () => (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
Device Entities
|
||||
{LL.DEVICE_ENTITIES()}
|
||||
</Typography>
|
||||
{renderDeviceList()}
|
||||
{renderDeviceData()}
|
||||
{restartNeeded && (
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
</MessageBox>
|
||||
)}
|
||||
{!restartNeeded && (
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1}>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<SaveIcon />} variant="outlined" color="primary" onClick={() => saveCustomization()}>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -548,18 +608,90 @@ const SettingsCustomization: FC = () => {
|
||||
color="error"
|
||||
onClick={() => setConfirmReset(true)}
|
||||
>
|
||||
Reset
|
||||
{LL.RESET(0)}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
)}
|
||||
{renderResetDialog()}
|
||||
</>
|
||||
);
|
||||
|
||||
const renderEditDialog = () => {
|
||||
if (deviceEntity) {
|
||||
const de = deviceEntity;
|
||||
return (
|
||||
<Dialog open={!!deviceEntity} onClose={() => setDeviceEntity(undefined)}>
|
||||
<DialogTitle>{LL.EDIT() + ' ' + LL.ENTITY() + ' "' + de.id + '"'}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box color="warning.main" mb={2}>
|
||||
<Typography variant="body2">
|
||||
{LL.DEFAULT(1) + ' ' + LL.NAME(1)}: {deviceEntity.n}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item>
|
||||
<TextField
|
||||
name="cn"
|
||||
label={LL.NEW_NAME_OF(LL.ENTITY())}
|
||||
value={deviceEntity.cn}
|
||||
autoFocus
|
||||
sx={{ width: '30ch' }}
|
||||
onChange={updateValue(setDeviceEntity)}
|
||||
/>
|
||||
</Grid>
|
||||
{typeof de.v === 'number' && de.w && !(de.m & DeviceEntityMask.DV_READONLY) && (
|
||||
<>
|
||||
<Grid item>
|
||||
<TextField
|
||||
name="mi"
|
||||
label={LL.MIN()}
|
||||
value={deviceEntity.mi}
|
||||
sx={{ width: '8ch' }}
|
||||
onChange={updateValue(setDeviceEntity)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<TextField
|
||||
name="ma"
|
||||
label={LL.MAX()}
|
||||
value={deviceEntity.ma}
|
||||
sx={{ width: '8ch' }}
|
||||
onChange={updateValue(setDeviceEntity)}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
onClick={() => setDeviceEntity(undefined)}
|
||||
color="secondary"
|
||||
>
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SaveIcon />}
|
||||
variant="outlined"
|
||||
type="submit"
|
||||
onClick={() => updateEntity()}
|
||||
color="warning"
|
||||
>
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="User Customization" titleGutter>
|
||||
{content()}
|
||||
<SectionContent title={LL.USER_CUSTOMIZATION()} titleGutter>
|
||||
{restarting ? <RestartMonitor /> : renderContent()}
|
||||
{renderEditDialog()}
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
DeviceData,
|
||||
DeviceEntity,
|
||||
UniqueID,
|
||||
MaskedEntities,
|
||||
CustomEntities,
|
||||
WriteValue,
|
||||
WriteSensor,
|
||||
WriteAnalog,
|
||||
@@ -63,8 +63,8 @@ export function readDeviceEntities(unique_id: UniqueID): AxiosPromise<DeviceEnti
|
||||
return AXIOS_BIN.post('/deviceEntities', unique_id);
|
||||
}
|
||||
|
||||
export function writeMaskedEntities(maskedEntities: MaskedEntities): AxiosPromise<void> {
|
||||
return AXIOS.post('/maskedEntities', maskedEntities);
|
||||
export function writeCustomEntities(customEntities: CustomEntities): AxiosPromise<void> {
|
||||
return AXIOS.post('/customEntities', customEntities);
|
||||
}
|
||||
|
||||
export function writeValue(writevalue: WriteValue): AxiosPromise<void> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface Settings {
|
||||
locale: string;
|
||||
tx_mode: number;
|
||||
ems_bus_id: number;
|
||||
syslog_enabled: boolean;
|
||||
@@ -6,6 +7,7 @@ export interface Settings {
|
||||
syslog_mark_interval: number;
|
||||
syslog_host: string;
|
||||
syslog_port: number;
|
||||
boiler_heatingoff: boolean;
|
||||
shower_timer: boolean;
|
||||
shower_alert: boolean;
|
||||
shower_alert_coldshot: number;
|
||||
@@ -32,6 +34,7 @@ export interface Settings {
|
||||
eth_power: number;
|
||||
eth_phy_addr: number;
|
||||
eth_clock_mode: number;
|
||||
platform: string;
|
||||
}
|
||||
|
||||
export enum busConnectionStatus {
|
||||
@@ -41,7 +44,7 @@ export enum busConnectionStatus {
|
||||
}
|
||||
|
||||
export interface Stat {
|
||||
id: string; // name
|
||||
id: string; // id - needs to be a string
|
||||
s: number; // success
|
||||
f: number; // fail
|
||||
q: number; // quality
|
||||
@@ -57,7 +60,8 @@ export interface Status {
|
||||
}
|
||||
export interface Device {
|
||||
id: string; // id index
|
||||
t: string; // type
|
||||
tn: string; // device type translated name
|
||||
t: number; // device type id
|
||||
b: string; // brand
|
||||
n: string; // name
|
||||
d: number; // deviceid
|
||||
@@ -99,6 +103,7 @@ export interface SensorData {
|
||||
export interface CoreData {
|
||||
connected: boolean;
|
||||
devices: Device[];
|
||||
s_n: string;
|
||||
active_sensors: number;
|
||||
analog_enabled: boolean;
|
||||
}
|
||||
@@ -108,7 +113,8 @@ export interface DeviceShort {
|
||||
d?: number; // deviceid
|
||||
p?: number; // productid
|
||||
s: string; // shortname
|
||||
t?: string; // device type name
|
||||
t?: number; // device type id
|
||||
tn?: string; // device type internal name
|
||||
}
|
||||
|
||||
export interface Devices {
|
||||
@@ -136,12 +142,18 @@ export interface DeviceEntity {
|
||||
id: string; // shortname
|
||||
v?: any; // value, in any format, optional
|
||||
n?: string; // fullname, optional
|
||||
cn?: string; // custom fullname, optional
|
||||
m: number; // mask
|
||||
om?: number; // original mask before edits
|
||||
o_m?: number; // original mask before edits
|
||||
o_cn?: string; // original cn before edits
|
||||
w: boolean; // writeable
|
||||
mi?: string; // min value
|
||||
ma?: string; // max value
|
||||
o_mi?: string;
|
||||
o_ma?: string;
|
||||
}
|
||||
|
||||
export interface MaskedEntities {
|
||||
export interface CustomEntities {
|
||||
id: number;
|
||||
entity_ids: string[];
|
||||
}
|
||||
@@ -171,7 +183,9 @@ export enum DeviceValueUOM {
|
||||
MV,
|
||||
SQM,
|
||||
M3,
|
||||
L
|
||||
L,
|
||||
KMIN,
|
||||
K
|
||||
}
|
||||
|
||||
export const DeviceValueUOM_s = [
|
||||
@@ -184,18 +198,20 @@ export const DeviceValueUOM_s = [
|
||||
'Wh',
|
||||
'hours',
|
||||
'minutes',
|
||||
'uA',
|
||||
'µA',
|
||||
'bar',
|
||||
'kW',
|
||||
'W',
|
||||
'KB',
|
||||
'second',
|
||||
'seconds',
|
||||
'dBm',
|
||||
'°F',
|
||||
'mV',
|
||||
'sqm',
|
||||
'm3',
|
||||
'l'
|
||||
'm²',
|
||||
'm³',
|
||||
'l',
|
||||
'K*min',
|
||||
'K'
|
||||
];
|
||||
|
||||
export enum AnalogType {
|
||||
@@ -235,7 +251,10 @@ export const BOARD_PROFILES: BoardProfiles = {
|
||||
'MH-ET': 'MH-ET Live D1 Mini',
|
||||
LOLIN: 'Lolin D32',
|
||||
OLIMEX: 'Olimex ESP32-EVB',
|
||||
OLIMEXPOE: 'Olimex ESP32-POE'
|
||||
OLIMEXPOE: 'Olimex ESP32-POE',
|
||||
C3MINI: 'Wemos C3 Mini',
|
||||
S2MINI: 'Wemos S2 Mini',
|
||||
S3MINI: 'Liligo S3'
|
||||
};
|
||||
|
||||
export interface BoardProfileName {
|
||||
@@ -280,5 +299,6 @@ export enum DeviceEntityMask {
|
||||
DV_WEB_EXCLUDE = 1,
|
||||
DV_API_MQTT_EXCLUDE = 2,
|
||||
DV_READONLY = 4,
|
||||
DV_FAVORITE = 8
|
||||
DV_FAVORITE = 8,
|
||||
DV_DELETED = 128
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ export const GPIO_VALIDATOR = {
|
||||
if (
|
||||
value &&
|
||||
(value === 1 ||
|
||||
(value >= 6 && value <= 12) ||
|
||||
(value >= 10 && value <= 12) ||
|
||||
(value >= 14 && value <= 15) ||
|
||||
value === 20 ||
|
||||
value === 24 ||
|
||||
(value >= 28 && value <= 31) ||
|
||||
value > 40)
|
||||
value > 40 ||
|
||||
value < 0)
|
||||
) {
|
||||
callback('Must be an valid GPIO port');
|
||||
} else {
|
||||
@@ -21,24 +22,61 @@ export const GPIO_VALIDATOR = {
|
||||
}
|
||||
};
|
||||
|
||||
export const GPIO_VALIDATORC3 = {
|
||||
validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) {
|
||||
if (value && ((value >= 11 && value <= 19) || value > 21 || value < 0)) {
|
||||
callback('Must be an valid GPIO port');
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const GPIO_VALIDATORS2 = {
|
||||
validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) {
|
||||
if (value && ((value >= 19 && value <= 20) || (value >= 22 && value <= 32) || value > 40 || value < 0)) {
|
||||
callback('Must be an valid GPIO port');
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const createSettingsValidator = (settings: Settings) =>
|
||||
new Schema({
|
||||
...(settings.board_profile === 'CUSTOM' && {
|
||||
...(settings.board_profile === 'CUSTOM' &&
|
||||
settings.platform === 'ESP32' && {
|
||||
led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATOR],
|
||||
dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATOR],
|
||||
pbutton_gpio: [{ required: true, message: 'Button 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]
|
||||
}),
|
||||
...(settings.board_profile === 'CUSTOM' &&
|
||||
settings.platform === 'ESP32-C3' && {
|
||||
led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATORC3],
|
||||
dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATORC3],
|
||||
pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATORC3],
|
||||
tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATORC3],
|
||||
rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATORC3]
|
||||
}),
|
||||
...(settings.board_profile === 'CUSTOM' &&
|
||||
settings.platform === 'ESP32-S2' && {
|
||||
led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATORS2],
|
||||
dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATORS2],
|
||||
pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATORS2],
|
||||
tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATORS2],
|
||||
rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATORS2]
|
||||
}),
|
||||
...(settings.syslog_enabled && {
|
||||
syslog_host: [{ required: true, message: 'Host is required' }, IP_OR_HOSTNAME_VALIDATOR],
|
||||
syslog_port: [
|
||||
{ required: true, message: 'Port is required' },
|
||||
{ type: 'number', min: 0, max: 65535, message: 'Port must be between 0 and 65535' }
|
||||
{ type: 'number', min: 0, max: 65535, message: 'Invalid Port' }
|
||||
],
|
||||
syslog_mark_interval: [
|
||||
{ required: true, message: 'Mark interval is required' },
|
||||
{ type: 'number', min: 0, max: 10, message: 'Port must be between 0 and 10' }
|
||||
{ type: 'number', min: 0, max: 10, message: ' must be between 0 and 10' }
|
||||
]
|
||||
}),
|
||||
...(settings.shower_alert && {
|
||||
|
||||
@@ -15,6 +15,8 @@ export interface MqttStatus {
|
||||
client_id: string;
|
||||
disconnect_reason: MqttDisconnectReason;
|
||||
mqtt_fails: number;
|
||||
mqtt_queued: number;
|
||||
connect_count: number;
|
||||
}
|
||||
|
||||
export interface MqttSettings {
|
||||
@@ -27,13 +29,14 @@ export interface MqttSettings {
|
||||
client_id: string;
|
||||
keep_alive: number;
|
||||
clean_session: boolean;
|
||||
max_topic_length: number;
|
||||
entity_format: number;
|
||||
publish_time_boiler: number;
|
||||
publish_time_thermostat: number;
|
||||
publish_time_solar: number;
|
||||
publish_time_mixer: number;
|
||||
publish_time_other: number;
|
||||
publish_time_sensor: number;
|
||||
publish_time_heartbeat: number;
|
||||
mqtt_qos: number;
|
||||
mqtt_retain: boolean;
|
||||
ha_enabled: boolean;
|
||||
|
||||
@@ -48,6 +48,8 @@ export interface NetworkSettings {
|
||||
dns_ip_1?: string;
|
||||
dns_ip_2?: string;
|
||||
enableMDNS: boolean;
|
||||
enableCORS: boolean;
|
||||
CORSOrigin: string;
|
||||
}
|
||||
|
||||
export interface WiFiNetworkList {
|
||||
|
||||
@@ -1,36 +1,23 @@
|
||||
export enum EspPlatform {
|
||||
ESP8266 = 'esp8266',
|
||||
ESP32 = 'esp32'
|
||||
}
|
||||
|
||||
interface ESPSystemStatus {
|
||||
export interface SystemStatus {
|
||||
emsesp_version: string;
|
||||
esp_platform: EspPlatform;
|
||||
esp_platform: string;
|
||||
max_alloc_heap: number;
|
||||
cpu_freq_mhz: number;
|
||||
free_heap: number;
|
||||
sdk_version: string;
|
||||
flash_chip_size: number;
|
||||
flash_chip_speed: number;
|
||||
app_used: number;
|
||||
app_free: number;
|
||||
fs_used: number;
|
||||
fs_total: number;
|
||||
fs_free: number;
|
||||
uptime: string;
|
||||
free_mem: number;
|
||||
psram_size?: number;
|
||||
free_psram?: number;
|
||||
has_loader: boolean;
|
||||
}
|
||||
|
||||
export interface ESP32SystemStatus extends ESPSystemStatus {
|
||||
esp_platform: EspPlatform.ESP32;
|
||||
psram_size: number;
|
||||
free_psram: number;
|
||||
}
|
||||
|
||||
export interface ESP8266SystemStatus extends ESPSystemStatus {
|
||||
esp_platform: EspPlatform.ESP8266;
|
||||
heap_fragmentation: number;
|
||||
}
|
||||
|
||||
export type SystemStatus = ESP8266SystemStatus | ESP32SystemStatus;
|
||||
|
||||
export interface OTASettings {
|
||||
enabled: boolean;
|
||||
port: number;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
export const extractErrorMessage = (error: unknown, defaultMessage: string) => {
|
||||
if (error instanceof AxiosError) {
|
||||
return defaultMessage + ' (' + error.request.statusText + ')';
|
||||
export const extractErrorMessage = (error: any, defaultMessage: string) => {
|
||||
if (error.request) {
|
||||
return defaultMessage + ' (' + error.request.status + ': ' + error.request.statusText + ')';
|
||||
} else if (error instanceof Error) {
|
||||
return defaultMessage + ' (' + error.message + ')';
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import parseMilliseconds from 'parse-ms';
|
||||
|
||||
const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
@@ -21,21 +19,6 @@ export const formatLocalDateTime = (date: Date) => {
|
||||
export const pluralize = (count: number, noun: string) =>
|
||||
`${Intl.NumberFormat().format(count)} ${noun}${count !== 1 ? 's' : ''}`;
|
||||
|
||||
export const formatDurationMin = (duration_min: number) => {
|
||||
const { days, hours, minutes } = parseMilliseconds(duration_min * 60000);
|
||||
let formatted = '';
|
||||
if (days) {
|
||||
formatted += pluralize(days, 'day') + ' ';
|
||||
}
|
||||
if (hours) {
|
||||
formatted += pluralize(hours, 'hour') + ' ';
|
||||
}
|
||||
if (minutes) {
|
||||
formatted += pluralize(minutes, 'minute') + ' ';
|
||||
}
|
||||
return formatted;
|
||||
};
|
||||
|
||||
export const formatDurationSec = (duration_sec: number) => {
|
||||
if (duration_sec === 0) {
|
||||
return ' ';
|
||||
|
||||
@@ -4,12 +4,16 @@ import { AxiosPromise } from 'axios';
|
||||
|
||||
import { extractErrorMessage } from '.';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
export interface RestRequestOptions<D> {
|
||||
read: () => AxiosPromise<D>;
|
||||
update?: (value: D) => AxiosPromise<D>;
|
||||
}
|
||||
|
||||
export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [saving, setSaving] = useState<boolean>(false);
|
||||
@@ -22,12 +26,12 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
|
||||
setErrorMessage(undefined);
|
||||
try {
|
||||
setData((await read()).data);
|
||||
} catch (error: unknown) {
|
||||
const message = extractErrorMessage(error, 'Problem loading data');
|
||||
} catch (error) {
|
||||
const message = extractErrorMessage(error, LL.PROBLEM_LOADING());
|
||||
enqueueSnackbar(message, { variant: 'error' });
|
||||
setErrorMessage(message);
|
||||
}
|
||||
}, [read, enqueueSnackbar]);
|
||||
}, [read, enqueueSnackbar, LL]);
|
||||
|
||||
const save = useCallback(
|
||||
async (toSave: D) => {
|
||||
@@ -43,17 +47,17 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
|
||||
if (response.status === 202) {
|
||||
setRestartNeeded(true);
|
||||
} else {
|
||||
enqueueSnackbar('Settings saved', { variant: 'success' });
|
||||
enqueueSnackbar(LL.SETTINGS_OF('') + ' ' + LL.SAVED(), { variant: 'success' });
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const message = extractErrorMessage(error, 'Problem saving data');
|
||||
} catch (error) {
|
||||
const message = extractErrorMessage(error, LL.PROBLEM_UPDATING());
|
||||
enqueueSnackbar(message, { variant: 'error' });
|
||||
setErrorMessage(message);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
},
|
||||
[update, enqueueSnackbar]
|
||||
[update, enqueueSnackbar, LL]
|
||||
);
|
||||
|
||||
const saveData = () => data && save(data);
|
||||
|
||||
@@ -2,11 +2,9 @@ import Schema from 'async-validator';
|
||||
|
||||
export const SIGN_IN_REQUEST_VALIDATOR = new Schema({
|
||||
username: {
|
||||
required: true,
|
||||
message: 'Please provide a username'
|
||||
required: true
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
message: 'Please provide a password'
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import Schema from 'async-validator';
|
||||
import { MqttSettings } from '../types';
|
||||
import { IP_OR_HOSTNAME_VALIDATOR } from './shared';
|
||||
|
||||
export const MQTT_SETTINGS_VALIDATOR = new Schema({
|
||||
export const createMqttSettingsValidator = (mqttSettings: MqttSettings) =>
|
||||
new Schema({
|
||||
...(mqttSettings.enabled && {
|
||||
host: [{ required: true, message: 'Host is required' }, IP_OR_HOSTNAME_VALIDATOR],
|
||||
base: { required: true, message: 'Base is required' },
|
||||
port: [
|
||||
{ required: true, message: 'Port is required' },
|
||||
{ type: 'number', min: 0, max: 65535, message: 'Port must be between 0 and 65535' }
|
||||
{ type: 'number', min: 0, max: 65535, message: 'Invalid Port' }
|
||||
],
|
||||
keep_alive: [
|
||||
{ required: true, message: 'Keep alive is required' },
|
||||
{ type: 'number', min: 1, max: 86400, message: 'Keep alive must be between 1 and 86400' }
|
||||
],
|
||||
max_topic_length: [
|
||||
{ required: true, message: 'Max topic length is required' },
|
||||
{ type: 'number', min: 16, max: 1024, message: 'Max topic length must be between 16 and 1024' }
|
||||
publish_time_heartbeat: [
|
||||
{ required: true, message: 'Heartbeat is required' },
|
||||
{ type: 'number', min: 10, max: 86400, message: 'Heartbeat must be between 10 and 86400' }
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"]
|
||||
|
||||