mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-05-10 07:55:53 +00:00
Compare commits
999 Commits
v3.7.2
...
0f1195de82
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f1195de82 | ||
|
|
267042f74f | ||
|
|
c203b8e6d2 | ||
|
|
c184a0af10 | ||
|
|
8a4e6d5ed5 | ||
|
|
3ef6bf8681 | ||
|
|
1647a10b1b | ||
|
|
55b893362c | ||
|
|
48f1928327 | ||
|
|
dc793f145d | ||
|
|
77e0b7d89c | ||
|
|
cec19860bc | ||
|
|
898acb90d0 | ||
|
|
a074ac732d | ||
|
|
c71526b95c | ||
|
|
f035e31dcf | ||
|
|
35192b9dde | ||
|
|
b5ac637231 | ||
|
|
4e0ef8b0e3 | ||
|
|
c32ee4dfb5 | ||
|
|
accfeab6fa | ||
|
|
d98f3cc8c5 | ||
|
|
342f238983 | ||
|
|
73f8ea0fc5 | ||
|
|
f2bd1ff575 | ||
|
|
b3ec23d6bd | ||
|
|
1fc7fa4720 | ||
|
|
58ae058465 | ||
|
|
7ece395d1b | ||
|
|
4bb876031e | ||
|
|
e4f129db04 | ||
|
|
90038e08dc | ||
|
|
4c30e930cf | ||
|
|
dd69e02f6b | ||
|
|
f2ccf1953d | ||
|
|
0e133840c9 | ||
|
|
e12b277472 | ||
|
|
dc7b1809c1 | ||
|
|
8c2146ff55 | ||
|
|
882d412409 | ||
|
|
3f88a9469c | ||
|
|
3ac7d43a81 | ||
|
|
d12446b6d9 | ||
|
|
865fb3a967 | ||
|
|
48538222b2 | ||
|
|
8d44f61517 | ||
|
|
0e4f6f4209 | ||
|
|
a2823563bf | ||
|
|
353cdb324d | ||
|
|
216f799db1 | ||
|
|
4d6f080263 | ||
|
|
b9d124618c | ||
|
|
ab5d9e8d36 | ||
|
|
4cdeecd952 | ||
|
|
59b3933cb6 | ||
|
|
179ddcb348 | ||
|
|
efa2c8fc4b | ||
|
|
c958c7d61a | ||
|
|
35fca9c450 | ||
|
|
61962fbc07 | ||
|
|
39e724befe | ||
|
|
43bb77b095 | ||
|
|
28e1e46586 | ||
|
|
2f5b879652 | ||
|
|
16930fe8ca | ||
|
|
1db1b6e524 | ||
|
|
43eba7a010 | ||
|
|
49278bdea4 | ||
|
|
2405e11af2 | ||
|
|
23b6894484 | ||
|
|
a837c9398c | ||
|
|
ea484e15f9 | ||
|
|
cce99a8b1d | ||
|
|
f29faafd78 | ||
|
|
c3eafbcd85 | ||
|
|
65e18ab4e2 | ||
|
|
8bb16ed3a7 | ||
|
|
e685284f72 | ||
|
|
86d2805642 | ||
|
|
29b98a15a4 | ||
|
|
ba9df92b12 | ||
|
|
0eac1c9bf9 | ||
|
|
515feb9f9e | ||
|
|
0b84b79e1d | ||
|
|
8fd129f4fe | ||
|
|
ba334930fe | ||
|
|
982d64ddca | ||
|
|
3577300361 | ||
|
|
0478e0ff7c | ||
|
|
c2cfd0a1b0 | ||
|
|
637ba30df1 | ||
|
|
fa8421b297 | ||
|
|
61c23a57d9 | ||
|
|
43f61fd2df | ||
|
|
85eed64fe9 | ||
|
|
43634a4312 | ||
|
|
9757db4438 | ||
|
|
17a2ba7f1a | ||
|
|
03c7417888 | ||
|
|
2b2c86ba5a | ||
|
|
2b2217e8ce | ||
|
|
08d3e8bab6 | ||
|
|
ded3552873 | ||
|
|
9a7893e99f | ||
|
|
307ef4e285 | ||
|
|
99b43b0379 | ||
|
|
6fb8fbba18 | ||
|
|
09c750e622 | ||
|
|
f75deb3505 | ||
|
|
6281f9cfe1 | ||
|
|
b4f174f2f4 | ||
|
|
70810b5e71 | ||
|
|
82cc91cb63 | ||
|
|
48a3fc5656 | ||
|
|
9d20eef12d | ||
|
|
d59e183415 | ||
|
|
50396a5def | ||
|
|
ab92d07716 | ||
|
|
9172dd9181 | ||
|
|
0852502f52 | ||
|
|
c3d066650c | ||
|
|
172e8d0028 | ||
|
|
812c6ac475 | ||
|
|
f7c1f0b0d0 | ||
|
|
125190a0ac | ||
|
|
35c7349e5c | ||
|
|
d7e916269d | ||
|
|
f5f78182b6 | ||
|
|
87bcd4598a | ||
|
|
21a814b5ec | ||
|
|
687d9a40c9 | ||
|
|
9d01791fcb | ||
|
|
028afbe85d | ||
|
|
4bdea56d78 | ||
|
|
bf0737aab8 | ||
|
|
42d879a87b | ||
|
|
79b5671533 | ||
|
|
f48d67d9e7 | ||
|
|
16f7a454db | ||
|
|
02b486ea80 | ||
|
|
61d50e2c79 | ||
|
|
a5af36e15b | ||
|
|
e11ba9e657 | ||
|
|
878f0702b2 | ||
|
|
fa711373f2 | ||
|
|
e9a4a33942 | ||
|
|
f445f36eb1 | ||
|
|
148124ef04 | ||
|
|
27fbafbe62 | ||
|
|
a949673539 | ||
|
|
32193e3c62 | ||
|
|
5f79f0848f | ||
|
|
4d91128aed | ||
|
|
6f1d507df9 | ||
|
|
9b50306172 | ||
|
|
0fbc8e2420 | ||
|
|
f0d162554b | ||
|
|
d92361a8bb | ||
|
|
13bf2c44e7 | ||
|
|
cd155ba680 | ||
|
|
2a565dc677 | ||
|
|
9bf57c3e22 | ||
|
|
766281d8d2 | ||
|
|
bd128072c0 | ||
|
|
88e4ba7ecf | ||
|
|
96e5251050 | ||
|
|
187b163ffd | ||
|
|
9d04058984 | ||
|
|
12b06aa657 | ||
|
|
47b3e4bf00 | ||
|
|
f0f40bbcac | ||
|
|
036e2917a5 | ||
|
|
d294c418c1 | ||
|
|
40da7572cd | ||
|
|
532dc66282 | ||
|
|
8913f38fd0 | ||
|
|
11782eef8b | ||
|
|
b5f4eb6c62 | ||
|
|
ee4f58ce20 | ||
|
|
0fe0ee77b3 | ||
|
|
74e58aaa3d | ||
|
|
d39d6c7f1f | ||
|
|
4043eaf271 | ||
|
|
73ac60a8b2 | ||
|
|
1d6b283033 | ||
|
|
08ca4e44e8 | ||
|
|
255c173469 | ||
|
|
aeee318cca | ||
|
|
eb14e89c35 | ||
|
|
8411ea6773 | ||
|
|
015110a72e | ||
|
|
dae345f359 | ||
|
|
4cfd9b699c | ||
|
|
bd6371fd9d | ||
|
|
7507596869 | ||
|
|
5d99bd923b | ||
|
|
7402776248 | ||
|
|
8b5cc82df9 | ||
|
|
7c5351f15f | ||
|
|
b29f02e5dd | ||
|
|
e9e7162bcd | ||
|
|
b24aae9123 | ||
|
|
9dbd634322 | ||
|
|
daffc94c7f | ||
|
|
93de0e2f42 | ||
|
|
145172b6e9 | ||
|
|
c4a2f8bac8 | ||
|
|
0c0c928efc | ||
|
|
4d829b0b78 | ||
|
|
01e7d9b027 | ||
|
|
439da1d1e9 | ||
|
|
ac45c17204 | ||
|
|
cd24c7815b | ||
|
|
9665efbf38 | ||
|
|
d8aafdbfd4 | ||
|
|
0c6aef5b60 | ||
|
|
48a1bd0fe6 | ||
|
|
44cfffe8a4 | ||
|
|
e75bf8871e | ||
|
|
22703f4100 | ||
|
|
c066ab8400 | ||
|
|
4a0625e31c | ||
|
|
9aaaba5bb7 | ||
|
|
689a3a9a69 | ||
|
|
391a312f0c | ||
|
|
f782eac0cf | ||
|
|
d88513d789 | ||
|
|
59d07e81d6 | ||
|
|
419fe8ef5d | ||
|
|
4cfcba18ee | ||
|
|
b1d6ab3c96 | ||
|
|
ae26754bc8 | ||
|
|
61c3b47269 | ||
|
|
50bedb2b39 | ||
|
|
13db83a6de | ||
|
|
ec43a07866 | ||
|
|
fbc11b8ef8 | ||
|
|
f1c5a911f9 | ||
|
|
76c0aa6be8 | ||
|
|
61bf2332bb | ||
|
|
39ca956e1f | ||
|
|
0683b77437 | ||
|
|
5da2760dc6 | ||
|
|
3fabaf900f | ||
|
|
b2a8738672 | ||
|
|
c3b9c1ef98 | ||
|
|
aef6b6e92d | ||
|
|
dc46dac02a | ||
|
|
025c430611 | ||
|
|
995ab7233d | ||
|
|
1507989ca3 | ||
|
|
022e808b14 | ||
|
|
9b604e9c78 | ||
|
|
cd3cc09386 | ||
|
|
0df21a7843 | ||
|
|
9225ad2ad9 | ||
|
|
227b1ac59b | ||
|
|
a9a6e32dd1 | ||
|
|
3c4278029f | ||
|
|
3b4e09208e | ||
|
|
e9e0688737 | ||
|
|
7bb1b7bb91 | ||
|
|
4302bc9978 | ||
|
|
60d884df88 | ||
|
|
177c635bc1 | ||
|
|
9a97c28bf0 | ||
|
|
deb87cf5d7 | ||
|
|
a50227638b | ||
|
|
92b1515c8a | ||
|
|
0c5cf0475c | ||
|
|
f26e937514 | ||
|
|
1e4ca8b57f | ||
|
|
4d88bbd28f | ||
|
|
0ce110df9e | ||
|
|
3759fc81ba | ||
|
|
7965ecd856 | ||
|
|
7b0169bb68 | ||
|
|
f10f3d5305 | ||
|
|
ed9e2704b0 | ||
|
|
c47dd0e523 | ||
|
|
80c75bae77 | ||
|
|
cfa973b08b | ||
|
|
83987b71e0 | ||
|
|
a318f34988 | ||
|
|
5cc1660675 | ||
|
|
8a48da38b8 | ||
|
|
d514e67eb8 | ||
|
|
69964482f8 | ||
|
|
2aa691212c | ||
|
|
c27134f185 | ||
|
|
c8033692b1 | ||
|
|
c537d0ab8b | ||
|
|
bee703eb1f | ||
|
|
5d2bd6a2af | ||
|
|
67f0f40a8a | ||
|
|
e796fbef7a | ||
|
|
da7ef04741 | ||
|
|
ddb318dfc6 | ||
|
|
88643dc8e3 | ||
|
|
cf3854563d | ||
|
|
4b2468d616 | ||
|
|
4b08aba9c4 | ||
|
|
0a18add447 | ||
|
|
ca8d23ff3a | ||
|
|
c7e833194f | ||
|
|
f63f658421 | ||
|
|
13fcf09470 | ||
|
|
f560cbd60c | ||
|
|
38ead7e10f | ||
|
|
326bba9b42 | ||
|
|
d9a18bf255 | ||
|
|
6c42cbfb4b | ||
|
|
6691c81956 | ||
|
|
2f95ef305d | ||
|
|
7afde0ce6e | ||
|
|
f3cdafe7d0 | ||
|
|
4bf23e1bda | ||
|
|
aca66457f9 | ||
|
|
121887bdce | ||
|
|
32d7cf4e9c | ||
|
|
3f8227e95e | ||
|
|
10d84261da | ||
|
|
51848d8347 | ||
|
|
1c0669144f | ||
|
|
41a2ba6e5d | ||
|
|
b6fe9e7569 | ||
|
|
faa2c5f1aa | ||
|
|
c71034ff12 | ||
|
|
71be615bbe | ||
|
|
ce53fd1d04 | ||
|
|
1772876f9e | ||
|
|
cd5dbebea9 | ||
|
|
6c67b78a1c | ||
|
|
9b4deb271b | ||
|
|
0e9283af5c | ||
|
|
58011700fe | ||
|
|
079a08ff7b | ||
|
|
b64a55e460 | ||
|
|
2b7ef5b6ba | ||
|
|
d62eef4eca | ||
|
|
0f6d6e69f5 | ||
|
|
97f689b8a7 | ||
|
|
be9b4a070c | ||
|
|
bc15dd4463 | ||
|
|
1613caea86 | ||
|
|
4b39ab76ab | ||
|
|
d04c882590 | ||
|
|
a199bf21e1 | ||
|
|
3ae8722ece | ||
|
|
6a6cef57cf | ||
|
|
090491aab6 | ||
|
|
ca81a02a8c | ||
|
|
f64188bd5d | ||
|
|
d3e0f180c5 | ||
|
|
3f4d87a1d2 | ||
|
|
1d89e651a4 | ||
|
|
fad67b4ef9 | ||
|
|
ce1c22ee35 | ||
|
|
169b5f34ea | ||
|
|
131c03714f | ||
|
|
024c4a0c21 | ||
|
|
b0d111d86f | ||
|
|
faeef9c821 | ||
|
|
60a5b28a21 | ||
|
|
f4b5cf04a0 | ||
|
|
d69f26acac | ||
|
|
2a50701107 | ||
|
|
ab099c45b8 | ||
|
|
47f21019a0 | ||
|
|
5c473c2b3d | ||
|
|
9ddc587334 | ||
|
|
9b0a7a4872 | ||
|
|
97528e9df6 | ||
|
|
b797e1e2bb | ||
|
|
bb52d35c99 | ||
|
|
4c9026e11a | ||
|
|
6cd4e8a5b6 | ||
|
|
dfe037a7d3 | ||
|
|
b87622185d | ||
|
|
0318f8156d | ||
|
|
0c03fa1308 | ||
|
|
2d6e02171f | ||
|
|
2da312bf15 | ||
|
|
52f59a7b1d | ||
|
|
6c8624298c | ||
|
|
ce2d2fb867 | ||
|
|
ba2ad4e175 | ||
|
|
b3320c3e48 | ||
|
|
dc1094b6ba | ||
|
|
bb60568d83 | ||
|
|
67b5c5dd26 | ||
|
|
6866d5b7a9 | ||
|
|
b4e9af89ee | ||
|
|
775ed99b22 | ||
|
|
2ec13273fd | ||
|
|
a846b01103 | ||
|
|
6dffb08545 | ||
|
|
16f7cc148d | ||
|
|
568431ada4 | ||
|
|
6034c1e5eb | ||
|
|
e3566feefb | ||
|
|
ea46c79278 | ||
|
|
a02831e04e | ||
|
|
3b8c973f2a | ||
|
|
2c65936b3e | ||
|
|
bc04c34d58 | ||
|
|
5047f1752e | ||
|
|
1fa7a6c549 | ||
|
|
f9ebe33a7d | ||
|
|
e719dd963d | ||
|
|
c6b0099581 | ||
|
|
71726530c0 | ||
|
|
a749ecb298 | ||
|
|
7e963529c4 | ||
|
|
c76409cf3f | ||
|
|
efd0872690 | ||
|
|
57098b578f | ||
|
|
6472e9e224 | ||
|
|
026828efc7 | ||
|
|
fe6e9be4d3 | ||
|
|
7745a6f9a1 | ||
|
|
c523a379fe | ||
|
|
2854e9cbe9 | ||
|
|
4985f104c7 | ||
|
|
a0ea5f7ea1 | ||
|
|
efc35d0594 | ||
|
|
ccd6c6f8ad | ||
|
|
b426e0eb45 | ||
|
|
c53e1de569 | ||
|
|
8058e98748 | ||
|
|
ba419d09eb | ||
|
|
c701247652 | ||
|
|
12754d1c07 | ||
|
|
9a4daba31a | ||
|
|
a2aa2dccdd | ||
|
|
25af51a8e8 | ||
|
|
ddc597ff03 | ||
|
|
75310afd63 | ||
|
|
3095323c93 | ||
|
|
4667718f12 | ||
|
|
2cb1c5d7e7 | ||
|
|
d7b9754ddb | ||
|
|
99b769626e | ||
|
|
70035b059c | ||
|
|
b252c2f95a | ||
|
|
baa4f2eb39 | ||
|
|
c7b970b5b0 | ||
|
|
3ed5b65191 | ||
|
|
6e01c00d46 | ||
|
|
e72afc9065 | ||
|
|
de2f3e712d | ||
|
|
cacb92cd47 | ||
|
|
3eb20fa700 | ||
|
|
d35574d494 | ||
|
|
3cb29220ab | ||
|
|
d91812ab8f | ||
|
|
856f8efd25 | ||
|
|
d32378ccd4 | ||
|
|
f5006d1a11 | ||
|
|
c3468e6308 | ||
|
|
9f4de56099 | ||
|
|
78738de811 | ||
|
|
e64596ad61 | ||
|
|
024357ae80 | ||
|
|
3f07d3b75f | ||
|
|
cb7c695c67 | ||
|
|
912a764c2d | ||
|
|
c9c0f55b64 | ||
|
|
6991677cf9 | ||
|
|
83330907cd | ||
|
|
3571998da3 | ||
|
|
77465ebe81 | ||
|
|
006e5493e2 | ||
|
|
b29136433d | ||
|
|
0347a4b8b0 | ||
|
|
3330103a8d | ||
|
|
0a02252fee | ||
|
|
642f2116d2 | ||
|
|
7b8e45c2f7 | ||
|
|
0f2244607f | ||
|
|
32ab9dda45 | ||
|
|
b6659b8586 | ||
|
|
5eb85066ef | ||
|
|
9398fc72a0 | ||
|
|
1bcd453e3f | ||
|
|
d405478a13 | ||
|
|
c927e5f496 | ||
|
|
c5dbd7452e | ||
|
|
3a0b4ea587 | ||
|
|
6f4cdb7122 | ||
|
|
b5d6757660 | ||
|
|
0dcde46296 | ||
|
|
ac7e91beff | ||
|
|
9ea1e2752d | ||
|
|
b5471aef94 | ||
|
|
f31329ceff | ||
|
|
40d48f4407 | ||
|
|
11b7e1f86e | ||
|
|
e364a71eda | ||
|
|
e006bebb86 | ||
|
|
fa3d42a1c7 | ||
|
|
e435fd4391 | ||
|
|
d42efb32ab | ||
|
|
ad8f2dc823 | ||
|
|
f2ff14f511 | ||
|
|
e5e9d4c713 | ||
|
|
4c5f93000b | ||
|
|
0c7301a020 | ||
|
|
74c63bf17e | ||
|
|
4564e0b828 | ||
|
|
2ab9607989 | ||
|
|
29d198b46d | ||
|
|
8387ca0c07 | ||
|
|
3796fb8027 | ||
|
|
1da08633ec | ||
|
|
b097e372e4 | ||
|
|
846776929e | ||
|
|
6e1d56b8ee | ||
|
|
32f7eb7299 | ||
|
|
3fc9c3b56c | ||
|
|
e2e46543d2 | ||
|
|
a2b22198ec | ||
|
|
642b59f729 | ||
|
|
9e81de2164 | ||
|
|
d44797db1d | ||
|
|
673ee3f79b | ||
|
|
38a8179544 | ||
|
|
cfb59ac6a0 | ||
|
|
80c26e1adb | ||
|
|
d0de6e8d0f | ||
|
|
68be7d00ff | ||
|
|
d3e6043911 | ||
|
|
373895b36a | ||
|
|
594e10dbe1 | ||
|
|
e3c5b462da | ||
|
|
8a1376b169 | ||
|
|
900e26cf9f | ||
|
|
071e81f29b | ||
|
|
d1bd861ff0 | ||
|
|
f5925dbb3b | ||
|
|
b1eedcb1d8 | ||
|
|
ed7a9f43de | ||
|
|
67885950ef | ||
|
|
74ddb771e9 | ||
|
|
584d0e0b48 | ||
|
|
8d9ca33ea3 | ||
|
|
f0eea1a6a3 | ||
|
|
79cc0377c0 | ||
|
|
115cec08fa | ||
|
|
6df592c2b8 | ||
|
|
f2b81489ba | ||
|
|
0d8760219a | ||
|
|
e8e8d9c130 | ||
|
|
8f712412f5 | ||
|
|
f8ece46163 | ||
|
|
cd8b1add54 | ||
|
|
1c415a9715 | ||
|
|
e1e3601640 | ||
|
|
c2f718b49a | ||
|
|
cee1874689 | ||
|
|
57d172aac2 | ||
|
|
bb26900213 | ||
|
|
9d1ee27533 | ||
|
|
7fd735f667 | ||
|
|
2e79c3a5c6 | ||
|
|
d769999f10 | ||
|
|
bd93d26361 | ||
|
|
72feefe709 | ||
|
|
1d6c2c9664 | ||
|
|
85c78bc8e9 | ||
|
|
18bdcfe050 | ||
|
|
a6f77250b5 | ||
|
|
9874ecde82 | ||
|
|
584b8788be | ||
|
|
7eb15652c7 | ||
|
|
2c28a607ba | ||
|
|
45eca462e7 | ||
|
|
4474868afc | ||
|
|
6c6b5b060d | ||
|
|
c03eb290d1 | ||
|
|
de9bd44071 | ||
|
|
b7458b0686 | ||
|
|
a660ec1afa | ||
|
|
c4f6f01f7e | ||
|
|
9f60560f2b | ||
|
|
7b50f80cb8 | ||
|
|
656d275c56 | ||
|
|
eca17f2b2c | ||
|
|
7f60279aa3 | ||
|
|
5227fafa1b | ||
|
|
4991e2b7cd | ||
|
|
34c514709a | ||
|
|
7dfedfeb10 | ||
|
|
c8bf4cae17 | ||
|
|
2818e268b6 | ||
|
|
7a95c11f62 | ||
|
|
d712b1cce9 | ||
|
|
03fa92352b | ||
|
|
e121fdb47f | ||
|
|
c05793f64f | ||
|
|
3836610d81 | ||
|
|
c3f87cd321 | ||
|
|
4d5a27f45e | ||
|
|
c37c1aaad5 | ||
|
|
110c0df6fb | ||
|
|
04ac3be242 | ||
|
|
44c4ee8bc0 | ||
|
|
57c4d550a3 | ||
|
|
4bcb95eece | ||
|
|
0a92d455c8 | ||
|
|
edb30931ae | ||
|
|
d03ab7a16f | ||
|
|
ea9b6b3e00 | ||
|
|
bae6b600bd | ||
|
|
7eac920985 | ||
|
|
00c2b5992c | ||
|
|
43d2fa1f00 | ||
|
|
92d40d9287 | ||
|
|
7a47a2090f | ||
|
|
0d98491a97 | ||
|
|
16cf16616e | ||
|
|
d9f56ef3ae | ||
|
|
c8934af9bb | ||
|
|
cbacaa98d9 | ||
|
|
f1b6a0baf3 | ||
|
|
4d15f48e2b | ||
|
|
2ffd00e28a | ||
|
|
8ef5be9a7f | ||
|
|
8769faeb83 | ||
|
|
cefbf2d4d6 | ||
|
|
bfd5082054 | ||
|
|
3d3a634d94 | ||
|
|
1cb078cd3c | ||
|
|
22683f5d12 | ||
|
|
57b42aa7c2 | ||
|
|
4a59743024 | ||
|
|
66cec18dee | ||
|
|
ad71938fde | ||
|
|
d4155d6e9e | ||
|
|
af6be4c6b1 | ||
|
|
cfbd0168c3 | ||
|
|
61b9bd7581 | ||
|
|
4c7ad7124e | ||
|
|
0413314cfb | ||
|
|
ab7cbe8ead | ||
|
|
d1264828eb | ||
|
|
76a317b5c0 | ||
|
|
ec11ae2ef7 | ||
|
|
b857c6eb44 | ||
|
|
3f8add73ac | ||
|
|
08b0ddbb7f | ||
|
|
c44cb7e7fd | ||
|
|
77ff61046e | ||
|
|
a8eb06bef2 | ||
|
|
566b5c8ea5 | ||
|
|
eeccd076a0 | ||
|
|
f45ac9d0ef | ||
|
|
cdd9acddfa | ||
|
|
e484f11d12 | ||
|
|
19572f313a | ||
|
|
af9ad5d624 | ||
|
|
8adca69140 | ||
|
|
01710316ed | ||
|
|
ab80c82a22 | ||
|
|
dceafe65a7 | ||
|
|
4734a81fdb | ||
|
|
a2c099e615 | ||
|
|
f9b88a1b6b | ||
|
|
7f2b8cc971 | ||
|
|
582fb3d72f | ||
|
|
bf4fa74742 | ||
|
|
c62b3b9864 | ||
|
|
0ae9795d6b | ||
|
|
7488c31cd3 | ||
|
|
dc0e634004 | ||
|
|
57e7c0ae4f | ||
|
|
7214acfa20 | ||
|
|
ff6d47bb9c | ||
|
|
81971ba53f | ||
|
|
a58f37e3eb | ||
|
|
09a746cf8e | ||
|
|
ea70119138 | ||
|
|
5a9c5b5e2d | ||
|
|
7fb09c5045 | ||
|
|
705171f305 | ||
|
|
400d1a5f1a | ||
|
|
c4855cc5f2 | ||
|
|
1e4b487299 | ||
|
|
cbd883103e | ||
|
|
2ce12943cd | ||
|
|
4151a82b3b | ||
|
|
b871081ef1 | ||
|
|
da51d1d7d9 | ||
|
|
82dae30224 | ||
|
|
1fdac2fdab | ||
|
|
56b23e27d7 | ||
|
|
ba29aa62d3 | ||
|
|
be2342285f | ||
|
|
5595c01221 | ||
|
|
133cddef5b | ||
|
|
0fdba1f84d | ||
|
|
e10ec26e79 | ||
|
|
ffbb397dba | ||
|
|
799076d0c4 | ||
|
|
1fbd10df27 | ||
|
|
4a2f82f1e8 | ||
|
|
d913e4d90b | ||
|
|
0958c29c9e | ||
|
|
36e1c9f79d | ||
|
|
8fedac53dd | ||
|
|
a1c6159fc5 | ||
|
|
5b33acba5e | ||
|
|
4abaef2943 | ||
|
|
ad71773293 | ||
|
|
5b07309939 | ||
|
|
7a044a1dcd | ||
|
|
6507764157 | ||
|
|
7dfa8fc883 | ||
|
|
6df7965bb2 | ||
|
|
02a3dee764 | ||
|
|
d5895f1710 | ||
|
|
2b90ad3f6d | ||
|
|
0bb61b4296 | ||
|
|
2378fb547c | ||
|
|
a79ff3f417 | ||
|
|
4bc93615c5 | ||
|
|
685f0d93e5 | ||
|
|
1d7b6674bb | ||
|
|
014405e451 | ||
|
|
aff3ca3ad3 | ||
|
|
b723d09952 | ||
|
|
bfff842c82 | ||
|
|
ad7d21764d | ||
|
|
f2ae84b004 | ||
|
|
a81956654e | ||
|
|
3cb662799f | ||
|
|
9188d03d61 | ||
|
|
3df2d36453 | ||
|
|
0ab7eb42e4 | ||
|
|
84d4fb37fa | ||
|
|
aa9b38da03 | ||
|
|
8342867807 | ||
|
|
d8cff865da | ||
|
|
096f7e1c88 | ||
|
|
0608d847f5 | ||
|
|
b20360c2a5 | ||
|
|
59b5086cab | ||
|
|
2e3024ab61 | ||
|
|
b5299719da | ||
|
|
2620f56e0d | ||
|
|
20b978c46c | ||
|
|
5c8a18df68 | ||
|
|
3464d6c324 | ||
|
|
eac0cc0521 | ||
|
|
0953d37303 | ||
|
|
1d3fec2a95 | ||
|
|
61b374b7c0 | ||
|
|
c47cc0d5f1 | ||
|
|
939882efbf | ||
|
|
f42cbf548e | ||
|
|
91075ace37 | ||
|
|
de6405f8d1 | ||
|
|
2ffcaf4a9e | ||
|
|
1bda62309b | ||
|
|
83659e5da8 | ||
|
|
a6e136561e | ||
|
|
a75d7487fc | ||
|
|
31b0dd8d58 | ||
|
|
696bd1f455 | ||
|
|
18355efde2 | ||
|
|
d5100134e4 | ||
|
|
b932242e04 | ||
|
|
73ccff3412 | ||
|
|
e5f852a7ed | ||
|
|
581f19462d | ||
|
|
eb59b37251 | ||
|
|
f3696f60cd | ||
|
|
6b4e21f5db | ||
|
|
3d4d5b7bbc | ||
|
|
e6f15681c0 | ||
|
|
5f52a646ff | ||
|
|
be4f9296a5 | ||
|
|
c3181f589c | ||
|
|
872cd40f56 | ||
|
|
30b9de49bf | ||
|
|
8a91c6eb2f | ||
|
|
243471e21d | ||
|
|
b8f97ec94d | ||
|
|
8576a6f253 | ||
|
|
b33e6ceca9 | ||
|
|
8eaf7f32cd | ||
|
|
becdc8cef5 | ||
|
|
651688219c | ||
|
|
b9a4bb3511 | ||
|
|
b318274129 | ||
|
|
8b0e5ba8e7 | ||
|
|
4a9b74b311 | ||
|
|
371b198eb6 | ||
|
|
a6dfdb2c4c | ||
|
|
3122c2b2a9 | ||
|
|
9ac8d149fb | ||
|
|
91e1b0b3b8 | ||
|
|
01636ced88 | ||
|
|
53e587537f | ||
|
|
77eeacf121 | ||
|
|
a89c42d659 | ||
|
|
ba4bc423f4 | ||
|
|
8cd341576d | ||
|
|
006eae5862 | ||
|
|
6e29de4463 | ||
|
|
92d816b990 | ||
|
|
37ad1968b5 | ||
|
|
01793dd4f6 | ||
|
|
9a7f7fa1d5 | ||
|
|
648675d002 | ||
|
|
462d865fc9 | ||
|
|
9f24851948 | ||
|
|
a5e5ec5098 | ||
|
|
db90546bc3 | ||
|
|
2d9ea3ee8d | ||
|
|
4d3cafcf29 | ||
|
|
8685ffb1bf | ||
|
|
86408b3452 | ||
|
|
eab94f3b84 | ||
|
|
9123dbcc9e | ||
|
|
ec6f426b06 | ||
|
|
5482937332 | ||
|
|
0b667703c2 | ||
|
|
c732c96fc2 | ||
|
|
e3d260429c | ||
|
|
77eb2c747b | ||
|
|
6853cd738f | ||
|
|
d58776beab | ||
|
|
94a7b1e438 | ||
|
|
c3f7540f74 | ||
|
|
4642a50f69 | ||
|
|
b23bcf3f0b | ||
|
|
570678e3d3 | ||
|
|
64a2f5eb11 | ||
|
|
24fba8b382 | ||
|
|
9339ef481a | ||
|
|
075789b902 | ||
|
|
1dd1b47faf | ||
|
|
525a164c69 | ||
|
|
b60f333edb | ||
|
|
2323fdfe56 | ||
|
|
9b7fed4d1f | ||
|
|
67c59c9b4b | ||
|
|
b5fea921e6 | ||
|
|
eeb071afc6 | ||
|
|
5669873101 | ||
|
|
d371c9bc82 | ||
|
|
153dd19fc6 | ||
|
|
ae258a75d9 | ||
|
|
37c4be321f | ||
|
|
c810d58064 | ||
|
|
5a27817d11 | ||
|
|
4be2f9283d | ||
|
|
a65162fbbc | ||
|
|
494cf3b6a8 | ||
|
|
b900194402 | ||
|
|
d6e72e72d7 | ||
|
|
960baadeca | ||
|
|
ed92b37869 | ||
|
|
1598809815 | ||
|
|
3131969fc1 | ||
|
|
fb44bc33b9 | ||
|
|
59a806ac8c | ||
|
|
6a2a27e47e | ||
|
|
251d0af028 | ||
|
|
8fa800e2f7 | ||
|
|
df9d20ad88 | ||
|
|
c07754047d | ||
|
|
9c00af317e | ||
|
|
d3d132ec45 | ||
|
|
7adba972e7 | ||
|
|
d83399bd1f | ||
|
|
4471da4aa9 | ||
|
|
b3be1d9351 | ||
|
|
c6b8c2a630 | ||
|
|
3f51c21dc7 | ||
|
|
a8cdbc4fd6 | ||
|
|
95e5babb13 | ||
|
|
74cb23a8bb | ||
|
|
6be304f295 | ||
|
|
e702b0b733 | ||
|
|
bfbd263c74 | ||
|
|
472f922369 | ||
|
|
b13c608ff3 | ||
|
|
5464909121 | ||
|
|
d874b3f808 | ||
|
|
92108bc743 | ||
|
|
66ca1e52bb | ||
|
|
3589094d06 | ||
|
|
801ed6ef79 | ||
|
|
2547ae45a8 | ||
|
|
93e4abe72d | ||
|
|
eb87651c47 | ||
|
|
4138598db2 | ||
|
|
913bbd6e3b | ||
|
|
d35881b05b | ||
|
|
33b54ccf12 | ||
|
|
a8775b2200 | ||
|
|
6d3746222d | ||
|
|
fe169ac80f | ||
|
|
83724e3d44 | ||
|
|
a4db3ef5c4 | ||
|
|
f8adad7865 | ||
|
|
12c094228e | ||
|
|
902ea80807 | ||
|
|
73831d9ac6 | ||
|
|
6bfda79441 | ||
|
|
112de78fc5 | ||
|
|
adbd2381e1 | ||
|
|
397f3f546e | ||
|
|
43d3c28e16 | ||
|
|
747a64b869 | ||
|
|
7c2e5560bd | ||
|
|
9194db9f70 | ||
|
|
fece00c0c6 | ||
|
|
ba3ae5ea56 | ||
|
|
4c69c9e445 | ||
|
|
97925c47fd | ||
|
|
ad89fe15b1 | ||
|
|
29035cabfe | ||
|
|
9b3d43d27f | ||
|
|
0b4f17473a | ||
|
|
b0c29b57c7 | ||
|
|
6d22f6aebf | ||
|
|
3e3e10e6a0 | ||
|
|
b854c777c8 | ||
|
|
b71fdd77e8 | ||
|
|
0f6f7cea19 | ||
|
|
58beb092c2 | ||
|
|
c3f200f73b | ||
|
|
98640c11b1 | ||
|
|
d7904bdcaf | ||
|
|
ce05a94d58 | ||
|
|
c0ed62dc7a | ||
|
|
1557fa98b1 | ||
|
|
81a530f153 | ||
|
|
a4733c3e6a | ||
|
|
7851b8e94c | ||
|
|
36838f7690 | ||
|
|
fdd87d0757 | ||
|
|
c8822aff64 | ||
|
|
4b3205fc9c | ||
|
|
afc05ae9e8 | ||
|
|
9c3044efa0 | ||
|
|
8caeb129c1 | ||
|
|
0427504f0e | ||
|
|
4f11a7caa1 | ||
|
|
150695c185 | ||
|
|
3558591480 | ||
|
|
f7a24052c2 | ||
|
|
75c452486c | ||
|
|
40cab6775c | ||
|
|
392829c7db | ||
|
|
4621c9d616 | ||
|
|
006d664ec9 | ||
|
|
0cc9ac4dd8 | ||
|
|
502096dc22 | ||
|
|
8c424c7a64 | ||
|
|
d7c118b88a | ||
|
|
1fdb0b7516 | ||
|
|
c2fc771756 | ||
|
|
6f759c5bc4 | ||
|
|
facbbf1353 | ||
|
|
a218c7a781 | ||
|
|
5f42709eab | ||
|
|
5ec0f657a0 | ||
|
|
812911ffbb | ||
|
|
55235687ba | ||
|
|
a970009d20 | ||
|
|
4afc16e2cb | ||
|
|
3772d72b43 | ||
|
|
607f949638 | ||
|
|
473cf7c8af | ||
|
|
e0909df06c | ||
|
|
5fc606ef6d | ||
|
|
a3032f4da7 | ||
|
|
7fdd65e8ca | ||
|
|
dc6bf883f1 | ||
|
|
ce5edd93b4 | ||
|
|
e36e6bec9c | ||
|
|
c8fd08b6d2 | ||
|
|
c0d693c1c8 | ||
|
|
5d2a6e2898 | ||
|
|
4547a5ceb0 | ||
|
|
76151c4395 | ||
|
|
481089b1b4 | ||
|
|
463787b7f4 | ||
|
|
e2258a1c43 | ||
|
|
5528f29b6a | ||
|
|
e3861d54c9 | ||
|
|
3368f2803c | ||
|
|
6ca1d68d23 | ||
|
|
cf93081252 | ||
|
|
30fca2a190 |
27
.devcontainer/devcontainer.json
Normal file
27
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "EMS-ESP Devcontainer",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {},
|
||||
"ghcr.io/devcontainers-extra/features/pnpm:2": {},
|
||||
"ghcr.io/devcontainers/features/python:1": {},
|
||||
"ghcr.io/shyim/devcontainers-features/bun:0": {}
|
||||
},
|
||||
|
||||
"forwardPorts": [
|
||||
3000,
|
||||
3080
|
||||
],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "cd mock-api && pnpm install && cd .. && cd interface && pnpm install",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"platformio.platformio-ide"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
101
.github/workflows/dev_release.yml
vendored
Normal file
101
.github/workflows/dev_release.yml
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
name: 'Build dev release'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'src/emsesp_version.h'
|
||||
branches:
|
||||
- 'dev'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
pre-release:
|
||||
name: 'Build Dev Release'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Install python 3.13
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
|
||||
- name: Install Node.js 24
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable pnpm
|
||||
|
||||
- name: Get the EMS-ESP version
|
||||
id: build_info
|
||||
run: |
|
||||
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/emsesp_version.h | awk -F'"' '{print $2}'`
|
||||
echo "VERSION=$version" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
python -m pip install intelhex
|
||||
|
||||
- name: Build webUI
|
||||
run: |
|
||||
platformio run -e build_webUI
|
||||
|
||||
- name: Build modbus
|
||||
run: |
|
||||
platformio run -e build_modbus
|
||||
|
||||
- name: Build standalone
|
||||
run: |
|
||||
platformio run -e build_standalone
|
||||
|
||||
- name: Build all PIO target environments, from default_envs
|
||||
run: |
|
||||
platformio run
|
||||
|
||||
- name: Commit the generated files
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: "chore: update generated files"
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
|
||||
- name: Check for changes and commit
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "Changes detected, committing..."
|
||||
git add .
|
||||
git commit -m "Auto-commit build artifacts and configuration updates
|
||||
|
||||
- Updated build configurations
|
||||
- Generated build artifacts
|
||||
- Version: ${{steps.build_info.outputs.VERSION}}"
|
||||
|
||||
echo "Pushing changes to repository..."
|
||||
git push origin dev
|
||||
else
|
||||
echo "No changes to commit"
|
||||
fi
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: 'automatic_releases'
|
||||
uses: emsesp/action-automatic-releases@v1.0.0
|
||||
with:
|
||||
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
title: Development Build v${{steps.build_info.outputs.VERSION}}
|
||||
automatic_release_tag: 'latest'
|
||||
prerelease: true
|
||||
files: |
|
||||
CHANGELOG_LATEST.md
|
||||
./build/firmware/*.*
|
||||
@@ -1,16 +1,17 @@
|
||||
name: 'github-releases-to-discord'
|
||||
name: 'Publish releases to discord'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
github-releases-to-discord:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: GitHub Releases To Discord
|
||||
uses: SethCohen/github-releases-to-discord@v1.13.1
|
||||
|
||||
27
.github/workflows/pr_check.yml
vendored
27
.github/workflows/pr_check.yml
vendored
@@ -1,37 +1,32 @@
|
||||
name: 'pr_check'
|
||||
name: 'Pre-check on PR'
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches: dev
|
||||
paths:
|
||||
- '**.c'
|
||||
- '**.cpp'
|
||||
- '**.h'
|
||||
- '**.hpp'
|
||||
- '**.json'
|
||||
- '**.py'
|
||||
- '**.md'
|
||||
- '.github/workflows/pr_check.yml'
|
||||
|
||||
- 'src/**'
|
||||
|
||||
jobs:
|
||||
pre-release:
|
||||
name: 'Automatic pre-release build'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
- name: Install python 3.13
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
python-version: '3.13'
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
pip install wheel
|
||||
pip install -U platformio
|
||||
|
||||
- name: Build native
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
platformio run -e native
|
||||
platformio run -e native-test -t exec
|
||||
|
||||
66
.github/workflows/pre_release.yml
vendored
66
.github/workflows/pre_release.yml
vendored
@@ -1,66 +0,0 @@
|
||||
name: 'pre-release'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'dev'
|
||||
|
||||
jobs:
|
||||
pre-release:
|
||||
name: 'Automatic pre-release build'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Get EMS-ESP version
|
||||
id: build_info
|
||||
run: |
|
||||
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/version.h | awk -F'"' '{print $2}'`
|
||||
echo "VERSION=$version" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
|
||||
- name: Build WebUI
|
||||
run: |
|
||||
cd interface
|
||||
yarn install
|
||||
yarn typesafe-i18n --no-watch
|
||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||
yarn build
|
||||
yarn webUI
|
||||
|
||||
- name: Build all PIO target environments from default_envs
|
||||
run: |
|
||||
platformio run
|
||||
env:
|
||||
NO_BUILD_WEBUI: true
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: 'automatic_releases'
|
||||
uses: emsesp/action-automatic-releases@v1.0.0
|
||||
with:
|
||||
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
title: Development Build v${{steps.build_info.outputs.VERSION}}
|
||||
automatic_release_tag: 'latest'
|
||||
prerelease: true
|
||||
files: |
|
||||
CHANGELOG_LATEST.md
|
||||
./build/firmware/*.*
|
||||
27
.github/workflows/sonar_check.yml
vendored
27
.github/workflows/sonar_check.yml
vendored
@@ -1,12 +1,14 @@
|
||||
# see https://github.com/marketplace/actions/sonarcloud-scan-for-c-and-c#usage
|
||||
name: Sonar Check
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
# pull_request:
|
||||
# types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- 'src/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -17,18 +19,15 @@ jobs:
|
||||
BUILD_WRAPPER_OUT_DIR: bw-output
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install sonar-scanner and build-wrapper
|
||||
uses: SonarSource/sonarcloud-github-c-cpp@v2
|
||||
|
||||
- name: Run build-wrapper
|
||||
run: build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make all
|
||||
|
||||
- name: Run sonar-scanner
|
||||
- name: Install Build Wrapper
|
||||
uses: SonarSource/sonarqube-scan-action/install-build-wrapper@master
|
||||
- name: Run Build Wrapper
|
||||
run: build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make all
|
||||
- name: SonarQube Scan
|
||||
uses: SonarSource/sonarqube-scan-action@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
run: sonar-scanner --define sonar.cfamily.compile-commands="${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json"
|
||||
|
||||
|
||||
63
.github/workflows/stable_release.yml
vendored
Normal file
63
.github/workflows/stable_release.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: 'Build stable release'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
tagged-release:
|
||||
name: 'Build Stable Release'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Install python 3.13
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
|
||||
- name: Install Node.js 24
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable pnpm
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
python -m pip install intelhex
|
||||
|
||||
- name: Build webUI
|
||||
run: |
|
||||
platformio run -e build_webUI
|
||||
|
||||
- name: Build modbus
|
||||
run: |
|
||||
platformio run -e build_modbus
|
||||
|
||||
- name: Build standalone
|
||||
run: |
|
||||
platformio run -e build_standalone
|
||||
|
||||
- name: Build all PIO target environments, from default_envs
|
||||
run: |
|
||||
platformio run
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: emsesp/action-automatic-releases@v1.0.0
|
||||
with:
|
||||
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
prerelease: false
|
||||
files: |
|
||||
CHANGELOG.md
|
||||
./build/firmware/*.*
|
||||
27
.github/workflows/stale_issues.yml
vendored
Normal file
27
.github/workflows/stale_issues.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: "Mark or close stale issues and PRs"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 30
|
||||
days-before-close: 5
|
||||
stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment otherwise this will be closed in 5 days."
|
||||
stale-pr-message: "This PR has been automatically marked as stale because there has been no activity in last 30 days. It will be closed if no further activity occurs. Thank you for your contributions."
|
||||
close-issue-message: "This issue was closed because it has been stalled for 5 days with no activity."
|
||||
close-pr-message: "This PR was automatically closed because of being stale."
|
||||
stale-pr-label: "stale"
|
||||
stale-issue-label: "stale"
|
||||
exempt-issue-labels: "bug,enhancement,pinned,security"
|
||||
exempt-pr-labels: "bug,enhancement,pinned,security"
|
||||
57
.github/workflows/tagged_release.yml
vendored
57
.github/workflows/tagged_release.yml
vendored
@@ -1,57 +0,0 @@
|
||||
name: 'tagged-release'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
tagged-release:
|
||||
name: 'Tagged Release'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
|
||||
- name: Build WebUI
|
||||
run: |
|
||||
cd interface
|
||||
yarn install
|
||||
yarn typesafe-i18n --no-watch
|
||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||
yarn build
|
||||
yarn webUI
|
||||
|
||||
- name: Build all PIO target environments from default_envs
|
||||
run: |
|
||||
platformio run
|
||||
env:
|
||||
NO_BUILD_WEBUI: true
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: emsesp/action-automatic-releases@v1.0.0
|
||||
with:
|
||||
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
prerelease: false
|
||||
files: |
|
||||
CHANGELOG.md
|
||||
./build/firmware/*.*
|
||||
62
.github/workflows/test_release.yml
vendored
62
.github/workflows/test_release.yml
vendored
@@ -1,55 +1,65 @@
|
||||
name: 'test-release'
|
||||
name: 'Build test release'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'dev2'
|
||||
- 'test'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pre-release:
|
||||
name: 'Automatic test-release build'
|
||||
name: 'Build Test Release'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install python 3.13
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
|
||||
- name: Install Node.js 24
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
run: corepack enable pnpm
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Get EMS-ESP source code and version
|
||||
- name: Get the EMS-ESP version
|
||||
id: build_info
|
||||
run: |
|
||||
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/version.h | awk -F'"' '{print $2}'`
|
||||
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/emsesp_version.h | awk -F'"' '{print $2}'`
|
||||
echo "VERSION=$version" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
python -m pip install intelhex
|
||||
|
||||
- name: Build WebUI
|
||||
- name: Build webUI
|
||||
run: |
|
||||
cd interface
|
||||
yarn install
|
||||
yarn typesafe-i18n --no-watch
|
||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||
yarn build
|
||||
yarn webUI
|
||||
platformio run -e build_webUI
|
||||
|
||||
- name: Build all target environments from default_envs
|
||||
- name: Build modbus
|
||||
run: |
|
||||
platformio run -e build_modbus
|
||||
|
||||
- name: Build standalone
|
||||
run: |
|
||||
platformio run -e build_standalone
|
||||
|
||||
- name: Build all PIO target environments, from default_envs
|
||||
run: |
|
||||
platformio run
|
||||
env:
|
||||
NO_BUILD_WEBUI: true
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: 'automatic_releases'
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -28,14 +28,10 @@ stats.html
|
||||
*.sln
|
||||
*.sw?
|
||||
.pnp.*
|
||||
*/.yarn/cache/*
|
||||
*/.yarn/install-state.gz
|
||||
analyse.html
|
||||
interface/vite.config.ts.timestamp*
|
||||
*.local
|
||||
src/ESP32React/WWWData.h
|
||||
.yarn/*
|
||||
.yarnrc.yml
|
||||
|
||||
# i18n generated files
|
||||
interface/src/i18n/i18n-react.tsx
|
||||
@@ -76,3 +72,5 @@ CMakeLists.txt
|
||||
logs/*
|
||||
sdkconfig.*
|
||||
sdkconfig_tasmota_esp32
|
||||
pnpm-lock.yaml
|
||||
package.json
|
||||
|
||||
@@ -217,7 +217,7 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
|
||||
|
||||
## **IMPORTANT! BREAKING CHANGES**
|
||||
|
||||
Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`. You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer.
|
||||
Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`... You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer.
|
||||
|
||||
## Added
|
||||
|
||||
|
||||
@@ -1 +1,58 @@
|
||||
# Changelog
|
||||
|
||||
For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
|
||||
|
||||
## [3.7.3]
|
||||
|
||||
## Added
|
||||
|
||||
- analogsensor types: NTC and RGB-Led
|
||||
- Flag for HMC310 [#2465](https://github.com/emsesp/EMS-ESP32/issues/2465)
|
||||
- boiler auxheatersource [#2489](https://github.com/emsesp/EMS-ESP32/discussions/2489)
|
||||
- thermostat last error for RC100/300 [#2501](https://github.com/emsesp/EMS-ESP32/issues/2501)
|
||||
- boiler 0xC6 telegram [#1963](https://github.com/emsesp/EMS-ESP32/issues/1963)
|
||||
- CS6800i changes [#2448](https://github.com/emsesp/EMS-ESP32/issues/2448), [#2449](https://github.com/emsesp/EMS-ESP32/issues/2449)
|
||||
- charging pump [#2544](https://github.com/emsesp/EMS-ESP32/issues/2544)
|
||||
- hybrid CSH5800iG [#2569](https://github.com/emsesp/EMS-ESP32/issues/2569)
|
||||
- add EMS Device details to Home Assistant MQTT Discovery
|
||||
- disinfection command [#2601](https://github.com/emsesp/EMS-ESP32/issues/2601)
|
||||
- added new board profile for upcoming BBQKees E32V2.2
|
||||
- set differential pressure entity in Mixer device
|
||||
- set set climate action cooling/heating in HA [#2583](https://github.com/emsesp/EMS-ESP32/issues/2583)
|
||||
- Internal sensors of E32V2_2
|
||||
- FW200 display options [#2610](https://github.com/emsesp/EMS-ESP32/discussions/2610)
|
||||
- CR11 mode settings OFF/MANUAL depends on selTemp [#2437](https://github.com/emsesp/EMS-ESP32/issues/2437)
|
||||
- Fuse settings for BBQKees boards
|
||||
- Analogsensors for pulse output [#2624](https://github.com/emsesp/EMS-ESP32/discussions/2624)
|
||||
- Analogsensors frequency input [#2631](https://github.com/emsesp/EMS-ESP32/discussions/2631)
|
||||
- SRC plus thermostats [#2636](https://github.com/emsesp/EMS-ESP32/issues/2636)
|
||||
- Greenstar 2000 [#2645](https://github.com/emsesp/EMS-ESP32/issues/2645)
|
||||
- RC3xx `dhw modetype` [#2659](https://github.com/emsesp/EMS-ESP32/discussions/2659)
|
||||
- new boiler entities VR0,VR1, compressor speed [#2669](https://github.com/emsesp/EMS-ESP32/issues/2669)
|
||||
- solar temperature TS16 [#2690](https://github.com/emsesp/EMS-ESP32/issues/2690)
|
||||
|
||||
## Fixed
|
||||
|
||||
- dhw/switchtime [#2490](https://github.com/emsesp/EMS-ESP32/issues/2490)
|
||||
- switch to secure mqtt [#2492](https://github.com/emsesp/EMS-ESP32/issues/2492)
|
||||
- update link buttons [#2497](https://github.com/emsesp/EMS-ESP32/issues/2497)
|
||||
- refresh scheduler states [#2502](https://github.com/emsesp/EMS-ESP32/discussions/2502)
|
||||
- also rebuild HA config on mqtt connect for scheduler, custom and shower
|
||||
- FB100 controls the hc, not the master [#2510](https://github.com/emsesp/EMS-ESP32/issues/2510)
|
||||
- IPM DHW module, [#2524](https://github.com/emsesp/EMS-ESP32/issues/2524)
|
||||
- charge optimization [#2543](https://github.com/emsesp/EMS-ESP32/issues/2543)
|
||||
- shower active state retained, shows correctly in HA
|
||||
- MQTT Command Topic with slashes [#2571](https://github.com/emsesp/EMS-ESP32/issues/2571)
|
||||
- Add pulsed water meter input to V1.3 gateway with Lilygo S3 [#2550](https://github.com/emsesp/EMS-ESP32/issues/2550)
|
||||
- fix missing long 10-second press of Button to perform a factory reset
|
||||
- fix wwMaxPower on Junkers ZBS14 [#2609](https://github.com/emsesp/EMS-ESP32/issues/2609)
|
||||
- ventilation bypass state from telegram 0x55C [#1197](https://github.com/emsesp/EMS-ESP32/issues/1197)
|
||||
- set selflowtemp for ems+ boilers [#2641](https://github.com/emsesp/EMS-ESP32/discussions/2641)
|
||||
|
||||
## Changed
|
||||
|
||||
- show console log with ISO date/time [#2533](https://github.com/emsesp/EMS-ESP32/discussions/2533)
|
||||
- remove ESP32 CPU temperature
|
||||
- updated core libraries like AsyncTCP, AsyncWebServer and Modbus
|
||||
- remove command `scan deep`
|
||||
- ignore repeated `forceheatingoff` commands [#2641](https://github.com/emsesp/EMS-ESP32/discussions/2641)
|
||||
|
||||
@@ -69,7 +69,7 @@ Format: `<type>(<scope>): <subject>`
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
```text
|
||||
feat: add hat wobble
|
||||
^--^ ^------------^
|
||||
| |
|
||||
@@ -96,7 +96,7 @@ References:
|
||||
|
||||
## Contributor License Agreement (CLA)
|
||||
|
||||
```
|
||||
```text
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
|
||||
58
Makefile
58
Makefile
@@ -19,7 +19,7 @@ C = $(words $N)$(eval N := x $N)
|
||||
ECHO = python3 $(I)/scripts/echo_progress.py --stepno=$C --nsteps=$T
|
||||
endif
|
||||
|
||||
# determine number of parallel compiles based on OS
|
||||
# Optimize parallel build configuration
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
EXTRA_CPPFLAGS = -D LINUX
|
||||
@@ -29,7 +29,9 @@ ifeq ($(UNAME_S),Darwin)
|
||||
EXTRA_CPPFLAGS = -D OSX -Wno-tautological-constant-out-of-range-compare
|
||||
JOBS ?= $(shell sysctl -n hw.ncpu)
|
||||
endif
|
||||
MAKEFLAGS += -j $(JOBS) -l $(JOBS)
|
||||
|
||||
# Set optimal parallel build settings
|
||||
MAKEFLAGS += -j$(JOBS) -l$(shell echo $$(($(JOBS) * 2)))
|
||||
|
||||
# $(info Number of jobs: $(JOBS))
|
||||
|
||||
@@ -72,16 +74,21 @@ DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DE
|
||||
OUTPUT := $(CURDIR)/$(TARGET)
|
||||
SYMBOLS := $(CURDIR)/$(BUILD)/$(TARGET).out
|
||||
|
||||
CSOURCES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.c))
|
||||
CXXSOURCES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.cpp))
|
||||
# Optimize source discovery - use shell find for better performance
|
||||
CSOURCES := $(shell find $(SOURCES) -name "*.c" 2>/dev/null)
|
||||
CXXSOURCES := $(shell find $(SOURCES) -name "*.cpp" 2>/dev/null)
|
||||
|
||||
OBJS := $(patsubst %,$(BUILD)/%.o,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)) )
|
||||
DEPS := $(patsubst %,$(BUILD)/%.d,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)) )
|
||||
OBJS := $(patsubst %,$(BUILD)/%.o,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)))
|
||||
DEPS := $(patsubst %,$(BUILD)/%.d,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)))
|
||||
|
||||
INCLUDE += $(addprefix -I,$(foreach dir,$(INCLUDES), $(wildcard $(dir))))
|
||||
INCLUDE += $(addprefix -I,$(foreach dir,$(LIBRARIES),$(wildcard $(dir)/include)))
|
||||
# Optimize include path discovery
|
||||
INCLUDE_DIRS := $(shell find $(INCLUDES) -type d 2>/dev/null)
|
||||
LIBRARY_INCLUDES := $(shell find $(LIBRARIES) -name "include" -type d 2>/dev/null)
|
||||
INCLUDE += $(addprefix -I,$(INCLUDE_DIRS) $(LIBRARY_INCLUDES))
|
||||
|
||||
LDLIBS += $(addprefix -L,$(foreach dir,$(LIBRARIES),$(wildcard $(dir)/lib)))
|
||||
# Optimize library path discovery
|
||||
LIBRARY_DIRS := $(shell find $(LIBRARIES) -name "lib" -type d 2>/dev/null)
|
||||
LDLIBS += $(addprefix -L,$(LIBRARY_DIRS))
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Compiler & Linker
|
||||
@@ -98,13 +105,12 @@ CXX := /usr/bin/g++
|
||||
# LDFLAGS Linker Flags
|
||||
#----------------------------------------------------------------------
|
||||
CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
|
||||
CPPFLAGS += -ggdb -g3 -O3
|
||||
CPPFLAGS += -MMD
|
||||
CPPFLAGS += -flto=auto -fno-lto
|
||||
CPPFLAGS += -Wall -Wextra -Werror
|
||||
CPPFLAGS += -Wswitch-enum
|
||||
CPPFLAGS += -Wno-unused-parameter
|
||||
CPPFLAGS += -Wno-missing-braces
|
||||
CPPFLAGS += -ggdb -g3 -MMD
|
||||
CPPFLAGS += -flto=auto
|
||||
CPPFLAGS += -Wall -Wextra -Werror -Wswitch-enum
|
||||
CPPFLAGS += -Wno-unused-parameter -Wno-missing-braces -Wno-vla-cxx-extension
|
||||
CPPFLAGS += -ffunction-sections -fdata-sections -fno-exceptions -fno-rtti -fno-threadsafe-statics
|
||||
CPPFLAGS += -Os -DNDEBUG
|
||||
|
||||
CPPFLAGS += $(EXTRA_CPPFLAGS)
|
||||
|
||||
@@ -125,7 +131,8 @@ else
|
||||
LD := $(CXX)
|
||||
endif
|
||||
|
||||
#DEPFLAGS += -MF $(BUILD)/$*.d
|
||||
# Dependency file generation
|
||||
DEPFLAGS += -MF $(BUILD)/$*.d -MT $@
|
||||
|
||||
LINK.o = $(LD) $(LDFLAGS) $(LDLIBS) $^ -o $@
|
||||
COMPILE.c = $(CC) $(C_STANDARD) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
|
||||
@@ -142,7 +149,10 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
|
||||
.SUFFIXES:
|
||||
.INTERMEDIATE:
|
||||
.PRECIOUS: $(OBJS) $(DEPS)
|
||||
.PHONY: all clean help
|
||||
.PHONY: all clean help cppcheck run
|
||||
|
||||
# Enable second expansion for more flexible rules
|
||||
.SECONDEXPANSION:
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Targets
|
||||
@@ -157,7 +167,6 @@ $(OUTPUT): $(OBJS)
|
||||
@mkdir -p $(@D)
|
||||
@$(ECHO) Linking $@
|
||||
$(LINK.o)
|
||||
$(SYMBOLS.out)
|
||||
|
||||
$(BUILD)/%.o: %.c
|
||||
@mkdir -p $(@D)
|
||||
@@ -185,8 +194,15 @@ clean:
|
||||
@$(RM) -rf $(BUILD) $(OUTPUT)
|
||||
|
||||
help:
|
||||
@echo available targets: all run clean
|
||||
@echo $(OUTPUT)
|
||||
@echo "Available targets:"
|
||||
@echo " all - Build the project (default)"
|
||||
@echo " run - Build and run the executable"
|
||||
@echo " clean - Remove build artifacts"
|
||||
@echo " cppcheck - Run static analysis"
|
||||
@echo " help - Show this help message"
|
||||
@echo ""
|
||||
@echo "Output: $(OUTPUT)"
|
||||
@echo "Jobs: $(JOBS)"
|
||||
|
||||
-include $(DEPS)
|
||||
|
||||
|
||||
16
README.md
16
README.md
@@ -35,7 +35,7 @@
|
||||
[](https://discord.gg/3J3GgnzpyT)
|
||||
|
||||
[](https://github.com/emsesp/EMS-ESP32/stargazers)
|
||||
[](https://github.com/emsesp/EMS-ES32P/network)
|
||||
[](https://github.com/emsesp/EMS-ESP32/network)
|
||||
[](https://www.paypal.com/paypalme/prderbyshire/2)
|
||||
|
||||
**EMS-ESP** is an open-source firmware for the Espressif ESP32 microcontroller to communicate with **EMS** (Energy Management System) compatible equipment from manufacturers such as Bosch, Buderus, Nefit, Junkers, Worcester, Sieger, elm.leblanc and iVT.
|
||||
@@ -60,7 +60,7 @@ It requires a small circuit to interface with the EMS bus which can be purchased
|
||||
|
||||
## 🚀 **Installing**
|
||||
|
||||
Head over to [download.emsesp.org](https://download.emsesp.org) for instructions on how to install EMS-ESP. There is also further details on which boards are supported in [this section](https://docs.emsesp.org/Getting-Started/#first-time-install) of the documentation.
|
||||
Head over to [download.emsesp.org](https://download.emsesp.org) for instructions on how to install EMS-ESP. There is also further details on which boards are supported in [this section](https://docs.emsesp.org/Installing/) of the documentation.
|
||||
|
||||
## 📋 **Documentation**
|
||||
|
||||
@@ -70,7 +70,7 @@ Visit [emsesp.org](https://docs.emsesp.org) for more details on how to install a
|
||||
|
||||
To chat with the community reach out on our [Discord Server](https://discord.gg/3J3GgnzpyT).
|
||||
|
||||
If you find an issue or have a request, see [here](https://docs.emsesp.org/Support/) on how to submit a bug report or feature request.
|
||||
If you find an issue or have a request, see [how to request support](https://docs.emsesp.org/Support/) on how to submit a bug report or feature request.
|
||||
|
||||
## 🎥 **Live Demo**
|
||||
|
||||
@@ -82,13 +82,19 @@ EMS-ESP is a project created by [proddy](https://github.com/proddy) and owned an
|
||||
|
||||
If you like **EMS-ESP**, please give it a ✨ on GitHub, or even better fork it and contribute. You can also offer a small donation. This is an open-source project maintained by volunteers, and your support is greatly appreciated.
|
||||
|
||||
## 📦 **Building**
|
||||
|
||||
To build the web interface only, run `platformio run -e build_webUI`. This will install the necessary dependencies and build the web interface and also create the embedded code used need to build the firmware. You can run the web interface locally by going to the `interface` directory and running `pnpm standalone`.
|
||||
|
||||
To build the firmware, run `platformio run`. This will build the firmware for all ESP32 modules and place the binaries in the `build/firmware` folder. If you want to configure the build for a single platform create a local `pio_local.ni` file in the root directory (see example in `pio_local.ini_example`).
|
||||
|
||||
## 📢 **Libraries used**
|
||||
|
||||
- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the core framework that provides the Web UI, which has been heavily modified
|
||||
- [esp8266-react](https://github.com/rjwats/esp8266-react) originally by @rjwats for the core framework that provides the Web UI, which has been heavily modified
|
||||
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these awesome open source libraries
|
||||
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON processing
|
||||
- [espMqttClient](https://github.com/bertmelis/espMqttClient) for the MQTT client
|
||||
- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance
|
||||
- [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) and [AsyncTCP](https://github.com/ESP32Async/AsyncTCP) for the Web server
|
||||
|
||||
## 📜 **License**
|
||||
|
||||
|
||||
48
boards/seeed_xiao_esp32c6.json
Normal file
48
boards/seeed_xiao_esp32c6.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"build": {
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_XIAO_ESP32C6",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1"
|
||||
],
|
||||
"f_cpu": "160000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [
|
||||
[
|
||||
"0x2886",
|
||||
"0x0046"
|
||||
],
|
||||
[
|
||||
"0x303a",
|
||||
"0x1001"
|
||||
]
|
||||
],
|
||||
"mcu": "esp32c6",
|
||||
"variant": "XIAO_ESP32C6"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi",
|
||||
"bluetooth",
|
||||
"zigbee",
|
||||
"thread"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_target": "esp32c6.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "Seeed Studio XIAO ESP32C6",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 4194304,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"url": "https://wiki.seeedstudio.com/XIAO_ESP32C6_Getting_Started/",
|
||||
"vendor": "Seeed Studio"
|
||||
}
|
||||
@@ -32,6 +32,8 @@
|
||||
"**/*.json",
|
||||
"src/core/modbus_entity_parameters.hpp",
|
||||
"sdkconfig.*",
|
||||
"managed_components/**"
|
||||
"managed_components/**",
|
||||
"pnpm-*.yaml",
|
||||
"vite.config.ts"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
11245
docs/dump_entities.csv
11245
docs/dump_entities.csv
File diff suppressed because it is too large
Load Diff
@@ -1,227 +1,231 @@
|
||||
telegram_type_id,name,is_fetched
|
||||
0x04,UBAFactory,fetched
|
||||
0x06,RCTime,
|
||||
0x0A,EasyMonitor,fetched
|
||||
0x10,UBAErrorMessage1,
|
||||
0x11,UBAErrorMessage2,
|
||||
0x12,RCErrorMessage,
|
||||
0x13,RCErrorMessage2,
|
||||
0x14,UBATotalUptime,fetched
|
||||
0x15,UBAMaintenanceData,
|
||||
0x16,UBAParameters,fetched
|
||||
0x18,UBAMonitorFast,
|
||||
0x19,UBAMonitorSlow,
|
||||
0x1A,UBASetPoints,
|
||||
0x1C,UBAMaintenanceStatus,
|
||||
0x1E,WM10TempMessage,
|
||||
0x23,JunkersSetMixer,fetched
|
||||
0x26,UBASettingsWW,fetched
|
||||
0x28,WeatherComp,fetched
|
||||
0x2A,MC110Status,
|
||||
0x2E,Meters,
|
||||
0x33,UBAParameterWW,fetched
|
||||
0x34,UBAMonitorWW,
|
||||
0x35,UBAFlags,
|
||||
0x37,WWSettings,fetched
|
||||
0x38,WWTimer,fetched
|
||||
0x39,WWCircTimer,fetched
|
||||
0x3A,RC30WWSettings,fetched
|
||||
0x3B,Energy,
|
||||
0x3D,RC35Set,
|
||||
0x3E,RC35Monitor,
|
||||
0x3F,RC35Timer,
|
||||
0x40,RC30Temp,
|
||||
0x41,RC30Monitor,
|
||||
0x42,RC35Timer2,
|
||||
0x47,RC35Set,
|
||||
0x48,RC35Monitor,
|
||||
0x49,RC35Timer,
|
||||
0x4C,RC35Timer2,
|
||||
0x51,RC35Set,
|
||||
0x52,RC35Monitor,
|
||||
0x53,RC35Timer,
|
||||
0x56,RC35Timer2,
|
||||
0x5B,RC35Set,
|
||||
0x5C,RC35Monitor,
|
||||
0x5D,RC35Timer,
|
||||
0x60,RC35Timer2,
|
||||
0x96,SM10Config,fetched
|
||||
0x97,SM10Monitor,
|
||||
0x9C,WM10MonitorMessage,
|
||||
0x9D,WM10SetMessage,
|
||||
0xA2,RCError,
|
||||
0xA3,RCOutdoorTemp,
|
||||
0xA5,IBASettings,fetched
|
||||
0xA7,RC30Set,
|
||||
0xA9,RC30Vacation,fetched
|
||||
0xAA,MMConfigMessage,fetched
|
||||
0xAB,MMStatusMessage,
|
||||
0xAC,MMSetMessage,
|
||||
0xAF,RC20Remote,
|
||||
0xB0,RC10Set,
|
||||
0xB1,RC10Monitor,
|
||||
0xBB,HybridSettings,fetched
|
||||
0xBF,ErrorMessage,
|
||||
0xC2,UBAErrorMessage3,
|
||||
0xD1,UBAOutdoorTemp,
|
||||
0xE3,UBAMonitorSlowPlus2,
|
||||
0xE4,UBAMonitorFastPlus,
|
||||
0xE5,UBAMonitorSlowPlus,
|
||||
0xE6,UBAParametersPlus,fetched
|
||||
0xE9,UBAMonitorWWPlus,
|
||||
0xEA,UBAParameterWWPlus,fetched
|
||||
0x0101,ISM1Set,fetched
|
||||
0x0103,ISM1StatusMessage,fetched
|
||||
0x0104,ISM2StatusMessage,
|
||||
0x010C,IPMStatusMessage,
|
||||
0x011E,IPMTempMessage,
|
||||
0x012E,HPEnergy1,
|
||||
0x013B,HPEnergy2,
|
||||
0x0165,JunkersSet,
|
||||
0x0166,JunkersSet,
|
||||
0x0167,JunkersSet,
|
||||
0x0168,JunkersSet,
|
||||
0x016E,Absent,fetched
|
||||
0x016F,JunkersMonitor,
|
||||
0x0170,JunkersMonitor,
|
||||
0x0171,JunkersMonitor,
|
||||
0x0172,JunkersMonitor,
|
||||
0x0179,JunkersSet,
|
||||
0x017A,JunkersSet,
|
||||
0x017B,JunkersSet,
|
||||
0x017C,JunkersSet,
|
||||
0x01D3,JunkersDhw,fetched
|
||||
0x023A,RC300OutdoorTemp,fetched
|
||||
0x023E,PVSettings,fetched
|
||||
0x0240,RC300Settings,fetched
|
||||
0x0241,RC300Settings,fetched
|
||||
0x0267,RC300Floordry,
|
||||
0x0269,RC300Holiday,fetched
|
||||
0x0291,HPMode,fetched
|
||||
0x0292,HPMode,fetched
|
||||
0x0293,HPMode,fetched
|
||||
0x0294,HPMode,fetched
|
||||
0x029B,RC300Curves,
|
||||
0x029C,RC300Curves,
|
||||
0x029D,RC300Curves,
|
||||
0x029E,RC300Curves,
|
||||
0x029F,RC300Curves,
|
||||
0x02A0,RC300Curves,
|
||||
0x02A1,RC300Curves,
|
||||
0x02A2,RC300Curves,
|
||||
0x02A5,RC300Monitor,fetched
|
||||
0x02A6,RC300Monitor,
|
||||
0x02A7,CRFMonitor,
|
||||
0x02A8,RC300Monitor,
|
||||
0x02A9,RC300Monitor,
|
||||
0x02AA,RC300Monitor,
|
||||
0x02AB,RC300Monitor,
|
||||
0x02AC,RC300Monitor,
|
||||
0x02AF,RC300Summer,
|
||||
0x02B0,RC300Summer,
|
||||
0x02B1,RC300Summer,
|
||||
0x02B2,RC300Summer,
|
||||
0x02B3,RC300Summer,
|
||||
0x02B4,RC300Summer,
|
||||
0x02B5,RC300Summer,
|
||||
0x02B6,RC300Summer,
|
||||
0x02B9,RC300Set,
|
||||
0x02BA,RC300Set,
|
||||
0x02BB,RC300Set,
|
||||
0x02BC,RC300Set,
|
||||
0x02BD,RC300Set,
|
||||
0x02BE,RC300Set,
|
||||
0x02BF,RC300Set,
|
||||
0x02C0,RC300Set,
|
||||
0x02CC,HPPressure,fetched
|
||||
0x02CD,MMPLUSConfigMessage,fetched
|
||||
0x02CE,RC300Set2,
|
||||
0x02D0,RC300Set2,
|
||||
0x02D2,RC300Set2,
|
||||
0x02D6,HPPump2,fetched
|
||||
0x02D7,MMPLUSStatusMessage,
|
||||
0x02F5,RC300WWmode,fetched
|
||||
0x02F6,RC300WW2mode,fetched
|
||||
0x0313,MMPLUSConfigMessage_WWC,fetched
|
||||
0x031B,RC300WWtemp,fetched
|
||||
0x031D,RC300WWmode2,
|
||||
0x031E,RC300WWmode2,
|
||||
0x0331,MMPLUSStatusMessage_WWC,
|
||||
0x0358,SM100SystemConfig,fetched
|
||||
0x035A,SM100CircuitConfig,fetched
|
||||
0x035C,SM100HeatAssist,fetched
|
||||
0x035D,SM100Circuit2Config,fetched
|
||||
0x035F,SM100Config1,fetched
|
||||
0x0361,SM100Differential,fetched
|
||||
0x0362,SM100Monitor,
|
||||
0x0363,SM100Monitor2,
|
||||
0x0364,SM100Status,
|
||||
0x0366,SM100Config,
|
||||
0x036A,SM100Status2,
|
||||
0x0380,SM100CollectorConfig,fetched
|
||||
0x038E,SM100Energy,fetched
|
||||
0x0391,SM100Time,fetched
|
||||
0x043F,CRHolidays,fetched
|
||||
0x0467,HPSet,
|
||||
0x0468,HPSet,
|
||||
0x0469,HPSet,
|
||||
0x046A,HPSet,
|
||||
0x0471,RC300Summer2,
|
||||
0x0472,RC300Summer2,
|
||||
0x0473,RC300Summer2,
|
||||
0x0474,RC300Summer2,
|
||||
0x0475,RC300Summer2,
|
||||
0x0476,RC300Summer2,
|
||||
0x0477,RC300Summer2,
|
||||
0x0478,RC300Summer2,
|
||||
0x047B,HP2,
|
||||
0x0484,HPSilentMode,fetched
|
||||
0x0485,HpCooling,fetched
|
||||
0x0486,HpInConfig,fetched
|
||||
0x0488,HPValve,fetched
|
||||
0x048A,HpPool,fetched
|
||||
0x048B,HPPumps,fetched
|
||||
0x048D,HpPower,fetched
|
||||
0x048F,HpTemperatures,
|
||||
0x0491,HPAdditionalHeater,fetched
|
||||
0x0492,HpHeaterConfig,fetched
|
||||
0x0494,UBAEnergySupplied,
|
||||
0x0495,UBAInformation,
|
||||
0x0499,HPDhwSettings,fetched
|
||||
0x049C,HPSettings2,fetched
|
||||
0x049D,HPSettings3,fetched
|
||||
0x04A2,HpInput,fetched
|
||||
0x04A5,HPFan,fetched
|
||||
0x04A7,HPPowerLimit,fetched
|
||||
0x04AA,HPPower2,fetched
|
||||
0x04AE,HPEnergy,fetched
|
||||
0x04AF,HPMeters,fetched
|
||||
0x056B,VentilationMode,fetched
|
||||
0x0583,VentilationMonitor,
|
||||
0x0585,Blowerspeed,
|
||||
0x0587,Bypass,
|
||||
0x05BA,HpPoolStatus,fetched
|
||||
0x05D9,Airquality,
|
||||
0x0772,HIUSettings,
|
||||
0x0779,HIUMonitor,
|
||||
0x07A5,SM100wwCirc,fetched
|
||||
0x07A6,SM100wwParam,fetched
|
||||
0x07AA,SM100wwStatus,
|
||||
0x07AB,SM100wwCommand,
|
||||
0x07AC,SM100wwParam1,
|
||||
0x07AD,SM100ValveStatus,
|
||||
0x07AE,SM100wwKeepWarm,fetched
|
||||
0x07D6,SM100wwTemperature,
|
||||
0x07E0,SM100wwStatus2,fetched
|
||||
0x0935,EM100SetMessage,fetched
|
||||
0x0936,EM100OutMessage,
|
||||
0x0937,EM100TempMessage,
|
||||
0x0938,EM100InputMessage,
|
||||
0x0939,EM100MonitorMessage,
|
||||
0x093A,EM100ConfigMessage,
|
||||
0x0998,HPSettings,fetched
|
||||
0x0999,HPFunctionTest,fetched
|
||||
0x099A,HPStarts,
|
||||
0x099B,HPFlowTemp,
|
||||
0x099C,HPComp,
|
||||
0x09A0,HPTemperature,
|
||||
telegram_type_id,name,is_fetched
|
||||
0x04,UBAFactory,fetched
|
||||
0x06,RCTime,
|
||||
0x0A,EasyMonitor,fetched
|
||||
0x10,UBAErrorMessage1,
|
||||
0x11,UBAErrorMessage2,
|
||||
0x12,RCErrorMessage,
|
||||
0x13,RCErrorMessage2,
|
||||
0x14,UBATotalUptime,fetched
|
||||
0x15,UBAMaintenanceData,
|
||||
0x16,UBAParameters,fetched
|
||||
0x18,UBAMonitorFast,
|
||||
0x19,UBAMonitorSlow,
|
||||
0x1A,UBASetPoints,
|
||||
0x1C,UBAMaintenanceStatus,
|
||||
0x1E,HydrTemp,
|
||||
0x23,JunkersSetMixer,fetched
|
||||
0x27,UBASettingsWW,fetched
|
||||
0x28,WeatherComp,fetched
|
||||
0x2A,MC110Status,
|
||||
0x2E,Meters,
|
||||
0x33,UBAParameterWW,fetched
|
||||
0x34,UBAMonitorWW,
|
||||
0x35,UBAFlags,
|
||||
0x37,WWSettings,fetched
|
||||
0x38,WWTimer,fetched
|
||||
0x39,WWCircTimer,fetched
|
||||
0x3A,RC30WWSettings,fetched
|
||||
0x3B,Energy,
|
||||
0x3D,RC35Set,
|
||||
0x3E,RC35Monitor,
|
||||
0x3F,RC35Timer,
|
||||
0x40,RC30Temp,
|
||||
0x41,RC30Monitor,
|
||||
0x42,RC35Timer2,
|
||||
0x47,RC35Set,
|
||||
0x48,RC35Monitor,
|
||||
0x49,RC35Timer,
|
||||
0x4C,RC35Timer2,
|
||||
0x51,RC35Set,
|
||||
0x52,RC35Monitor,
|
||||
0x53,RC35Timer,
|
||||
0x56,RC35Timer2,
|
||||
0x5B,RC35Set,
|
||||
0x5C,RC35Monitor,
|
||||
0x5D,RC35Timer,
|
||||
0x60,RC35Timer2,
|
||||
0x96,SM10Config,fetched
|
||||
0x97,SM10Monitor,
|
||||
0x9C,WM10MonitorMessage,
|
||||
0x9D,WM10SetMessage,
|
||||
0xA2,RCError,
|
||||
0xA3,RCOutdoorTemp,
|
||||
0xA5,IBASettings,fetched
|
||||
0xA7,RC30Set,
|
||||
0xA9,RC30Vacation,fetched
|
||||
0xAA,MMConfigMessage,fetched
|
||||
0xAB,MMStatusMessage,
|
||||
0xAC,MMSetMessage,
|
||||
0xAF,RC20Remote,
|
||||
0xB0,RC10Set,
|
||||
0xB1,RC10Monitor,
|
||||
0xBB,HybridSettings,fetched
|
||||
0xBF,ErrorMessage,
|
||||
0xC0,RCErrorMessage,
|
||||
0xC2,UBAErrorMessage3,
|
||||
0xC6,UBAErrorMessage3,
|
||||
0xD1,UBAOutdoorTemp,
|
||||
0xE3,UBAMonitorSlowPlus2,
|
||||
0xE4,UBAMonitorFastPlus,
|
||||
0xE5,UBAMonitorSlowPlus,
|
||||
0xE6,UBAParametersPlus,fetched
|
||||
0xE9,UBAMonitorWWPlus,
|
||||
0xEA,UBAParameterWWPlus,fetched
|
||||
0x0101,ISM1Set,fetched
|
||||
0x0103,ISM1StatusMessage,fetched
|
||||
0x0104,ISM2StatusMessage,
|
||||
0x010C,IPMStatusMessage,
|
||||
0x011E,JunkersDisp,fetched
|
||||
0x012E,HPEnergy1,
|
||||
0x013B,HPEnergy2,
|
||||
0x0165,JunkersSet,
|
||||
0x0166,JunkersSet,
|
||||
0x0167,JunkersSet,
|
||||
0x0168,JunkersSet,
|
||||
0x016E,Absent,fetched
|
||||
0x016F,JunkersMonitor,
|
||||
0x0170,JunkersMonitor,
|
||||
0x0171,JunkersMonitor,
|
||||
0x0172,JunkersMonitor,
|
||||
0x0179,JunkersSet,
|
||||
0x017A,JunkersSet,
|
||||
0x017B,JunkersSet,
|
||||
0x017C,JunkersSet,
|
||||
0x01D3,JunkersDhw,fetched
|
||||
0x023A,RC300OutdoorTemp,fetched
|
||||
0x023E,PVSettings,fetched
|
||||
0x0240,RC300Settings,fetched
|
||||
0x0241,RC300Settings,fetched
|
||||
0x0267,RC300Floordry,
|
||||
0x0269,RC300Holiday,fetched
|
||||
0x0291,HPMode,fetched
|
||||
0x0292,HPMode,fetched
|
||||
0x0293,HPMode,fetched
|
||||
0x0294,HPMode,fetched
|
||||
0x029B,RC300Curves,
|
||||
0x029C,RC300Curves,
|
||||
0x029D,RC300Curves,
|
||||
0x029E,RC300Curves,
|
||||
0x029F,RC300Curves,
|
||||
0x02A0,RC300Curves,
|
||||
0x02A1,RC300Curves,
|
||||
0x02A2,RC300Curves,
|
||||
0x02A5,RC300Monitor,
|
||||
0x02A6,RC300Monitor,
|
||||
0x02A7,RC300Monitor,
|
||||
0x02A8,RC300Monitor,
|
||||
0x02A9,RC300Monitor,
|
||||
0x02AA,RC300Monitor,
|
||||
0x02AB,RC300Monitor,
|
||||
0x02AC,RC300Monitor,
|
||||
0x02AF,RC300Summer,
|
||||
0x02B0,RC300Summer,
|
||||
0x02B1,RC300Summer,
|
||||
0x02B2,RC300Summer,
|
||||
0x02B3,RC300Summer,
|
||||
0x02B4,RC300Summer,
|
||||
0x02B5,RC300Summer,
|
||||
0x02B6,RC300Summer,
|
||||
0x02B9,RC300Set,
|
||||
0x02BA,RC300Set,
|
||||
0x02BB,RC300Set,
|
||||
0x02BC,RC300Set,
|
||||
0x02BD,RC300Set,
|
||||
0x02BE,RC300Set,
|
||||
0x02BF,RC300Set,
|
||||
0x02C0,RC300Set,
|
||||
0x02CC,HPPressure,fetched
|
||||
0x02CD,MMPLUSConfigMessage,fetched
|
||||
0x02CE,RC300Set2,
|
||||
0x02D0,RC300Set2,
|
||||
0x02D2,RC300Set2,
|
||||
0x02D6,HPPump2,fetched
|
||||
0x02D7,MMPLUSStatusMessage,
|
||||
0x02E0,UBASetPoints,
|
||||
0x02F5,RC300WWmode,fetched
|
||||
0x02F6,RC300WW2mode,fetched
|
||||
0x0313,MMPLUSConfigMessage_WWC,fetched
|
||||
0x031B,RC300WWtemp,fetched
|
||||
0x031D,RC300WWmode2,
|
||||
0x031E,RC300WWmode2,
|
||||
0x0331,MMPLUSStatusMessage_WWC,
|
||||
0x0358,SM100SystemConfig,fetched
|
||||
0x035A,SM100CircuitConfig,fetched
|
||||
0x035C,SM100HeatAssist,fetched
|
||||
0x035D,SM100Circuit2Config,fetched
|
||||
0x035F,SM100Config1,fetched
|
||||
0x0361,SM100Differential,fetched
|
||||
0x0362,SM100Monitor,
|
||||
0x0363,SM100Monitor2,
|
||||
0x0364,SM100Status,
|
||||
0x0366,SM100Config,
|
||||
0x036A,SM100Status2,
|
||||
0x0380,SM100CollectorConfig,fetched
|
||||
0x038E,SM100Energy,fetched
|
||||
0x0391,SM100Time,fetched
|
||||
0x043F,CRHolidays,fetched
|
||||
0x0467,HPSet,
|
||||
0x0468,HPSet,
|
||||
0x0469,HPSet,
|
||||
0x046A,HPSet,
|
||||
0x0471,RC300Summer2,
|
||||
0x0472,RC300Summer2,
|
||||
0x0473,RC300Summer2,
|
||||
0x0474,RC300Summer2,
|
||||
0x0475,RC300Summer2,
|
||||
0x0476,RC300Summer2,
|
||||
0x0477,RC300Summer2,
|
||||
0x0478,RC300Summer2,
|
||||
0x047B,HP2,
|
||||
0x0484,HPSilentMode,fetched
|
||||
0x0485,HpCooling,fetched
|
||||
0x0486,HpInConfig,fetched
|
||||
0x0488,HPValve,fetched
|
||||
0x048A,HpPool,fetched
|
||||
0x048B,HPPumps,fetched
|
||||
0x048D,HpPower,fetched
|
||||
0x048F,HpTemperatures,
|
||||
0x0491,HPAdditionalHeater,fetched
|
||||
0x0492,HpHeaterConfig,fetched
|
||||
0x0494,UBAEnergySupplied,
|
||||
0x0495,UBAInformation,
|
||||
0x0499,HPDhwSettings,fetched
|
||||
0x049C,HPSettings2,fetched
|
||||
0x049D,HPSettings3,fetched
|
||||
0x04A2,HpInput,fetched
|
||||
0x04A5,HPFan,fetched
|
||||
0x04A7,HPPowerLimit,fetched
|
||||
0x04AA,HPPower2,fetched
|
||||
0x04AE,HPEnergy,fetched
|
||||
0x04AF,HPMeters,fetched
|
||||
0x055C,VentilationSet,fetched
|
||||
0x056B,VentilationMode,fetched
|
||||
0x0583,VentilationMonitor,
|
||||
0x0585,Blowerspeed,
|
||||
0x0587,Bypass,
|
||||
0x05BA,HpPoolStatus,fetched
|
||||
0x05D9,Airquality,
|
||||
0x0772,HIUSettings,
|
||||
0x0779,HIUMonitor,
|
||||
0x07A5,SM100wwCirc,fetched
|
||||
0x07A6,SM100wwParam,fetched
|
||||
0x07AA,SM100wwStatus,
|
||||
0x07AB,SM100wwCommand,
|
||||
0x07AC,SM100wwParam1,
|
||||
0x07AD,SM100ValveStatus,
|
||||
0x07AE,SM100wwKeepWarm,fetched
|
||||
0x07D6,SM100wwTemperature,
|
||||
0x07E0,SM100wwStatus2,fetched
|
||||
0x0935,EM100SetMessage,fetched
|
||||
0x0936,EM100OutMessage,
|
||||
0x0937,EM100TempMessage,
|
||||
0x0938,EM100InputMessage,
|
||||
0x0939,EM100MonitorMessage,
|
||||
0x093A,EM100ConfigMessage,
|
||||
0x0998,HPSettings,fetched
|
||||
0x0999,HPFunctionTest,fetched
|
||||
0x099A,HPStarts,
|
||||
0x099B,HPFlowTemp,
|
||||
0x099C,HPComp,
|
||||
0x09A0,HPTemperature,
|
||||
|
||||
|
4
interface/.gitattributes
vendored
4
interface/.gitattributes
vendored
@@ -1,4 +0,0 @@
|
||||
/.yarn/** linguist-vendored
|
||||
/.yarn/releases/* binary
|
||||
/.yarn/plugins/**/* binary
|
||||
/.pnp.* binary linguist-generated
|
||||
@@ -4,5 +4,4 @@ dist/
|
||||
src/i18n/*
|
||||
|
||||
.prettierrc
|
||||
.yarn/
|
||||
.typesafe-i18n.json
|
||||
.typesafe-i18n.json
|
||||
|
||||
935
interface/.yarn/releases/yarn-4.7.0.cjs
vendored
935
interface/.yarn/releases/yarn-4.7.0.cjs
vendored
File diff suppressed because one or more lines are too long
@@ -1,3 +0,0 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.7.0.cjs
|
||||
@@ -10,8 +10,7 @@ export default tseslint.config(
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: import.meta.dirname
|
||||
project: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "EMS-ESP",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.3",
|
||||
"description": "EMS-ESP WebUI",
|
||||
"homepage": "https://emsesp.org",
|
||||
"author": "proddy, emsesp.org",
|
||||
@@ -8,59 +8,61 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"build-hosted": "typesafe-i18n --no-watch && vite build --mode hosted",
|
||||
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"yarn:mock-rest\" \"vite preview\"",
|
||||
"mock-rest": "bun --watch ../mock-api/rest_server.ts",
|
||||
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"yarn:mock-rest\" \"vite\"",
|
||||
"build-hosted": "typesafe-i18n && vite build --mode hosted",
|
||||
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"pnpm:mock-rest\" \"vite preview\"",
|
||||
"mock-rest": "bun --watch ../mock-api/restServer.ts",
|
||||
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite\"",
|
||||
"typesafe-i18n": "typesafe-i18n --no-watch",
|
||||
"webUI": "node progmem-generator.js",
|
||||
"format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'",
|
||||
"lint": "eslint . --fix"
|
||||
"lint": "eslint . --fix",
|
||||
"standalone-devcontainer": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite --host\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@alova/adapter-xhr": "2.1.1",
|
||||
"@alova/adapter-xhr": "2.2.1",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^6.4.8",
|
||||
"@mui/material": "^6.4.8",
|
||||
"@table-library/react-table-library": "4.1.12",
|
||||
"alova": "3.2.10",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@mui/icons-material": "^7.3.4",
|
||||
"@mui/material": "^7.3.4",
|
||||
"@table-library/react-table-library": "4.1.15",
|
||||
"alova": "3.3.4",
|
||||
"async-validator": "^4.2.5",
|
||||
"formidable": "^3.5.4",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"preact": "^10.26.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"magic-string": "^0.30.21",
|
||||
"mime-types": "^3.0.1",
|
||||
"preact": "^10.27.2",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router": "^7.4.0",
|
||||
"react-router": "^7.9.4",
|
||||
"react-toastify": "^11.0.5",
|
||||
"typesafe-i18n": "^5.26.2",
|
||||
"typescript": "^5.8.2"
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.10",
|
||||
"@eslint/js": "^9.23.0",
|
||||
"@babel/core": "^7.28.5",
|
||||
"@eslint/js": "^9.38.0",
|
||||
"@preact/compat": "^18.3.1",
|
||||
"@preact/preset-vite": "^2.10.1",
|
||||
"@preact/preset-vite": "^2.10.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@types/formidable": "^3",
|
||||
"@types/node": "^22.13.11",
|
||||
"@types/react": "^19.0.12",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"formidable": "^3.5.2",
|
||||
"prettier": "^3.5.3",
|
||||
"rollup-plugin-visualizer": "^5.14.0",
|
||||
"terser": "^5.39.0",
|
||||
"typescript-eslint": "8.27.0",
|
||||
"vite": "^6.2.2",
|
||||
"@types/node": "^24.9.1",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"concurrently": "^9.2.1",
|
||||
"eslint": "^9.38.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prettier": "^3.6.2",
|
||||
"rollup-plugin-visualizer": "^6.0.5",
|
||||
"terser": "^5.44.0",
|
||||
"typescript-eslint": "^8.46.2",
|
||||
"vite": "^7.1.12",
|
||||
"vite-plugin-imagemin": "^0.6.1",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"packageManager": "yarn@4.7.0"
|
||||
"packageManager": "pnpm@10.19.0+sha512.c9fc7236e92adf5c8af42fd5bf1612df99c2ceb62f27047032f4720b33f8eacdde311865e91c411f2774f618d82f320808ecb51718bfa82c060c4ba7c76a32b8"
|
||||
}
|
||||
|
||||
6056
interface/pnpm-lock.yaml
generated
Normal file
6056
interface/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
8
interface/pnpm-workspace.yaml
Normal file
8
interface/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
onlyBuiltDependencies:
|
||||
- cwebp-bin
|
||||
- esbuild
|
||||
- gifsicle
|
||||
- jpegtran-bin
|
||||
- mozjpeg
|
||||
- optipng-bin
|
||||
- pngquant-bin
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
existsSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
statSync,
|
||||
unlinkSync
|
||||
} from 'fs';
|
||||
import mime from 'mime-types';
|
||||
@@ -15,67 +16,79 @@ const INDENT = ' ';
|
||||
const outputPath = '../src/ESP32React/WWWData.h';
|
||||
const sourcePath = './dist';
|
||||
const bytesPerLine = 20;
|
||||
var totalSize = 0;
|
||||
let totalSize = 0;
|
||||
let bundleStats = {
|
||||
js: { count: 0, uncompressed: 0, compressed: 0 },
|
||||
css: { count: 0, uncompressed: 0, compressed: 0 },
|
||||
html: { count: 0, uncompressed: 0, compressed: 0 },
|
||||
svg: { count: 0, uncompressed: 0, compressed: 0 },
|
||||
other: { count: 0, uncompressed: 0, compressed: 0 }
|
||||
};
|
||||
|
||||
const generateWWWClass = () =>
|
||||
`typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
|
||||
// Total size is ${totalSize} bytes
|
||||
const generateWWWClass =
|
||||
() => `typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
|
||||
// Bundle Statistics:
|
||||
// - Total compressed size: ${(totalSize / 1000).toFixed(1)} KB
|
||||
// - Total uncompressed size: ${(Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) / 1000).toFixed(1)} KB
|
||||
// - Compression ratio: ${(((Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) - totalSize) / Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0)) * 100).toFixed(1)}%
|
||||
// - Generated on: ${new Date().toISOString()}
|
||||
|
||||
class WWWData {
|
||||
${indent}public:
|
||||
${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
|
||||
${fileInfo.map((file) => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size}, "${file.hash}");`).join('\n')}
|
||||
${indent.repeat(2)}}
|
||||
${INDENT}public:
|
||||
${INDENT.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
|
||||
${fileInfo.map((f) => `${INDENT.repeat(3)}handler("${f.uri}", "${f.mimeType}", ${f.variable}, ${f.size}, "${f.hash}");`).join('\n')}
|
||||
${INDENT.repeat(2)}}
|
||||
};
|
||||
`;
|
||||
|
||||
function getFilesSync(dir, files = []) {
|
||||
const getFilesSync = (dir, files = []) => {
|
||||
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
|
||||
const entryPath = resolve(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
getFilesSync(entryPath, files);
|
||||
} else {
|
||||
files.push(entryPath);
|
||||
}
|
||||
entry.isDirectory() ? getFilesSync(entryPath, files) : files.push(entryPath);
|
||||
});
|
||||
return files;
|
||||
}
|
||||
};
|
||||
|
||||
function cleanAndOpen(path) {
|
||||
if (existsSync(path)) {
|
||||
unlinkSync(path);
|
||||
}
|
||||
const cleanAndOpen = (path) => {
|
||||
existsSync(path) && unlinkSync(path);
|
||||
return createWriteStream(path, { flags: 'w+' });
|
||||
}
|
||||
};
|
||||
|
||||
const getFileType = (filePath) => {
|
||||
const ext = filePath.split('.').pop().toLowerCase();
|
||||
if (ext === 'js') return 'js';
|
||||
if (ext === 'css') return 'css';
|
||||
if (ext === 'html') return 'html';
|
||||
if (ext === 'svg') return 'svg';
|
||||
return 'other';
|
||||
};
|
||||
|
||||
const writeFile = (relativeFilePath, buffer) => {
|
||||
const variable = 'ESP_REACT_DATA_' + fileInfo.length;
|
||||
const variable = `ESP_REACT_DATA_${fileInfo.length}`;
|
||||
const mimeType = mime.lookup(relativeFilePath);
|
||||
var size = 0;
|
||||
writeStream.write('const uint8_t ' + variable + '[] = {');
|
||||
// const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 });
|
||||
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
||||
const fileType = getFileType(relativeFilePath);
|
||||
let size = 0;
|
||||
writeStream.write(`const uint8_t ${variable}[] = {`);
|
||||
|
||||
// create sha
|
||||
const hashSum = crypto.createHash('sha256');
|
||||
hashSum.update(zipBuffer);
|
||||
const hash = hashSum.digest('hex');
|
||||
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
||||
const hash = crypto.createHash('sha256').update(zipBuffer).digest('hex');
|
||||
|
||||
zipBuffer.forEach((b) => {
|
||||
if (!(size % bytesPerLine)) {
|
||||
writeStream.write('\n');
|
||||
writeStream.write(indent);
|
||||
writeStream.write('\n' + INDENT);
|
||||
}
|
||||
writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).slice(-2) + ',');
|
||||
writeStream.write('0x' + b.toString(16).toUpperCase().padStart(2, '0') + ',');
|
||||
size++;
|
||||
});
|
||||
|
||||
if (size % bytesPerLine) {
|
||||
writeStream.write('\n');
|
||||
}
|
||||
|
||||
size % bytesPerLine && writeStream.write('\n');
|
||||
writeStream.write('};\n\n');
|
||||
|
||||
// Update bundle statistics
|
||||
bundleStats[fileType].count++;
|
||||
bundleStats[fileType].uncompressed += buffer.length;
|
||||
bundleStats[fileType].compressed += zipBuffer.length;
|
||||
|
||||
fileInfo.push({
|
||||
uri: '/' + relativeFilePath.replace(sep, '/'),
|
||||
mimeType,
|
||||
@@ -84,32 +97,52 @@ const writeFile = (relativeFilePath, buffer) => {
|
||||
hash
|
||||
});
|
||||
|
||||
// console.log(relativeFilePath + ' (size ' + size + ' bytes)');
|
||||
totalSize += size;
|
||||
};
|
||||
|
||||
// start
|
||||
console.log('Generating ' + outputPath + ' from ' + sourcePath);
|
||||
const includes = ARDUINO_INCLUDES;
|
||||
const indent = INDENT;
|
||||
console.log(`Generating ${outputPath} from ${sourcePath}`);
|
||||
const fileInfo = [];
|
||||
const writeStream = cleanAndOpen(resolve(outputPath));
|
||||
|
||||
// includes
|
||||
writeStream.write(includes);
|
||||
writeStream.write(ARDUINO_INCLUDES);
|
||||
|
||||
// process static files
|
||||
const buildPath = resolve(sourcePath);
|
||||
for (const filePath of getFilesSync(buildPath)) {
|
||||
const readStream = readFileSync(filePath);
|
||||
const relativeFilePath = relative(buildPath, filePath);
|
||||
writeFile(relativeFilePath, readStream);
|
||||
writeFile(relative(buildPath, filePath), readFileSync(filePath));
|
||||
}
|
||||
|
||||
// add class
|
||||
writeStream.write(generateWWWClass());
|
||||
|
||||
// end
|
||||
writeStream.end();
|
||||
|
||||
console.log('Total size: ' + totalSize / 1000 + ' KB');
|
||||
// Calculate and display bundle statistics
|
||||
const totalUncompressed = Object.values(bundleStats).reduce(
|
||||
(sum, stat) => sum + stat.uncompressed,
|
||||
0
|
||||
);
|
||||
const totalCompressed = Object.values(bundleStats).reduce(
|
||||
(sum, stat) => sum + stat.compressed,
|
||||
0
|
||||
);
|
||||
const compressionRatio = (
|
||||
((totalUncompressed - totalCompressed) / totalUncompressed) *
|
||||
100
|
||||
).toFixed(1);
|
||||
|
||||
console.log('\n📊 Bundle Size Analysis:');
|
||||
console.log('='.repeat(50));
|
||||
console.log(`Total compressed size: ${(totalSize / 1000).toFixed(1)} KB`);
|
||||
console.log(`Total uncompressed size: ${(totalUncompressed / 1000).toFixed(1)} KB`);
|
||||
console.log(`Compression ratio: ${compressionRatio}%`);
|
||||
console.log('\n📁 File Type Breakdown:');
|
||||
Object.entries(bundleStats).forEach(([type, stats]) => {
|
||||
if (stats.count > 0) {
|
||||
const ratio = (
|
||||
((stats.uncompressed - stats.compressed) / stats.uncompressed) *
|
||||
100
|
||||
).toFixed(1);
|
||||
console.log(
|
||||
`${type.toUpperCase().padEnd(4)}: ${stats.count} files, ${(stats.uncompressed / 1000).toFixed(1)} KB → ${(stats.compressed / 1000).toFixed(1)} KB (${ratio}% compression)`
|
||||
);
|
||||
}
|
||||
});
|
||||
console.log('='.repeat(50));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ToastContainer, Zoom } from 'react-toastify';
|
||||
|
||||
import AppRouting from 'AppRouting';
|
||||
@@ -8,7 +8,8 @@ import type { Locales } from 'i18n/i18n-types';
|
||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||
import { detectLocale, navigatorDetector } from 'typesafe-i18n/detectors';
|
||||
|
||||
const availableLocales = [
|
||||
// Memoize available locales to prevent recreation on every render
|
||||
const AVAILABLE_LOCALES = [
|
||||
'de',
|
||||
'en',
|
||||
'it',
|
||||
@@ -20,47 +21,59 @@ const availableLocales = [
|
||||
'sv',
|
||||
'tr',
|
||||
'cz'
|
||||
];
|
||||
] as Locales[];
|
||||
|
||||
const App = () => {
|
||||
const App = memo(() => {
|
||||
const [wasLoaded, setWasLoaded] = useState(false);
|
||||
const [locale, setLocale] = useState<Locales>('en');
|
||||
|
||||
useEffect(() => {
|
||||
// determine locale, take from session if set other default to browser language
|
||||
const browserLocale = detectLocale('en', availableLocales, navigatorDetector);
|
||||
// Memoize locale initialization to prevent unnecessary re-runs
|
||||
const initializeLocale = useCallback(async () => {
|
||||
const browserLocale = detectLocale('en', AVAILABLE_LOCALES, navigatorDetector);
|
||||
const newLocale = (localStorage.getItem('lang') || browserLocale) as Locales;
|
||||
localStorage.setItem('lang', newLocale);
|
||||
setLocale(newLocale);
|
||||
void loadLocaleAsync(newLocale).then(() => setWasLoaded(true));
|
||||
await loadLocaleAsync(newLocale);
|
||||
setWasLoaded(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void initializeLocale();
|
||||
}, [initializeLocale]);
|
||||
|
||||
// Memoize toast container props to prevent recreation
|
||||
const toastContainerProps = useMemo(
|
||||
() => ({
|
||||
position: 'bottom-left' as const,
|
||||
autoClose: 3000,
|
||||
hideProgressBar: false,
|
||||
newestOnTop: false,
|
||||
closeOnClick: true,
|
||||
rtl: false,
|
||||
pauseOnFocusLoss: true,
|
||||
draggable: false,
|
||||
pauseOnHover: false,
|
||||
transition: Zoom,
|
||||
closeButton: false,
|
||||
theme: 'dark' as const,
|
||||
toastStyle: {
|
||||
border: '1px solid #177ac9',
|
||||
width: 'fit-content'
|
||||
}
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
if (!wasLoaded) return null;
|
||||
|
||||
return (
|
||||
<TypesafeI18n locale={locale}>
|
||||
<CustomTheme>
|
||||
<AppRouting />
|
||||
<ToastContainer
|
||||
position="bottom-left"
|
||||
autoClose={3000}
|
||||
hideProgressBar={false}
|
||||
newestOnTop={false}
|
||||
closeOnClick
|
||||
rtl={false}
|
||||
pauseOnFocusLoss
|
||||
draggable={false}
|
||||
pauseOnHover={false}
|
||||
transition={Zoom}
|
||||
closeButton={false}
|
||||
theme="dark"
|
||||
toastStyle={{
|
||||
border: '1px solid #177ac9'
|
||||
}}
|
||||
/>
|
||||
<ToastContainer {...toastContainerProps} />
|
||||
</CustomTheme>
|
||||
</TypesafeI18n>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -37,10 +37,9 @@ const AuthenticatedRouting = () => {
|
||||
<Route path="/dashboard/*" element={<Dashboard />} />
|
||||
<Route path="/devices/*" element={<Devices />} />
|
||||
<Route path="/sensors/*" element={<Sensors />} />
|
||||
<Route path="/status/*" element={<Status />} />
|
||||
<Route path="/help/*" element={<Help />} />
|
||||
<Route path="/*" element={<Navigate to="/" />} />
|
||||
|
||||
<Route path="/status/*" element={<Status />} />
|
||||
<Route path="/status/hardwarestatus/*" element={<HardwareStatus />} />
|
||||
<Route path="/status/activity" element={<Activity />} />
|
||||
<Route path="/status/log" element={<SystemLog />} />
|
||||
@@ -68,6 +67,8 @@ const AuthenticatedRouting = () => {
|
||||
<Route path="/customentities" element={<CustomEntities />} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Route path="/*" element={<Navigate to="/" />} />
|
||||
</Routes>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { memo } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { CssBaseline, ThemeProvider, responsiveFontSizes } from '@mui/material';
|
||||
import {
|
||||
CssBaseline,
|
||||
ThemeProvider,
|
||||
responsiveFontSizes,
|
||||
tooltipClasses
|
||||
} from '@mui/material';
|
||||
import { createTheme } from '@mui/material/styles';
|
||||
|
||||
import type { RequiredChildrenProps } from 'utils';
|
||||
|
||||
// Memoize dialog style to prevent recreation
|
||||
export const dialogStyle = {
|
||||
'& .MuiDialog-paper': {
|
||||
borderRadius: '8px',
|
||||
@@ -12,8 +19,9 @@ export const dialogStyle = {
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '1px'
|
||||
}
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Memoize theme creation to prevent recreation
|
||||
const theme = responsiveFontSizes(
|
||||
createTheme({
|
||||
typography: {
|
||||
@@ -30,15 +38,45 @@ const theme = responsiveFontSizes(
|
||||
text: {
|
||||
disabled: '#eee' // white
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MuiListItemText: {
|
||||
styleOverrides: {
|
||||
primary: {
|
||||
fontSize: 14
|
||||
},
|
||||
secondary: {
|
||||
color: '#9e9e9e' // grey[500]
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTooltip: {
|
||||
defaultProps: {
|
||||
placement: 'top',
|
||||
arrow: true
|
||||
},
|
||||
styleOverrides: {
|
||||
tooltip: {
|
||||
padding: '4px 8px',
|
||||
fontSize: 10,
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
backgroundColor: '#4caf50', // MUI success.main default color
|
||||
boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.15)',
|
||||
[`& .${tooltipClasses.arrow}`]: {
|
||||
color: '#4caf50'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const CustomTheme: FC<RequiredChildrenProps> = ({ children }) => (
|
||||
const CustomTheme: FC<RequiredChildrenProps> = memo(({ children }) => (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
));
|
||||
|
||||
export default CustomTheme;
|
||||
|
||||
@@ -98,7 +98,7 @@ const SignIn = () => {
|
||||
|
||||
<Box display="flex" flexDirection="column" alignItems="center">
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
disabled={processing}
|
||||
sx={{
|
||||
width: 240
|
||||
@@ -117,7 +117,7 @@ const SignIn = () => {
|
||||
}}
|
||||
/>
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
disabled={processing}
|
||||
sx={{
|
||||
width: 240
|
||||
|
||||
@@ -70,6 +70,7 @@ export const readDeviceEntities = (id: number) =>
|
||||
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
|
||||
params: { id },
|
||||
responseType: 'arraybuffer',
|
||||
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
|
||||
transform(data) {
|
||||
return (data as DeviceEntity[]).map((de: DeviceEntity) => ({
|
||||
...de,
|
||||
@@ -92,6 +93,7 @@ export const writeDeviceName = (data: { id: number; name: string }) =>
|
||||
// SettingsScheduler
|
||||
export const readSchedule = () =>
|
||||
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
|
||||
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
|
||||
transform(data) {
|
||||
return (data as Schedule).schedule.map((si: ScheduleItem) => ({
|
||||
...si,
|
||||
@@ -129,6 +131,7 @@ export const writeModules = (data: {
|
||||
// CustomEntities
|
||||
export const readCustomEntities = () =>
|
||||
alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
|
||||
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
|
||||
transform(data) {
|
||||
return (data as Entities).entities.map((ei: EntityItem) => ({
|
||||
...ei,
|
||||
@@ -143,7 +146,8 @@ export const readCustomEntities = () =>
|
||||
o_name: ei.name,
|
||||
o_writeable: ei.writeable,
|
||||
o_value: ei.value,
|
||||
o_deleted: ei.deleted
|
||||
o_deleted: ei.deleted,
|
||||
o_hide: ei.hide
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ export const getDevVersion = () =>
|
||||
cacheFor: 60 * 10 * 1000,
|
||||
transform(response: { data: { name: string; published_at: string } }) {
|
||||
return {
|
||||
name: response.data.name.split(/\s+/).splice(-1)[0].substring(1),
|
||||
name: response.data.name.split(/\s+/).splice(-1)[0]?.substring(1) || '',
|
||||
published_at: response.data.published_at
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,40 +1,37 @@
|
||||
let decoder;
|
||||
// @ts-nocheck - Optimized MessagePack unpacking library for EMS-ESP32
|
||||
let decoder,
|
||||
src,
|
||||
srcEnd,
|
||||
position = 0,
|
||||
strings = [],
|
||||
stringPosition = 0,
|
||||
currentUnpackr = {},
|
||||
currentStructures,
|
||||
srcString,
|
||||
srcStringStart = 0,
|
||||
srcStringEnd = 0,
|
||||
bundledStrings,
|
||||
referenceMap,
|
||||
dataView;
|
||||
const EMPTY_ARRAY = [],
|
||||
currentExtensions = [];
|
||||
const defaultOptions = { useRecords: false, mapsAsObjects: true };
|
||||
try {
|
||||
decoder = new TextDecoder();
|
||||
} catch (error) {}
|
||||
let src;
|
||||
let srcEnd;
|
||||
let position = 0;
|
||||
const EMPTY_ARRAY = [];
|
||||
let strings = EMPTY_ARRAY;
|
||||
let stringPosition = 0;
|
||||
let currentUnpackr = {};
|
||||
let currentStructures;
|
||||
let srcString;
|
||||
let srcStringStart = 0;
|
||||
let srcStringEnd = 0;
|
||||
let bundledStrings;
|
||||
let referenceMap;
|
||||
const currentExtensions = [];
|
||||
let dataView;
|
||||
const defaultOptions = {
|
||||
useRecords: false,
|
||||
mapsAsObjects: true
|
||||
};
|
||||
export class C1Type {}
|
||||
export const C1 = new C1Type();
|
||||
class C1Type {}
|
||||
const C1 = new C1Type();
|
||||
C1.name = 'MessagePack 0xC1';
|
||||
let sequentialMode = false;
|
||||
let inlineObjectReadThreshold = 2;
|
||||
let readStruct, onLoadedStructures, onSaveState;
|
||||
// no-eval build
|
||||
let sequentialMode = false,
|
||||
inlineObjectReadThreshold = 2,
|
||||
readStruct,
|
||||
onLoadedStructures,
|
||||
onSaveState;
|
||||
try {
|
||||
new Function('');
|
||||
} catch (error) {
|
||||
// if eval variants are not supported, do not create inline object readers ever
|
||||
inlineObjectReadThreshold = Infinity;
|
||||
}
|
||||
|
||||
export class Unpackr {
|
||||
constructor(options) {
|
||||
if (options) {
|
||||
@@ -50,19 +47,15 @@ export class Unpackr {
|
||||
if (options.structures)
|
||||
options.structures.sharedLength = options.structures.length;
|
||||
else if (options.getStructures) {
|
||||
(options.structures = []).uninitialized = true; // this is what we use to denote an uninitialized structures
|
||||
(options.structures = []).uninitialized = true;
|
||||
options.structures.sharedLength = 0;
|
||||
}
|
||||
if (options.int64AsNumber) {
|
||||
options.int64AsType = 'number';
|
||||
}
|
||||
if (options.int64AsNumber) options.int64AsType = 'number';
|
||||
}
|
||||
Object.assign(this, options);
|
||||
}
|
||||
|
||||
unpack(source, options?: any) {
|
||||
if (src) {
|
||||
// re-entrant execution, save the state and restore it after we do this unpack
|
||||
return saveState(() => {
|
||||
clearSource();
|
||||
return this
|
||||
@@ -86,9 +79,6 @@ export class Unpackr {
|
||||
strings = EMPTY_ARRAY;
|
||||
bundledStrings = null;
|
||||
src = source;
|
||||
// this provides cached access to the data view for a buffer if it is getting reused, which is a recommend
|
||||
// technique for getting data from a database where it can be copied into an existing buffer instead of creating
|
||||
// new ones
|
||||
try {
|
||||
dataView =
|
||||
source.dataView ||
|
||||
@@ -191,10 +181,10 @@ export class Unpackr {
|
||||
return this.unpack(source, end);
|
||||
}
|
||||
}
|
||||
export function getPosition() {
|
||||
function getPosition() {
|
||||
return position;
|
||||
}
|
||||
export function checkedRead(options: any) {
|
||||
function checkedRead(options: any) {
|
||||
try {
|
||||
if (!currentUnpackr.trusted && !sequentialMode) {
|
||||
const sharedLength = currentStructures.sharedLength || 0;
|
||||
@@ -264,7 +254,7 @@ function restoreStructures() {
|
||||
currentStructures.restoreStructures = null;
|
||||
}
|
||||
|
||||
export function read() {
|
||||
function read() {
|
||||
let token = src[position++];
|
||||
if (token < 0xa0) {
|
||||
if (token < 0x80) {
|
||||
@@ -589,7 +579,7 @@ const createSecondByteReader = (firstId, read0) =>
|
||||
return structure.read();
|
||||
};
|
||||
|
||||
export function loadStructures() {
|
||||
function loadStructures() {
|
||||
const loadedStructures = saveState(() => {
|
||||
// save the state in case getStructures modifies our buffer
|
||||
src = null;
|
||||
@@ -605,9 +595,8 @@ var readFixedString = readStringJS;
|
||||
var readString8 = readStringJS;
|
||||
var readString16 = readStringJS;
|
||||
var readString32 = readStringJS;
|
||||
export let isNativeAccelerationEnabled = false;
|
||||
|
||||
export function setExtractor(extractStrings) {
|
||||
let isNativeAccelerationEnabled = false;
|
||||
function setExtractor(extractStrings) {
|
||||
isNativeAccelerationEnabled = true;
|
||||
readFixedString = readString(1);
|
||||
readString8 = readString(2);
|
||||
@@ -701,7 +690,7 @@ function readStringJS(length) {
|
||||
|
||||
return result;
|
||||
}
|
||||
export function readString(source, start, length) {
|
||||
function readString(source, start, length) {
|
||||
const existingSrc = src;
|
||||
src = source;
|
||||
position = start;
|
||||
@@ -1065,7 +1054,7 @@ currentExtensions[0x70] = (data) => {
|
||||
|
||||
currentExtensions[0x73] = () => new Set(read());
|
||||
|
||||
export const typedArrays = [
|
||||
const typedArrays = [
|
||||
'Int8',
|
||||
'Uint8',
|
||||
'Uint8Clamped',
|
||||
@@ -1177,44 +1166,20 @@ function saveState(callback) {
|
||||
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
|
||||
return value;
|
||||
}
|
||||
export function clearSource() {
|
||||
function clearSource() {
|
||||
src = null;
|
||||
referenceMap = null;
|
||||
currentStructures = null;
|
||||
}
|
||||
|
||||
export function addExtension(extension) {
|
||||
function addExtension(extension) {
|
||||
if (extension.unpack) currentExtensions[extension.type] = extension.unpack;
|
||||
else currentExtensions[extension.type] = extension;
|
||||
}
|
||||
|
||||
export const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding
|
||||
const mult10 = new Array(147);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103));
|
||||
}
|
||||
export const Decoder = Unpackr;
|
||||
var defaultUnpackr = new Unpackr({ useRecords: false });
|
||||
const defaultUnpackr = new Unpackr({ useRecords: false });
|
||||
export const unpack = defaultUnpackr.unpack;
|
||||
export const unpackMultiple = defaultUnpackr.unpackMultiple;
|
||||
export const decode = defaultUnpackr.unpack;
|
||||
export const FLOAT32_OPTIONS = {
|
||||
NEVER: 0,
|
||||
ALWAYS: 1,
|
||||
DECIMAL_ROUND: 3,
|
||||
DECIMAL_FIT: 4
|
||||
};
|
||||
const f32Array = new Float32Array(1);
|
||||
const u8Array = new Uint8Array(f32Array.buffer, 0, 4);
|
||||
export function roundFloat32(float32Number) {
|
||||
f32Array[0] = float32Number;
|
||||
const multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)];
|
||||
return (
|
||||
((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) /
|
||||
multiplier
|
||||
);
|
||||
}
|
||||
export function setReadStruct(updatedReadStruct, loadedStructs, saveState) {
|
||||
readStruct = updatedReadStruct;
|
||||
onLoadedStructures = loadedStructs;
|
||||
onSaveState = saveState;
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ const CustomEntities = () => {
|
||||
ei.factor !== ei.o_factor ||
|
||||
ei.value_type !== ei.o_value_type ||
|
||||
ei.writeable !== ei.o_writeable ||
|
||||
ei.hide !== ei.o_hide ||
|
||||
ei.deleted !== ei.o_deleted ||
|
||||
(ei.value || '') !== (ei.o_value || '')
|
||||
);
|
||||
@@ -136,8 +137,8 @@ const CustomEntities = () => {
|
||||
const saveEntities = async () => {
|
||||
await writeEntities({
|
||||
entities: entities
|
||||
.filter((ei) => !ei.deleted)
|
||||
.map((condensed_ei) => ({
|
||||
.filter((ei: EntityItem) => !ei.deleted)
|
||||
.map((condensed_ei: EntityItem) => ({
|
||||
id: condensed_ei.id,
|
||||
ram: condensed_ei.ram,
|
||||
name: condensed_ei.name,
|
||||
@@ -147,6 +148,7 @@ const CustomEntities = () => {
|
||||
factor: condensed_ei.factor,
|
||||
uom: condensed_ei.uom,
|
||||
writeable: condensed_ei.writeable,
|
||||
hide: condensed_ei.hide,
|
||||
value_type: condensed_ei.value_type,
|
||||
value: condensed_ei.value
|
||||
}))
|
||||
@@ -209,6 +211,7 @@ const CustomEntities = () => {
|
||||
value_type: item.value_type,
|
||||
writeable: item.writeable,
|
||||
deleted: false,
|
||||
hide: item.hide,
|
||||
value: item.value
|
||||
});
|
||||
setDialogOpen(true);
|
||||
@@ -228,6 +231,7 @@ const CustomEntities = () => {
|
||||
value_type: 0,
|
||||
writeable: false,
|
||||
deleted: false,
|
||||
hide: false,
|
||||
value: ''
|
||||
});
|
||||
setDialogOpen(true);
|
||||
@@ -248,15 +252,17 @@ const CustomEntities = () => {
|
||||
|
||||
const renderEntity = () => {
|
||||
if (!entities) {
|
||||
return <FormLoader onRetry={fetchEntities} errorMessage={error?.message} />;
|
||||
return (
|
||||
<FormLoader onRetry={fetchEntities} errorMessage={error?.message || ''} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
data={{
|
||||
nodes: entities
|
||||
.filter((ei) => !ei.deleted)
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.filter((ei: EntityItem) => !ei.deleted)
|
||||
.sort((a: EntityItem, b: EntityItem) => a.name.localeCompare(b.name))
|
||||
}}
|
||||
theme={entity_theme}
|
||||
layout={{ custom: true }}
|
||||
@@ -321,7 +327,7 @@ const CustomEntities = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<Box mt={1} display="flex" flexWrap="wrap">
|
||||
<Box mt={2} display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1}>
|
||||
{numChanges > 0 && (
|
||||
<ButtonRow>
|
||||
|
||||
@@ -2,7 +2,11 @@ import { useEffect, useState } from 'react';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||
import DoneIcon from '@mui/icons-material/Done';
|
||||
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
|
||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
|
||||
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
|
||||
import {
|
||||
Box,
|
||||
@@ -12,7 +16,7 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField
|
||||
@@ -70,7 +74,10 @@ const CustomEntitiesDialog = ({
|
||||
}
|
||||
}, [open, selectedItem]);
|
||||
|
||||
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
|
||||
const handleClose = (
|
||||
_event: React.SyntheticEvent,
|
||||
reason: 'backdropClick' | 'escapeKeyDown'
|
||||
) => {
|
||||
if (reason !== 'backdropClick') {
|
||||
onClose();
|
||||
}
|
||||
@@ -119,7 +126,7 @@ const CustomEntitiesDialog = ({
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid size={12}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="name"
|
||||
label={LL.NAME(0)}
|
||||
value={editItem.name}
|
||||
@@ -128,6 +135,20 @@ const CustomEntitiesDialog = ({
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid mt={3}>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
icon={<InsertCommentOutlinedIcon htmlColor="white" />}
|
||||
checkedIcon={<CommentsDisabledOutlinedIcon color="primary" />}
|
||||
checked={editItem.hide}
|
||||
onChange={updateFormValue}
|
||||
name="hide"
|
||||
/>
|
||||
}
|
||||
label="API/MQTT"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="ram"
|
||||
@@ -177,10 +198,12 @@ const CustomEntitiesDialog = ({
|
||||
)}
|
||||
{editItem.ram === 0 && (
|
||||
<>
|
||||
<Grid mt={3} size={9}>
|
||||
<Grid mt={3}>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
icon={<EditOffOutlinedIcon color="primary" />}
|
||||
checkedIcon={<EditOutlinedIcon htmlColor="white" />}
|
||||
checked={editItem.writeable}
|
||||
onChange={updateFormValue}
|
||||
name="writeable"
|
||||
@@ -191,7 +214,7 @@ const CustomEntitiesDialog = ({
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="device_id"
|
||||
label={LL.ID_OF(LL.DEVICE())}
|
||||
margin="normal"
|
||||
@@ -211,7 +234,7 @@ const CustomEntitiesDialog = ({
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="type_id"
|
||||
label={LL.ID_OF(LL.TYPE(1))}
|
||||
margin="normal"
|
||||
@@ -231,7 +254,7 @@ const CustomEntitiesDialog = ({
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="offset"
|
||||
label={LL.OFFSET()}
|
||||
margin="normal"
|
||||
@@ -323,7 +346,7 @@ const CustomEntitiesDialog = ({
|
||||
editItem.device_id !== '0' && (
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="factor"
|
||||
label={LL.BYTES()}
|
||||
value={numberValue(editItem.factor as number)}
|
||||
@@ -341,7 +364,7 @@ const CustomEntitiesDialog = ({
|
||||
{editItem.value_type === DeviceValueType.BOOL && (
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="factor"
|
||||
label={LL.BITMASK()}
|
||||
value={editItem.factor as string}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
Link,
|
||||
MenuItem,
|
||||
@@ -125,13 +125,22 @@ const Customizations = () => {
|
||||
|
||||
const setOriginalSettings = (data: DeviceEntity[]) => {
|
||||
setDeviceEntities(
|
||||
data.map((de) => ({
|
||||
...de,
|
||||
o_m: de.m,
|
||||
o_cn: de.cn,
|
||||
o_mi: de.mi,
|
||||
o_ma: de.ma
|
||||
}))
|
||||
data.map((de) => {
|
||||
const result: DeviceEntity = {
|
||||
...de,
|
||||
o_m: de.m
|
||||
};
|
||||
if (de.cn !== undefined) {
|
||||
result.o_cn = de.cn;
|
||||
}
|
||||
if (de.mi !== undefined) {
|
||||
result.o_mi = de.mi;
|
||||
}
|
||||
if (de.ma !== undefined) {
|
||||
result.o_ma = de.ma;
|
||||
}
|
||||
return result;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
@@ -244,8 +253,11 @@ const Customizations = () => {
|
||||
setSelectedDevice(-1);
|
||||
setSelectedDeviceTypeNameURL('');
|
||||
} else {
|
||||
setSelectedDeviceTypeNameURL(devices.devices[index].url || '');
|
||||
setSelectedDeviceName(devices.devices[index].n);
|
||||
const device = devices.devices[index];
|
||||
if (device) {
|
||||
setSelectedDeviceTypeNameURL(device.url || '');
|
||||
setSelectedDeviceName(device.n);
|
||||
}
|
||||
setNumChanges(0);
|
||||
setRestartNeeded(false);
|
||||
}
|
||||
@@ -306,7 +318,7 @@ const Customizations = () => {
|
||||
|
||||
const filter_entity = (de: DeviceEntity) =>
|
||||
(de.m & selectedFilters || !selectedFilters) &&
|
||||
formatName(de, true).includes(search);
|
||||
formatName(de, true).toLowerCase().includes(search.toLowerCase());
|
||||
|
||||
const maskDisabled = (set: boolean) => {
|
||||
setDeviceEntities(
|
||||
@@ -396,14 +408,20 @@ const Customizations = () => {
|
||||
await sendCustomizationEntities({
|
||||
id: selectedDevice,
|
||||
entity_ids: masked_entities
|
||||
}).catch((error: Error) => {
|
||||
if (error.message === 'Reboot required') {
|
||||
setRestartNeeded(true);
|
||||
} else {
|
||||
toast.error(error.message);
|
||||
}
|
||||
});
|
||||
setOriginalSettings(deviceEntities);
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(LL.CUSTOMIZATIONS_SAVED());
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (error.message === 'Reboot required') {
|
||||
setRestartNeeded(true);
|
||||
} else {
|
||||
toast.error(error.message);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setOriginalSettings(deviceEntities);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -545,7 +563,7 @@ const Customizations = () => {
|
||||
size="small"
|
||||
color="secondary"
|
||||
value={getMaskString(selectedFilters)}
|
||||
onChange={(event, mask: string[]) => {
|
||||
onChange={(_, mask: string[]) => {
|
||||
setSelectedFilters(getMaskNumber(mask));
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
@@ -54,7 +54,10 @@ const CustomizationsDialog = ({
|
||||
}
|
||||
}, [open, selectedItem]);
|
||||
|
||||
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
|
||||
const handleClose = (
|
||||
_event: React.SyntheticEvent,
|
||||
reason: 'backdropClick' | 'escapeKeyDown'
|
||||
) => {
|
||||
if (reason !== 'backdropClick') {
|
||||
onClose();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { IconContext } from 'react-icons/lib';
|
||||
import { Link } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
IconButton,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
@@ -44,7 +45,7 @@ import {
|
||||
} from './types';
|
||||
import { deviceValueItemValidation } from './validators';
|
||||
|
||||
const Dashboard = () => {
|
||||
const Dashboard = memo(() => {
|
||||
const { LL } = useI18nContext();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
@@ -76,35 +77,40 @@ const Dashboard = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
||||
if (!selectedDashboardItem) {
|
||||
return;
|
||||
}
|
||||
const id = selectedDashboardItem.parentNode.id; // this is the parent ID
|
||||
await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
|
||||
.then(() => {
|
||||
toast.success(LL.WRITE_CMD_SENT());
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setDeviceValueDialogOpen(false);
|
||||
setSelectedDashboardItem(undefined);
|
||||
});
|
||||
};
|
||||
const deviceValueDialogSave = useCallback(
|
||||
async (devicevalue: DeviceValue) => {
|
||||
if (!selectedDashboardItem) {
|
||||
return;
|
||||
}
|
||||
const id = selectedDashboardItem.parentNode.id; // this is the parent ID
|
||||
await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
|
||||
.then(() => {
|
||||
toast.success(LL.WRITE_CMD_SENT());
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setDeviceValueDialogOpen(false);
|
||||
setSelectedDashboardItem(undefined);
|
||||
});
|
||||
},
|
||||
[selectedDashboardItem, sendDeviceValue, LL]
|
||||
);
|
||||
|
||||
const dashboard_theme = useTheme({
|
||||
Table: `
|
||||
const dashboard_theme = useMemo(
|
||||
() =>
|
||||
useTheme({
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: minmax(80px, auto) 120px 32px;
|
||||
`,
|
||||
BaseRow: `
|
||||
BaseRow: `
|
||||
font-size: 14px;
|
||||
.td {
|
||||
height: 28px;
|
||||
}
|
||||
`,
|
||||
Row: `
|
||||
Row: `
|
||||
cursor: pointer;
|
||||
background-color: #1e1e1e;
|
||||
&:nth-of-type(odd) .td {
|
||||
@@ -114,7 +120,7 @@ const Dashboard = () => {
|
||||
background-color: #177ac9;
|
||||
},
|
||||
`,
|
||||
BaseCell: `
|
||||
BaseCell: `
|
||||
&:nth-of-type(2) {
|
||||
text-align: right;
|
||||
}
|
||||
@@ -122,12 +128,14 @@ const Dashboard = () => {
|
||||
text-align: right;
|
||||
}
|
||||
`
|
||||
});
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const tree = useTree(
|
||||
{ nodes: data.nodes },
|
||||
{
|
||||
onChange: undefined // not used but needed
|
||||
onChange: () => {} // not used but needed
|
||||
},
|
||||
{
|
||||
treeIcon: {
|
||||
@@ -156,65 +164,82 @@ const Dashboard = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const nodeIds = useMemo(
|
||||
() => data.nodes.map((item: DashboardItem) => item.id),
|
||||
[data.nodes]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
showAll
|
||||
? tree.fns.onAddAll(data.nodes.map((item: DashboardItem) => item.id)) // expand tree
|
||||
? tree.fns.onAddAll(nodeIds) // expand tree
|
||||
: tree.fns.onRemoveAll(); // collapse tree
|
||||
}, [parentNodes]);
|
||||
|
||||
const showType = (n?: string, t?: number) => {
|
||||
// if we have a name show it
|
||||
if (n) {
|
||||
return n;
|
||||
}
|
||||
if (t) {
|
||||
// otherwise pick translation based on type
|
||||
switch (t) {
|
||||
case DeviceType.CUSTOM:
|
||||
return LL.CUSTOM_ENTITIES(0);
|
||||
case DeviceType.ANALOGSENSOR:
|
||||
return LL.ANALOG_SENSORS();
|
||||
case DeviceType.TEMPERATURESENSOR:
|
||||
return LL.TEMP_SENSORS();
|
||||
case DeviceType.SCHEDULER:
|
||||
return LL.SCHEDULER();
|
||||
default:
|
||||
break;
|
||||
const showType = useCallback(
|
||||
(n?: string, t?: number) => {
|
||||
// if we have a name show it
|
||||
if (n) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const showName = (di: DashboardItem) => {
|
||||
if (di.id < 100) {
|
||||
// if its a device (parent node) and has entities
|
||||
if (di.nodes?.length) {
|
||||
return (
|
||||
<span style={{ fontWeight: 'bold', fontSize: '14px' }}>
|
||||
<DeviceIcon type_id={di.t ?? 0} />
|
||||
{showType(di.n, di.t)}
|
||||
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</span>
|
||||
</span>
|
||||
);
|
||||
if (t) {
|
||||
// otherwise pick translation based on type
|
||||
switch (t) {
|
||||
case DeviceType.CUSTOM:
|
||||
return LL.CUSTOM_ENTITIES(0);
|
||||
case DeviceType.ANALOGSENSOR:
|
||||
return LL.ANALOG_SENSORS();
|
||||
case DeviceType.TEMPERATURESENSOR:
|
||||
return LL.TEMP_SENSORS();
|
||||
case DeviceType.SCHEDULER:
|
||||
return LL.SCHEDULER();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (di.dv) {
|
||||
return <span>{di.dv.id.slice(2)}</span>;
|
||||
}
|
||||
};
|
||||
return '';
|
||||
},
|
||||
[LL]
|
||||
);
|
||||
|
||||
const hasMask = (id: string, mask: number) =>
|
||||
(parseInt(id.slice(0, 2), 16) & mask) === mask;
|
||||
const showName = useCallback(
|
||||
(di: DashboardItem) => {
|
||||
if (di.id < 100) {
|
||||
// if its a device (parent node) and has entities
|
||||
if (di.nodes?.length) {
|
||||
return (
|
||||
<span style={{ fontSize: '15px' }}>
|
||||
<DeviceIcon type_id={di.t ?? 0} />
|
||||
{showType(di.n, di.t)}
|
||||
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (di.dv) {
|
||||
return <span>{di.dv.id.slice(2)}</span>;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[showType]
|
||||
);
|
||||
|
||||
const editDashboardValue = (di: DashboardItem) => {
|
||||
if (me.admin && di.dv?.c) {
|
||||
setSelectedDashboardItem(di);
|
||||
setDeviceValueDialogOpen(true);
|
||||
}
|
||||
};
|
||||
const hasMask = useCallback(
|
||||
(id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask,
|
||||
[]
|
||||
);
|
||||
|
||||
const editDashboardValue = useCallback(
|
||||
(di: DashboardItem) => {
|
||||
if (me.admin && di.dv?.c) {
|
||||
setSelectedDashboardItem(di);
|
||||
setDeviceValueDialogOpen(true);
|
||||
}
|
||||
},
|
||||
[me.admin]
|
||||
);
|
||||
|
||||
const handleShowAll = (
|
||||
event: React.MouseEvent<HTMLElement>,
|
||||
_event: React.MouseEvent<HTMLElement>,
|
||||
toggle: boolean | null
|
||||
) => {
|
||||
if (toggle !== null) {
|
||||
@@ -223,15 +248,18 @@ const Dashboard = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const hasFavEntities = useMemo(
|
||||
() => data.nodes.filter((item: DashboardItem) => item.id <= 90).length,
|
||||
[data.nodes]
|
||||
);
|
||||
|
||||
const renderContent = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={fetchDashboard} errorMessage={error?.message} />;
|
||||
return (
|
||||
<FormLoader onRetry={fetchDashboard} errorMessage={error?.message || ''} />
|
||||
);
|
||||
}
|
||||
|
||||
const hasFavEntities = data.nodes.filter(
|
||||
(item: DashboardItem) => item.id <= 90
|
||||
).length;
|
||||
|
||||
return (
|
||||
<>
|
||||
{!data.connected && (
|
||||
@@ -257,37 +285,44 @@ const Dashboard = () => {
|
||||
|
||||
{data.nodes.length > 0 && (
|
||||
<>
|
||||
<ToggleButtonGroup
|
||||
color="primary"
|
||||
size="small"
|
||||
value={showAll}
|
||||
exclusive
|
||||
onChange={handleShowAll}
|
||||
>
|
||||
<ButtonTooltip title={LL.ALLVALUES()} arrow>
|
||||
<ToggleButton value={true}>
|
||||
<UnfoldMoreIcon sx={{ fontSize: 18 }} />
|
||||
</ToggleButton>
|
||||
</ButtonTooltip>
|
||||
<ButtonTooltip title={LL.COMPACT()} arrow>
|
||||
<ToggleButton value={false}>
|
||||
<UnfoldLessIcon sx={{ fontSize: 18 }} />
|
||||
</ToggleButton>
|
||||
</ButtonTooltip>
|
||||
</ToggleButtonGroup>
|
||||
<ButtonTooltip title={LL.DASHBOARD_1()} arrow>
|
||||
<HelpOutlineIcon color="primary" sx={{ ml: 1, fontSize: 20 }} />
|
||||
</ButtonTooltip>
|
||||
|
||||
<Box
|
||||
padding={1}
|
||||
justifyContent="center"
|
||||
flexDirection="column"
|
||||
sx={{
|
||||
borderRadius: 1,
|
||||
border: '1px solid grey'
|
||||
}}
|
||||
display="flex"
|
||||
justifyContent="flex-end"
|
||||
flexWrap="nowrap"
|
||||
whiteSpace="nowrap"
|
||||
>
|
||||
<ToggleButtonGroup
|
||||
size="small"
|
||||
color="primary"
|
||||
value={showAll}
|
||||
exclusive
|
||||
onChange={handleShowAll}
|
||||
>
|
||||
<ButtonTooltip title={LL.ALLVALUES()}>
|
||||
<ToggleButton value={true}>
|
||||
<UnfoldMoreIcon sx={{ fontSize: 18 }} />
|
||||
</ToggleButton>
|
||||
</ButtonTooltip>
|
||||
<ButtonTooltip title={LL.COMPACT()}>
|
||||
<ToggleButton value={false}>
|
||||
<UnfoldLessIcon sx={{ fontSize: 18 }} />
|
||||
</ToggleButton>
|
||||
</ButtonTooltip>
|
||||
</ToggleButtonGroup>
|
||||
<Tooltip title={LL.DASHBOARD_1()}>
|
||||
<HelpOutlineIcon
|
||||
sx={{
|
||||
ml: 1,
|
||||
mt: 1,
|
||||
fontSize: 20,
|
||||
verticalAlign: 'middle'
|
||||
}}
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
<Box mt={1} justifyContent="center" flexDirection="column">
|
||||
<IconContext.Provider
|
||||
value={{
|
||||
color: 'lightblue',
|
||||
@@ -375,6 +410,6 @@ const Dashboard = () => {
|
||||
)}
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Dashboard;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useState
|
||||
} from 'react';
|
||||
import { IconContext } from 'react-icons';
|
||||
@@ -31,7 +33,7 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
List,
|
||||
@@ -75,7 +77,7 @@ import { DeviceEntityMask, DeviceType, DeviceValueUOM_s } from './types';
|
||||
import type { Device, DeviceValue } from './types';
|
||||
import { deviceValueItemValidation } from './validators';
|
||||
|
||||
const Devices = () => {
|
||||
const Devices = memo(() => {
|
||||
const { LL } = useI18nContext();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
@@ -141,11 +143,13 @@ const Devices = () => {
|
||||
return left + (right - left < 400 ? 0 : 200);
|
||||
};
|
||||
|
||||
const common_theme = useTheme({
|
||||
BaseRow: `
|
||||
const common_theme = useMemo(
|
||||
() =>
|
||||
useTheme({
|
||||
BaseRow: `
|
||||
font-size: 14px;
|
||||
`,
|
||||
HeaderRow: `
|
||||
HeaderRow: `
|
||||
text-transform: uppercase;
|
||||
background-color: black;
|
||||
color: #90CAF9;
|
||||
@@ -153,7 +157,7 @@ const Devices = () => {
|
||||
border-bottom: 1px solid #565656;
|
||||
}
|
||||
`,
|
||||
Row: `
|
||||
Row: `
|
||||
cursor: pointer;
|
||||
background-color: #1E1E1E;
|
||||
.td {
|
||||
@@ -163,30 +167,47 @@ const Devices = () => {
|
||||
background-color: #177ac9;
|
||||
}
|
||||
`
|
||||
});
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const device_theme = useTheme([
|
||||
common_theme,
|
||||
{
|
||||
Table: `
|
||||
const device_theme = useMemo(
|
||||
() =>
|
||||
useTheme([
|
||||
common_theme,
|
||||
{
|
||||
BaseRow: `
|
||||
font-size: 15px;
|
||||
.td {
|
||||
height: 28px;
|
||||
}
|
||||
`,
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 130px;
|
||||
`,
|
||||
HeaderRow: `
|
||||
HeaderRow: `
|
||||
.th {
|
||||
padding: 8px;
|
||||
`,
|
||||
Row: `
|
||||
font-weight: bold;
|
||||
Row: `
|
||||
&:nth-of-type(odd) .td {
|
||||
background-color: #303030;
|
||||
},
|
||||
&:hover .td {
|
||||
background-color: #177ac9;
|
||||
},
|
||||
`
|
||||
}
|
||||
]);
|
||||
}
|
||||
]),
|
||||
[common_theme]
|
||||
);
|
||||
|
||||
const data_theme = useTheme([
|
||||
common_theme,
|
||||
{
|
||||
Table: `
|
||||
const data_theme = useMemo(
|
||||
() =>
|
||||
useTheme([
|
||||
common_theme,
|
||||
{
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: minmax(200px, auto) minmax(150px, auto) 40px;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
@@ -195,12 +216,12 @@ const Devices = () => {
|
||||
display:none;
|
||||
}
|
||||
`,
|
||||
BaseRow: `
|
||||
BaseRow: `
|
||||
.td {
|
||||
height: 32px;
|
||||
}
|
||||
`,
|
||||
BaseCell: `
|
||||
BaseCell: `
|
||||
&:nth-of-type(1) {
|
||||
border-left: 1px solid #177ac9;
|
||||
},
|
||||
@@ -211,12 +232,12 @@ const Devices = () => {
|
||||
border-right: 1px solid #177ac9;
|
||||
}
|
||||
`,
|
||||
HeaderRow: `
|
||||
HeaderRow: `
|
||||
.th {
|
||||
border-top: 1px solid #565656;
|
||||
}
|
||||
`,
|
||||
Row: `
|
||||
Row: `
|
||||
&:nth-of-type(odd) .td {
|
||||
background-color: #303030;
|
||||
},
|
||||
@@ -224,8 +245,10 @@ const Devices = () => {
|
||||
background-color: #177ac9;
|
||||
}
|
||||
`
|
||||
}
|
||||
]);
|
||||
}
|
||||
]),
|
||||
[common_theme]
|
||||
);
|
||||
|
||||
const getSortIcon = (state: State, sortKey: unknown) => {
|
||||
if (state.sortKey === sortKey && state.reverse) {
|
||||
@@ -324,18 +347,23 @@ const Devices = () => {
|
||||
return sc;
|
||||
};
|
||||
|
||||
const hasMask = (id: string, mask: number) =>
|
||||
(parseInt(id.slice(0, 2), 16) & mask) === mask;
|
||||
const hasMask = useCallback(
|
||||
(id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask,
|
||||
[]
|
||||
);
|
||||
|
||||
const handleDownloadCsv = () => {
|
||||
const deviceIndex = coreData.devices.findIndex(
|
||||
(d) => d.id === device_select.state.id
|
||||
(d: Device) => d.id === device_select.state.id
|
||||
);
|
||||
if (deviceIndex === -1) {
|
||||
return;
|
||||
}
|
||||
const filename =
|
||||
coreData.devices[deviceIndex].tn + '_' + coreData.devices[deviceIndex].n;
|
||||
const selectedDevice = coreData.devices[deviceIndex];
|
||||
if (!selectedDevice) {
|
||||
return;
|
||||
}
|
||||
const filename = selectedDevice.tn + '_' + selectedDevice.n;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@@ -350,7 +378,7 @@ const Devices = () => {
|
||||
{
|
||||
accessor: (dv: DeviceValue) =>
|
||||
dv.u !== undefined && DeviceValueUOM_s[dv.u]
|
||||
? DeviceValueUOM_s[dv.u].replace(/[^a-zA-Z0-9]/g, '')
|
||||
? DeviceValueUOM_s[dv.u]?.replace(/[^a-zA-Z0-9]/g, '')
|
||||
: '',
|
||||
name: 'UoM'
|
||||
},
|
||||
@@ -373,7 +401,9 @@ const Devices = () => {
|
||||
];
|
||||
|
||||
const data = onlyFav
|
||||
? deviceData.nodes.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE))
|
||||
? deviceData.nodes.filter((dv: DeviceValue) =>
|
||||
hasMask(dv.id, DeviceEntityMask.DV_FAVORITE)
|
||||
)
|
||||
: deviceData.nodes;
|
||||
|
||||
const csvData = data.reduce(
|
||||
@@ -433,10 +463,14 @@ const Devices = () => {
|
||||
const renderDeviceDetails = () => {
|
||||
if (showDeviceInfo) {
|
||||
const deviceIndex = coreData.devices.findIndex(
|
||||
(d) => d.id === device_select.state.id
|
||||
(d: Device) => d.id === device_select.state.id
|
||||
);
|
||||
if (deviceIndex === -1) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
const deviceDetails = coreData.devices[deviceIndex];
|
||||
if (!deviceDetails) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -449,47 +483,35 @@ const Devices = () => {
|
||||
<DialogContent dividers>
|
||||
<List dense={true}>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={LL.TYPE(0)}
|
||||
secondary={coreData.devices[deviceIndex].tn}
|
||||
/>
|
||||
<ListItemText primary={LL.TYPE(0)} secondary={deviceDetails.tn} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={LL.NAME(0)}
|
||||
secondary={coreData.devices[deviceIndex].n}
|
||||
/>
|
||||
<ListItemText primary={LL.NAME(0)} secondary={deviceDetails.n} />
|
||||
</ListItem>
|
||||
{coreData.devices[deviceIndex].t !== DeviceType.CUSTOM && (
|
||||
{deviceDetails.t !== DeviceType.CUSTOM && (
|
||||
<>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={LL.BRAND()}
|
||||
secondary={coreData.devices[deviceIndex].b}
|
||||
/>
|
||||
<ListItemText primary={LL.BRAND()} secondary={deviceDetails.b} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={LL.ID_OF(LL.DEVICE())}
|
||||
secondary={
|
||||
'0x' +
|
||||
(
|
||||
'00' +
|
||||
coreData.devices[deviceIndex].d.toString(16).toUpperCase()
|
||||
).slice(-2)
|
||||
('00' + deviceDetails.d.toString(16).toUpperCase()).slice(-2)
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={LL.ID_OF(LL.PRODUCT())}
|
||||
secondary={coreData.devices[deviceIndex].p}
|
||||
secondary={deviceDetails.p}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={LL.VERSION()}
|
||||
secondary={coreData.devices[deviceIndex].v}
|
||||
secondary={deviceDetails.v}
|
||||
/>
|
||||
</ListItem>
|
||||
</>
|
||||
@@ -508,59 +530,62 @@ const Devices = () => {
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderCoreData = () => (
|
||||
<>
|
||||
<IconContext.Provider
|
||||
value={{
|
||||
color: 'lightblue',
|
||||
size: '18',
|
||||
style: { verticalAlign: 'middle' }
|
||||
}}
|
||||
>
|
||||
{!coreData.connected && (
|
||||
<MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />
|
||||
)}
|
||||
<Box justifyContent="center" flexDirection="column">
|
||||
<IconContext.Provider
|
||||
value={{
|
||||
color: 'lightblue',
|
||||
size: '18',
|
||||
style: { verticalAlign: 'middle' }
|
||||
}}
|
||||
>
|
||||
{!coreData.connected && (
|
||||
<MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />
|
||||
)}
|
||||
|
||||
{coreData.connected && (
|
||||
<Table
|
||||
data={{ nodes: coreData.devices }}
|
||||
select={device_select}
|
||||
theme={device_theme}
|
||||
layout={{ custom: true }}
|
||||
>
|
||||
{(tableList: Device[]) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
<Body>
|
||||
{tableList.length === 0 && (
|
||||
<CircularProgress sx={{ margin: 1 }} size={18} />
|
||||
)}
|
||||
{tableList.map((device: Device) => (
|
||||
<Row key={device.id} item={device}>
|
||||
<Cell>
|
||||
<DeviceIcon type_id={device.t} />
|
||||
|
||||
{device.n}
|
||||
<span style={{ color: 'lightblue' }}>
|
||||
({device.e})
|
||||
</span>
|
||||
</Cell>
|
||||
<Cell stiff>{device.tn}</Cell>
|
||||
</Row>
|
||||
))}
|
||||
</Body>
|
||||
</>
|
||||
)}
|
||||
</Table>
|
||||
)}
|
||||
</IconContext.Provider>
|
||||
{coreData.connected && (
|
||||
<Table
|
||||
data={{ nodes: coreData.devices }}
|
||||
select={device_select}
|
||||
theme={device_theme}
|
||||
layout={{ custom: true }}
|
||||
>
|
||||
{(tableList: Device[]) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
<Body>
|
||||
{tableList.length === 0 && (
|
||||
<CircularProgress sx={{ margin: 1 }} size={18} />
|
||||
)}
|
||||
{tableList.map((device: Device) => (
|
||||
<Row key={device.id} item={device}>
|
||||
<Cell>
|
||||
<DeviceIcon type_id={device.t} />
|
||||
|
||||
{device.n}
|
||||
<span style={{ color: 'lightblue' }}>
|
||||
({device.e})
|
||||
</span>
|
||||
</Cell>
|
||||
<Cell stiff>{device.tn}</Cell>
|
||||
</Row>
|
||||
))}
|
||||
</Body>
|
||||
</>
|
||||
)}
|
||||
</Table>
|
||||
)}
|
||||
</IconContext.Provider>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -576,41 +601,54 @@ const Devices = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const showDeviceValue = (dv: DeviceValue) => {
|
||||
const showDeviceValue = useCallback((dv: DeviceValue) => {
|
||||
setSelectedDeviceValue(dv);
|
||||
setDeviceValueDialogOpen(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderNameCell = (dv: DeviceValue) => (
|
||||
<>
|
||||
{dv.id.slice(2)}
|
||||
{hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) && (
|
||||
<StarIcon color="primary" sx={{ fontSize: 12 }} />
|
||||
)}
|
||||
{hasMask(dv.id, DeviceEntityMask.DV_READONLY) && (
|
||||
<EditOffOutlinedIcon color="primary" sx={{ fontSize: 12 }} />
|
||||
)}
|
||||
{hasMask(dv.id, DeviceEntityMask.DV_API_MQTT_EXCLUDE) && (
|
||||
<CommentsDisabledOutlinedIcon color="primary" sx={{ fontSize: 12 }} />
|
||||
)}
|
||||
</>
|
||||
const renderNameCell = useCallback(
|
||||
(dv: DeviceValue) => (
|
||||
<>
|
||||
{dv.id.slice(2)}
|
||||
{hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) && (
|
||||
<StarIcon color="primary" sx={{ fontSize: 12 }} />
|
||||
)}
|
||||
{hasMask(dv.id, DeviceEntityMask.DV_READONLY) && (
|
||||
<EditOffOutlinedIcon color="primary" sx={{ fontSize: 12 }} />
|
||||
)}
|
||||
{hasMask(dv.id, DeviceEntityMask.DV_API_MQTT_EXCLUDE) && (
|
||||
<CommentsDisabledOutlinedIcon color="primary" sx={{ fontSize: 12 }} />
|
||||
)}
|
||||
</>
|
||||
),
|
||||
[hasMask]
|
||||
);
|
||||
|
||||
const shown_data = onlyFav
|
||||
? deviceData.nodes.filter(
|
||||
(dv) =>
|
||||
const shown_data = useMemo(() => {
|
||||
if (onlyFav) {
|
||||
return deviceData.nodes.filter(
|
||||
(dv: DeviceValue) =>
|
||||
hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) &&
|
||||
dv.id.slice(2).includes(search)
|
||||
)
|
||||
: deviceData.nodes.filter((dv) => dv.id.slice(2).includes(search));
|
||||
dv.id.slice(2).toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
}
|
||||
return deviceData.nodes.filter((dv: DeviceValue) =>
|
||||
dv.id.slice(2).toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
}, [deviceData.nodes, onlyFav, search]);
|
||||
|
||||
const deviceIndex = coreData.devices.findIndex(
|
||||
(d) => d.id === device_select.state.id
|
||||
(d: Device) => d.id === device_select.state.id
|
||||
);
|
||||
if (deviceIndex === -1) {
|
||||
return;
|
||||
}
|
||||
const deviceInfo = coreData.devices[deviceIndex];
|
||||
if (!deviceInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [, height] = size;
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -621,15 +659,15 @@ const Devices = () => {
|
||||
bottom: 0,
|
||||
top: 64,
|
||||
zIndex: 'modal',
|
||||
maxHeight: () => size[1] - 126,
|
||||
maxHeight: () => (height || 0) - 126,
|
||||
border: '1px solid #177ac9'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 1 }}>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography noWrap variant="subtitle1" color="warning.main">
|
||||
{coreData.devices[deviceIndex].n} (
|
||||
{coreData.devices[deviceIndex].tn})
|
||||
{deviceInfo.n} (
|
||||
{deviceInfo.tn})
|
||||
</Typography>
|
||||
<Grid justifyContent="flex-end">
|
||||
<ButtonTooltip title={LL.CLOSE()}>
|
||||
@@ -699,7 +737,7 @@ const Devices = () => {
|
||||
' ' +
|
||||
shown_data.length +
|
||||
'/' +
|
||||
coreData.devices[deviceIndex].e +
|
||||
deviceInfo.e +
|
||||
' ' +
|
||||
LL.ENTITIES(shown_data.length)}
|
||||
</span>
|
||||
@@ -790,6 +828,6 @@ const Devices = () => {
|
||||
)}
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Devices;
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormHelperText,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField,
|
||||
@@ -120,7 +120,7 @@ const DevicesDialog = ({
|
||||
{editItem.l ? (
|
||||
<TextField
|
||||
name="v"
|
||||
label={LL.VALUE(0)}
|
||||
// label={LL.VALUE(0)}
|
||||
value={editItem.v}
|
||||
disabled={!writeable}
|
||||
sx={{ width: '30ch' }}
|
||||
@@ -135,7 +135,7 @@ const DevicesDialog = ({
|
||||
</TextField>
|
||||
) : editItem.s || editItem.u !== DeviceValueUOM.NONE ? (
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="v"
|
||||
label={LL.VALUE(0)}
|
||||
value={numberValue(Math.round((editItem.v as number) * 10) / 10)}
|
||||
@@ -159,7 +159,7 @@ const DevicesDialog = ({
|
||||
/>
|
||||
) : (
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="v"
|
||||
label={LL.VALUE(0)}
|
||||
value={editItem.v}
|
||||
|
||||
@@ -43,7 +43,7 @@ const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => {
|
||||
size="small"
|
||||
color="secondary"
|
||||
value={getMaskString(de.m)}
|
||||
onChange={(event, mask: string[]) => {
|
||||
onChange={(_event, mask: string[]) => {
|
||||
de.m = getMaskNumber(mask);
|
||||
if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) {
|
||||
de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE;
|
||||
|
||||
@@ -41,7 +41,8 @@ const Help = () => {
|
||||
|
||||
useRequest(() => callAction({ action: 'getCustomSupport' })).onSuccess((event) => {
|
||||
if (event && event.data && Object.keys(event.data).length !== 0) {
|
||||
const data = event.data.Support;
|
||||
const data = (event.data as { Support: { img_url?: string; html?: string[] } })
|
||||
.Support;
|
||||
if (data.img_url) {
|
||||
setCustomSupportIMG(data.img_url);
|
||||
}
|
||||
@@ -59,7 +60,7 @@ const Help = () => {
|
||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||
})
|
||||
.onError((error) => {
|
||||
toast.error(error.message);
|
||||
toast.error(String(error.error?.message || 'An error occurred'));
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -72,7 +73,7 @@ const Help = () => {
|
||||
divider={<Divider orientation="vertical" flexItem />}
|
||||
sx={{
|
||||
borderRadius: 3,
|
||||
border: '2px solid grey',
|
||||
border: '1px solid lightblue',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
@@ -98,7 +99,7 @@ const Help = () => {
|
||||
)}
|
||||
|
||||
{me.admin && (
|
||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemButton
|
||||
component="a"
|
||||
|
||||
@@ -133,13 +133,15 @@ const Modules = () => {
|
||||
};
|
||||
|
||||
const saveModules = async () => {
|
||||
await updateModules({
|
||||
modules: modules.map((condensed_mi) => ({
|
||||
key: condensed_mi.key,
|
||||
enabled: condensed_mi.enabled,
|
||||
license: condensed_mi.license
|
||||
}))
|
||||
})
|
||||
await Promise.all(
|
||||
modules.map((condensed_mi: ModuleItem) =>
|
||||
updateModules({
|
||||
key: condensed_mi.key,
|
||||
enabled: condensed_mi.enabled,
|
||||
license: condensed_mi.license
|
||||
})
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
toast.success(LL.MODULES_UPDATED());
|
||||
})
|
||||
@@ -154,7 +156,9 @@ const Modules = () => {
|
||||
|
||||
const renderContent = () => {
|
||||
if (!modules) {
|
||||
return <FormLoader onRetry={fetchModules} errorMessage={error?.message} />;
|
||||
return (
|
||||
<FormLoader onRetry={fetchModules} errorMessage={error?.message || ''} />
|
||||
);
|
||||
}
|
||||
|
||||
if (modules.length === 0) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
import { readSchedule, writeSchedule } from '../../api/app';
|
||||
import SettingsSchedulerDialog from './SchedulerDialog';
|
||||
@@ -73,6 +74,12 @@ const Scheduler = () => {
|
||||
);
|
||||
}
|
||||
|
||||
useInterval(() => {
|
||||
if (numChanges === 0) {
|
||||
void fetchSchedule();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const formatter = new Intl.DateTimeFormat(locale, {
|
||||
weekday: 'short',
|
||||
@@ -128,8 +135,8 @@ const Scheduler = () => {
|
||||
const saveSchedule = async () => {
|
||||
await updateSchedule({
|
||||
schedule: schedule
|
||||
.filter((si) => !si.deleted)
|
||||
.map((condensed_si) => ({
|
||||
.filter((si: ScheduleItem) => !si.deleted)
|
||||
.map((condensed_si: ScheduleItem) => ({
|
||||
id: condensed_si.id,
|
||||
active: condensed_si.active,
|
||||
flags: condensed_si.flags,
|
||||
@@ -205,7 +212,9 @@ const Scheduler = () => {
|
||||
|
||||
const renderSchedule = () => {
|
||||
if (!schedule) {
|
||||
return <FormLoader onRetry={fetchSchedule} errorMessage={error?.message} />;
|
||||
return (
|
||||
<FormLoader onRetry={fetchSchedule} errorMessage={error?.message || ''} />
|
||||
);
|
||||
}
|
||||
|
||||
const dayBox = (si: ScheduleItem, flag: number) => (
|
||||
@@ -244,8 +253,8 @@ const Scheduler = () => {
|
||||
<Table
|
||||
data={{
|
||||
nodes: schedule
|
||||
.filter((si) => !si.deleted)
|
||||
.sort((a, b) => a.flags - b.flags)
|
||||
.filter((si: ScheduleItem) => !si.deleted)
|
||||
.sort((a: ScheduleItem, b: ScheduleItem) => a.flags - b.flags)
|
||||
}}
|
||||
theme={schedule_theme}
|
||||
layout={{ custom: true }}
|
||||
@@ -329,7 +338,7 @@ const Scheduler = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<Box mt={1} display="flex" flexWrap="wrap">
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1}>
|
||||
{numChanges !== 0 && (
|
||||
<ButtonRow>
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
TextField,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
@@ -144,7 +144,10 @@ const SchedulerDialog = ({
|
||||
</Typography>
|
||||
);
|
||||
|
||||
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
|
||||
const handleClose = (
|
||||
_event: React.SyntheticEvent,
|
||||
reason: 'backdropClick' | 'escapeKeyDown'
|
||||
) => {
|
||||
if (reason !== 'backdropClick') {
|
||||
onClose();
|
||||
}
|
||||
@@ -325,7 +328,7 @@ const SchedulerDialog = ({
|
||||
</>
|
||||
)}
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="cmd"
|
||||
label={LL.COMMAND(0)}
|
||||
multiline
|
||||
@@ -344,7 +347,7 @@ const SchedulerDialog = ({
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="name"
|
||||
label={LL.NAME(0) + ' (' + LL.OPTIONAL() + ')'}
|
||||
value={editItem.name}
|
||||
|
||||
@@ -105,8 +105,6 @@ const Sensors = () => {
|
||||
color: #90CAF9;
|
||||
.th {
|
||||
border-bottom: 1px solid #565656;
|
||||
}
|
||||
.th {
|
||||
height: 36px;
|
||||
}
|
||||
`,
|
||||
@@ -439,7 +437,8 @@ const Sensors = () => {
|
||||
<Cell>{a.n}</Cell>
|
||||
<Cell stiff>{AnalogTypeNames[a.t]} </Cell>
|
||||
{(a.t === AnalogType.DIGITAL_OUT && a.g !== 25 && a.g !== 26) ||
|
||||
a.t === AnalogType.DIGITAL_IN ? (
|
||||
a.t === AnalogType.DIGITAL_IN ||
|
||||
a.t === AnalogType.PULSE ? (
|
||||
<Cell stiff>{a.v ? LL.ON() : LL.OFF()}</Cell>
|
||||
) : (
|
||||
<Cell stiff>{a.t ? formatValue(a.v, a.u) : ''}</Cell>
|
||||
@@ -490,7 +489,7 @@ const Sensors = () => {
|
||||
/>
|
||||
)}
|
||||
{sensorData?.analog_enabled === true && me.admin && (
|
||||
<Box mt={1} display="flex" flexWrap="wrap" justifyContent="flex-end">
|
||||
<Box mt={2} display="flex" flexWrap="wrap" justifyContent="flex-end">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField,
|
||||
@@ -57,7 +57,10 @@ const SensorsAnalogDialog = ({
|
||||
}
|
||||
}, [open, selectedItem]);
|
||||
|
||||
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
|
||||
const handleClose = (
|
||||
_event: React.SyntheticEvent,
|
||||
reason: 'backdropClick' | 'escapeKeyDown'
|
||||
) => {
|
||||
if (reason !== 'backdropClick') {
|
||||
onClose();
|
||||
}
|
||||
@@ -88,7 +91,7 @@ const SensorsAnalogDialog = ({
|
||||
<Grid container spacing={2}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="g"
|
||||
label="GPIO"
|
||||
sx={{ width: '11ch' }}
|
||||
@@ -107,7 +110,7 @@ const SensorsAnalogDialog = ({
|
||||
)}
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="n"
|
||||
label={LL.NAME(0)}
|
||||
value={editItem.n}
|
||||
@@ -132,7 +135,9 @@ const SensorsAnalogDialog = ({
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
{editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && (
|
||||
{((editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE) ||
|
||||
(editItem.t >= AnalogType.FREQ_0 &&
|
||||
editItem.t <= AnalogType.FREQ_2)) && (
|
||||
<Grid>
|
||||
<TextField
|
||||
name="u"
|
||||
@@ -171,6 +176,27 @@ const SensorsAnalogDialog = ({
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.t === AnalogType.NTC && (
|
||||
<Grid>
|
||||
<TextField
|
||||
name="o"
|
||||
label={LL.OFFSET()}
|
||||
value={numberValue(editItem.o)}
|
||||
sx={{ width: '11ch' }}
|
||||
type="number"
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">°C</InputAdornment>
|
||||
)
|
||||
},
|
||||
htmlInput: { min: '-20', max: '20', step: '0.1' }
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.t === AnalogType.COUNTER && (
|
||||
<Grid>
|
||||
<TextField
|
||||
@@ -187,6 +213,19 @@ const SensorsAnalogDialog = ({
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.t === AnalogType.RGB && (
|
||||
<Grid>
|
||||
<TextField
|
||||
name="o"
|
||||
label={'RGB ' + LL.VALUE(0)}
|
||||
value={numberValue(editItem.o)}
|
||||
type="number"
|
||||
sx={{ width: '11ch' }}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && (
|
||||
<Grid>
|
||||
<TextField
|
||||
@@ -314,6 +353,42 @@ const SensorsAnalogDialog = ({
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{editItem.t === AnalogType.PULSE && (
|
||||
<>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="o"
|
||||
label={LL.POLARITY()}
|
||||
value={editItem.o}
|
||||
sx={{ width: '11ch' }}
|
||||
select
|
||||
onChange={updateFormValue}
|
||||
>
|
||||
<MenuItem value={0}>{LL.ACTIVEHIGH()}</MenuItem>
|
||||
<MenuItem value={1}>{LL.ACTIVELOW()}</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="f"
|
||||
label="Pulse"
|
||||
value={numberValue(editItem.f)}
|
||||
type="number"
|
||||
sx={{ width: '15ch' }}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">s</InputAdornment>
|
||||
)
|
||||
},
|
||||
htmlInput: { min: '0', max: '10000', step: '0.1' }
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
TextField,
|
||||
Typography
|
||||
@@ -52,7 +52,10 @@ const SensorsTemperatureDialog = ({
|
||||
}
|
||||
}, [open, selectedItem]);
|
||||
|
||||
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
|
||||
const handleClose = (
|
||||
_event: React.SyntheticEvent,
|
||||
reason: 'backdropClick' | 'escapeKeyDown'
|
||||
) => {
|
||||
if (reason !== 'backdropClick') {
|
||||
onClose();
|
||||
}
|
||||
@@ -82,7 +85,7 @@ const SensorsTemperatureDialog = ({
|
||||
<Grid container spacing={2}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="n"
|
||||
label={LL.NAME(0)}
|
||||
value={editItem.n}
|
||||
|
||||
@@ -188,7 +188,8 @@ export enum DeviceValueUOM {
|
||||
VOLTS,
|
||||
MBAR,
|
||||
LH,
|
||||
CTKWH
|
||||
CTKWH,
|
||||
HZ
|
||||
}
|
||||
|
||||
export const DeviceValueUOM_s = [
|
||||
@@ -218,7 +219,8 @@ export const DeviceValueUOM_s = [
|
||||
'V',
|
||||
'mbar',
|
||||
'l/h',
|
||||
'ct/kWh'
|
||||
'ct/kWh',
|
||||
'Hz'
|
||||
];
|
||||
|
||||
export enum AnalogType {
|
||||
@@ -232,20 +234,32 @@ export enum AnalogType {
|
||||
DIGITAL_OUT = 6,
|
||||
PWM_0 = 7,
|
||||
PWM_1 = 8,
|
||||
PWM_2 = 9
|
||||
PWM_2 = 9,
|
||||
NTC = 10,
|
||||
RGB = 11,
|
||||
PULSE = 12,
|
||||
FREQ_0 = 13,
|
||||
FREQ_1 = 14,
|
||||
FREQ_2 = 15
|
||||
}
|
||||
|
||||
export const AnalogTypeNames = [
|
||||
'(disabled)',
|
||||
'Digital In',
|
||||
'Counter',
|
||||
'ADC',
|
||||
'ADC In',
|
||||
'Timer',
|
||||
'Rate',
|
||||
'Digital Out',
|
||||
'PWM 0',
|
||||
'PWM 1',
|
||||
'PWM 2'
|
||||
'PWM 2',
|
||||
'NTC Temp.',
|
||||
'RGB Led',
|
||||
'Pulse',
|
||||
'Freq 0',
|
||||
'Freq 1',
|
||||
'Freq 2'
|
||||
];
|
||||
|
||||
type BoardProfiles = Record<string, string>;
|
||||
@@ -255,6 +269,7 @@ export const BOARD_PROFILES: BoardProfiles = {
|
||||
S32S3: 'BBQKees Gateway S3',
|
||||
E32: 'BBQKees Gateway E32',
|
||||
E32V2: 'BBQKees Gateway E32 V2',
|
||||
E32V2_2: 'BBQKees Gateway E32 V2.2',
|
||||
NODEMCU: 'NodeMCU 32S',
|
||||
'MH-ET': 'MH-ET Live D1 Mini',
|
||||
LOLIN: 'Lolin D32',
|
||||
@@ -380,6 +395,7 @@ export interface EntityItem {
|
||||
value_type: number;
|
||||
value?: unknown;
|
||||
writeable: boolean;
|
||||
hide: boolean;
|
||||
deleted?: boolean;
|
||||
o_id?: number;
|
||||
o_ram?: number;
|
||||
@@ -393,6 +409,7 @@ export interface EntityItem {
|
||||
o_deleted?: boolean;
|
||||
o_writeable?: boolean;
|
||||
o_value?: unknown;
|
||||
o_hide?: boolean;
|
||||
}
|
||||
|
||||
export interface Entities {
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
|
||||
export const GPIO_VALIDATOR = {
|
||||
validator(
|
||||
rule: InternalRuleItem,
|
||||
_rule: InternalRuleItem,
|
||||
value: number,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
@@ -36,7 +36,7 @@ export const GPIO_VALIDATOR = {
|
||||
|
||||
export const GPIO_VALIDATORR = {
|
||||
validator(
|
||||
rule: InternalRuleItem,
|
||||
_rule: InternalRuleItem,
|
||||
value: number,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
@@ -60,7 +60,7 @@ export const GPIO_VALIDATORR = {
|
||||
|
||||
export const GPIO_VALIDATORC3 = {
|
||||
validator(
|
||||
rule: InternalRuleItem,
|
||||
_rule: InternalRuleItem,
|
||||
value: number,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
@@ -74,7 +74,7 @@ export const GPIO_VALIDATORC3 = {
|
||||
|
||||
export const GPIO_VALIDATORS2 = {
|
||||
validator(
|
||||
rule: InternalRuleItem,
|
||||
_rule: InternalRuleItem,
|
||||
value: number,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
@@ -94,7 +94,7 @@ export const GPIO_VALIDATORS2 = {
|
||||
|
||||
export const GPIO_VALIDATORS3 = {
|
||||
validator(
|
||||
rule: InternalRuleItem,
|
||||
_rule: InternalRuleItem,
|
||||
value: number,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
@@ -279,7 +279,7 @@ export const createSettingsValidator = (settings: Settings) =>
|
||||
|
||||
export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) => ({
|
||||
validator(
|
||||
rule: InternalRuleItem,
|
||||
_rule: InternalRuleItem,
|
||||
name: string,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
@@ -324,7 +324,7 @@ export const uniqueCustomNameValidator = (
|
||||
o_name?: string
|
||||
) => ({
|
||||
validator(
|
||||
rule: InternalRuleItem,
|
||||
_rule: InternalRuleItem,
|
||||
name: string,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
@@ -353,7 +353,7 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte
|
||||
device_id: [
|
||||
{
|
||||
validator(
|
||||
rule: InternalRuleItem,
|
||||
_rule: InternalRuleItem,
|
||||
value: string,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
@@ -367,7 +367,7 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte
|
||||
type_id: [
|
||||
{
|
||||
validator(
|
||||
rule: InternalRuleItem,
|
||||
_rule: InternalRuleItem,
|
||||
value: string,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
@@ -389,7 +389,7 @@ export const uniqueTemperatureNameValidator = (
|
||||
sensors: TemperatureSensor[],
|
||||
o_name?: string
|
||||
) => ({
|
||||
validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
||||
validator(_rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
||||
if (
|
||||
(o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) &&
|
||||
n !== '' &&
|
||||
@@ -419,7 +419,7 @@ export const temperatureSensorItemValidation = (
|
||||
|
||||
export const isGPIOUniqueValidator = (sensors: AnalogSensor[]) => ({
|
||||
validator(
|
||||
rule: InternalRuleItem,
|
||||
_rule: InternalRuleItem,
|
||||
gpio: number,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
@@ -435,7 +435,7 @@ export const uniqueAnalogNameValidator = (
|
||||
sensors: AnalogSensor[],
|
||||
o_name?: string
|
||||
) => ({
|
||||
validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
||||
validator(_rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
||||
if (
|
||||
(o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) &&
|
||||
n !== '' &&
|
||||
@@ -482,7 +482,7 @@ export const deviceValueItemValidation = (dv: DeviceValue) =>
|
||||
{ required: true, message: 'Value is required' },
|
||||
{
|
||||
validator(
|
||||
rule: InternalRuleItem,
|
||||
_rule: InternalRuleItem,
|
||||
value: unknown,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
|
||||
@@ -54,12 +54,12 @@ const APSettings = () => {
|
||||
origData,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue
|
||||
updateDataValue as (value: unknown) => void
|
||||
);
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
|
||||
const validateAndSubmit = async () => {
|
||||
@@ -80,7 +80,7 @@ const APSettings = () => {
|
||||
return (
|
||||
<>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="provision_mode"
|
||||
label={LL.AP_PROVIDE() + '...'}
|
||||
value={data.provision_mode}
|
||||
@@ -103,7 +103,7 @@ const APSettings = () => {
|
||||
{isAPEnabled(data) && (
|
||||
<>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="ssid"
|
||||
label={LL.ACCESS_POINT(2) + ' SSID'}
|
||||
fullWidth
|
||||
@@ -113,7 +113,7 @@ const APSettings = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="password"
|
||||
label={LL.ACCESS_POINT(2) + ' ' + LL.PASSWORD()}
|
||||
fullWidth
|
||||
@@ -123,7 +123,7 @@ const APSettings = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="channel"
|
||||
label={LL.AP_PREFERRED_CHANNEL()}
|
||||
value={numberValue(data.channel)}
|
||||
@@ -151,7 +151,7 @@ const APSettings = () => {
|
||||
label={LL.AP_HIDE_SSID()}
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="max_clients"
|
||||
label={LL.AP_MAX_CLIENTS()}
|
||||
value={numberValue(data.max_clients)}
|
||||
@@ -169,7 +169,7 @@ const APSettings = () => {
|
||||
))}
|
||||
</ValidatedTextField>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="local_ip"
|
||||
label={LL.AP_LOCAL_IP()}
|
||||
fullWidth
|
||||
@@ -179,7 +179,7 @@ const APSettings = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="gateway_ip"
|
||||
label={LL.NETWORK_GATEWAY()}
|
||||
fullWidth
|
||||
@@ -189,7 +189,7 @@ const APSettings = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="subnet_mask"
|
||||
label={LL.NETWORK_SUBNET()}
|
||||
fullWidth
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField,
|
||||
@@ -75,7 +75,7 @@ const ApplicationSettings = () => {
|
||||
origData,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue
|
||||
updateDataValue as (value: unknown) => void
|
||||
);
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
@@ -135,7 +135,7 @@ const ApplicationSettings = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data || !hardwareData) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
|
||||
const validateAndSubmit = async () => {
|
||||
@@ -219,7 +219,7 @@ const ApplicationSettings = () => {
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="modbus_max_clients"
|
||||
label={LL.AP_MAX_CLIENTS()}
|
||||
variant="outlined"
|
||||
@@ -231,7 +231,7 @@ const ApplicationSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="modbus_port"
|
||||
label="Port"
|
||||
variant="outlined"
|
||||
@@ -243,7 +243,7 @@ const ApplicationSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="modbus_timeout"
|
||||
label="Timeout"
|
||||
slotProps={{
|
||||
@@ -273,7 +273,7 @@ const ApplicationSettings = () => {
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="syslog_host"
|
||||
label="Host"
|
||||
variant="outlined"
|
||||
@@ -284,7 +284,7 @@ const ApplicationSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="syslog_port"
|
||||
label="Port"
|
||||
variant="outlined"
|
||||
@@ -315,7 +315,7 @@ const ApplicationSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="syslog_mark_interval"
|
||||
label={LL.MARK_INTERVAL()}
|
||||
slotProps={{
|
||||
@@ -485,7 +485,7 @@ const ApplicationSettings = () => {
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="rx_gpio"
|
||||
label={LL.GPIO_OF('Rx')}
|
||||
fullWidth
|
||||
@@ -498,7 +498,7 @@ const ApplicationSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="tx_gpio"
|
||||
label={LL.GPIO_OF('Tx')}
|
||||
fullWidth
|
||||
@@ -511,7 +511,7 @@ const ApplicationSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="pbutton_gpio"
|
||||
label={LL.GPIO_OF(LL.BUTTON())}
|
||||
fullWidth
|
||||
@@ -524,7 +524,7 @@ const ApplicationSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="dallas_gpio"
|
||||
label={
|
||||
LL.GPIO_OF(LL.TEMPERATURE()) + ' (0=' + LL.DISABLED(1) + ')'
|
||||
@@ -539,7 +539,7 @@ const ApplicationSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="led_gpio"
|
||||
label={LL.GPIO_OF('LED') + ' (0=' + LL.DISABLED(1) + ')'}
|
||||
fullWidth
|
||||
@@ -554,7 +554,7 @@ const ApplicationSettings = () => {
|
||||
<Grid>
|
||||
<TextField
|
||||
name="led_type"
|
||||
label={'LED ' + LL.TYPE()}
|
||||
label={'LED ' + LL.TYPE(0)}
|
||||
value={data.led_type}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -743,7 +743,7 @@ const ApplicationSettings = () => {
|
||||
{data.remote_timeout_en && (
|
||||
<Box mt={2}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="remote_timeout"
|
||||
label={LL.REMOTE_TIMEOUT()}
|
||||
slotProps={{
|
||||
@@ -783,7 +783,7 @@ const ApplicationSettings = () => {
|
||||
{data.shower_timer && (
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="shower_min_duration"
|
||||
label={LL.MIN_DURATION()}
|
||||
slotProps={{
|
||||
@@ -801,7 +801,7 @@ const ApplicationSettings = () => {
|
||||
<>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="shower_alert_trigger"
|
||||
label={LL.TRIGGER_TIME()}
|
||||
slotProps={{
|
||||
@@ -817,7 +817,7 @@ const ApplicationSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="shower_alert_coldshot"
|
||||
label={LL.COLD_SHOT_DURATION()}
|
||||
slotProps={{
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import { Box, Button, Grid2 as Grid, Typography } from '@mui/material';
|
||||
import { Box, Button, Grid, Typography } from '@mui/material';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
import { API, callAction } from 'api/app';
|
||||
@@ -35,7 +35,7 @@ const DownloadUpload = () => {
|
||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||
})
|
||||
.onError((error) => {
|
||||
toast.error(error.message);
|
||||
toast.error(String(error.error?.message || 'An error occurred'));
|
||||
});
|
||||
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
@@ -57,7 +57,7 @@ const DownloadUpload = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,7 @@ import WarningIcon from '@mui/icons-material/Warning';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField,
|
||||
@@ -56,7 +56,7 @@ const MqttSettings = () => {
|
||||
origData,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue
|
||||
updateDataValue as (value: unknown) => void
|
||||
);
|
||||
|
||||
const SecondsInputProps = {
|
||||
@@ -65,7 +65,7 @@ const MqttSettings = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
|
||||
const validateAndSubmit = async () => {
|
||||
@@ -93,7 +93,7 @@ const MqttSettings = () => {
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="host"
|
||||
label={LL.ADDRESS_OF(LL.BROKER())}
|
||||
multiline
|
||||
@@ -105,7 +105,7 @@ const MqttSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="port"
|
||||
label="Port"
|
||||
variant="outlined"
|
||||
@@ -117,7 +117,7 @@ const MqttSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="base"
|
||||
label={LL.BASE_TOPIC()}
|
||||
variant="outlined"
|
||||
@@ -158,7 +158,7 @@ const MqttSettings = () => {
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="keep_alive"
|
||||
label="Keep Alive"
|
||||
slotProps={{
|
||||
@@ -254,109 +254,107 @@ const MqttSettings = () => {
|
||||
}
|
||||
label={LL.MQTT_RESPONSE()}
|
||||
/>
|
||||
{!data.ha_enabled && (
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
name="publish_single"
|
||||
checked={data.publish_single}
|
||||
onChange={updateFormValue}
|
||||
disabled={data.ha_enabled}
|
||||
/>
|
||||
}
|
||||
label={LL.MQTT_PUBLISH_TEXT_1()}
|
||||
/>
|
||||
</Grid>
|
||||
{data.publish_single && (
|
||||
<Grid>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
name="publish_single"
|
||||
checked={data.publish_single}
|
||||
name="publish_single2cmd"
|
||||
checked={data.publish_single2cmd}
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
}
|
||||
label={LL.MQTT_PUBLISH_TEXT_1()}
|
||||
label={LL.MQTT_PUBLISH_TEXT_2()}
|
||||
/>
|
||||
</Grid>
|
||||
{data.publish_single && (
|
||||
)}
|
||||
</Grid>
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
name="ha_enabled"
|
||||
checked={data.ha_enabled}
|
||||
onChange={updateFormValue}
|
||||
disabled={data.publish_single}
|
||||
/>
|
||||
}
|
||||
label={LL.MQTT_PUBLISH_TEXT_3()}
|
||||
/>
|
||||
</Grid>
|
||||
{data.ha_enabled && (
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
name="publish_single2cmd"
|
||||
checked={data.publish_single2cmd}
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
}
|
||||
label={LL.MQTT_PUBLISH_TEXT_2()}
|
||||
<TextField
|
||||
name="discovery_type"
|
||||
label={LL.MQTT_PUBLISH_TEXT_5()}
|
||||
value={data.discovery_type}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>Home Assistant</MenuItem>
|
||||
<MenuItem value={1}>Domoticz</MenuItem>
|
||||
<MenuItem value={2}>Domoticz (latest)</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="discovery_prefix"
|
||||
label={LL.MQTT_PUBLISH_TEXT_4()}
|
||||
variant="outlined"
|
||||
value={data.discovery_prefix}
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
{!data.publish_single && (
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
name="ha_enabled"
|
||||
checked={data.ha_enabled}
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
}
|
||||
label={LL.MQTT_PUBLISH_TEXT_3()}
|
||||
/>
|
||||
</Grid>
|
||||
{data.ha_enabled && (
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="discovery_type"
|
||||
label={LL.MQTT_PUBLISH_TEXT_5()}
|
||||
value={data.discovery_type}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>Home Assistant</MenuItem>
|
||||
<MenuItem value={1}>Domoticz</MenuItem>
|
||||
<MenuItem value={2}>Domoticz (latest)</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="discovery_prefix"
|
||||
label={LL.MQTT_PUBLISH_TEXT_4()}
|
||||
variant="outlined"
|
||||
value={data.discovery_prefix}
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="entity_format"
|
||||
label={LL.MQTT_ENTITY_FORMAT()}
|
||||
value={data.entity_format}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
|
||||
<MenuItem value={3}>
|
||||
{LL.MQTT_ENTITY_FORMAT_1()} (v3.6)
|
||||
</MenuItem>
|
||||
<MenuItem value={4}>
|
||||
{LL.MQTT_ENTITY_FORMAT_2()} (v3.6)
|
||||
</MenuItem>
|
||||
<MenuItem value={1}>{LL.MQTT_ENTITY_FORMAT_1()}</MenuItem>
|
||||
<MenuItem value={2}>{LL.MQTT_ENTITY_FORMAT_2()}</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="entity_format"
|
||||
label={LL.MQTT_ENTITY_FORMAT()}
|
||||
value={data.entity_format}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
|
||||
<MenuItem value={3}>
|
||||
{LL.MQTT_ENTITY_FORMAT_1()} (v3.6)
|
||||
</MenuItem>
|
||||
<MenuItem value={4}>
|
||||
{LL.MQTT_ENTITY_FORMAT_2()} (v3.6)
|
||||
</MenuItem>
|
||||
<MenuItem value={1}>{LL.MQTT_ENTITY_FORMAT_1()}</MenuItem>
|
||||
<MenuItem value={2}>{LL.MQTT_ENTITY_FORMAT_2()}</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
{LL.MQTT_PUBLISH_INTERVALS()} (0=auto)
|
||||
</Typography>
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="publish_time_heartbeat"
|
||||
label="Heartbeat"
|
||||
slotProps={{
|
||||
@@ -442,7 +440,7 @@ const MqttSettings = () => {
|
||||
<Grid>
|
||||
<TextField
|
||||
name="publish_time_sensor"
|
||||
label={LL.TEMP_SENSORS()}
|
||||
label={LL.SENSORS()}
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_sensor)}
|
||||
type="number"
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Button, Checkbox, MenuItem } from '@mui/material';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
import * as NTPApi from 'api/ntp';
|
||||
import { readNTPSettings } from 'api/ntp';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { updateState } from 'alova/client';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import {
|
||||
@@ -19,8 +34,8 @@ import {
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { NTPSettingsType } from 'types';
|
||||
import { updateValueDirty, useRest } from 'utils';
|
||||
import type { NTPSettingsType, Time } from 'types';
|
||||
import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
||||
|
||||
@@ -46,18 +61,101 @@ const NTPSettings = () => {
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle('NTP');
|
||||
|
||||
const [localTime, setLocalTime] = useState<string>('');
|
||||
const [settingTime, setSettingTime] = useState<boolean>(false);
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
|
||||
const { send: updateTime } = useRequest(
|
||||
(local_time: Time) => NTPApi.updateTime(local_time),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
const updateFormValue = updateValueDirty(
|
||||
origData,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue
|
||||
updateDataValue as (value: unknown) => void
|
||||
);
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setLocalTime(event.target.value);
|
||||
|
||||
const openSetTime = () => {
|
||||
setLocalTime(formatLocalDateTime(new Date()));
|
||||
setSettingTime(true);
|
||||
};
|
||||
|
||||
const configureTime = async () => {
|
||||
setProcessing(true);
|
||||
|
||||
await updateTime({ local_time: formatLocalDateTime(new Date(localTime)) })
|
||||
.then(async () => {
|
||||
toast.success(LL.TIME_SET());
|
||||
setSettingTime(false);
|
||||
await loadData();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(LL.PROBLEM_UPDATING());
|
||||
})
|
||||
.finally(() => {
|
||||
setProcessing(false);
|
||||
});
|
||||
};
|
||||
|
||||
const renderSetTimeDialog = () => (
|
||||
<Dialog
|
||||
sx={dialogStyle}
|
||||
open={settingTime}
|
||||
onClose={() => setSettingTime(false)}
|
||||
>
|
||||
<DialogTitle>{LL.SET_TIME(1)}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<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={LL.LOCAL_TIME(0)}
|
||||
type="datetime-local"
|
||||
value={localTime}
|
||||
onChange={updateLocalTime}
|
||||
disabled={processing}
|
||||
fullWidth
|
||||
slotProps={{
|
||||
inputLabel: {
|
||||
shrink: true
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
onClick={() => setSettingTime(false)}
|
||||
color="secondary"
|
||||
>
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<AccessTimeIcon />}
|
||||
variant="outlined"
|
||||
onClick={configureTime}
|
||||
disabled={processing}
|
||||
color="primary"
|
||||
>
|
||||
{LL.UPDATE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
|
||||
const validateAndSubmit = async () => {
|
||||
@@ -92,7 +190,7 @@ const NTPSettings = () => {
|
||||
label={LL.ENABLE_NTP()}
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="server"
|
||||
label={LL.NTP_SERVER()}
|
||||
fullWidth
|
||||
@@ -102,7 +200,7 @@ const NTPSettings = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="tz_label"
|
||||
label={LL.TIME_ZONE()}
|
||||
fullWidth
|
||||
@@ -115,6 +213,25 @@ const NTPSettings = () => {
|
||||
<MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
|
||||
{timeZoneSelectItems()}
|
||||
</ValidatedTextField>
|
||||
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
{!data.enabled && !dirtyFlags.length && (
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
<ButtonRow>
|
||||
<Button
|
||||
onClick={openSetTime}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<AccessTimeIcon />}
|
||||
>
|
||||
{LL.SET_TIME(0)}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{renderSetTimeDialog()}
|
||||
|
||||
{dirtyFlags && dirtyFlags.length !== 0 && (
|
||||
<ButtonRow>
|
||||
<Button
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
List
|
||||
} from '@mui/material';
|
||||
|
||||
@@ -74,9 +75,9 @@ const Settings = () => {
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
const content = () => (
|
||||
<>
|
||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
||||
return (
|
||||
<SectionContent>
|
||||
<List>
|
||||
<ListMenuItem
|
||||
icon={TuneIcon}
|
||||
bgcolor="#134ba2"
|
||||
@@ -143,7 +144,15 @@ const Settings = () => {
|
||||
|
||||
{renderFactoryResetDialog()}
|
||||
|
||||
<Box mt={2} display="flex" flexWrap="wrap">
|
||||
<Divider />
|
||||
|
||||
<Box
|
||||
mt={2}
|
||||
display="flex"
|
||||
justifyContent="flex-end"
|
||||
flexWrap="nowrap"
|
||||
whiteSpace="nowrap"
|
||||
>
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
variant="outlined"
|
||||
@@ -153,10 +162,8 @@ const Settings = () => {
|
||||
{LL.FACTORY_RESET()}
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
return <SectionContent>{content()}</SectionContent>;
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -35,7 +35,7 @@ const Network = () => {
|
||||
],
|
||||
useLocation()
|
||||
);
|
||||
const routerTab = matchedRoutes?.[0].route.path || false;
|
||||
const routerTab = matchedRoutes?.[0]?.route.path || false;
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -56,7 +56,7 @@ const Network = () => {
|
||||
return (
|
||||
<WiFiConnectionContext.Provider
|
||||
value={{
|
||||
selectedNetwork,
|
||||
...(selectedNetwork && { selectedNetwork }),
|
||||
selectNetwork,
|
||||
deselectNetwork
|
||||
}}
|
||||
|
||||
@@ -104,7 +104,7 @@ const NetworkSettings = () => {
|
||||
origData,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue
|
||||
updateDataValue as (value: unknown) => void
|
||||
);
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
@@ -113,7 +113,7 @@ const NetworkSettings = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
|
||||
const validateAndSubmit = async () => {
|
||||
@@ -172,7 +172,7 @@ const NetworkSettings = () => {
|
||||
</List>
|
||||
) : (
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="ssid"
|
||||
label={'SSID (' + LL.NETWORK_BLANK_SSID() + ')'}
|
||||
fullWidth
|
||||
@@ -183,7 +183,7 @@ const NetworkSettings = () => {
|
||||
/>
|
||||
)}
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="bssid"
|
||||
label={'BSSID (' + LL.NETWORK_BLANK_BSSID() + ')'}
|
||||
fullWidth
|
||||
@@ -194,7 +194,7 @@ const NetworkSettings = () => {
|
||||
/>
|
||||
{(!selectedNetwork || !isNetworkOpen(selectedNetwork)) && (
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="password"
|
||||
label={LL.PASSWORD()}
|
||||
fullWidth
|
||||
@@ -251,7 +251,7 @@ const NetworkSettings = () => {
|
||||
{LL.GENERAL_OPTIONS()}
|
||||
</Typography>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="hostname"
|
||||
label={LL.HOSTNAME()}
|
||||
fullWidth
|
||||
@@ -304,7 +304,7 @@ const NetworkSettings = () => {
|
||||
{data.static_ip_config && (
|
||||
<>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="local_ip"
|
||||
label={LL.AP_LOCAL_IP()}
|
||||
fullWidth
|
||||
@@ -314,7 +314,7 @@ const NetworkSettings = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="gateway_ip"
|
||||
label={LL.NETWORK_GATEWAY()}
|
||||
fullWidth
|
||||
@@ -324,7 +324,7 @@ const NetworkSettings = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="subnet_mask"
|
||||
label={LL.NETWORK_SUBNET()}
|
||||
fullWidth
|
||||
@@ -334,7 +334,7 @@ const NetworkSettings = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="dns_ip_1"
|
||||
label="DNS #1"
|
||||
fullWidth
|
||||
@@ -344,7 +344,7 @@ const NetworkSettings = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="dns_ip_2"
|
||||
label="DNS #2"
|
||||
fullWidth
|
||||
|
||||
@@ -50,9 +50,7 @@ const WiFiNetworkScanner = () => {
|
||||
|
||||
const renderNetworkScanner = () => {
|
||||
if (!networkList) {
|
||||
return (
|
||||
<FormLoader message={LL.SCANNING() + '...'} errorMessage={errorMessage} />
|
||||
);
|
||||
return <FormLoader errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
return <WiFiNetworkSelector networkList={networkList} />;
|
||||
};
|
||||
|
||||
@@ -97,7 +97,7 @@ const ManageUsers = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
|
||||
const noAdminConfigured = () => !data.users.find((u) => u.admin);
|
||||
@@ -260,15 +260,20 @@ const ManageUsers = () => {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<GenerateToken username={generatingToken} onClose={closeGenerateToken} />
|
||||
<User
|
||||
user={user}
|
||||
setUser={setUser}
|
||||
creating={creating}
|
||||
onDoneEditing={doneEditingUser}
|
||||
onCancelEditing={cancelEditingUser}
|
||||
validator={createUserValidator(data.users, creating)}
|
||||
<GenerateToken
|
||||
username={generatingToken || ''}
|
||||
onClose={closeGenerateToken}
|
||||
/>
|
||||
{user && (
|
||||
<User
|
||||
user={user}
|
||||
setUser={setUser}
|
||||
creating={creating}
|
||||
onDoneEditing={doneEditingUser}
|
||||
onCancelEditing={cancelEditingUser}
|
||||
validator={createUserValidator(data.users, creating)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ const Security = () => {
|
||||
],
|
||||
useLocation()
|
||||
);
|
||||
const routerTab = matchedRoutes?.[0].route.path || false;
|
||||
const routerTab = matchedRoutes?.[0]?.route.path || false;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -47,12 +47,12 @@ const SecuritySettings = () => {
|
||||
origData,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue
|
||||
updateDataValue as (value: unknown) => void
|
||||
);
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
|
||||
const validateAndSubmit = async () => {
|
||||
@@ -69,7 +69,7 @@ const SecuritySettings = () => {
|
||||
return (
|
||||
<>
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="jwt_secret"
|
||||
label={LL.SU_PASSWORD()}
|
||||
fullWidth
|
||||
|
||||
@@ -82,7 +82,7 @@ const User: FC<UserFormProps> = ({
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="username"
|
||||
label={LL.USERNAME(1)}
|
||||
fullWidth
|
||||
@@ -93,7 +93,7 @@ const User: FC<UserFormProps> = ({
|
||||
margin="normal"
|
||||
/>
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="password"
|
||||
label={LL.PASSWORD()}
|
||||
fullWidth
|
||||
|
||||
@@ -61,7 +61,7 @@ const APStatus = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -67,7 +67,8 @@ const SystemActivity = () => {
|
||||
});
|
||||
|
||||
const showName = (id: number) => {
|
||||
const name: keyof Translation['STATUS_NAMES'] = id;
|
||||
const name: keyof Translation['STATUS_NAMES'] =
|
||||
id.toString() as keyof Translation['STATUS_NAMES'];
|
||||
return LL.STATUS_NAMES[name]();
|
||||
};
|
||||
|
||||
@@ -87,7 +88,7 @@ const SystemActivity = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -41,7 +41,7 @@ const HardwareStatus = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -99,7 +99,7 @@ const MqttStatus = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||
}
|
||||
|
||||
const renderConnectionStatus = () => (
|
||||
|
||||
@@ -1,40 +1,27 @@
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DnsIcon from '@mui/icons-material/Dns';
|
||||
import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle';
|
||||
import UpdateIcon from '@mui/icons-material/Update';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
TextField,
|
||||
Typography,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import type { Theme } from '@mui/material';
|
||||
|
||||
import * as NTPApi from 'api/ntp';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { NTPStatusType, Time } from 'types';
|
||||
import type { NTPStatusType } from 'types';
|
||||
import { NTPSyncStatus } from 'types';
|
||||
import { useInterval } from 'utils';
|
||||
import { formatDateTime, formatLocalDateTime } from 'utils';
|
||||
import { formatDateTime } from 'utils';
|
||||
|
||||
const NTPStatus = () => {
|
||||
const { data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
|
||||
@@ -43,24 +30,11 @@ const NTPStatus = () => {
|
||||
void loadData();
|
||||
});
|
||||
|
||||
const [localTime, setLocalTime] = useState<string>('');
|
||||
const [settingTime, setSettingTime] = useState<boolean>(false);
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle('NTP');
|
||||
|
||||
const { send: updateTime } = useRequest(
|
||||
(local_time: Time) => NTPApi.updateTime(local_time),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
NTPApi.updateTime;
|
||||
|
||||
const isNtpActive = ({ status }: NTPStatusType) =>
|
||||
status === NTPSyncStatus.NTP_ACTIVE;
|
||||
const isNtpEnabled = ({ status }: NTPStatusType) =>
|
||||
status !== NTPSyncStatus.NTP_DISABLED;
|
||||
|
||||
@@ -77,14 +51,6 @@ const NTPStatus = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setLocalTime(event.target.value);
|
||||
|
||||
const openSetTime = () => {
|
||||
setLocalTime(formatLocalDateTime(new Date()));
|
||||
setSettingTime(true);
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const ntpStatus = ({ status }: NTPStatusType) => {
|
||||
@@ -100,73 +66,9 @@ const NTPStatus = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const configureTime = async () => {
|
||||
setProcessing(true);
|
||||
|
||||
await updateTime({ local_time: formatLocalDateTime(new Date(localTime)) })
|
||||
.then(async () => {
|
||||
toast.success(LL.TIME_SET());
|
||||
setSettingTime(false);
|
||||
await loadData();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(LL.PROBLEM_UPDATING());
|
||||
})
|
||||
.finally(() => {
|
||||
setProcessing(false);
|
||||
});
|
||||
};
|
||||
|
||||
const renderSetTimeDialog = () => (
|
||||
<Dialog
|
||||
sx={dialogStyle}
|
||||
open={settingTime}
|
||||
onClose={() => setSettingTime(false)}
|
||||
>
|
||||
<DialogTitle>{LL.SET_TIME(1)}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<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={LL.LOCAL_TIME(0)}
|
||||
type="datetime-local"
|
||||
value={localTime}
|
||||
onChange={updateLocalTime}
|
||||
disabled={processing}
|
||||
fullWidth
|
||||
slotProps={{
|
||||
inputLabel: {
|
||||
shrink: true
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
onClick={() => setSettingTime(false)}
|
||||
color="secondary"
|
||||
>
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<AccessTimeIcon />}
|
||||
variant="outlined"
|
||||
onClick={configureTime}
|
||||
disabled={processing}
|
||||
color="primary"
|
||||
>
|
||||
{LL.UPDATE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -219,23 +121,6 @@ const NTPStatus = () => {
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</List>
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
{data && !isNtpActive(data) && (
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
<ButtonRow>
|
||||
<Button
|
||||
onClick={openSetTime}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<AccessTimeIcon />}
|
||||
>
|
||||
{LL.SET_TIME(0)}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{renderSetTimeDialog()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -120,7 +120,7 @@ const NetworkStatus = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -248,12 +248,12 @@ const SystemStatus = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data || !LL) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
||||
<List>
|
||||
<ListMenuItem
|
||||
icon={BuildIcon}
|
||||
bgcolor="#72caf9"
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
TextField,
|
||||
@@ -31,13 +31,14 @@ import type { LogEntry, LogSettings } from 'types';
|
||||
import { LogLevel } from 'types';
|
||||
import { updateValueDirty, useRest } from 'utils';
|
||||
|
||||
const TextColors = {
|
||||
const TextColors: Record<LogLevel, string> = {
|
||||
[LogLevel.ERROR]: '#ff0000', // red
|
||||
[LogLevel.WARNING]: '#ff0000', // red
|
||||
[LogLevel.NOTICE]: '#ffffff', // white
|
||||
[LogLevel.INFO]: '#ffcc00', // yellow
|
||||
[LogLevel.DEBUG]: '#00ffff', // cyan
|
||||
[LogLevel.TRACE]: '#00ffff' // cyan
|
||||
[LogLevel.TRACE]: '#00ffff', // cyan
|
||||
[LogLevel.ALL]: '#ffffff' // white
|
||||
};
|
||||
|
||||
const LogEntryLine = styled('span')(
|
||||
@@ -109,7 +110,7 @@ const SystemLog = () => {
|
||||
origData,
|
||||
dirtyFlags,
|
||||
setDirtyFlags,
|
||||
updateDataValue
|
||||
updateDataValue as (value: unknown) => void
|
||||
);
|
||||
|
||||
useSSE(fetchLogES, {
|
||||
@@ -190,7 +191,7 @@ const SystemLog = () => {
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -51,7 +51,7 @@ const SystemMonitor = () => {
|
||||
}
|
||||
})
|
||||
.onError((error) => {
|
||||
setErrorMessage(error.message);
|
||||
setErrorMessage(String(error.error?.message || 'An error occurred'));
|
||||
});
|
||||
|
||||
useInterval(() => {
|
||||
@@ -97,7 +97,7 @@ const SystemMonitor = () => {
|
||||
color="error"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{LL.RESET(0)}
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
</MessageBox>
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import CheckIcon from '@mui/icons-material/Done';
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
@@ -15,8 +16,13 @@ import {
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormControlLabel,
|
||||
Grid2 as Grid,
|
||||
Grid,
|
||||
IconButton,
|
||||
Link,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
@@ -36,247 +42,403 @@ import {
|
||||
} from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { TranslationFunctions } from 'i18n/i18n-types';
|
||||
import { prettyDateTime } from 'utils/time';
|
||||
|
||||
// Constants moved outside component to avoid recreation
|
||||
const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/';
|
||||
const STABLE_RELNOTES_URL =
|
||||
'https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md';
|
||||
const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/';
|
||||
const DEV_RELNOTES_URL =
|
||||
'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md';
|
||||
|
||||
// Types for better type safety
|
||||
interface VersionData {
|
||||
emsesp_version: string;
|
||||
arduino_version: string;
|
||||
esp_platform: string;
|
||||
flash_chip_size: number;
|
||||
psram: boolean;
|
||||
build_flags?: string;
|
||||
}
|
||||
|
||||
interface UpgradeCheckData {
|
||||
emsesp_version: string;
|
||||
dev_upgradeable: boolean;
|
||||
stable_upgradeable: boolean;
|
||||
}
|
||||
|
||||
interface VersionInfo {
|
||||
name: string;
|
||||
published_at?: string;
|
||||
}
|
||||
|
||||
// Memoized components for better performance
|
||||
const VersionInfoDialog = memo(
|
||||
({
|
||||
showVersionInfo,
|
||||
latestVersion,
|
||||
latestDevVersion,
|
||||
locale,
|
||||
LL,
|
||||
onClose
|
||||
}: {
|
||||
showVersionInfo: number;
|
||||
latestVersion?: VersionInfo;
|
||||
latestDevVersion?: VersionInfo;
|
||||
locale: string;
|
||||
LL: TranslationFunctions;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
if (showVersionInfo === 0) return null;
|
||||
|
||||
const isStable = showVersionInfo === 1;
|
||||
const version = isStable ? latestVersion : latestDevVersion;
|
||||
const relNotesUrl = isStable ? STABLE_RELNOTES_URL : DEV_RELNOTES_URL;
|
||||
|
||||
return (
|
||||
<Dialog sx={dialogStyle} open={showVersionInfo !== 0} onClose={onClose}>
|
||||
<DialogTitle>{LL.FIRMWARE_VERSION_INFO()}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Table size="small" sx={{ borderCollapse: 'collapse', minWidth: 0 }}>
|
||||
<TableBody>
|
||||
<TableRow sx={{ height: 24, borderBottom: 'none' }}>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
sx={{
|
||||
color: 'lightblue',
|
||||
borderBottom: 'none',
|
||||
pr: 1,
|
||||
py: 0.5,
|
||||
fontSize: 13,
|
||||
width: 90
|
||||
}}
|
||||
>
|
||||
{LL.TYPE(0)}
|
||||
</TableCell>
|
||||
<TableCell sx={{ borderBottom: 'none', py: 0.5, fontSize: 13 }}>
|
||||
{isStable ? LL.STABLE() : LL.DEVELOPMENT()}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow sx={{ height: 24, borderBottom: 'none' }}>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
sx={{
|
||||
color: 'lightblue',
|
||||
borderBottom: 'none',
|
||||
pr: 1,
|
||||
py: 0.5,
|
||||
fontSize: 13
|
||||
}}
|
||||
>
|
||||
{LL.VERSION()}
|
||||
</TableCell>
|
||||
<TableCell sx={{ borderBottom: 'none', py: 0.5, fontSize: 13 }}>
|
||||
{version?.name}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{version?.published_at && (
|
||||
<TableRow sx={{ height: 24, borderBottom: 'none' }}>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
sx={{
|
||||
color: 'lightblue',
|
||||
borderBottom: 'none',
|
||||
pr: 1,
|
||||
py: 0.5,
|
||||
fontSize: 13
|
||||
}}
|
||||
>
|
||||
Build Date
|
||||
</TableCell>
|
||||
<TableCell sx={{ borderBottom: 'none', py: 0.5, fontSize: 13 }}>
|
||||
{prettyDateTime(locale, new Date(version.published_at))}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="a"
|
||||
href={relNotesUrl}
|
||||
target="_blank"
|
||||
color="primary"
|
||||
>
|
||||
Changelog
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={onClose} color="secondary">
|
||||
{LL.CLOSE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const InstallDialog = memo(
|
||||
({
|
||||
openInstallDialog,
|
||||
fetchDevVersion,
|
||||
latestVersion,
|
||||
latestDevVersion,
|
||||
downloadOnly,
|
||||
platform,
|
||||
LL,
|
||||
onClose,
|
||||
onInstall
|
||||
}: {
|
||||
openInstallDialog: boolean;
|
||||
fetchDevVersion: boolean;
|
||||
latestVersion?: VersionInfo;
|
||||
latestDevVersion?: VersionInfo;
|
||||
downloadOnly: boolean;
|
||||
platform: string;
|
||||
LL: TranslationFunctions;
|
||||
onClose: () => void;
|
||||
onInstall: (url: string) => void;
|
||||
}) => {
|
||||
const binURL = useMemo(() => {
|
||||
if (!latestVersion || !latestDevVersion) return '';
|
||||
|
||||
const version = fetchDevVersion ? latestDevVersion : latestVersion;
|
||||
const filename = `EMS-ESP-${version.name.replaceAll('.', '_')}-${platform}.bin`;
|
||||
|
||||
return fetchDevVersion
|
||||
? `${DEV_URL}${filename}`
|
||||
: `${STABLE_URL}v${version.name}/${filename}`;
|
||||
}, [fetchDevVersion, latestVersion, latestDevVersion, platform]);
|
||||
|
||||
return (
|
||||
<Dialog sx={dialogStyle} open={openInstallDialog} onClose={onClose}>
|
||||
<DialogTitle>
|
||||
{`${LL.UPDATE()} ${fetchDevVersion ? LL.DEVELOPMENT() : LL.STABLE()} Firmware`}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Typography mb={2}>
|
||||
{LL.INSTALL_VERSION(
|
||||
downloadOnly ? LL.DOWNLOAD(1) : LL.INSTALL(),
|
||||
fetchDevVersion ? latestDevVersion?.name : latestVersion?.name
|
||||
)}
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
onClick={onClose}
|
||||
color="secondary"
|
||||
>
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
onClick={onClose}
|
||||
color="primary"
|
||||
>
|
||||
<Link underline="none" target="_blank" href={binURL} color="primary">
|
||||
{LL.DOWNLOAD(0)}
|
||||
</Link>
|
||||
</Button>
|
||||
{!downloadOnly && (
|
||||
<Button
|
||||
startIcon={<WarningIcon color="warning" />}
|
||||
variant="outlined"
|
||||
onClick={() => onInstall(binURL)}
|
||||
color="primary"
|
||||
>
|
||||
{LL.INSTALL()}
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Helper function moved outside component
|
||||
const getPlatform = (data: VersionData): string => {
|
||||
return `${data.esp_platform}-${data.flash_chip_size >= 16384 ? '16MB' : '4MB'}${data.psram ? '+' : ''}`;
|
||||
};
|
||||
|
||||
const Version = () => {
|
||||
const { LL, locale } = useI18nContext();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
// State management
|
||||
const [restarting, setRestarting] = useState<boolean>(false);
|
||||
const [openInstallDialog, setOpenInstallDialog] = useState<boolean>(false);
|
||||
const [usingDevVersion, setUsingDevVersion] = useState<boolean>(false);
|
||||
const [upgradeAvailable, setUpgradeAvailable] = useState<boolean>(false);
|
||||
const [fetchDevVersion, setFetchDevVersion] = useState<boolean>(false);
|
||||
const [devUpgradeAvailable, setDevUpgradeAvailable] = useState<boolean>(false);
|
||||
const [stableUpgradeAvailable, setStableUpgradeAvailable] =
|
||||
useState<boolean>(false);
|
||||
const [internetLive, setInternetLive] = useState<boolean>(false);
|
||||
const [downloadOnly, setDownloadOnly] = useState<boolean>(false);
|
||||
const [showVersionInfo, setShowVersionInfo] = useState<number>(0);
|
||||
|
||||
const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/';
|
||||
const STABLE_RELNOTES_URL =
|
||||
'https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md';
|
||||
|
||||
const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/';
|
||||
const DEV_RELNOTES_URL =
|
||||
'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md';
|
||||
|
||||
// API calls with optimized error handling
|
||||
const { send: sendCheckUpgrade } = useRequest(
|
||||
(versions: string) => callAction({ action: 'checkUpgrade', param: versions }),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
{ immediate: false }
|
||||
).onSuccess((event) => {
|
||||
const data = event.data as { emsesp_version: string; upgradeable: boolean };
|
||||
setUpgradeAvailable(data.upgradeable);
|
||||
const data = event.data as UpgradeCheckData;
|
||||
setDevUpgradeAvailable(data.dev_upgradeable);
|
||||
setStableUpgradeAvailable(data.stable_upgradeable);
|
||||
});
|
||||
|
||||
const {
|
||||
data: data,
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useRequest(SystemApi.readSystemStatus).onSuccess((event) => {
|
||||
// older version of EMS-ESP on 4MB boards, can't use OTA because of SSL support in HttpClient
|
||||
if (event.data.arduino_version.startsWith('Tasmota')) {
|
||||
const systemData = event.data as VersionData;
|
||||
if (systemData.arduino_version.startsWith('Tasmota')) {
|
||||
setDownloadOnly(true);
|
||||
}
|
||||
setUsingDevVersion(event.data.emsesp_version.includes('dev'));
|
||||
setUsingDevVersion(systemData.emsesp_version.includes('dev'));
|
||||
});
|
||||
|
||||
const { send: sendUploadURL } = useRequest(
|
||||
(url: string) => callAction({ action: 'uploadURL', param: url }),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
// called immediately to get the latest versions on page load
|
||||
const { data: latestVersion } = useRequest(getStableVersion);
|
||||
const { data: latestDevVersion } = useRequest(getDevVersion);
|
||||
|
||||
useEffect(() => {
|
||||
if (latestVersion && latestDevVersion) {
|
||||
sendCheckUpgrade(latestDevVersion.name + ',' + latestVersion.name)
|
||||
.catch((error: Error) => {
|
||||
toast.error('Failed to check for upgrades: ' + error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setInternetLive(true);
|
||||
});
|
||||
}
|
||||
}, [latestVersion, latestDevVersion]);
|
||||
|
||||
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
|
||||
const DIVISIONS = [
|
||||
{ amount: 60, name: 'seconds' },
|
||||
{ amount: 60, name: 'minutes' },
|
||||
{ amount: 24, name: 'hours' },
|
||||
{ amount: 7, name: 'days' },
|
||||
{ amount: 4.34524, name: 'weeks' },
|
||||
{ amount: 12, name: 'months' },
|
||||
{ amount: Number.POSITIVE_INFINITY, name: 'years' }
|
||||
];
|
||||
function formatTimeAgo(date) {
|
||||
let duration = (date.getTime() - new Date().getTime()) / 1000;
|
||||
for (let i = 0; i < DIVISIONS.length; i++) {
|
||||
const division = DIVISIONS[i];
|
||||
if (Math.abs(duration) < division.amount) {
|
||||
return rtf.format(
|
||||
Math.round(duration),
|
||||
division.name as Intl.RelativeTimeFormatUnit
|
||||
);
|
||||
}
|
||||
duration /= division.amount;
|
||||
}
|
||||
}
|
||||
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const doRestart = async () => {
|
||||
// Memoized values
|
||||
const platform = useMemo(() => (data ? getPlatform(data) : ''), [data]);
|
||||
const isDev = useMemo(
|
||||
() => data?.emsesp_version.includes('dev') ?? false,
|
||||
[data?.emsesp_version]
|
||||
);
|
||||
|
||||
const doRestart = useCallback(async () => {
|
||||
setRestarting(true);
|
||||
await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch(
|
||||
(error: Error) => {
|
||||
toast.error(error.message);
|
||||
}
|
||||
);
|
||||
};
|
||||
}, [sendAPI]);
|
||||
|
||||
const getBinURL = () => {
|
||||
if (!internetLive) {
|
||||
return '';
|
||||
const installFirmwareURL = useCallback(
|
||||
async (url: string) => {
|
||||
await sendUploadURL(url).catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
setRestarting(true);
|
||||
},
|
||||
[sendUploadURL]
|
||||
);
|
||||
|
||||
const showFirmwareDialog = useCallback((useDevVersion: boolean) => {
|
||||
setFetchDevVersion(useDevVersion);
|
||||
setOpenInstallDialog(true);
|
||||
}, []);
|
||||
|
||||
const closeInstallDialog = useCallback(() => {
|
||||
setOpenInstallDialog(false);
|
||||
}, []);
|
||||
|
||||
const handleVersionInfoClose = useCallback(() => {
|
||||
setShowVersionInfo(0);
|
||||
}, []);
|
||||
|
||||
// Effect for checking upgrades
|
||||
useEffect(() => {
|
||||
if (latestVersion && latestDevVersion) {
|
||||
const versions = `${latestDevVersion.name},${latestVersion.name}`;
|
||||
sendCheckUpgrade(versions)
|
||||
.catch((error: Error) => {
|
||||
toast.error(`Failed to check for upgrades: ${error.message}`);
|
||||
})
|
||||
.finally(() => {
|
||||
setInternetLive(true);
|
||||
});
|
||||
}
|
||||
const filename =
|
||||
'EMS-ESP-' +
|
||||
(usingDevVersion ? latestDevVersion.name : latestVersion.name).replaceAll(
|
||||
'.',
|
||||
'_'
|
||||
) +
|
||||
'-' +
|
||||
getPlatform() +
|
||||
'.bin';
|
||||
return usingDevVersion
|
||||
? DEV_URL + filename
|
||||
: STABLE_URL + 'v' + latestVersion.name + '/' + filename;
|
||||
};
|
||||
|
||||
const getPlatform = () => {
|
||||
return (
|
||||
[data.esp_platform, data.flash_chip_size >= 16384 ? '16MB' : '4MB'].join('-') +
|
||||
(data.psram ? '+' : '')
|
||||
);
|
||||
};
|
||||
|
||||
const installFirmwareURL = async (url: string) => {
|
||||
await sendUploadURL(url).catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
setRestarting(true);
|
||||
};
|
||||
}, [latestVersion, latestDevVersion, sendCheckUpgrade]);
|
||||
|
||||
useLayoutTitle('EMS-ESP Firmware');
|
||||
|
||||
const renderInstallDialog = () => (
|
||||
<Dialog
|
||||
sx={dialogStyle}
|
||||
open={openInstallDialog}
|
||||
onClose={() => closeInstallDialog()}
|
||||
>
|
||||
<DialogTitle>
|
||||
{LL.INSTALL() +
|
||||
' ' +
|
||||
(usingDevVersion ? LL.DEVELOPMENT() : LL.STABLE()) +
|
||||
' Firmware'}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Typography mb={2}>
|
||||
{LL.INSTALL_VERSION(
|
||||
usingDevVersion ? latestDevVersion?.name : latestVersion?.name
|
||||
)}
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
onClick={() => closeInstallDialog()}
|
||||
color="secondary"
|
||||
>
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
onClick={() => closeInstallDialog()}
|
||||
color="primary"
|
||||
>
|
||||
<Link underline="none" target="_blank" href={getBinURL()} color="primary">
|
||||
{LL.DOWNLOAD(1)}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<WarningIcon color="warning" />}
|
||||
variant="outlined"
|
||||
onClick={() => installFirmwareURL(getBinURL())}
|
||||
color="primary"
|
||||
>
|
||||
{LL.INSTALL()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
// Memoized button rendering logic
|
||||
const showButtons = useCallback(
|
||||
(showingDev: boolean) => {
|
||||
const choice = showingDev
|
||||
? !usingDevVersion
|
||||
? LL.SWITCH_RELEASE_TYPE(LL.DEVELOPMENT())
|
||||
: devUpgradeAvailable
|
||||
? LL.UPDATE_AVAILABLE()
|
||||
: undefined
|
||||
: usingDevVersion
|
||||
? LL.SWITCH_RELEASE_TYPE(LL.STABLE())
|
||||
: stableUpgradeAvailable
|
||||
? LL.UPDATE_AVAILABLE()
|
||||
: undefined;
|
||||
|
||||
const showFirmwareDialog = (useDevVersion?: boolean) => {
|
||||
setUsingDevVersion(useDevVersion || usingDevVersion);
|
||||
setOpenInstallDialog(true);
|
||||
};
|
||||
if (!choice) {
|
||||
return (
|
||||
<>
|
||||
<CheckIcon
|
||||
color="success"
|
||||
sx={{ verticalAlign: 'middle', ml: 0.5, mr: 0.5 }}
|
||||
/>
|
||||
<span style={{ color: '#66bb6a', fontSize: '0.8em' }}>
|
||||
{LL.LATEST_VERSION(usingDevVersion ? LL.DEVELOPMENT() : LL.STABLE())}
|
||||
</span>
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={() => showFirmwareDialog(showingDev)}
|
||||
>
|
||||
{LL.REINSTALL()}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const closeInstallDialog = () => {
|
||||
setOpenInstallDialog(false);
|
||||
setUsingDevVersion(data.emsesp_version.includes('dev'));
|
||||
};
|
||||
if (!me.admin) return null;
|
||||
|
||||
const showButtons = (showDev?: boolean) => {
|
||||
if (!me.admin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (downloadOnly) {
|
||||
return (
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
onClick={() => setOpenInstallDialog(false)}
|
||||
color="warning"
|
||||
color={choice === LL.UPDATE_AVAILABLE() ? 'success' : 'warning'}
|
||||
size="small"
|
||||
onClick={() => showFirmwareDialog(showingDev)}
|
||||
>
|
||||
<Link underline="none" target="_blank" href={getBinURL()} color="warning">
|
||||
{LL.DOWNLOAD(1)}
|
||||
</Link>
|
||||
{choice}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
usingDevVersion,
|
||||
devUpgradeAvailable,
|
||||
stableUpgradeAvailable,
|
||||
me.admin,
|
||||
LL,
|
||||
showFirmwareDialog
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
size="small"
|
||||
onClick={() => showFirmwareDialog()}
|
||||
>
|
||||
{upgradeAvailable || (!usingDevVersion && showDev)
|
||||
? LL.UPGRADE()
|
||||
: LL.REINSTALL()}
|
||||
…
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
const content = useMemo(() => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||
}
|
||||
|
||||
const isDev = data.emsesp_version.includes('dev');
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box p={2} border="1px solid grey" borderRadius={2}>
|
||||
@@ -312,9 +474,27 @@ const Version = () => {
|
||||
</Grid>
|
||||
<Grid size={{ xs: 8, md: 10 }}>
|
||||
<Typography>
|
||||
{getPlatform()}
|
||||
{platform}
|
||||
<Typography variant="caption">
|
||||
({data.psram ? '+PSRAM' : '-PSRAM'})
|
||||
(
|
||||
{data.psram ? (
|
||||
<CheckIcon
|
||||
color="success"
|
||||
sx={{
|
||||
fontSize: '1.5em',
|
||||
verticalAlign: 'middle'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CloseIcon
|
||||
color="error"
|
||||
sx={{
|
||||
fontSize: '1.5em',
|
||||
verticalAlign: 'middle'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
PSRAM)
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Grid>
|
||||
@@ -386,16 +566,11 @@ const Version = () => {
|
||||
</Grid>
|
||||
<Grid size={{ xs: 8, md: 10 }}>
|
||||
<Typography>
|
||||
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
|
||||
{latestVersion.name}
|
||||
</Link>
|
||||
{latestVersion.published_at && (
|
||||
<Typography component="span" variant="caption">
|
||||
(
|
||||
{formatTimeAgo(new Date(latestVersion.published_at))})
|
||||
</Typography>
|
||||
)}
|
||||
{!usingDevVersion && showButtons(false)}
|
||||
{latestVersion?.name}
|
||||
<IconButton onClick={() => setShowVersionInfo(1)}>
|
||||
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
{showButtons(false)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
@@ -404,37 +579,14 @@ const Version = () => {
|
||||
</Grid>
|
||||
<Grid size={{ xs: 8, md: 10 }}>
|
||||
<Typography>
|
||||
<Link target="_blank" href={DEV_RELNOTES_URL} color="primary">
|
||||
{latestDevVersion.name}
|
||||
</Link>
|
||||
{latestDevVersion.published_at && (
|
||||
<Typography component="span" variant="caption">
|
||||
(
|
||||
{formatTimeAgo(new Date(latestDevVersion.published_at))})
|
||||
</Typography>
|
||||
)}
|
||||
{latestDevVersion?.name}
|
||||
<IconButton onClick={() => setShowVersionInfo(2)}>
|
||||
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
{showButtons(true)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{upgradeAvailable ? (
|
||||
<Typography mt={2} color="warning">
|
||||
<InfoOutlinedIcon
|
||||
color="warning"
|
||||
sx={{ verticalAlign: 'middle', mr: 2 }}
|
||||
/>
|
||||
{LL.UPGRADE_AVAILABLE()}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography mt={2} color="success">
|
||||
<CheckIcon
|
||||
color="success"
|
||||
sx={{ verticalAlign: 'middle', mr: 2 }}
|
||||
/>
|
||||
{LL.LATEST_VERSION()}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Typography mt={2} color="warning">
|
||||
@@ -444,7 +596,25 @@ const Version = () => {
|
||||
)}
|
||||
{me.admin && (
|
||||
<>
|
||||
{renderInstallDialog()}
|
||||
<VersionInfoDialog
|
||||
showVersionInfo={showVersionInfo}
|
||||
latestVersion={latestVersion}
|
||||
latestDevVersion={latestDevVersion}
|
||||
locale={locale}
|
||||
LL={LL}
|
||||
onClose={handleVersionInfoClose}
|
||||
/>
|
||||
<InstallDialog
|
||||
openInstallDialog={openInstallDialog}
|
||||
fetchDevVersion={fetchDevVersion}
|
||||
latestVersion={latestVersion}
|
||||
latestDevVersion={latestDevVersion}
|
||||
downloadOnly={downloadOnly}
|
||||
platform={platform}
|
||||
LL={LL}
|
||||
onClose={closeInstallDialog}
|
||||
onInstall={installFirmwareURL}
|
||||
/>
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
{LL.UPLOAD()}
|
||||
</Typography>
|
||||
@@ -454,11 +624,30 @@ const Version = () => {
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}, [
|
||||
data,
|
||||
error,
|
||||
loadData,
|
||||
LL,
|
||||
platform,
|
||||
isDev,
|
||||
internetLive,
|
||||
latestVersion,
|
||||
latestDevVersion,
|
||||
showVersionInfo,
|
||||
locale,
|
||||
openInstallDialog,
|
||||
fetchDevVersion,
|
||||
downloadOnly,
|
||||
me.admin,
|
||||
showButtons,
|
||||
handleVersionInfoClose,
|
||||
closeInstallDialog,
|
||||
installFirmwareURL,
|
||||
doRestart
|
||||
]);
|
||||
|
||||
return (
|
||||
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
|
||||
);
|
||||
return <SectionContent>{restarting ? <SystemMonitor /> : content}</SectionContent>;
|
||||
};
|
||||
|
||||
export default Version;
|
||||
export default memo(Version);
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import type { FC } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { Box } from '@mui/material';
|
||||
import type { BoxProps } from '@mui/material';
|
||||
|
||||
const ButtonRow: FC<BoxProps> = ({ children, ...rest }) => (
|
||||
const ButtonRow = memo<BoxProps>(({ children, ...rest }) => (
|
||||
<Box
|
||||
sx={{
|
||||
'& button, & a, & .MuiCard-root': {
|
||||
mt: 2,
|
||||
mx: 0.6,
|
||||
'&:last-child': {
|
||||
mr: 0
|
||||
},
|
||||
'&:first-of-type': {
|
||||
ml: 0
|
||||
}
|
||||
'&:last-child': { mr: 0 },
|
||||
'&:first-of-type': { ml: 0 }
|
||||
}
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
));
|
||||
|
||||
ButtonRow.displayName = 'ButtonRow';
|
||||
|
||||
export default ButtonRow;
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
import { Tooltip, type TooltipProps, styled, tooltipClasses } from '@mui/material';
|
||||
import { Tooltip, type TooltipProps } from '@mui/material';
|
||||
|
||||
export const ButtonTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} placement="top" arrow classes={{ popper: className }} />
|
||||
))(({ theme }) => ({
|
||||
[`& .${tooltipClasses.arrow}`]: {
|
||||
color: theme.palette.success.main
|
||||
},
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
backgroundColor: theme.palette.success.main,
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
boxShadow: theme.shadows[1],
|
||||
fontSize: 10
|
||||
}
|
||||
}));
|
||||
export const ButtonTooltip = ({ children, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props}>{children}</Tooltip>
|
||||
);
|
||||
|
||||
export default ButtonTooltip;
|
||||
|
||||
@@ -1,30 +1,20 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { Divider, Paper } from '@mui/material';
|
||||
import { Paper } from '@mui/material';
|
||||
|
||||
import type { RequiredChildrenProps } from 'utils';
|
||||
|
||||
interface SectionContentProps extends RequiredChildrenProps {
|
||||
title?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const SectionContent: FC<SectionContentProps> = (props) => {
|
||||
const { children, title, id } = props;
|
||||
const { children, id } = props;
|
||||
return (
|
||||
<Paper id={id} sx={{ p: 2, m: 2 }}>
|
||||
{title && (
|
||||
<Divider
|
||||
sx={{
|
||||
pb: 2,
|
||||
borderColor: 'primary.main',
|
||||
fontSize: 20,
|
||||
color: 'primary.main'
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Divider>
|
||||
)}
|
||||
<Paper
|
||||
id={id}
|
||||
sx={{ p: 1.5, m: 1.5, borderRadius: 3, border: '1px solid rgb(65, 65, 65)' }}
|
||||
>
|
||||
{children}
|
||||
</Paper>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
// Optimized exports - use direct exports to reduce bundle size
|
||||
export { default as SectionContent } from './SectionContent';
|
||||
export { default as ButtonRow } from './ButtonRow';
|
||||
export { default as MessageBox } from './MessageBox';
|
||||
export { default as ButtonTooltip } from './ButtonTooltip';
|
||||
|
||||
// Re-export sub-modules
|
||||
export * from './inputs';
|
||||
export * from './layout';
|
||||
export * from './loading';
|
||||
export * from './routing';
|
||||
export * from './upload';
|
||||
export { default as SectionContent } from './SectionContent';
|
||||
export { default as ButtonRow } from './ButtonRow';
|
||||
export { default as MessageBox } from './MessageBox';
|
||||
|
||||
// Specific routing exports
|
||||
export { default as BlockNavigation } from './routing/BlockNavigation';
|
||||
export { default as ButtonTooltip } from './ButtonTooltip';
|
||||
|
||||
@@ -16,14 +16,14 @@ const ValidatedTextField: FC<ValidatedTextFieldProps> = ({
|
||||
fieldErrors,
|
||||
...rest
|
||||
}) => {
|
||||
const errors = fieldErrors && fieldErrors[rest.name];
|
||||
const renderErrors = () =>
|
||||
errors &&
|
||||
errors.map((e) => <FormHelperText key={e.message}>{e.message}</FormHelperText>);
|
||||
const errors = fieldErrors?.[rest.name];
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextField error={!!errors} {...rest} />
|
||||
{renderErrors()}
|
||||
{errors?.map((e) => (
|
||||
<FormHelperText key={e.message}>{e.message}</FormHelperText>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useLocation } from 'react-router';
|
||||
|
||||
@@ -13,22 +13,26 @@ import { LayoutContext } from './context';
|
||||
|
||||
export const DRAWER_WIDTH = 210;
|
||||
|
||||
const Layout: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
const Layout: FC<RequiredChildrenProps> = memo(({ children }) => {
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const [title, setTitle] = useState(PROJECT_NAME);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
setMobileOpen(!mobileOpen);
|
||||
};
|
||||
// Memoize drawer toggle handler to prevent unnecessary re-renders
|
||||
const handleDrawerToggle = useCallback(() => {
|
||||
setMobileOpen((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
useEffect(() => setMobileOpen(false), [pathname]);
|
||||
// Close drawer when route changes
|
||||
useEffect(() => {
|
||||
setMobileOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
// cache the object to prevent unnecessary re-renders
|
||||
const obj = useMemo(() => ({ title, setTitle }), [title]);
|
||||
// Memoize context value to prevent unnecessary re-renders
|
||||
const contextValue = useMemo(() => ({ title, setTitle }), [title]);
|
||||
|
||||
return (
|
||||
<LayoutContext.Provider value={obj}>
|
||||
<LayoutContext.Provider value={contextValue}>
|
||||
<LayoutAppBar title={title} onToggleDrawer={handleDrawerToggle} />
|
||||
<LayoutDrawer mobileOpen={mobileOpen} onClose={handleDrawerToggle} />
|
||||
<Box component="main" sx={{ marginLeft: { md: `${DRAWER_WIDTH}px` } }}>
|
||||
@@ -37,6 +41,6 @@ const Layout: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
</Box>
|
||||
</LayoutContext.Provider>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Layout;
|
||||
|
||||
@@ -73,19 +73,6 @@ const LayoutMenu = () => {
|
||||
>
|
||||
<ListItemText
|
||||
primary={LL.MODULES()}
|
||||
// secondary={
|
||||
// LL.CUSTOMIZATIONS() +
|
||||
// ', ' +
|
||||
// LL.SCHEDULER() +
|
||||
// ', ' +
|
||||
// LL.CUSTOM_ENTITIES(0) +
|
||||
// '...'
|
||||
// }
|
||||
// secondaryTypographyProps={{
|
||||
// noWrap: true,
|
||||
// fontSize: 12,
|
||||
// color: menuOpen ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)'
|
||||
// }}
|
||||
sx={{ my: 0 }}
|
||||
slotProps={{
|
||||
primary: {
|
||||
|
||||
@@ -23,11 +23,52 @@ const LayoutMenuItem = ({
|
||||
const selected = routeMatches(to, pathname);
|
||||
|
||||
return (
|
||||
<ListItemButton component={Link} to={to} disabled={disabled} selected={selected}>
|
||||
<ListItemIcon sx={{ color: selected ? '#90caf9' : '#9e9e9e' }}>
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
to={to}
|
||||
disabled={disabled || false}
|
||||
selected={selected}
|
||||
sx={{
|
||||
transition: 'all 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)',
|
||||
transform: selected ? 'scale(1.02)' : 'scale(1)',
|
||||
backgroundColor: selected ? 'rgba(144, 202, 249, 0.1)' : 'transparent',
|
||||
borderRadius: '8px',
|
||||
margin: '2px 8px',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(68, 82, 211, 0.39)',
|
||||
transform: selected ? 'scale(1.02)' : 'scale(1.01)'
|
||||
},
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: selected ? '4px' : '0px',
|
||||
backgroundColor: '#90caf9',
|
||||
borderRadius: '0 2px 2px 0',
|
||||
transition: 'width 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
color: selected ? '#90caf9' : '#9e9e9e',
|
||||
transition: 'color 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)',
|
||||
transform: selected ? 'scale(1.1)' : 'scale(1)',
|
||||
transitionProperty: 'color, transform'
|
||||
}}
|
||||
>
|
||||
<Icon />
|
||||
</ListItemIcon>
|
||||
<ListItemText sx={{ color: selected ? '#90caf9' : '#f5f5f5' }}>
|
||||
<ListItemText
|
||||
sx={{
|
||||
color: selected ? '#90caf9' : '#f5f5f5',
|
||||
transition: 'color 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)',
|
||||
// fontWeight: selected ? '600' : '400',
|
||||
transitionProperty: 'color, font-weight'
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</ListItemText>
|
||||
</ListItemButton>
|
||||
|
||||
@@ -58,12 +58,22 @@ const LayoutMenuItem = ({
|
||||
}
|
||||
>
|
||||
<ListItemButton component={Link} to={to}>
|
||||
<RenderIcon icon={icon} bgcolor={bgcolor} label={label} text={text} />
|
||||
<RenderIcon
|
||||
icon={icon}
|
||||
{...(bgcolor && { bgcolor })}
|
||||
label={label}
|
||||
text={text}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
) : (
|
||||
<ListItem>
|
||||
<RenderIcon icon={icon} bgcolor={bgcolor} label={label} text={text} />
|
||||
<RenderIcon
|
||||
icon={icon}
|
||||
{...(bgcolor && { bgcolor })}
|
||||
label={label}
|
||||
text={text}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
</>
|
||||
|
||||
22
interface/src/components/loading/LazyLoader.tsx
Normal file
22
interface/src/components/loading/LazyLoader.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
import { Box, CircularProgress } from '@mui/material';
|
||||
|
||||
const LazyLoader = memo(() => (
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
minHeight="200px"
|
||||
sx={{
|
||||
backgroundColor: 'background.default',
|
||||
borderRadius: 1,
|
||||
border: '1px solid',
|
||||
borderColor: 'divider'
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={40} />
|
||||
</Box>
|
||||
));
|
||||
|
||||
export default LazyLoader;
|
||||
@@ -1,2 +1,3 @@
|
||||
export { default as LoadingSpinner } from './LoadingSpinner';
|
||||
export { default as FormLoader } from './FormLoader';
|
||||
export { default as LazyLoader } from './LazyLoader';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Path } from 'react-router';
|
||||
|
||||
import type * as H from 'history';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import type { Me, SignInRequest, SignInResponse } from 'types';
|
||||
|
||||
@@ -18,7 +17,7 @@ export function getStorage() {
|
||||
return localStorage || sessionStorage;
|
||||
}
|
||||
|
||||
export function storeLoginRedirect(location?: H.Location) {
|
||||
export function storeLoginRedirect(location?: { pathname: string; search: string }) {
|
||||
if (location) {
|
||||
getStorage().setItem(SIGN_IN_PATHNAME, location.pathname);
|
||||
getStorage().setItem(SIGN_IN_SEARCH, location.search);
|
||||
@@ -36,7 +35,7 @@ export function fetchLoginRedirect(): Partial<Path> {
|
||||
clearLoginRedirect();
|
||||
return {
|
||||
pathname: signInPathname || `/dashboard`,
|
||||
search: (signInPathname && signInSearch) || undefined
|
||||
...(signInPathname && signInSearch && { search: signInSearch })
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,59 @@
|
||||
// Code inspired by Prince Azubuike from https://medium.com/@dprincecoder/creating-a-drag-and-drop-file-upload-component-in-react-a-step-by-step-guide-4d93b6cc21e0
|
||||
import { type ChangeEvent, useRef, useState } from 'react';
|
||||
import {
|
||||
type ChangeEvent,
|
||||
type DragEvent,
|
||||
type MouseEvent,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||
import UploadIcon from '@mui/icons-material/Upload';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { Box, Button, Typography, styled } from '@mui/material';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import './dragNdrop.css';
|
||||
const DocumentUploader = styled(Box)<{ active?: boolean }>(({ theme, active }) => ({
|
||||
border: `2px dashed ${active ? '#6dc24b' : '#4282fe'}`,
|
||||
backgroundColor: '#2e3339',
|
||||
padding: theme.spacing(1.25),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
borderRadius: theme.spacing(1),
|
||||
cursor: 'pointer',
|
||||
minHeight: '120px',
|
||||
transition: 'border-color 0.2s ease-in-out'
|
||||
}));
|
||||
|
||||
const DragNdrop = ({ text, onFileSelected }) => {
|
||||
const UploadInfo = styled(Box)({
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
});
|
||||
|
||||
const FileInfo = styled(Box)({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
});
|
||||
|
||||
const FileName = styled(Typography)(({ theme }) => ({
|
||||
fontSize: '14px',
|
||||
color: '#6dc24b',
|
||||
margin: theme.spacing(1, 0)
|
||||
}));
|
||||
|
||||
interface DragNdropProps {
|
||||
text: string;
|
||||
onFileSelected: (file: File) => void;
|
||||
}
|
||||
|
||||
const DragNdrop = ({ text, onFileSelected }: DragNdropProps) => {
|
||||
const [file, setFile] = useState<File>();
|
||||
const [dragged, setDragged] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
@@ -28,14 +71,17 @@ const DragNdrop = ({ text, onFileSelected }) => {
|
||||
};
|
||||
|
||||
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.files) {
|
||||
if (!e.target.files || e.target.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
checkFileExtension(e.target.files[0]);
|
||||
const selectedFile = e.target.files[0];
|
||||
if (selectedFile) {
|
||||
checkFileExtension(selectedFile);
|
||||
}
|
||||
e.target.value = ''; // this is to allow the same file to be selected again
|
||||
};
|
||||
|
||||
const handleDrop = (event) => {
|
||||
const handleDrop = (event: DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
const droppedFiles = event.dataTransfer.files;
|
||||
if (droppedFiles.length > 0) {
|
||||
@@ -43,38 +89,40 @@ const DragNdrop = ({ text, onFileSelected }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (event) => {
|
||||
const handleRemoveFile = (event: MouseEvent<HTMLButtonElement>) => {
|
||||
event.stopPropagation();
|
||||
setFile(undefined);
|
||||
setDragged(false);
|
||||
};
|
||||
|
||||
const handleUploadClick = (event) => {
|
||||
const handleUploadClick = (event: MouseEvent<HTMLButtonElement>) => {
|
||||
event.stopPropagation();
|
||||
onFileSelected(file);
|
||||
if (file) {
|
||||
onFileSelected(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBrowseClick = () => {
|
||||
inputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleDragOver = (event) => {
|
||||
const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault(); // prevent file from being opened
|
||||
setDragged(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`document-uploader ${file || dragged ? 'active' : ''}`}
|
||||
<DocumentUploader
|
||||
active={!!(file || dragged)}
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={() => setDragged(false)}
|
||||
onClick={handleBrowseClick}
|
||||
>
|
||||
<div className="upload-info">
|
||||
<UploadInfo>
|
||||
<CloudUploadIcon sx={{ marginRight: 4 }} color="primary" fontSize="large" />
|
||||
<p>{text}</p>
|
||||
</div>
|
||||
<Typography>{text}</Typography>
|
||||
</UploadInfo>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
@@ -88,9 +136,9 @@ const DragNdrop = ({ text, onFileSelected }) => {
|
||||
|
||||
{file && (
|
||||
<>
|
||||
<div className="file-info">
|
||||
<p>{file.name}</p>
|
||||
</div>
|
||||
<FileInfo>
|
||||
<FileName>{file.name}</FileName>
|
||||
</FileInfo>
|
||||
<Box>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
@@ -112,7 +160,7 @@ const DragNdrop = ({ text, onFileSelected }) => {
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DocumentUploader>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,12 @@ import { useI18nContext } from 'i18n/i18n-react';
|
||||
import DragNdrop from './DragNdrop';
|
||||
import { LinearProgressWithLabel } from './LinearProgressWithLabel';
|
||||
|
||||
const SingleUpload = ({ text, doRestart }) => {
|
||||
interface SingleUploadProps {
|
||||
text: string;
|
||||
doRestart: () => void;
|
||||
}
|
||||
|
||||
const SingleUpload = ({ text, doRestart }: SingleUploadProps) => {
|
||||
const [md5, setMd5] = useState<string>();
|
||||
const [file, setFile] = useState<File>();
|
||||
const { LL } = useI18nContext();
|
||||
@@ -25,8 +30,8 @@ const SingleUpload = ({ text, doRestart }) => {
|
||||
} = useRequest(SystemApi.uploadFile, {
|
||||
immediate: false
|
||||
}).onSuccess(({ data }) => {
|
||||
if (data) {
|
||||
setMd5(data.md5 as string);
|
||||
if (data && typeof data === 'object' && 'md5' in data) {
|
||||
setMd5((data as { md5: string }).md5);
|
||||
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
|
||||
setFile(undefined);
|
||||
} else {
|
||||
@@ -34,16 +39,19 @@ const SingleUpload = ({ text, doRestart }) => {
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(async () => {
|
||||
if (file) {
|
||||
await sendUpload(file).catch((error: Error) => {
|
||||
if (error.message === 'The user aborted a request') {
|
||||
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
|
||||
} else {
|
||||
toast.warning('Invalid file extension or incompatible bin file');
|
||||
}
|
||||
});
|
||||
}
|
||||
useEffect(() => {
|
||||
const uploadFile = async () => {
|
||||
if (file) {
|
||||
await sendUpload(file).catch((error: Error) => {
|
||||
if (error.message.includes('The user aborted a request')) {
|
||||
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
|
||||
} else {
|
||||
toast.warning('Invalid file extension or incompatible bin file');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
void uploadFile();
|
||||
}, [file]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
.document-uploader {
|
||||
border: 2px dashed #4282fe;
|
||||
background-color: #2e3339;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
border-color: #6dc24b;
|
||||
}
|
||||
|
||||
.upload-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #6dc24b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,12 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
|
||||
// cache object to prevent re-renders
|
||||
const obj = useMemo(
|
||||
() => ({ signIn, signOut, me, refresh }),
|
||||
() => ({
|
||||
signIn,
|
||||
signOut,
|
||||
refresh,
|
||||
...(me && { me })
|
||||
}),
|
||||
[signIn, signOut, me, refresh]
|
||||
);
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ const cz: Translation = {
|
||||
UPLOAD: 'Nahrát',
|
||||
DOWNLOAD: '{{S|s|s}}táhnout',
|
||||
INSTALL: 'Instalovat',
|
||||
REINSTALL: 'Znovu instalovat',
|
||||
ABORTED: 'přerušeno',
|
||||
FAILED: 'neúspěšné',
|
||||
SUCCESSFUL: 'úspěšné',
|
||||
@@ -187,7 +188,7 @@ const cz: Translation = {
|
||||
COMPACT: 'Kompaktní',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Vytvořte zálohu svého nastavení a konfigurace',
|
||||
UPLOAD_TEXT: 'Nahrajte nový soubor firmwaru (.bin) nebo záložní soubor (.json)',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
||||
UPLOAD_DROP_TEXT: 'Přetáhněte soubor sem nebo klikněte pro výběr',
|
||||
ERROR: 'Neočekávaná chyba, zkuste to prosím znovu',
|
||||
TIME_SET: 'Čas nastaven',
|
||||
MANAGE_USERS: 'Spravovat uživatele',
|
||||
@@ -331,28 +332,28 @@ const cz: Translation = {
|
||||
ALLVALUES: 'Všechny hodnoty',
|
||||
SPECIAL_FUNCTIONS: 'Speciální funkce',
|
||||
WAIT_FIRMWARE: 'Firmware se nahrává a instaluje',
|
||||
INSTALL_VERSION: 'Tímto se instalovat verze {0}. Jste si jistí?',
|
||||
UPGRADE_AVAILABLE: 'Je k dispozici aktualizace firmwaru!',
|
||||
LATEST_VERSION: 'Používáte nejnovější verzi firmwaru',
|
||||
INSTALL_VERSION: 'Tímto se {0} verze {1}. Jste si jistí?',
|
||||
UPDATE_AVAILABLE: 'aktualizace dostupná',
|
||||
LATEST_VERSION: 'Používáte nejnovější verzi {0}firmwaru',
|
||||
PLEASE_WAIT: 'Prosím čekejte',
|
||||
RESTARTING_PRE: 'Inicializace',
|
||||
RESTARTING_POST: 'Příprava',
|
||||
AUTO_SCROLL: 'Automatické rolování',
|
||||
DASHBOARD: 'Dashboard',
|
||||
DEVELOPER_MODE: 'Režim vývojáře',
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
BYTES: 'Bajty',
|
||||
BITMASK: 'Bit Mask',
|
||||
DUPLICATE: 'Duplikát',
|
||||
UPGRADE: 'Upgrade',
|
||||
DASHBOARD_1: 'Všechny aktivní entity EMS jsou označené jako oblíbené. Všechny vlastní entity, harmonogramy a externí sensory jsou zobrazeny níže.',
|
||||
NO_DATA_1: 'Nebyly nalezeny žádné oblíbené entity. Použijte',
|
||||
NO_DATA_2: 'modul sloužící k jejich výběru.',
|
||||
NO_DATA_3: 'Pro zobrazení všech dostupných entit navštivte',
|
||||
NO_DATA_1: 'Nebyly nalezeny žádné oblíbené entity. Použijte modul',
|
||||
NO_DATA_2: 'pro jejich výběr.',
|
||||
NO_DATA_3: 'Pro zobrazení všech dostupných entit navštivte stránku',
|
||||
THIS_VERSION: 'Tato verze',
|
||||
PLATFORM: 'Platforma',
|
||||
RELEASE_TYPE: 'Typ sestavení',
|
||||
REINSTALL: 'Přeinstalovat',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Pro automatickou kontrolu a instalaci aktualizací je třeba internetové připojení',
|
||||
SWITCH_RELEASE_TYPE: 'Přepnout na {0} verzi',
|
||||
FIRMWARE_VERSION_INFO: 'Informace o verzi firmwaru'
|
||||
};
|
||||
|
||||
export default cz;
|
||||
|
||||
@@ -42,7 +42,7 @@ const de: Translation = {
|
||||
CANCEL: 'Abbrechen',
|
||||
RESET: 'Zurücksetzen',
|
||||
APPLY_CHANGES: 'Änderungen anwenden ({0})',
|
||||
UPDATE: 'Update',
|
||||
UPDATE: 'Aktualisieren',
|
||||
EXECUTE: 'Ausführen',
|
||||
REMOVE: 'Entfernen',
|
||||
PROBLEM_UPDATING: 'Problem beim Aktualisieren',
|
||||
@@ -160,8 +160,9 @@ const de: Translation = {
|
||||
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 Systemdetails und hängen Sie sie an das Support-Issue an',
|
||||
UPLOAD: 'Hochladen',
|
||||
DOWNLOAD: '{{H|h|h}}erunterladen',
|
||||
DOWNLOAD: '{{Herunterladen|heruntergeladen|}}',
|
||||
INSTALL: 'Installieren',
|
||||
REINSTALL: 'Neu installieren',
|
||||
ABORTED: 'abgebrochen',
|
||||
FAILED: 'gescheitert',
|
||||
SUCCESSFUL: 'erfolgreich',
|
||||
@@ -331,9 +332,9 @@ const de: Translation = {
|
||||
ALLVALUES: 'Alle Werte',
|
||||
SPECIAL_FUNCTIONS: 'Sonderfunktionen',
|
||||
WAIT_FIRMWARE: 'Die Firmware wird hochgeladen und installiert.',
|
||||
INSTALL_VERSION: 'Dadurch wird die Version {0} heruntergeladen. Sind Sie sicher?',
|
||||
UPGRADE_AVAILABLE: 'Es ist ein Firmware-Upgrade verfügbar.',
|
||||
LATEST_VERSION: 'Sie verwenden die neueste Firmware-Version',
|
||||
INSTALL_VERSION: 'Dadurch wird die Version {1} {0}. Sind Sie sicher?',
|
||||
UPDATE_AVAILABLE: 'Firmware-Update verfügbar',
|
||||
LATEST_VERSION: 'Sie verwenden die neueste {0} Firmware-Version',
|
||||
PLEASE_WAIT: 'Bitte warten',
|
||||
RESTARTING_PRE: 'Initialisierung',
|
||||
RESTARTING_POST: 'Vorbereitung',
|
||||
@@ -343,7 +344,6 @@ const de: Translation = {
|
||||
BYTES: 'Bytes',
|
||||
BITMASK: 'Bit Maske',
|
||||
DUPLICATE: 'Kopieren',
|
||||
UPGRADE: 'Aktualisieren',
|
||||
DASHBOARD_1: 'Alle EMS-Entitäten, die aktiv und als Favorit markiert sind, sowie alle benutzerdefinierten Entitäten, Zeitpläne und externen Sensordaten werden unten angezeigt.',
|
||||
NO_DATA_1: 'Keine favorisierten EMS-Entitäten gefunden! Verwenden Sie das Modul',
|
||||
NO_DATA_2: ', um sie zu markieren.',
|
||||
@@ -351,8 +351,9 @@ const de: Translation = {
|
||||
THIS_VERSION: 'Diese Version',
|
||||
PLATFORM: 'Plattform',
|
||||
RELEASE_TYPE: 'Release Typ',
|
||||
REINSTALL: 'Neu installieren',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internetverbindung erforderlich für automatische Version-Überprüfung und -Aktualisierung',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Für die automatische Versionsprüfung und Aktualisierung ist eine Internetverbindung erforderlich',
|
||||
SWITCH_RELEASE_TYPE: 'Zum {0}-Release wechseln',
|
||||
FIRMWARE_VERSION_INFO: 'Firmware-Versionsinformation'
|
||||
};
|
||||
|
||||
export default de;
|
||||
|
||||
@@ -162,6 +162,7 @@ const en: Translation = {
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: '{{D|d|d}}ownload',
|
||||
INSTALL: 'Install',
|
||||
REINSTALL: 'Reinstall',
|
||||
ABORTED: 'aborted',
|
||||
FAILED: 'failed',
|
||||
SUCCESSFUL: 'successful',
|
||||
@@ -179,10 +180,10 @@ const en: Translation = {
|
||||
DEVELOPMENT: 'Development',
|
||||
UPTIME: 'System Uptime',
|
||||
FREE_MEMORY: 'Free Memory',
|
||||
PSRAM: 'PSRAM (Size / Free)',
|
||||
FLASH: 'Flash Chip (Size , Speed)',
|
||||
APPSIZE: 'Application (Partition: Used / Free)',
|
||||
FILESYSTEM: 'File System (Used / Free)',
|
||||
PSRAM: 'PSRAM (size / free)',
|
||||
FLASH: 'Flash Chip (size , speed)',
|
||||
APPSIZE: 'Application (partition: used / free)',
|
||||
FILESYSTEM: 'File System (used / free)',
|
||||
BUFFER_SIZE: 'Max Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
|
||||
@@ -331,9 +332,9 @@ const en: Translation = {
|
||||
ALLVALUES: 'All Values',
|
||||
SPECIAL_FUNCTIONS: 'Special Functions',
|
||||
WAIT_FIRMWARE: 'Firmware is uploading and installing',
|
||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?',
|
||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!',
|
||||
LATEST_VERSION: 'You are using the latest firmware version',
|
||||
INSTALL_VERSION: 'This will {0} version {1}. Are you sure?',
|
||||
UPDATE_AVAILABLE: 'update available',
|
||||
LATEST_VERSION: 'You are using the latest {0} firmware version',
|
||||
PLEASE_WAIT: 'Please wait',
|
||||
RESTARTING_PRE: 'Initializing',
|
||||
RESTARTING_POST: 'Preparing',
|
||||
@@ -344,15 +345,15 @@ const en: Translation = {
|
||||
BYTES: 'Bytes',
|
||||
BITMASK: 'Bit Mask',
|
||||
DUPLICATE: 'Duplicate',
|
||||
UPGRADE: 'Upgrade',
|
||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the',
|
||||
NO_DATA_2: 'module to mark them.',
|
||||
NO_DATA_3: 'To see all available entities go to',
|
||||
THIS_VERSION: 'This Version',
|
||||
PLATFORM: 'Platform',
|
||||
RELEASE_TYPE: 'Release Type',
|
||||
REINSTALL: 'Re-install',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
|
||||
SWITCH_RELEASE_TYPE: 'Switch to {0} release',
|
||||
FIRMWARE_VERSION_INFO: 'Firmware Version Information'
|
||||
};
|
||||
|
||||
export default en;
|
||||
|
||||
@@ -41,9 +41,9 @@ const fr: Translation = {
|
||||
CHANGE_VALUE: 'Changer la valeur',
|
||||
CANCEL: 'Annuler',
|
||||
RESET: 'Réinitialiser',
|
||||
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
||||
UPDATE: 'Update', // TODO translate
|
||||
EXECUTE: 'Execute', // TODO translate
|
||||
APPLY_CHANGES: 'Appliquer les changements ({0})',
|
||||
UPDATE: 'Update',
|
||||
EXECUTE: 'Execute',
|
||||
REMOVE: 'Enlever',
|
||||
PROBLEM_UPDATING: 'Problème lors de la mise à jour',
|
||||
PROBLEM_LOADING: 'Problème lors du chargement',
|
||||
@@ -66,13 +66,13 @@ const fr: Translation = {
|
||||
TEMP_SENSOR: 'Capteur de température',
|
||||
TEMP_SENSORS: 'Capteurs de température',
|
||||
WRITE_CMD_SENT: 'Envoyer la commande sent',
|
||||
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_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 ?',
|
||||
DATA_TRAFFIC: 'Data Traffic', // TODO translate
|
||||
DATA_TRAFFIC: 'Traffic des données',
|
||||
EMS_DEVICE: 'Appareils EMS',
|
||||
SUCCESS: 'SUCCÈS',
|
||||
FAIL: 'ÉCHEC',
|
||||
@@ -114,9 +114,9 @@ const fr: Translation = {
|
||||
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', // TODO translate
|
||||
HEATINGOFF: 'Démarrer le chauffage avec le chauffage forcé éteint',
|
||||
REMOTE_TIMEOUT: 'Remote timeout',
|
||||
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature', // TODO translate
|
||||
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature',
|
||||
MIN_DURATION: 'Wait time',
|
||||
ENABLE_SHOWER_TIMER: 'Activer la minuterie de la douche',
|
||||
ENABLE_SHOWER_ALERT: 'Activer les alertes de durée de douche',
|
||||
@@ -148,7 +148,7 @@ const fr: Translation = {
|
||||
CUSTOMIZATIONS_HELP_3: "désactiver l'action d'écriture",
|
||||
CUSTOMIZATIONS_HELP_4: "exclure de MQTT et de l'API",
|
||||
CUSTOMIZATIONS_HELP_5: 'cacher des appareils',
|
||||
CUSTOMIZATIONS_HELP_6: 'remove from memory', // TODO translate
|
||||
CUSTOMIZATIONS_HELP_6: 'supprimer de la mémoire',
|
||||
SELECT_DEVICE: 'Sélectionnez un appareil',
|
||||
SET_ALL: 'tout régler',
|
||||
OPTIONS: 'Options',
|
||||
@@ -162,20 +162,21 @@ const fr: Translation = {
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: '{{D|d|d}}ownload',
|
||||
INSTALL: 'Installer',
|
||||
REINSTALL: 'Réinstaller',
|
||||
ABORTED: 'annulé',
|
||||
FAILED: 'échoué',
|
||||
SUCCESSFUL: 'réussi',
|
||||
SYSTEM: 'Système',
|
||||
LOG_OF: '{0} Log',
|
||||
STATUS_OF: 'Statut {0}',
|
||||
DOWNLOAD_UPLOAD: 'Download/Upload', // TODO translate
|
||||
DOWNLOAD_UPLOAD: 'Télécharger/Mettre à jour',
|
||||
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 ?",
|
||||
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
|
||||
STABLE: 'Stable', // TODO translate
|
||||
AVAILABLE_VERSION: 'Versions disponibles',
|
||||
STABLE: 'Stable',
|
||||
DEVELOPMENT: 'Développement',
|
||||
UPTIME: 'Durée de fonctionnement du système',
|
||||
FREE_MEMORY: 'Libre Memory',
|
||||
@@ -185,9 +186,9 @@ const fr: Translation = {
|
||||
FILESYSTEM: 'File System (Utilisée / Libre)',
|
||||
BUFFER_SIZE: 'Max taille du buffer',
|
||||
COMPACT: 'Compact',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
|
||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Créer une sauvegarde de vos paramètres et configurations',
|
||||
UPLOAD_TEXT: 'Télécharger un nouveau fichier firmware (.bin) ou une sauvegarde (.json)',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
|
||||
ERROR: 'Erreur inattendue, veuillez réessayer',
|
||||
TIME_SET: 'Time set',
|
||||
MANAGE_USERS: 'Gérer les utilisateurs',
|
||||
@@ -217,7 +218,7 @@ const fr: Translation = {
|
||||
MQTT_PUBLISH_TEXT_2: 'Publier vers des topics de commande (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Activer la découverte MQTT',
|
||||
MQTT_PUBLISH_TEXT_4: 'Préfixe pour les topics découverte',
|
||||
MQTT_PUBLISH_TEXT_5: 'Discovery type', // TODO translate
|
||||
MQTT_PUBLISH_TEXT_5: 'Type de découverte',
|
||||
MQTT_PUBLISH_INTERVALS: 'Intervalles de publication',
|
||||
MQTT_INT_BOILER: 'Chaudières et pompes à chaleur',
|
||||
MQTT_INT_THERMOSTATS: 'Thermostats',
|
||||
@@ -226,10 +227,10 @@ const fr: Translation = {
|
||||
MQTT_INT_WATER: 'Modules eau',
|
||||
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_ENTITY_FORMAT: 'Format de l\'ID de l\'entité',
|
||||
MQTT_ENTITY_FORMAT_0: 'Instance unique, nom long (v3.4)',
|
||||
MQTT_ENTITY_FORMAT_1: 'Instance unique, nom court',
|
||||
MQTT_ENTITY_FORMAT_2: 'Instances multiples, nom court',
|
||||
MQTT_CLEAN_SESSION: 'Flag Clean Session',
|
||||
MQTT_RETAIN_FLAG: 'Toujours activer le Retain Flag',
|
||||
INACTIVE: 'Inactif',
|
||||
@@ -260,7 +261,7 @@ const fr: Translation = {
|
||||
NETWORK_SCANNER: 'Scan réseau',
|
||||
NETWORK_NO_WIFI: 'Pas de réseau WiFi trouvé',
|
||||
NETWORK_BLANK_SSID: 'laisser vide pour désactiver le WiFi',
|
||||
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
|
||||
NETWORK_BLANK_BSSID: 'laisser vide pour utiliser uniquement le SSID',
|
||||
TX_POWER: 'Puissance Tx',
|
||||
HOSTNAME: "Nom d'hôte",
|
||||
NETWORK_DISABLE_SLEEP: 'Désactiver le mode veille du WiFi',
|
||||
@@ -280,79 +281,79 @@ const fr: Translation = {
|
||||
ENTITY: 'entité',
|
||||
MIN: 'min',
|
||||
MAX: 'max',
|
||||
BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate
|
||||
BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', // TODO translate
|
||||
STAY: 'Stay', // TODO translate
|
||||
LEAVE: 'Leave', // TODO translate
|
||||
SCHEDULER: 'Scheduler', // TODO translate
|
||||
SCHEDULER_HELP_1: 'Automate commands by adding scheduled events below. Set a unique Name to enable/disable activation via API/MQTT', // TODO translate
|
||||
SCHEDULER_HELP_2: 'Use 00:00 to trigger once on start-up', // TODO translate
|
||||
SCHEDULE: 'Schedule', // TODO translate
|
||||
TIME: 'Time', // TODO translate
|
||||
TIMER: 'Timer', // TODO translate
|
||||
ONCHANGE: 'Sur le changement', // TODO translate
|
||||
CONDITION: 'Condition', // TODO translate
|
||||
IMMEDIATE: 'Immédiate', // TODO translate
|
||||
SCHEDULE_UPDATED: 'Schedule updated', // TODO translate
|
||||
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
||||
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
||||
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
||||
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
|
||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
|
||||
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||
WRITEABLE: 'Writeable', // TODO translate
|
||||
SHOWING: 'Showing', // TODO translate
|
||||
SEARCH: 'Search', // TODO translate
|
||||
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
|
||||
BLOCK_NAVIGATE_1: 'Vous avez des modifications non enregistrées',
|
||||
BLOCK_NAVIGATE_2: 'Si vous naviguez vers une autre page, vos modifications non enregistrées seront perdues. Êtes-vous sûr de vouloir quitter cette page ?',
|
||||
STAY: 'Rester',
|
||||
LEAVE: 'Quitter',
|
||||
SCHEDULER: 'Scheduler',
|
||||
SCHEDULER_HELP_1: 'Automatiser les commandes en ajoutant des événements programmés ci-dessous. Définissez un nom unique pour activer/désactiver l\'activation via API/MQTT',
|
||||
SCHEDULER_HELP_2: 'Utiliser 00:00 pour déclencher une fois au démarrage',
|
||||
SCHEDULE: 'Programme',
|
||||
TIME: 'Temps',
|
||||
TIMER: 'Minuteur',
|
||||
ONCHANGE: 'Sur le changement',
|
||||
CONDITION: 'Condition',
|
||||
IMMEDIATE: 'Immédiat',
|
||||
SCHEDULE_UPDATED: 'Programme mis à jour',
|
||||
SCHEDULE_TIMER_1: 'au démarrage',
|
||||
SCHEDULE_TIMER_2: 'toutes les minutes',
|
||||
SCHEDULE_TIMER_3: 'toutes les heures',
|
||||
CUSTOM_ENTITIES: 'Entités personnalisées',
|
||||
ENTITIES_HELP_1: 'Récupérer les entités personnalisées du bus EMS',
|
||||
ENTITIES_UPDATED: 'Entités mises à jour',
|
||||
WRITEABLE: 'Écriture',
|
||||
SHOWING: 'Affichage',
|
||||
SEARCH: 'Rechercher',
|
||||
CERT: 'Certificat racine TLS (laisser vide pour l\'insecurité)',
|
||||
ENABLE_TLS: 'Activer TLS',
|
||||
ON: 'On', // TODO translate
|
||||
OFF: 'Off', // TODO translate
|
||||
POLARITY: 'Polarity', // TODO translate
|
||||
ACTIVEHIGH: 'Active High', // TODO translate
|
||||
ACTIVELOW: 'Active Low', // TODO translate
|
||||
UNCHANGED: 'Unchanged', // TODO translate
|
||||
ALWAYS: 'Always', // TODO translate
|
||||
ACTIVITY: 'Activity', // TODO translate
|
||||
CONFIGURE: 'Configure {0}', // TODO translate
|
||||
SYSTEM_MEMORY: 'System Memory', // TODO translate
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
|
||||
SECURITY_1: 'Add or remove users', // TODO translate
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
|
||||
MODULES: 'Module', // TODO translate
|
||||
ON: 'On',
|
||||
OFF: 'Off',
|
||||
POLARITY: 'Polarity',
|
||||
ACTIVEHIGH: 'Actif haut',
|
||||
ACTIVELOW: 'Actif bas',
|
||||
UNCHANGED: 'Inchangé',
|
||||
ALWAYS: 'Toujours',
|
||||
ACTIVITY: 'Activité',
|
||||
CONFIGURE: 'Configurer {0}',
|
||||
SYSTEM_MEMORY: 'Mémoire système',
|
||||
APPLICATION_SETTINGS_1: 'Modifier les paramètres de l\'application EMS-ESP',
|
||||
SECURITY_1: 'Ajouter ou supprimer des utilisateurs',
|
||||
DOWNLOAD_UPLOAD_1: 'Télécharger et mettre à jour les paramètres et le firmware',
|
||||
MODULES: 'Module',
|
||||
MODULES_1: 'Activer ou désactiver les modules externes',
|
||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
||||
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
|
||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
||||
RENAME: 'Rename', // TODO translate
|
||||
MODULES_UPDATED: 'Modules mis à jour',
|
||||
MODULES_DESCRIPTION: 'Cliquer sur le module pour activer ou désactiver les modules EMS-ESP',
|
||||
MODULES_NONE: 'Aucun module externe détecté',
|
||||
RENAME: 'Renommer',
|
||||
ENABLE_MODBUS: 'Activer Modbus',
|
||||
VIEW_LOG: 'View log to diagnose issues', // TODO translate
|
||||
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
|
||||
SERVICES: 'Services', // TODO translate
|
||||
ALLVALUES: 'All Values', // TODO translate
|
||||
SPECIAL_FUNCTIONS: 'Special Functions',
|
||||
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
|
||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
|
||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
|
||||
PLEASE_WAIT: 'Please wait', // TODO translate
|
||||
RESTARTING_PRE: 'Initializing', // TODO translate
|
||||
RESTARTING_POST: 'Preparing', // TODO translate
|
||||
AUTO_SCROLL: 'Auto Scroll', // TODO translate
|
||||
DASHBOARD: 'Dashboard', // TODO translate
|
||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
DUPLICATE: 'Duplicate', // TODO translate
|
||||
UPGRADE: 'Upgrade', // TODO translate
|
||||
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
|
||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
|
||||
NO_DATA_2: 'module to mark them.', // TODO translate
|
||||
NO_DATA_3: 'To see all available entities go to', // TODO translate
|
||||
THIS_VERSION: 'This Version', // TODO translate
|
||||
PLATFORM: 'Platform', // TODO translate
|
||||
RELEASE_TYPE: 'Release Type', // TODO translate
|
||||
REINSTALL: 'Re-install', // TODO translate
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
|
||||
VIEW_LOG: 'Voir le journal pour diagnostiquer les problèmes',
|
||||
UPLOAD_DRAG: 'glisser-déposer un fichier ici ou cliquer pour en sélectionner un',
|
||||
SERVICES: 'Services',
|
||||
ALLVALUES: 'Toutes les valeurs',
|
||||
SPECIAL_FUNCTIONS: 'Fonctions spéciales',
|
||||
WAIT_FIRMWARE: 'Firmware en cours de téléchargement et d\'installation',
|
||||
INSTALL_VERSION: 'Cela va {0} la version {1}. Êtes-vous sûr ?',
|
||||
UPDATE_AVAILABLE: 'mise à jour disponible',
|
||||
LATEST_VERSION: 'Vous utilisez la dernière version {0} du firmware',
|
||||
PLEASE_WAIT: 'Veuillez patienter',
|
||||
RESTARTING_PRE: 'Initialisation',
|
||||
RESTARTING_POST: 'Préparation',
|
||||
AUTO_SCROLL: 'Défilement automatique',
|
||||
DASHBOARD: 'Tableau de bord',
|
||||
DEVELOPER_MODE: 'Mode développeur',
|
||||
BYTES: 'Octets',
|
||||
BITMASK: 'Masque de bits',
|
||||
DUPLICATE: 'Dupliquer',
|
||||
DASHBOARD_1: 'Toutes les entités EMS actives et marquées comme favoris, plus toutes les entités personnalisées, les programmes et les données des capteurs externes sont affichées ci-dessous.',
|
||||
NO_DATA_1: 'Aucune entité EMS favorite trouvée. Utilisez le',
|
||||
NO_DATA_2: 'module pour les marquer.',
|
||||
NO_DATA_3: 'Pour voir toutes les entités disponibles, aller à',
|
||||
THIS_VERSION: 'Cette version',
|
||||
PLATFORM: 'Plateforme',
|
||||
RELEASE_TYPE: 'Type de version',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Connexion Internet requise pour la vérification automatique des versions et la mise à niveau',
|
||||
SWITCH_RELEASE_TYPE: 'Passer à la version {0}',
|
||||
FIRMWARE_VERSION_INFO: 'Informations sur la version du firmware'
|
||||
};
|
||||
|
||||
export default fr;
|
||||
|
||||
@@ -72,7 +72,7 @@ const it: Translation = {
|
||||
TX_ISSUES: 'Problema di Tx - prova una modalità differente',
|
||||
DISCONNECTED: 'Disconnesso',
|
||||
EMS_SCAN: 'Sei sicuro di voler iniziare una scansione completa del bus EMS ?',
|
||||
DATA_TRAFFIC: 'Data Traffic', // TODO translate
|
||||
DATA_TRAFFIC: 'Traffico dati',
|
||||
EMS_DEVICE: 'Dispositivo EMS ',
|
||||
SUCCESS: 'SUCCESSO',
|
||||
FAIL: 'FALLITO',
|
||||
@@ -115,7 +115,7 @@ const it: Translation = {
|
||||
READONLY: 'Abilita modalità sola-lettura (blocca tutti i comandi di scrittura EMS Tx in uscita)',
|
||||
UNDERCLOCK_CPU: 'Abbassa velocità della CPU',
|
||||
REMOTE_TIMEOUT: 'Remote timeout',
|
||||
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature', // TODO translate
|
||||
REMOTE_TIMEOUT_EN: 'Disabilitare il telecomando in caso di temperatura ambiente mancante',
|
||||
HEATINGOFF: 'Avviamento caldaia con riscaldamento forzato spento',
|
||||
MIN_DURATION: 'Wait time',
|
||||
ENABLE_SHOWER_TIMER: 'Abilita timer doccia',
|
||||
@@ -162,6 +162,7 @@ const it: Translation = {
|
||||
UPLOAD: 'Carica',
|
||||
DOWNLOAD: 'Scarica',
|
||||
INSTALL: 'Installare {0}',
|
||||
REINSTALL: 'Riavviare',
|
||||
ABORTED: 'Annullato',
|
||||
FAILED: 'Fallito',
|
||||
SUCCESSFUL: 'Riuscito',
|
||||
@@ -174,8 +175,8 @@ const it: Translation = {
|
||||
FACTORY_RESET: 'Impostazioni di fabbrica',
|
||||
SYSTEM_FACTORY_TEXT: 'Il dispositivo è stato ripristinato alle impostazioni di fabbrica e ora verrà riavviato',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Sei sicuro di voler ripristinare il dispositivo alle impostazioni di fabbrica??',
|
||||
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
|
||||
STABLE: 'Stable', // TODO translate
|
||||
AVAILABLE_VERSION: 'Versioni disponibili',
|
||||
STABLE: 'Stabile',
|
||||
DEVELOPMENT: 'Sviluppo',
|
||||
UPTIME: 'Tempo di attività del sistema',
|
||||
FREE_MEMORY: 'Free Memory',
|
||||
@@ -184,10 +185,10 @@ const it: Translation = {
|
||||
APPSIZE: 'Applicazione (Partizione: Usata / Libera)',
|
||||
FILESYSTEM: 'Memoria Sistema (Usata / Libera)',
|
||||
BUFFER_SIZE: 'Max Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
|
||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
||||
COMPACT: 'Compatto',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
|
||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
|
||||
ERROR: 'Errore Inaspettato, prego tenta ancora',
|
||||
TIME_SET: 'Imposta Ora',
|
||||
MANAGE_USERS: 'Gestione Utenti',
|
||||
@@ -260,7 +261,7 @@ const it: Translation = {
|
||||
NETWORK_SCANNER: 'Scansione Rete',
|
||||
NETWORK_NO_WIFI: 'Nessuana rete WiFi trovata',
|
||||
NETWORK_BLANK_SSID: 'lasciare vuoto per disattivare WiFi',
|
||||
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
|
||||
NETWORK_BLANK_BSSID: 'lasciare vuoto per usare solo SSID',
|
||||
TX_POWER: 'Potenza Tx',
|
||||
HOSTNAME: 'Nome ospite',
|
||||
NETWORK_DISABLE_SLEEP: 'Disabilita la modalità sospensione Wi-Fi',
|
||||
@@ -303,56 +304,56 @@ const it: Translation = {
|
||||
WRITEABLE: 'Scrivibile',
|
||||
SHOWING: 'Visualizza',
|
||||
SEARCH: 'Ricerca',
|
||||
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
|
||||
CERT: 'Certificato radice TLS (lasciare vuoto per l\'insecure)',
|
||||
ENABLE_TLS: 'Abilita TLS',
|
||||
ON: 'On', // TODO translate
|
||||
OFF: 'Off', // TODO translate
|
||||
POLARITY: 'Polarity', // TODO translate
|
||||
ACTIVEHIGH: 'Active High', // TODO translate
|
||||
ACTIVELOW: 'Active Low', // TODO translate
|
||||
UNCHANGED: 'Unchanged', // TODO translate
|
||||
ALWAYS: 'Always', // TODO translate
|
||||
ACTIVITY: 'Activity', // TODO translate
|
||||
CONFIGURE: 'Configure {0}', // TODO translate
|
||||
SYSTEM_MEMORY: 'System Memory', // TODO translate
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
|
||||
SECURITY_1: 'Add or remove users', // TODO translate
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
|
||||
MODULES: 'Module', // TODO translate
|
||||
MODULES_1: 'Attiva o disattiva i moduli esterni', // TODO translate
|
||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
||||
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
|
||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
||||
RENAME: 'Rename', // TODO translate
|
||||
ON: 'On',
|
||||
OFF: 'Off',
|
||||
POLARITY: 'Polarity',
|
||||
ACTIVEHIGH: 'Active High',
|
||||
ACTIVELOW: 'Active Low',
|
||||
UNCHANGED: 'Unchanged',
|
||||
ALWAYS: 'Always',
|
||||
ACTIVITY: 'Activity',
|
||||
CONFIGURE: 'Configure {0}',
|
||||
SYSTEM_MEMORY: 'System Memory',
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings',
|
||||
SECURITY_1: 'Add or remove users',
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware',
|
||||
MODULES: 'Module',
|
||||
MODULES_1: 'Attiva o disattiva i moduli esterni',
|
||||
MODULES_UPDATED: 'Modules updated',
|
||||
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules',
|
||||
MODULES_NONE: 'No external modules detected',
|
||||
RENAME: 'Rename',
|
||||
ENABLE_MODBUS: 'Abilita Modbus',
|
||||
VIEW_LOG: 'View log to diagnose issues', // TODO translate
|
||||
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
|
||||
SERVICES: 'Services', // TODO translate
|
||||
ALLVALUES: 'All Values', // TODO translate
|
||||
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
|
||||
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
|
||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
|
||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
|
||||
PLEASE_WAIT: 'Please wait', // TODO translate
|
||||
RESTARTING_PRE: 'Initializing', // TODO translate
|
||||
RESTARTING_POST: 'Preparing', // TODO translate
|
||||
AUTO_SCROLL: 'Auto Scroll', // TODO translate
|
||||
DASHBOARD: 'Dashboard', // TODO translate
|
||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
DUPLICATE: 'Duplicate', // TODO translate
|
||||
UPGRADE: 'Upgrade', // TODO translate
|
||||
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
|
||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
|
||||
NO_DATA_2: 'module to mark them.', // TODO translate
|
||||
NO_DATA_3: 'To see all available entities go to', // TODO translate
|
||||
THIS_VERSION: 'This Version', // TODO translate
|
||||
PLATFORM: 'Platform', // TODO translate
|
||||
RELEASE_TYPE: 'Release Type', // TODO translate
|
||||
REINSTALL: 'Re-install', // TODO translate
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
|
||||
VIEW_LOG: 'Visualizza log per diagnosticare problemi',
|
||||
UPLOAD_DRAG: 'trascina e rilascia un file qui o clicca per selezionare uno',
|
||||
SERVICES: 'Servizi',
|
||||
ALLVALUES: 'Tutti i valori',
|
||||
SPECIAL_FUNCTIONS: 'Funzioni speciali',
|
||||
WAIT_FIRMWARE: 'Firmware è in upload e installazione',
|
||||
INSTALL_VERSION: 'Questo installerà la versione {1} {0}. Sei sicuro?',
|
||||
UPDATE_AVAILABLE: 'aggiornamento disponibile',
|
||||
LATEST_VERSION: 'Stai usando la versione più recente del firmware {0}',
|
||||
PLEASE_WAIT: 'Attendere',
|
||||
RESTARTING_PRE: 'Inizializzazione',
|
||||
RESTARTING_POST: 'Preparazione',
|
||||
AUTO_SCROLL: 'Scorrimento automatico',
|
||||
DASHBOARD: 'Pannello di controllo',
|
||||
DEVELOPER_MODE: 'Modalità sviluppatore',
|
||||
BYTES: 'Byte',
|
||||
BITMASK: 'Bitmask',
|
||||
DUPLICATE: 'Duplicato',
|
||||
DASHBOARD_1: 'Tutte le entità EMS che sono attive e marcate come preferite, più tutte le entità personalizzate, piani di programmazione e dati dei sensori esterni sono visualizzati di seguito.',
|
||||
NO_DATA_1: 'Nessuna entità EMS preferita trovata. Usa il',
|
||||
NO_DATA_2: 'modulo per marcarle.',
|
||||
NO_DATA_3: 'Per vedere tutte le entità disponibili vai a',
|
||||
THIS_VERSION: 'Questa versione',
|
||||
PLATFORM: 'Piattaforma',
|
||||
RELEASE_TYPE: 'Tipo di rilascio',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Connessione internet richiesta per il controllo automatico delle versioni e l\'aggiornamento',
|
||||
SWITCH_RELEASE_TYPE: 'Cambia in {0} rilascio',
|
||||
FIRMWARE_VERSION_INFO: 'Informazioni sulla versione del firmware'
|
||||
};
|
||||
|
||||
export default it;
|
||||
|
||||
@@ -162,6 +162,7 @@ const nl: Translation = {
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: '{{D|d|d}}ownload',
|
||||
INSTALL: 'Installeren',
|
||||
REINSTALL: 'Opnieuw installeren',
|
||||
ABORTED: 'afgebroken',
|
||||
FAILED: 'mislukt',
|
||||
SUCCESSFUL: 'successvol',
|
||||
@@ -178,12 +179,12 @@ const nl: Translation = {
|
||||
STABLE: 'Stable',
|
||||
DEVELOPMENT: 'Development',
|
||||
UPTIME: 'Systeem Uptime',
|
||||
FREE_MEMORY: 'Free Memory',
|
||||
PSRAM: 'PSRAM (Size / Free)',
|
||||
FLASH: 'Flash Chip (Size , Speed)',
|
||||
APPSIZE: 'Application (Partition: Used / Free)',
|
||||
FILESYSTEM: 'File System (Used / Free)',
|
||||
BUFFER_SIZE: 'Max Buffer Size',
|
||||
FREE_MEMORY: 'Vrij geheugen',
|
||||
PSRAM: 'PSRAM (grootte / vrij)',
|
||||
FLASH: 'Flash Chip (grootte , snelheid)',
|
||||
APPSIZE: 'Applicatie (partition: gebruikt / vrij)',
|
||||
FILESYSTEM: 'Bestandssysteem (gebruikt / vrij)',
|
||||
BUFFER_SIZE: 'Max buffer grootte',
|
||||
COMPACT: 'Compact',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Maak een back-up van uw configuratie en instellingen',
|
||||
UPLOAD_TEXT: 'Upload een nieuw firmwarebestand (.bin) of een back-upbestand (.json)',
|
||||
@@ -225,7 +226,7 @@ const nl: Translation = {
|
||||
MQTT_INT_MIXER: 'Mixer Modules',
|
||||
MQTT_INT_WATER: 'Water Modules',
|
||||
MQTT_QUEUE: 'MQTT Queue',
|
||||
DEFAULT: 'Default',
|
||||
DEFAULT: 'Standaard',
|
||||
MQTT_ENTITY_FORMAT: 'Entity ID formaat',
|
||||
MQTT_ENTITY_FORMAT_0: 'Eén instantie, lange naam (v3.4)',
|
||||
MQTT_ENTITY_FORMAT_1: 'Eén instantie, korte naam',
|
||||
@@ -249,20 +250,20 @@ const nl: Translation = {
|
||||
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',
|
||||
AP_CLIENTS: 'AP Gebruikers',
|
||||
AP_MAX_CLIENTS: 'Max Gebruikers',
|
||||
AP_LOCAL_IP: 'Lokale IP',
|
||||
NETWORK_SCAN: 'Scan WiFi Netwerken',
|
||||
IDLE: 'Inactief',
|
||||
LOST: 'Verloren',
|
||||
SCANNING: 'Scannen',
|
||||
SCAN_AGAIN: 'Opnieuw scannen',
|
||||
NETWORK_SCANNER: 'Netwerk Scanner',
|
||||
NETWORK_NO_WIFI: 'Geen WiFi networken gevonden',
|
||||
NETWORK_SCANNER: 'Netwerk Scannen',
|
||||
NETWORK_NO_WIFI: 'Geen WiFi netwerken gevonden',
|
||||
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen',
|
||||
NETWORK_BLANK_BSSID: 'laat leeg om alleen SSID te bebruiken',
|
||||
TX_POWER: 'Tx Vermogen',
|
||||
HOSTNAME: 'Hostname',
|
||||
HOSTNAME: 'Hostnaam',
|
||||
NETWORK_DISABLE_SLEEP: 'WiFi Sleep Mode uitzetten',
|
||||
NETWORK_LOW_BAND: 'Lagere WiFi bandbreedte gebruiken',
|
||||
NETWORK_USE_DNS: 'Activeer mDNS Service',
|
||||
@@ -272,9 +273,9 @@ const nl: Translation = {
|
||||
NETWORK_GATEWAY: 'Gateway',
|
||||
NETWORK_SUBNET: 'Subnetmasker',
|
||||
NETWORK_DNS: 'DNS Servers',
|
||||
ADDRESS_OF: '{0} Address',
|
||||
ADMINISTRATOR: 'Administrator',
|
||||
GUEST: 'Gast',
|
||||
ADDRESS_OF: '{0} Adres',
|
||||
ADMINISTRATOR: 'Beheerder',
|
||||
GUEST: 'Bezoeker',
|
||||
NEW: 'Nieuwe',
|
||||
NEW_NAME_OF: 'Hernoem {0}',
|
||||
ENTITY: 'Entiteit',
|
||||
@@ -320,7 +321,7 @@ const nl: Translation = {
|
||||
DOWNLOAD_UPLOAD_1: 'Download en upload instellingen en firmware',
|
||||
MODULES: 'Module',
|
||||
MODULES_1: 'Externe modules activeren of deactiveren',
|
||||
MODULES_UPDATED: 'Modules geüpdatet',
|
||||
MODULES_UPDATED: 'Modules geüpdatet',
|
||||
MODULES_DESCRIPTION: 'Klik op de module om EMS-ESP library modules te activeren of te deactiveren',
|
||||
MODULES_NONE: 'Geen externe modules gedetecteerd',
|
||||
RENAME: 'Hernoemen',
|
||||
@@ -331,19 +332,18 @@ const nl: Translation = {
|
||||
ALLVALUES: 'All waarden',
|
||||
SPECIAL_FUNCTIONS: 'Speciale functies',
|
||||
WAIT_FIRMWARE: 'Firmware wordt geüpload en geïnstalleerd',
|
||||
INSTALL_VERSION: 'Hiermee wordt versie {0} geïnstalleerd. Weet je het zeker?',
|
||||
UPGRADE_AVAILABLE: 'Er is een firmware-upgrade beschikbaar!',
|
||||
LATEST_VERSION: 'U gebruikt de nieuwste firmwareversie',
|
||||
INSTALL_VERSION: 'Hiermee wordt versie {1} {0}. Weet je het zeker?',
|
||||
UPDATE_AVAILABLE: 'update beschikbaar',
|
||||
LATEST_VERSION: 'U gebruikt de nieuwste {0} firmwareversie',
|
||||
PLEASE_WAIT: 'Een ogenblik geduld',
|
||||
RESTARTING_PRE: 'Initialiseren',
|
||||
RESTARTING_POST: 'Voorbereiding',
|
||||
AUTO_SCROLL: 'Automatisch Scrollen',
|
||||
DASHBOARD: 'Dashboard',
|
||||
DEVELOPER_MODE: 'Ontwikkelaarsmodus',
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
BYTES: 'Bytes',
|
||||
BITMASK: 'Bit Mask',
|
||||
DUPLICATE: 'Duplicaat',
|
||||
UPGRADE: 'Upgraden',
|
||||
DASHBOARD_1: 'Alle EMS-entiteiten die actief zijn en als favoriet zijn gemarkeerd, plus alle aangepaste entiteiten en externe sensorgegevens worden hieronder weergegeven.',
|
||||
NO_DATA_1: 'Er zijn nog geen favoriete EMS-entiteiten gevonden. Gebruik de',
|
||||
NO_DATA_2: 'module om ze te markeren.',
|
||||
@@ -351,8 +351,9 @@ const nl: Translation = {
|
||||
THIS_VERSION: 'Deze Versie',
|
||||
PLATFORM: 'Platform',
|
||||
RELEASE_TYPE: 'Release Typ',
|
||||
REINSTALL: 'Opnieuw Installeren',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internetverbinding vereist voor automatische versiecontrole en -upgrade',
|
||||
SWITCH_RELEASE_TYPE: 'Switch naar {0} release',
|
||||
FIRMWARE_VERSION_INFO: 'Informatie over firmwareversie'
|
||||
};
|
||||
|
||||
export default nl;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user