mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-05-10 16:05:52 +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:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
github-releases-to-discord:
|
github-releases-to-discord:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: GitHub Releases To Discord
|
- name: GitHub Releases To Discord
|
||||||
uses: SethCohen/github-releases-to-discord@v1.13.1
|
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:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: dev
|
branches: dev
|
||||||
paths:
|
paths:
|
||||||
- '**.c'
|
- 'src/**'
|
||||||
- '**.cpp'
|
|
||||||
- '**.h'
|
|
||||||
- '**.hpp'
|
|
||||||
- '**.json'
|
|
||||||
- '**.py'
|
|
||||||
- '**.md'
|
|
||||||
- '.github/workflows/pr_check.yml'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-release:
|
pre-release:
|
||||||
name: 'Automatic pre-release build'
|
name: 'Automatic pre-release build'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install python 3.11
|
- name: Install python 3.13
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.13'
|
||||||
|
|
||||||
- name: Install PlatformIO
|
- name: Install PlatformIO
|
||||||
run: |
|
run: |
|
||||||
pip install wheel
|
pip install wheel
|
||||||
pip install -U platformio
|
pip install -U platformio
|
||||||
|
|
||||||
- name: Build native
|
- name: Run unit tests
|
||||||
run: |
|
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
|
# see https://github.com/marketplace/actions/sonarcloud-scan-for-c-and-c#usage
|
||||||
name: Sonar Check
|
name: Sonar Check
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
# pull_request:
|
paths:
|
||||||
# types: [opened, synchronize, reopened]
|
- 'src/**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -17,18 +19,15 @@ jobs:
|
|||||||
BUILD_WRAPPER_OUT_DIR: bw-output
|
BUILD_WRAPPER_OUT_DIR: bw-output
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
- name: Install Build Wrapper
|
||||||
- name: Install sonar-scanner and build-wrapper
|
uses: SonarSource/sonarqube-scan-action/install-build-wrapper@master
|
||||||
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 build-wrapper
|
- name: SonarQube Scan
|
||||||
run: build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make all
|
uses: SonarSource/sonarqube-scan-action@master
|
||||||
|
|
||||||
- name: Run sonar-scanner
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_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:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'dev2'
|
- 'test'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-release:
|
pre-release:
|
||||||
name: 'Automatic test-release build'
|
name: 'Build Test Release'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
steps:
|
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
|
- name: Enable Corepack
|
||||||
run: corepack enable
|
run: corepack enable pnpm
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- name: Get the EMS-ESP version
|
||||||
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
|
|
||||||
id: build_info
|
id: build_info
|
||||||
run: |
|
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
|
echo "VERSION=$version" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Install PlatformIO
|
- name: Install PlatformIO
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -U platformio
|
pip install -U platformio
|
||||||
|
python -m pip install intelhex
|
||||||
|
|
||||||
- name: Build WebUI
|
- name: Build webUI
|
||||||
run: |
|
run: |
|
||||||
cd interface
|
platformio run -e build_webUI
|
||||||
yarn install
|
|
||||||
yarn typesafe-i18n --no-watch
|
|
||||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
|
||||||
yarn build
|
|
||||||
yarn 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: |
|
run: |
|
||||||
platformio run
|
platformio run
|
||||||
env:
|
|
||||||
NO_BUILD_WEBUI: true
|
|
||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
id: 'automatic_releases'
|
id: 'automatic_releases'
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -28,14 +28,10 @@ stats.html
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
.pnp.*
|
.pnp.*
|
||||||
*/.yarn/cache/*
|
|
||||||
*/.yarn/install-state.gz
|
|
||||||
analyse.html
|
analyse.html
|
||||||
interface/vite.config.ts.timestamp*
|
interface/vite.config.ts.timestamp*
|
||||||
*.local
|
*.local
|
||||||
src/ESP32React/WWWData.h
|
src/ESP32React/WWWData.h
|
||||||
.yarn/*
|
|
||||||
.yarnrc.yml
|
|
||||||
|
|
||||||
# i18n generated files
|
# i18n generated files
|
||||||
interface/src/i18n/i18n-react.tsx
|
interface/src/i18n/i18n-react.tsx
|
||||||
@@ -76,3 +72,5 @@ CMakeLists.txt
|
|||||||
logs/*
|
logs/*
|
||||||
sdkconfig.*
|
sdkconfig.*
|
||||||
sdkconfig_tasmota_esp32
|
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**
|
## **IMPORTANT! BREAKING CHANGES**
|
||||||
|
|
||||||
Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`. You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer.
|
Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`... You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer.
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,58 @@
|
|||||||
# Changelog
|
# 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
|
## Example
|
||||||
|
|
||||||
```
|
```text
|
||||||
feat: add hat wobble
|
feat: add hat wobble
|
||||||
^--^ ^------------^
|
^--^ ^------------^
|
||||||
| |
|
| |
|
||||||
@@ -96,7 +96,7 @@ References:
|
|||||||
|
|
||||||
## Contributor License Agreement (CLA)
|
## Contributor License Agreement (CLA)
|
||||||
|
|
||||||
```
|
```text
|
||||||
By making a contribution to this project, I certify that:
|
By making a contribution to this project, I certify that:
|
||||||
|
|
||||||
(a) The contribution was created in whole or in part by me and I
|
(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
|
ECHO = python3 $(I)/scripts/echo_progress.py --stepno=$C --nsteps=$T
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# determine number of parallel compiles based on OS
|
# Optimize parallel build configuration
|
||||||
UNAME_S := $(shell uname -s)
|
UNAME_S := $(shell uname -s)
|
||||||
ifeq ($(UNAME_S),Linux)
|
ifeq ($(UNAME_S),Linux)
|
||||||
EXTRA_CPPFLAGS = -D LINUX
|
EXTRA_CPPFLAGS = -D LINUX
|
||||||
@@ -29,7 +29,9 @@ ifeq ($(UNAME_S),Darwin)
|
|||||||
EXTRA_CPPFLAGS = -D OSX -Wno-tautological-constant-out-of-range-compare
|
EXTRA_CPPFLAGS = -D OSX -Wno-tautological-constant-out-of-range-compare
|
||||||
JOBS ?= $(shell sysctl -n hw.ncpu)
|
JOBS ?= $(shell sysctl -n hw.ncpu)
|
||||||
endif
|
endif
|
||||||
MAKEFLAGS += -j $(JOBS) -l $(JOBS)
|
|
||||||
|
# Set optimal parallel build settings
|
||||||
|
MAKEFLAGS += -j$(JOBS) -l$(shell echo $$(($(JOBS) * 2)))
|
||||||
|
|
||||||
# $(info Number of jobs: $(JOBS))
|
# $(info Number of jobs: $(JOBS))
|
||||||
|
|
||||||
@@ -72,16 +74,21 @@ DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DE
|
|||||||
OUTPUT := $(CURDIR)/$(TARGET)
|
OUTPUT := $(CURDIR)/$(TARGET)
|
||||||
SYMBOLS := $(CURDIR)/$(BUILD)/$(TARGET).out
|
SYMBOLS := $(CURDIR)/$(BUILD)/$(TARGET).out
|
||||||
|
|
||||||
CSOURCES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.c))
|
# Optimize source discovery - use shell find for better performance
|
||||||
CXXSOURCES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.cpp))
|
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)) )
|
OBJS := $(patsubst %,$(BUILD)/%.o,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)))
|
||||||
DEPS := $(patsubst %,$(BUILD)/%.d,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)) )
|
DEPS := $(patsubst %,$(BUILD)/%.d,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)))
|
||||||
|
|
||||||
INCLUDE += $(addprefix -I,$(foreach dir,$(INCLUDES), $(wildcard $(dir))))
|
# Optimize include path discovery
|
||||||
INCLUDE += $(addprefix -I,$(foreach dir,$(LIBRARIES),$(wildcard $(dir)/include)))
|
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
|
# Compiler & Linker
|
||||||
@@ -98,13 +105,12 @@ CXX := /usr/bin/g++
|
|||||||
# LDFLAGS Linker Flags
|
# LDFLAGS Linker Flags
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
|
CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
|
||||||
CPPFLAGS += -ggdb -g3 -O3
|
CPPFLAGS += -ggdb -g3 -MMD
|
||||||
CPPFLAGS += -MMD
|
CPPFLAGS += -flto=auto
|
||||||
CPPFLAGS += -flto=auto -fno-lto
|
CPPFLAGS += -Wall -Wextra -Werror -Wswitch-enum
|
||||||
CPPFLAGS += -Wall -Wextra -Werror
|
CPPFLAGS += -Wno-unused-parameter -Wno-missing-braces -Wno-vla-cxx-extension
|
||||||
CPPFLAGS += -Wswitch-enum
|
CPPFLAGS += -ffunction-sections -fdata-sections -fno-exceptions -fno-rtti -fno-threadsafe-statics
|
||||||
CPPFLAGS += -Wno-unused-parameter
|
CPPFLAGS += -Os -DNDEBUG
|
||||||
CPPFLAGS += -Wno-missing-braces
|
|
||||||
|
|
||||||
CPPFLAGS += $(EXTRA_CPPFLAGS)
|
CPPFLAGS += $(EXTRA_CPPFLAGS)
|
||||||
|
|
||||||
@@ -125,7 +131,8 @@ else
|
|||||||
LD := $(CXX)
|
LD := $(CXX)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
#DEPFLAGS += -MF $(BUILD)/$*.d
|
# Dependency file generation
|
||||||
|
DEPFLAGS += -MF $(BUILD)/$*.d -MT $@
|
||||||
|
|
||||||
LINK.o = $(LD) $(LDFLAGS) $(LDLIBS) $^ -o $@
|
LINK.o = $(LD) $(LDFLAGS) $(LDLIBS) $^ -o $@
|
||||||
COMPILE.c = $(CC) $(C_STANDARD) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
|
COMPILE.c = $(CC) $(C_STANDARD) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
|
||||||
@@ -142,7 +149,10 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
|
|||||||
.SUFFIXES:
|
.SUFFIXES:
|
||||||
.INTERMEDIATE:
|
.INTERMEDIATE:
|
||||||
.PRECIOUS: $(OBJS) $(DEPS)
|
.PRECIOUS: $(OBJS) $(DEPS)
|
||||||
.PHONY: all clean help
|
.PHONY: all clean help cppcheck run
|
||||||
|
|
||||||
|
# Enable second expansion for more flexible rules
|
||||||
|
.SECONDEXPANSION:
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# Targets
|
# Targets
|
||||||
@@ -157,7 +167,6 @@ $(OUTPUT): $(OBJS)
|
|||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
@$(ECHO) Linking $@
|
@$(ECHO) Linking $@
|
||||||
$(LINK.o)
|
$(LINK.o)
|
||||||
$(SYMBOLS.out)
|
|
||||||
|
|
||||||
$(BUILD)/%.o: %.c
|
$(BUILD)/%.o: %.c
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
@@ -185,8 +194,15 @@ clean:
|
|||||||
@$(RM) -rf $(BUILD) $(OUTPUT)
|
@$(RM) -rf $(BUILD) $(OUTPUT)
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo available targets: all run clean
|
@echo "Available targets:"
|
||||||
@echo $(OUTPUT)
|
@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)
|
-include $(DEPS)
|
||||||
|
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -35,7 +35,7 @@
|
|||||||
[](https://discord.gg/3J3GgnzpyT)
|
[](https://discord.gg/3J3GgnzpyT)
|
||||||
|
|
||||||
[](https://github.com/emsesp/EMS-ESP32/stargazers)
|
[](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)
|
[](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.
|
**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**
|
## 🚀 **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**
|
## 📋 **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).
|
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**
|
## 🎥 **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.
|
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**
|
## 📢 **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
|
- [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
|
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON processing
|
||||||
- [espMqttClient](https://github.com/bertmelis/espMqttClient) for the MQTT client
|
- [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**
|
## 📜 **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",
|
"**/*.json",
|
||||||
"src/core/modbus_entity_parameters.hpp",
|
"src/core/modbus_entity_parameters.hpp",
|
||||||
"sdkconfig.*",
|
"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
|
telegram_type_id,name,is_fetched
|
||||||
0x04,UBAFactory,fetched
|
0x04,UBAFactory,fetched
|
||||||
0x06,RCTime,
|
0x06,RCTime,
|
||||||
0x0A,EasyMonitor,fetched
|
0x0A,EasyMonitor,fetched
|
||||||
0x10,UBAErrorMessage1,
|
0x10,UBAErrorMessage1,
|
||||||
0x11,UBAErrorMessage2,
|
0x11,UBAErrorMessage2,
|
||||||
0x12,RCErrorMessage,
|
0x12,RCErrorMessage,
|
||||||
0x13,RCErrorMessage2,
|
0x13,RCErrorMessage2,
|
||||||
0x14,UBATotalUptime,fetched
|
0x14,UBATotalUptime,fetched
|
||||||
0x15,UBAMaintenanceData,
|
0x15,UBAMaintenanceData,
|
||||||
0x16,UBAParameters,fetched
|
0x16,UBAParameters,fetched
|
||||||
0x18,UBAMonitorFast,
|
0x18,UBAMonitorFast,
|
||||||
0x19,UBAMonitorSlow,
|
0x19,UBAMonitorSlow,
|
||||||
0x1A,UBASetPoints,
|
0x1A,UBASetPoints,
|
||||||
0x1C,UBAMaintenanceStatus,
|
0x1C,UBAMaintenanceStatus,
|
||||||
0x1E,WM10TempMessage,
|
0x1E,HydrTemp,
|
||||||
0x23,JunkersSetMixer,fetched
|
0x23,JunkersSetMixer,fetched
|
||||||
0x26,UBASettingsWW,fetched
|
0x27,UBASettingsWW,fetched
|
||||||
0x28,WeatherComp,fetched
|
0x28,WeatherComp,fetched
|
||||||
0x2A,MC110Status,
|
0x2A,MC110Status,
|
||||||
0x2E,Meters,
|
0x2E,Meters,
|
||||||
0x33,UBAParameterWW,fetched
|
0x33,UBAParameterWW,fetched
|
||||||
0x34,UBAMonitorWW,
|
0x34,UBAMonitorWW,
|
||||||
0x35,UBAFlags,
|
0x35,UBAFlags,
|
||||||
0x37,WWSettings,fetched
|
0x37,WWSettings,fetched
|
||||||
0x38,WWTimer,fetched
|
0x38,WWTimer,fetched
|
||||||
0x39,WWCircTimer,fetched
|
0x39,WWCircTimer,fetched
|
||||||
0x3A,RC30WWSettings,fetched
|
0x3A,RC30WWSettings,fetched
|
||||||
0x3B,Energy,
|
0x3B,Energy,
|
||||||
0x3D,RC35Set,
|
0x3D,RC35Set,
|
||||||
0x3E,RC35Monitor,
|
0x3E,RC35Monitor,
|
||||||
0x3F,RC35Timer,
|
0x3F,RC35Timer,
|
||||||
0x40,RC30Temp,
|
0x40,RC30Temp,
|
||||||
0x41,RC30Monitor,
|
0x41,RC30Monitor,
|
||||||
0x42,RC35Timer2,
|
0x42,RC35Timer2,
|
||||||
0x47,RC35Set,
|
0x47,RC35Set,
|
||||||
0x48,RC35Monitor,
|
0x48,RC35Monitor,
|
||||||
0x49,RC35Timer,
|
0x49,RC35Timer,
|
||||||
0x4C,RC35Timer2,
|
0x4C,RC35Timer2,
|
||||||
0x51,RC35Set,
|
0x51,RC35Set,
|
||||||
0x52,RC35Monitor,
|
0x52,RC35Monitor,
|
||||||
0x53,RC35Timer,
|
0x53,RC35Timer,
|
||||||
0x56,RC35Timer2,
|
0x56,RC35Timer2,
|
||||||
0x5B,RC35Set,
|
0x5B,RC35Set,
|
||||||
0x5C,RC35Monitor,
|
0x5C,RC35Monitor,
|
||||||
0x5D,RC35Timer,
|
0x5D,RC35Timer,
|
||||||
0x60,RC35Timer2,
|
0x60,RC35Timer2,
|
||||||
0x96,SM10Config,fetched
|
0x96,SM10Config,fetched
|
||||||
0x97,SM10Monitor,
|
0x97,SM10Monitor,
|
||||||
0x9C,WM10MonitorMessage,
|
0x9C,WM10MonitorMessage,
|
||||||
0x9D,WM10SetMessage,
|
0x9D,WM10SetMessage,
|
||||||
0xA2,RCError,
|
0xA2,RCError,
|
||||||
0xA3,RCOutdoorTemp,
|
0xA3,RCOutdoorTemp,
|
||||||
0xA5,IBASettings,fetched
|
0xA5,IBASettings,fetched
|
||||||
0xA7,RC30Set,
|
0xA7,RC30Set,
|
||||||
0xA9,RC30Vacation,fetched
|
0xA9,RC30Vacation,fetched
|
||||||
0xAA,MMConfigMessage,fetched
|
0xAA,MMConfigMessage,fetched
|
||||||
0xAB,MMStatusMessage,
|
0xAB,MMStatusMessage,
|
||||||
0xAC,MMSetMessage,
|
0xAC,MMSetMessage,
|
||||||
0xAF,RC20Remote,
|
0xAF,RC20Remote,
|
||||||
0xB0,RC10Set,
|
0xB0,RC10Set,
|
||||||
0xB1,RC10Monitor,
|
0xB1,RC10Monitor,
|
||||||
0xBB,HybridSettings,fetched
|
0xBB,HybridSettings,fetched
|
||||||
0xBF,ErrorMessage,
|
0xBF,ErrorMessage,
|
||||||
0xC2,UBAErrorMessage3,
|
0xC0,RCErrorMessage,
|
||||||
0xD1,UBAOutdoorTemp,
|
0xC2,UBAErrorMessage3,
|
||||||
0xE3,UBAMonitorSlowPlus2,
|
0xC6,UBAErrorMessage3,
|
||||||
0xE4,UBAMonitorFastPlus,
|
0xD1,UBAOutdoorTemp,
|
||||||
0xE5,UBAMonitorSlowPlus,
|
0xE3,UBAMonitorSlowPlus2,
|
||||||
0xE6,UBAParametersPlus,fetched
|
0xE4,UBAMonitorFastPlus,
|
||||||
0xE9,UBAMonitorWWPlus,
|
0xE5,UBAMonitorSlowPlus,
|
||||||
0xEA,UBAParameterWWPlus,fetched
|
0xE6,UBAParametersPlus,fetched
|
||||||
0x0101,ISM1Set,fetched
|
0xE9,UBAMonitorWWPlus,
|
||||||
0x0103,ISM1StatusMessage,fetched
|
0xEA,UBAParameterWWPlus,fetched
|
||||||
0x0104,ISM2StatusMessage,
|
0x0101,ISM1Set,fetched
|
||||||
0x010C,IPMStatusMessage,
|
0x0103,ISM1StatusMessage,fetched
|
||||||
0x011E,IPMTempMessage,
|
0x0104,ISM2StatusMessage,
|
||||||
0x012E,HPEnergy1,
|
0x010C,IPMStatusMessage,
|
||||||
0x013B,HPEnergy2,
|
0x011E,JunkersDisp,fetched
|
||||||
0x0165,JunkersSet,
|
0x012E,HPEnergy1,
|
||||||
0x0166,JunkersSet,
|
0x013B,HPEnergy2,
|
||||||
0x0167,JunkersSet,
|
0x0165,JunkersSet,
|
||||||
0x0168,JunkersSet,
|
0x0166,JunkersSet,
|
||||||
0x016E,Absent,fetched
|
0x0167,JunkersSet,
|
||||||
0x016F,JunkersMonitor,
|
0x0168,JunkersSet,
|
||||||
0x0170,JunkersMonitor,
|
0x016E,Absent,fetched
|
||||||
0x0171,JunkersMonitor,
|
0x016F,JunkersMonitor,
|
||||||
0x0172,JunkersMonitor,
|
0x0170,JunkersMonitor,
|
||||||
0x0179,JunkersSet,
|
0x0171,JunkersMonitor,
|
||||||
0x017A,JunkersSet,
|
0x0172,JunkersMonitor,
|
||||||
0x017B,JunkersSet,
|
0x0179,JunkersSet,
|
||||||
0x017C,JunkersSet,
|
0x017A,JunkersSet,
|
||||||
0x01D3,JunkersDhw,fetched
|
0x017B,JunkersSet,
|
||||||
0x023A,RC300OutdoorTemp,fetched
|
0x017C,JunkersSet,
|
||||||
0x023E,PVSettings,fetched
|
0x01D3,JunkersDhw,fetched
|
||||||
0x0240,RC300Settings,fetched
|
0x023A,RC300OutdoorTemp,fetched
|
||||||
0x0241,RC300Settings,fetched
|
0x023E,PVSettings,fetched
|
||||||
0x0267,RC300Floordry,
|
0x0240,RC300Settings,fetched
|
||||||
0x0269,RC300Holiday,fetched
|
0x0241,RC300Settings,fetched
|
||||||
0x0291,HPMode,fetched
|
0x0267,RC300Floordry,
|
||||||
0x0292,HPMode,fetched
|
0x0269,RC300Holiday,fetched
|
||||||
0x0293,HPMode,fetched
|
0x0291,HPMode,fetched
|
||||||
0x0294,HPMode,fetched
|
0x0292,HPMode,fetched
|
||||||
0x029B,RC300Curves,
|
0x0293,HPMode,fetched
|
||||||
0x029C,RC300Curves,
|
0x0294,HPMode,fetched
|
||||||
0x029D,RC300Curves,
|
0x029B,RC300Curves,
|
||||||
0x029E,RC300Curves,
|
0x029C,RC300Curves,
|
||||||
0x029F,RC300Curves,
|
0x029D,RC300Curves,
|
||||||
0x02A0,RC300Curves,
|
0x029E,RC300Curves,
|
||||||
0x02A1,RC300Curves,
|
0x029F,RC300Curves,
|
||||||
0x02A2,RC300Curves,
|
0x02A0,RC300Curves,
|
||||||
0x02A5,RC300Monitor,fetched
|
0x02A1,RC300Curves,
|
||||||
0x02A6,RC300Monitor,
|
0x02A2,RC300Curves,
|
||||||
0x02A7,CRFMonitor,
|
0x02A5,RC300Monitor,
|
||||||
0x02A8,RC300Monitor,
|
0x02A6,RC300Monitor,
|
||||||
0x02A9,RC300Monitor,
|
0x02A7,RC300Monitor,
|
||||||
0x02AA,RC300Monitor,
|
0x02A8,RC300Monitor,
|
||||||
0x02AB,RC300Monitor,
|
0x02A9,RC300Monitor,
|
||||||
0x02AC,RC300Monitor,
|
0x02AA,RC300Monitor,
|
||||||
0x02AF,RC300Summer,
|
0x02AB,RC300Monitor,
|
||||||
0x02B0,RC300Summer,
|
0x02AC,RC300Monitor,
|
||||||
0x02B1,RC300Summer,
|
0x02AF,RC300Summer,
|
||||||
0x02B2,RC300Summer,
|
0x02B0,RC300Summer,
|
||||||
0x02B3,RC300Summer,
|
0x02B1,RC300Summer,
|
||||||
0x02B4,RC300Summer,
|
0x02B2,RC300Summer,
|
||||||
0x02B5,RC300Summer,
|
0x02B3,RC300Summer,
|
||||||
0x02B6,RC300Summer,
|
0x02B4,RC300Summer,
|
||||||
0x02B9,RC300Set,
|
0x02B5,RC300Summer,
|
||||||
0x02BA,RC300Set,
|
0x02B6,RC300Summer,
|
||||||
0x02BB,RC300Set,
|
0x02B9,RC300Set,
|
||||||
0x02BC,RC300Set,
|
0x02BA,RC300Set,
|
||||||
0x02BD,RC300Set,
|
0x02BB,RC300Set,
|
||||||
0x02BE,RC300Set,
|
0x02BC,RC300Set,
|
||||||
0x02BF,RC300Set,
|
0x02BD,RC300Set,
|
||||||
0x02C0,RC300Set,
|
0x02BE,RC300Set,
|
||||||
0x02CC,HPPressure,fetched
|
0x02BF,RC300Set,
|
||||||
0x02CD,MMPLUSConfigMessage,fetched
|
0x02C0,RC300Set,
|
||||||
0x02CE,RC300Set2,
|
0x02CC,HPPressure,fetched
|
||||||
0x02D0,RC300Set2,
|
0x02CD,MMPLUSConfigMessage,fetched
|
||||||
0x02D2,RC300Set2,
|
0x02CE,RC300Set2,
|
||||||
0x02D6,HPPump2,fetched
|
0x02D0,RC300Set2,
|
||||||
0x02D7,MMPLUSStatusMessage,
|
0x02D2,RC300Set2,
|
||||||
0x02F5,RC300WWmode,fetched
|
0x02D6,HPPump2,fetched
|
||||||
0x02F6,RC300WW2mode,fetched
|
0x02D7,MMPLUSStatusMessage,
|
||||||
0x0313,MMPLUSConfigMessage_WWC,fetched
|
0x02E0,UBASetPoints,
|
||||||
0x031B,RC300WWtemp,fetched
|
0x02F5,RC300WWmode,fetched
|
||||||
0x031D,RC300WWmode2,
|
0x02F6,RC300WW2mode,fetched
|
||||||
0x031E,RC300WWmode2,
|
0x0313,MMPLUSConfigMessage_WWC,fetched
|
||||||
0x0331,MMPLUSStatusMessage_WWC,
|
0x031B,RC300WWtemp,fetched
|
||||||
0x0358,SM100SystemConfig,fetched
|
0x031D,RC300WWmode2,
|
||||||
0x035A,SM100CircuitConfig,fetched
|
0x031E,RC300WWmode2,
|
||||||
0x035C,SM100HeatAssist,fetched
|
0x0331,MMPLUSStatusMessage_WWC,
|
||||||
0x035D,SM100Circuit2Config,fetched
|
0x0358,SM100SystemConfig,fetched
|
||||||
0x035F,SM100Config1,fetched
|
0x035A,SM100CircuitConfig,fetched
|
||||||
0x0361,SM100Differential,fetched
|
0x035C,SM100HeatAssist,fetched
|
||||||
0x0362,SM100Monitor,
|
0x035D,SM100Circuit2Config,fetched
|
||||||
0x0363,SM100Monitor2,
|
0x035F,SM100Config1,fetched
|
||||||
0x0364,SM100Status,
|
0x0361,SM100Differential,fetched
|
||||||
0x0366,SM100Config,
|
0x0362,SM100Monitor,
|
||||||
0x036A,SM100Status2,
|
0x0363,SM100Monitor2,
|
||||||
0x0380,SM100CollectorConfig,fetched
|
0x0364,SM100Status,
|
||||||
0x038E,SM100Energy,fetched
|
0x0366,SM100Config,
|
||||||
0x0391,SM100Time,fetched
|
0x036A,SM100Status2,
|
||||||
0x043F,CRHolidays,fetched
|
0x0380,SM100CollectorConfig,fetched
|
||||||
0x0467,HPSet,
|
0x038E,SM100Energy,fetched
|
||||||
0x0468,HPSet,
|
0x0391,SM100Time,fetched
|
||||||
0x0469,HPSet,
|
0x043F,CRHolidays,fetched
|
||||||
0x046A,HPSet,
|
0x0467,HPSet,
|
||||||
0x0471,RC300Summer2,
|
0x0468,HPSet,
|
||||||
0x0472,RC300Summer2,
|
0x0469,HPSet,
|
||||||
0x0473,RC300Summer2,
|
0x046A,HPSet,
|
||||||
0x0474,RC300Summer2,
|
0x0471,RC300Summer2,
|
||||||
0x0475,RC300Summer2,
|
0x0472,RC300Summer2,
|
||||||
0x0476,RC300Summer2,
|
0x0473,RC300Summer2,
|
||||||
0x0477,RC300Summer2,
|
0x0474,RC300Summer2,
|
||||||
0x0478,RC300Summer2,
|
0x0475,RC300Summer2,
|
||||||
0x047B,HP2,
|
0x0476,RC300Summer2,
|
||||||
0x0484,HPSilentMode,fetched
|
0x0477,RC300Summer2,
|
||||||
0x0485,HpCooling,fetched
|
0x0478,RC300Summer2,
|
||||||
0x0486,HpInConfig,fetched
|
0x047B,HP2,
|
||||||
0x0488,HPValve,fetched
|
0x0484,HPSilentMode,fetched
|
||||||
0x048A,HpPool,fetched
|
0x0485,HpCooling,fetched
|
||||||
0x048B,HPPumps,fetched
|
0x0486,HpInConfig,fetched
|
||||||
0x048D,HpPower,fetched
|
0x0488,HPValve,fetched
|
||||||
0x048F,HpTemperatures,
|
0x048A,HpPool,fetched
|
||||||
0x0491,HPAdditionalHeater,fetched
|
0x048B,HPPumps,fetched
|
||||||
0x0492,HpHeaterConfig,fetched
|
0x048D,HpPower,fetched
|
||||||
0x0494,UBAEnergySupplied,
|
0x048F,HpTemperatures,
|
||||||
0x0495,UBAInformation,
|
0x0491,HPAdditionalHeater,fetched
|
||||||
0x0499,HPDhwSettings,fetched
|
0x0492,HpHeaterConfig,fetched
|
||||||
0x049C,HPSettings2,fetched
|
0x0494,UBAEnergySupplied,
|
||||||
0x049D,HPSettings3,fetched
|
0x0495,UBAInformation,
|
||||||
0x04A2,HpInput,fetched
|
0x0499,HPDhwSettings,fetched
|
||||||
0x04A5,HPFan,fetched
|
0x049C,HPSettings2,fetched
|
||||||
0x04A7,HPPowerLimit,fetched
|
0x049D,HPSettings3,fetched
|
||||||
0x04AA,HPPower2,fetched
|
0x04A2,HpInput,fetched
|
||||||
0x04AE,HPEnergy,fetched
|
0x04A5,HPFan,fetched
|
||||||
0x04AF,HPMeters,fetched
|
0x04A7,HPPowerLimit,fetched
|
||||||
0x056B,VentilationMode,fetched
|
0x04AA,HPPower2,fetched
|
||||||
0x0583,VentilationMonitor,
|
0x04AE,HPEnergy,fetched
|
||||||
0x0585,Blowerspeed,
|
0x04AF,HPMeters,fetched
|
||||||
0x0587,Bypass,
|
0x055C,VentilationSet,fetched
|
||||||
0x05BA,HpPoolStatus,fetched
|
0x056B,VentilationMode,fetched
|
||||||
0x05D9,Airquality,
|
0x0583,VentilationMonitor,
|
||||||
0x0772,HIUSettings,
|
0x0585,Blowerspeed,
|
||||||
0x0779,HIUMonitor,
|
0x0587,Bypass,
|
||||||
0x07A5,SM100wwCirc,fetched
|
0x05BA,HpPoolStatus,fetched
|
||||||
0x07A6,SM100wwParam,fetched
|
0x05D9,Airquality,
|
||||||
0x07AA,SM100wwStatus,
|
0x0772,HIUSettings,
|
||||||
0x07AB,SM100wwCommand,
|
0x0779,HIUMonitor,
|
||||||
0x07AC,SM100wwParam1,
|
0x07A5,SM100wwCirc,fetched
|
||||||
0x07AD,SM100ValveStatus,
|
0x07A6,SM100wwParam,fetched
|
||||||
0x07AE,SM100wwKeepWarm,fetched
|
0x07AA,SM100wwStatus,
|
||||||
0x07D6,SM100wwTemperature,
|
0x07AB,SM100wwCommand,
|
||||||
0x07E0,SM100wwStatus2,fetched
|
0x07AC,SM100wwParam1,
|
||||||
0x0935,EM100SetMessage,fetched
|
0x07AD,SM100ValveStatus,
|
||||||
0x0936,EM100OutMessage,
|
0x07AE,SM100wwKeepWarm,fetched
|
||||||
0x0937,EM100TempMessage,
|
0x07D6,SM100wwTemperature,
|
||||||
0x0938,EM100InputMessage,
|
0x07E0,SM100wwStatus2,fetched
|
||||||
0x0939,EM100MonitorMessage,
|
0x0935,EM100SetMessage,fetched
|
||||||
0x093A,EM100ConfigMessage,
|
0x0936,EM100OutMessage,
|
||||||
0x0998,HPSettings,fetched
|
0x0937,EM100TempMessage,
|
||||||
0x0999,HPFunctionTest,fetched
|
0x0938,EM100InputMessage,
|
||||||
0x099A,HPStarts,
|
0x0939,EM100MonitorMessage,
|
||||||
0x099B,HPFlowTemp,
|
0x093A,EM100ConfigMessage,
|
||||||
0x099C,HPComp,
|
0x0998,HPSettings,fetched
|
||||||
0x09A0,HPTemperature,
|
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/*
|
src/i18n/*
|
||||||
|
|
||||||
.prettierrc
|
.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: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: true,
|
project: true
|
||||||
tsconfigRootDir: import.meta.dirname
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "EMS-ESP",
|
"name": "EMS-ESP",
|
||||||
"version": "3.7.2",
|
"version": "3.7.3",
|
||||||
"description": "EMS-ESP WebUI",
|
"description": "EMS-ESP WebUI",
|
||||||
"homepage": "https://emsesp.org",
|
"homepage": "https://emsesp.org",
|
||||||
"author": "proddy, emsesp.org",
|
"author": "proddy, emsesp.org",
|
||||||
@@ -8,59 +8,61 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"preinstall": "npx only-allow pnpm",
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build-hosted": "typesafe-i18n --no-watch && vite build --mode hosted",
|
"build-hosted": "typesafe-i18n && vite build --mode hosted",
|
||||||
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"yarn:mock-rest\" \"vite preview\"",
|
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"pnpm:mock-rest\" \"vite preview\"",
|
||||||
"mock-rest": "bun --watch ../mock-api/rest_server.ts",
|
"mock-rest": "bun --watch ../mock-api/restServer.ts",
|
||||||
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"yarn:mock-rest\" \"vite\"",
|
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite\"",
|
||||||
"typesafe-i18n": "typesafe-i18n --no-watch",
|
"typesafe-i18n": "typesafe-i18n --no-watch",
|
||||||
"webUI": "node progmem-generator.js",
|
"webUI": "node progmem-generator.js",
|
||||||
"format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'",
|
"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": {
|
"dependencies": {
|
||||||
"@alova/adapter-xhr": "2.1.1",
|
"@alova/adapter-xhr": "2.2.1",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/icons-material": "^6.4.8",
|
"@mui/icons-material": "^7.3.4",
|
||||||
"@mui/material": "^6.4.8",
|
"@mui/material": "^7.3.4",
|
||||||
"@table-library/react-table-library": "4.1.12",
|
"@table-library/react-table-library": "4.1.15",
|
||||||
"alova": "3.2.10",
|
"alova": "3.3.4",
|
||||||
"async-validator": "^4.2.5",
|
"async-validator": "^4.2.5",
|
||||||
|
"formidable": "^3.5.4",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"mime-types": "^2.1.35",
|
"magic-string": "^0.30.21",
|
||||||
"preact": "^10.26.4",
|
"mime-types": "^3.0.1",
|
||||||
"react": "^19.0.0",
|
"preact": "^10.27.2",
|
||||||
"react-dom": "^19.0.0",
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router": "^7.4.0",
|
"react-router": "^7.9.4",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"typesafe-i18n": "^5.26.2",
|
"typesafe-i18n": "^5.26.2",
|
||||||
"typescript": "^5.8.2"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.26.10",
|
"@babel/core": "^7.28.5",
|
||||||
"@eslint/js": "^9.23.0",
|
"@eslint/js": "^9.38.0",
|
||||||
"@preact/compat": "^18.3.1",
|
"@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",
|
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||||
"@types/formidable": "^3",
|
"@types/node": "^24.9.1",
|
||||||
"@types/node": "^22.13.11",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react": "^19.0.12",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@types/react-dom": "^19.0.4",
|
"concurrently": "^9.2.1",
|
||||||
"concurrently": "^9.1.2",
|
"eslint": "^9.38.0",
|
||||||
"eslint": "^9.23.0",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-config-prettier": "^10.1.1",
|
"prettier": "^3.6.2",
|
||||||
"formidable": "^3.5.2",
|
"rollup-plugin-visualizer": "^6.0.5",
|
||||||
"prettier": "^3.5.3",
|
"terser": "^5.44.0",
|
||||||
"rollup-plugin-visualizer": "^5.14.0",
|
"typescript-eslint": "^8.46.2",
|
||||||
"terser": "^5.39.0",
|
"vite": "^7.1.12",
|
||||||
"typescript-eslint": "8.27.0",
|
|
||||||
"vite": "^6.2.2",
|
|
||||||
"vite-plugin-imagemin": "^0.6.1",
|
"vite-plugin-imagemin": "^0.6.1",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"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,
|
existsSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
readdirSync,
|
readdirSync,
|
||||||
|
statSync,
|
||||||
unlinkSync
|
unlinkSync
|
||||||
} from 'fs';
|
} from 'fs';
|
||||||
import mime from 'mime-types';
|
import mime from 'mime-types';
|
||||||
@@ -15,67 +16,79 @@ const INDENT = ' ';
|
|||||||
const outputPath = '../src/ESP32React/WWWData.h';
|
const outputPath = '../src/ESP32React/WWWData.h';
|
||||||
const sourcePath = './dist';
|
const sourcePath = './dist';
|
||||||
const bytesPerLine = 20;
|
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 = () =>
|
const generateWWWClass =
|
||||||
`typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
|
() => `typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
|
||||||
// Total size is ${totalSize} bytes
|
// 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 {
|
class WWWData {
|
||||||
${indent}public:
|
${INDENT}public:
|
||||||
${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
|
${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')}
|
${fileInfo.map((f) => `${INDENT.repeat(3)}handler("${f.uri}", "${f.mimeType}", ${f.variable}, ${f.size}, "${f.hash}");`).join('\n')}
|
||||||
${indent.repeat(2)}}
|
${INDENT.repeat(2)}}
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function getFilesSync(dir, files = []) {
|
const getFilesSync = (dir, files = []) => {
|
||||||
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
|
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
|
||||||
const entryPath = resolve(dir, entry.name);
|
const entryPath = resolve(dir, entry.name);
|
||||||
if (entry.isDirectory()) {
|
entry.isDirectory() ? getFilesSync(entryPath, files) : files.push(entryPath);
|
||||||
getFilesSync(entryPath, files);
|
|
||||||
} else {
|
|
||||||
files.push(entryPath);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return files;
|
return files;
|
||||||
}
|
};
|
||||||
|
|
||||||
function cleanAndOpen(path) {
|
const cleanAndOpen = (path) => {
|
||||||
if (existsSync(path)) {
|
existsSync(path) && unlinkSync(path);
|
||||||
unlinkSync(path);
|
|
||||||
}
|
|
||||||
return createWriteStream(path, { flags: 'w+' });
|
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 writeFile = (relativeFilePath, buffer) => {
|
||||||
const variable = 'ESP_REACT_DATA_' + fileInfo.length;
|
const variable = `ESP_REACT_DATA_${fileInfo.length}`;
|
||||||
const mimeType = mime.lookup(relativeFilePath);
|
const mimeType = mime.lookup(relativeFilePath);
|
||||||
var size = 0;
|
const fileType = getFileType(relativeFilePath);
|
||||||
writeStream.write('const uint8_t ' + variable + '[] = {');
|
let size = 0;
|
||||||
// const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 });
|
writeStream.write(`const uint8_t ${variable}[] = {`);
|
||||||
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
|
||||||
|
|
||||||
// create sha
|
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
||||||
const hashSum = crypto.createHash('sha256');
|
const hash = crypto.createHash('sha256').update(zipBuffer).digest('hex');
|
||||||
hashSum.update(zipBuffer);
|
|
||||||
const hash = hashSum.digest('hex');
|
|
||||||
|
|
||||||
zipBuffer.forEach((b) => {
|
zipBuffer.forEach((b) => {
|
||||||
if (!(size % bytesPerLine)) {
|
if (!(size % bytesPerLine)) {
|
||||||
writeStream.write('\n');
|
writeStream.write('\n' + INDENT);
|
||||||
writeStream.write(indent);
|
|
||||||
}
|
}
|
||||||
writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).slice(-2) + ',');
|
writeStream.write('0x' + b.toString(16).toUpperCase().padStart(2, '0') + ',');
|
||||||
size++;
|
size++;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (size % bytesPerLine) {
|
size % bytesPerLine && writeStream.write('\n');
|
||||||
writeStream.write('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
writeStream.write('};\n\n');
|
writeStream.write('};\n\n');
|
||||||
|
|
||||||
|
// Update bundle statistics
|
||||||
|
bundleStats[fileType].count++;
|
||||||
|
bundleStats[fileType].uncompressed += buffer.length;
|
||||||
|
bundleStats[fileType].compressed += zipBuffer.length;
|
||||||
|
|
||||||
fileInfo.push({
|
fileInfo.push({
|
||||||
uri: '/' + relativeFilePath.replace(sep, '/'),
|
uri: '/' + relativeFilePath.replace(sep, '/'),
|
||||||
mimeType,
|
mimeType,
|
||||||
@@ -84,32 +97,52 @@ const writeFile = (relativeFilePath, buffer) => {
|
|||||||
hash
|
hash
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log(relativeFilePath + ' (size ' + size + ' bytes)');
|
|
||||||
totalSize += size;
|
totalSize += size;
|
||||||
};
|
};
|
||||||
|
|
||||||
// start
|
console.log(`Generating ${outputPath} from ${sourcePath}`);
|
||||||
console.log('Generating ' + outputPath + ' from ' + sourcePath);
|
|
||||||
const includes = ARDUINO_INCLUDES;
|
|
||||||
const indent = INDENT;
|
|
||||||
const fileInfo = [];
|
const fileInfo = [];
|
||||||
const writeStream = cleanAndOpen(resolve(outputPath));
|
const writeStream = cleanAndOpen(resolve(outputPath));
|
||||||
|
|
||||||
// includes
|
writeStream.write(ARDUINO_INCLUDES);
|
||||||
writeStream.write(includes);
|
|
||||||
|
|
||||||
// process static files
|
|
||||||
const buildPath = resolve(sourcePath);
|
const buildPath = resolve(sourcePath);
|
||||||
for (const filePath of getFilesSync(buildPath)) {
|
for (const filePath of getFilesSync(buildPath)) {
|
||||||
const readStream = readFileSync(filePath);
|
writeFile(relative(buildPath, filePath), readFileSync(filePath));
|
||||||
const relativeFilePath = relative(buildPath, filePath);
|
|
||||||
writeFile(relativeFilePath, readStream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add class
|
|
||||||
writeStream.write(generateWWWClass());
|
writeStream.write(generateWWWClass());
|
||||||
|
|
||||||
// end
|
|
||||||
writeStream.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 { ToastContainer, Zoom } from 'react-toastify';
|
||||||
|
|
||||||
import AppRouting from 'AppRouting';
|
import AppRouting from 'AppRouting';
|
||||||
@@ -8,7 +8,8 @@ import type { Locales } from 'i18n/i18n-types';
|
|||||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||||
import { detectLocale, navigatorDetector } from 'typesafe-i18n/detectors';
|
import { detectLocale, navigatorDetector } from 'typesafe-i18n/detectors';
|
||||||
|
|
||||||
const availableLocales = [
|
// Memoize available locales to prevent recreation on every render
|
||||||
|
const AVAILABLE_LOCALES = [
|
||||||
'de',
|
'de',
|
||||||
'en',
|
'en',
|
||||||
'it',
|
'it',
|
||||||
@@ -20,47 +21,59 @@ const availableLocales = [
|
|||||||
'sv',
|
'sv',
|
||||||
'tr',
|
'tr',
|
||||||
'cz'
|
'cz'
|
||||||
];
|
] as Locales[];
|
||||||
|
|
||||||
const App = () => {
|
const App = memo(() => {
|
||||||
const [wasLoaded, setWasLoaded] = useState(false);
|
const [wasLoaded, setWasLoaded] = useState(false);
|
||||||
const [locale, setLocale] = useState<Locales>('en');
|
const [locale, setLocale] = useState<Locales>('en');
|
||||||
|
|
||||||
useEffect(() => {
|
// Memoize locale initialization to prevent unnecessary re-runs
|
||||||
// determine locale, take from session if set other default to browser language
|
const initializeLocale = useCallback(async () => {
|
||||||
const browserLocale = detectLocale('en', availableLocales, navigatorDetector);
|
const browserLocale = detectLocale('en', AVAILABLE_LOCALES, navigatorDetector);
|
||||||
const newLocale = (localStorage.getItem('lang') || browserLocale) as Locales;
|
const newLocale = (localStorage.getItem('lang') || browserLocale) as Locales;
|
||||||
localStorage.setItem('lang', newLocale);
|
localStorage.setItem('lang', newLocale);
|
||||||
setLocale(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;
|
if (!wasLoaded) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypesafeI18n locale={locale}>
|
<TypesafeI18n locale={locale}>
|
||||||
<CustomTheme>
|
<CustomTheme>
|
||||||
<AppRouting />
|
<AppRouting />
|
||||||
<ToastContainer
|
<ToastContainer {...toastContainerProps} />
|
||||||
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'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</CustomTheme>
|
</CustomTheme>
|
||||||
</TypesafeI18n>
|
</TypesafeI18n>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -37,10 +37,9 @@ const AuthenticatedRouting = () => {
|
|||||||
<Route path="/dashboard/*" element={<Dashboard />} />
|
<Route path="/dashboard/*" element={<Dashboard />} />
|
||||||
<Route path="/devices/*" element={<Devices />} />
|
<Route path="/devices/*" element={<Devices />} />
|
||||||
<Route path="/sensors/*" element={<Sensors />} />
|
<Route path="/sensors/*" element={<Sensors />} />
|
||||||
<Route path="/status/*" element={<Status />} />
|
|
||||||
<Route path="/help/*" element={<Help />} />
|
<Route path="/help/*" element={<Help />} />
|
||||||
<Route path="/*" element={<Navigate to="/" />} />
|
|
||||||
|
|
||||||
|
<Route path="/status/*" element={<Status />} />
|
||||||
<Route path="/status/hardwarestatus/*" element={<HardwareStatus />} />
|
<Route path="/status/hardwarestatus/*" element={<HardwareStatus />} />
|
||||||
<Route path="/status/activity" element={<Activity />} />
|
<Route path="/status/activity" element={<Activity />} />
|
||||||
<Route path="/status/log" element={<SystemLog />} />
|
<Route path="/status/log" element={<SystemLog />} />
|
||||||
@@ -68,6 +67,8 @@ const AuthenticatedRouting = () => {
|
|||||||
<Route path="/customentities" element={<CustomEntities />} />
|
<Route path="/customentities" element={<CustomEntities />} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Route path="/*" element={<Navigate to="/" />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
import type { FC } 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 { createTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
import type { RequiredChildrenProps } from 'utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
|
||||||
|
// Memoize dialog style to prevent recreation
|
||||||
export const dialogStyle = {
|
export const dialogStyle = {
|
||||||
'& .MuiDialog-paper': {
|
'& .MuiDialog-paper': {
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
@@ -12,8 +19,9 @@ export const dialogStyle = {
|
|||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
borderWidth: '1px'
|
borderWidth: '1px'
|
||||||
}
|
}
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
|
// Memoize theme creation to prevent recreation
|
||||||
const theme = responsiveFontSizes(
|
const theme = responsiveFontSizes(
|
||||||
createTheme({
|
createTheme({
|
||||||
typography: {
|
typography: {
|
||||||
@@ -30,15 +38,45 @@ const theme = responsiveFontSizes(
|
|||||||
text: {
|
text: {
|
||||||
disabled: '#eee' // white
|
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}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
{children}
|
{children}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
));
|
||||||
|
|
||||||
export default CustomTheme;
|
export default CustomTheme;
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ const SignIn = () => {
|
|||||||
|
|
||||||
<Box display="flex" flexDirection="column" alignItems="center">
|
<Box display="flex" flexDirection="column" alignItems="center">
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
disabled={processing}
|
disabled={processing}
|
||||||
sx={{
|
sx={{
|
||||||
width: 240
|
width: 240
|
||||||
@@ -117,7 +117,7 @@ const SignIn = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ValidatedPasswordField
|
<ValidatedPasswordField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
disabled={processing}
|
disabled={processing}
|
||||||
sx={{
|
sx={{
|
||||||
width: 240
|
width: 240
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export const readDeviceEntities = (id: number) =>
|
|||||||
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
|
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
|
||||||
params: { id },
|
params: { id },
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
|
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
|
||||||
transform(data) {
|
transform(data) {
|
||||||
return (data as DeviceEntity[]).map((de: DeviceEntity) => ({
|
return (data as DeviceEntity[]).map((de: DeviceEntity) => ({
|
||||||
...de,
|
...de,
|
||||||
@@ -92,6 +93,7 @@ export const writeDeviceName = (data: { id: number; name: string }) =>
|
|||||||
// SettingsScheduler
|
// SettingsScheduler
|
||||||
export const readSchedule = () =>
|
export const readSchedule = () =>
|
||||||
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
|
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
|
||||||
|
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
|
||||||
transform(data) {
|
transform(data) {
|
||||||
return (data as Schedule).schedule.map((si: ScheduleItem) => ({
|
return (data as Schedule).schedule.map((si: ScheduleItem) => ({
|
||||||
...si,
|
...si,
|
||||||
@@ -129,6 +131,7 @@ export const writeModules = (data: {
|
|||||||
// CustomEntities
|
// CustomEntities
|
||||||
export const readCustomEntities = () =>
|
export const readCustomEntities = () =>
|
||||||
alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
|
alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
|
||||||
|
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
|
||||||
transform(data) {
|
transform(data) {
|
||||||
return (data as Entities).entities.map((ei: EntityItem) => ({
|
return (data as Entities).entities.map((ei: EntityItem) => ({
|
||||||
...ei,
|
...ei,
|
||||||
@@ -143,7 +146,8 @@ export const readCustomEntities = () =>
|
|||||||
o_name: ei.name,
|
o_name: ei.name,
|
||||||
o_writeable: ei.writeable,
|
o_writeable: ei.writeable,
|
||||||
o_value: ei.value,
|
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,
|
cacheFor: 60 * 10 * 1000,
|
||||||
transform(response: { data: { name: string; published_at: string } }) {
|
transform(response: { data: { name: string; published_at: string } }) {
|
||||||
return {
|
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
|
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 {
|
try {
|
||||||
decoder = new TextDecoder();
|
decoder = new TextDecoder();
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
let src;
|
class C1Type {}
|
||||||
let srcEnd;
|
const C1 = new C1Type();
|
||||||
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();
|
|
||||||
C1.name = 'MessagePack 0xC1';
|
C1.name = 'MessagePack 0xC1';
|
||||||
let sequentialMode = false;
|
let sequentialMode = false,
|
||||||
let inlineObjectReadThreshold = 2;
|
inlineObjectReadThreshold = 2,
|
||||||
let readStruct, onLoadedStructures, onSaveState;
|
readStruct,
|
||||||
// no-eval build
|
onLoadedStructures,
|
||||||
|
onSaveState;
|
||||||
try {
|
try {
|
||||||
new Function('');
|
new Function('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// if eval variants are not supported, do not create inline object readers ever
|
|
||||||
inlineObjectReadThreshold = Infinity;
|
inlineObjectReadThreshold = Infinity;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Unpackr {
|
export class Unpackr {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
if (options) {
|
if (options) {
|
||||||
@@ -50,19 +47,15 @@ export class Unpackr {
|
|||||||
if (options.structures)
|
if (options.structures)
|
||||||
options.structures.sharedLength = options.structures.length;
|
options.structures.sharedLength = options.structures.length;
|
||||||
else if (options.getStructures) {
|
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;
|
options.structures.sharedLength = 0;
|
||||||
}
|
}
|
||||||
if (options.int64AsNumber) {
|
if (options.int64AsNumber) options.int64AsType = 'number';
|
||||||
options.int64AsType = 'number';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Object.assign(this, options);
|
Object.assign(this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
unpack(source, options?: any) {
|
unpack(source, options?: any) {
|
||||||
if (src) {
|
if (src) {
|
||||||
// re-entrant execution, save the state and restore it after we do this unpack
|
|
||||||
return saveState(() => {
|
return saveState(() => {
|
||||||
clearSource();
|
clearSource();
|
||||||
return this
|
return this
|
||||||
@@ -86,9 +79,6 @@ export class Unpackr {
|
|||||||
strings = EMPTY_ARRAY;
|
strings = EMPTY_ARRAY;
|
||||||
bundledStrings = null;
|
bundledStrings = null;
|
||||||
src = source;
|
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 {
|
try {
|
||||||
dataView =
|
dataView =
|
||||||
source.dataView ||
|
source.dataView ||
|
||||||
@@ -191,10 +181,10 @@ export class Unpackr {
|
|||||||
return this.unpack(source, end);
|
return this.unpack(source, end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export function getPosition() {
|
function getPosition() {
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
export function checkedRead(options: any) {
|
function checkedRead(options: any) {
|
||||||
try {
|
try {
|
||||||
if (!currentUnpackr.trusted && !sequentialMode) {
|
if (!currentUnpackr.trusted && !sequentialMode) {
|
||||||
const sharedLength = currentStructures.sharedLength || 0;
|
const sharedLength = currentStructures.sharedLength || 0;
|
||||||
@@ -264,7 +254,7 @@ function restoreStructures() {
|
|||||||
currentStructures.restoreStructures = null;
|
currentStructures.restoreStructures = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function read() {
|
function read() {
|
||||||
let token = src[position++];
|
let token = src[position++];
|
||||||
if (token < 0xa0) {
|
if (token < 0xa0) {
|
||||||
if (token < 0x80) {
|
if (token < 0x80) {
|
||||||
@@ -589,7 +579,7 @@ const createSecondByteReader = (firstId, read0) =>
|
|||||||
return structure.read();
|
return structure.read();
|
||||||
};
|
};
|
||||||
|
|
||||||
export function loadStructures() {
|
function loadStructures() {
|
||||||
const loadedStructures = saveState(() => {
|
const loadedStructures = saveState(() => {
|
||||||
// save the state in case getStructures modifies our buffer
|
// save the state in case getStructures modifies our buffer
|
||||||
src = null;
|
src = null;
|
||||||
@@ -605,9 +595,8 @@ var readFixedString = readStringJS;
|
|||||||
var readString8 = readStringJS;
|
var readString8 = readStringJS;
|
||||||
var readString16 = readStringJS;
|
var readString16 = readStringJS;
|
||||||
var readString32 = readStringJS;
|
var readString32 = readStringJS;
|
||||||
export let isNativeAccelerationEnabled = false;
|
let isNativeAccelerationEnabled = false;
|
||||||
|
function setExtractor(extractStrings) {
|
||||||
export function setExtractor(extractStrings) {
|
|
||||||
isNativeAccelerationEnabled = true;
|
isNativeAccelerationEnabled = true;
|
||||||
readFixedString = readString(1);
|
readFixedString = readString(1);
|
||||||
readString8 = readString(2);
|
readString8 = readString(2);
|
||||||
@@ -701,7 +690,7 @@ function readStringJS(length) {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
export function readString(source, start, length) {
|
function readString(source, start, length) {
|
||||||
const existingSrc = src;
|
const existingSrc = src;
|
||||||
src = source;
|
src = source;
|
||||||
position = start;
|
position = start;
|
||||||
@@ -1065,7 +1054,7 @@ currentExtensions[0x70] = (data) => {
|
|||||||
|
|
||||||
currentExtensions[0x73] = () => new Set(read());
|
currentExtensions[0x73] = () => new Set(read());
|
||||||
|
|
||||||
export const typedArrays = [
|
const typedArrays = [
|
||||||
'Int8',
|
'Int8',
|
||||||
'Uint8',
|
'Uint8',
|
||||||
'Uint8Clamped',
|
'Uint8Clamped',
|
||||||
@@ -1177,44 +1166,20 @@ function saveState(callback) {
|
|||||||
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
|
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
export function clearSource() {
|
function clearSource() {
|
||||||
src = null;
|
src = null;
|
||||||
referenceMap = null;
|
referenceMap = null;
|
||||||
currentStructures = null;
|
currentStructures = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addExtension(extension) {
|
function addExtension(extension) {
|
||||||
if (extension.unpack) currentExtensions[extension.type] = extension.unpack;
|
if (extension.unpack) currentExtensions[extension.type] = extension.unpack;
|
||||||
else currentExtensions[extension.type] = extension;
|
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++) {
|
for (let i = 0; i < 256; i++) {
|
||||||
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103));
|
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103));
|
||||||
}
|
}
|
||||||
export const Decoder = Unpackr;
|
const defaultUnpackr = new Unpackr({ useRecords: false });
|
||||||
var defaultUnpackr = new Unpackr({ useRecords: false });
|
|
||||||
export const unpack = defaultUnpackr.unpack;
|
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.factor !== ei.o_factor ||
|
||||||
ei.value_type !== ei.o_value_type ||
|
ei.value_type !== ei.o_value_type ||
|
||||||
ei.writeable !== ei.o_writeable ||
|
ei.writeable !== ei.o_writeable ||
|
||||||
|
ei.hide !== ei.o_hide ||
|
||||||
ei.deleted !== ei.o_deleted ||
|
ei.deleted !== ei.o_deleted ||
|
||||||
(ei.value || '') !== (ei.o_value || '')
|
(ei.value || '') !== (ei.o_value || '')
|
||||||
);
|
);
|
||||||
@@ -136,8 +137,8 @@ const CustomEntities = () => {
|
|||||||
const saveEntities = async () => {
|
const saveEntities = async () => {
|
||||||
await writeEntities({
|
await writeEntities({
|
||||||
entities: entities
|
entities: entities
|
||||||
.filter((ei) => !ei.deleted)
|
.filter((ei: EntityItem) => !ei.deleted)
|
||||||
.map((condensed_ei) => ({
|
.map((condensed_ei: EntityItem) => ({
|
||||||
id: condensed_ei.id,
|
id: condensed_ei.id,
|
||||||
ram: condensed_ei.ram,
|
ram: condensed_ei.ram,
|
||||||
name: condensed_ei.name,
|
name: condensed_ei.name,
|
||||||
@@ -147,6 +148,7 @@ const CustomEntities = () => {
|
|||||||
factor: condensed_ei.factor,
|
factor: condensed_ei.factor,
|
||||||
uom: condensed_ei.uom,
|
uom: condensed_ei.uom,
|
||||||
writeable: condensed_ei.writeable,
|
writeable: condensed_ei.writeable,
|
||||||
|
hide: condensed_ei.hide,
|
||||||
value_type: condensed_ei.value_type,
|
value_type: condensed_ei.value_type,
|
||||||
value: condensed_ei.value
|
value: condensed_ei.value
|
||||||
}))
|
}))
|
||||||
@@ -209,6 +211,7 @@ const CustomEntities = () => {
|
|||||||
value_type: item.value_type,
|
value_type: item.value_type,
|
||||||
writeable: item.writeable,
|
writeable: item.writeable,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
|
hide: item.hide,
|
||||||
value: item.value
|
value: item.value
|
||||||
});
|
});
|
||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
@@ -228,6 +231,7 @@ const CustomEntities = () => {
|
|||||||
value_type: 0,
|
value_type: 0,
|
||||||
writeable: false,
|
writeable: false,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
|
hide: false,
|
||||||
value: ''
|
value: ''
|
||||||
});
|
});
|
||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
@@ -248,15 +252,17 @@ const CustomEntities = () => {
|
|||||||
|
|
||||||
const renderEntity = () => {
|
const renderEntity = () => {
|
||||||
if (!entities) {
|
if (!entities) {
|
||||||
return <FormLoader onRetry={fetchEntities} errorMessage={error?.message} />;
|
return (
|
||||||
|
<FormLoader onRetry={fetchEntities} errorMessage={error?.message || ''} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
data={{
|
data={{
|
||||||
nodes: entities
|
nodes: entities
|
||||||
.filter((ei) => !ei.deleted)
|
.filter((ei: EntityItem) => !ei.deleted)
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
.sort((a: EntityItem, b: EntityItem) => a.name.localeCompare(b.name))
|
||||||
}}
|
}}
|
||||||
theme={entity_theme}
|
theme={entity_theme}
|
||||||
layout={{ custom: true }}
|
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}>
|
<Box flexGrow={1}>
|
||||||
{numChanges > 0 && (
|
{numChanges > 0 && (
|
||||||
<ButtonRow>
|
<ButtonRow>
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import { useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||||
import DoneIcon from '@mui/icons-material/Done';
|
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 RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -12,7 +16,7 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField
|
TextField
|
||||||
@@ -70,7 +74,10 @@ const CustomEntitiesDialog = ({
|
|||||||
}
|
}
|
||||||
}, [open, selectedItem]);
|
}, [open, selectedItem]);
|
||||||
|
|
||||||
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
|
const handleClose = (
|
||||||
|
_event: React.SyntheticEvent,
|
||||||
|
reason: 'backdropClick' | 'escapeKeyDown'
|
||||||
|
) => {
|
||||||
if (reason !== 'backdropClick') {
|
if (reason !== 'backdropClick') {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
@@ -119,7 +126,7 @@ const CustomEntitiesDialog = ({
|
|||||||
<Grid container spacing={2} rowSpacing={0}>
|
<Grid container spacing={2} rowSpacing={0}>
|
||||||
<Grid size={12}>
|
<Grid size={12}>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="name"
|
name="name"
|
||||||
label={LL.NAME(0)}
|
label={LL.NAME(0)}
|
||||||
value={editItem.name}
|
value={editItem.name}
|
||||||
@@ -128,6 +135,20 @@ const CustomEntitiesDialog = ({
|
|||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</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>
|
<Grid>
|
||||||
<TextField
|
<TextField
|
||||||
name="ram"
|
name="ram"
|
||||||
@@ -177,10 +198,12 @@ const CustomEntitiesDialog = ({
|
|||||||
)}
|
)}
|
||||||
{editItem.ram === 0 && (
|
{editItem.ram === 0 && (
|
||||||
<>
|
<>
|
||||||
<Grid mt={3} size={9}>
|
<Grid mt={3}>
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
icon={<EditOffOutlinedIcon color="primary" />}
|
||||||
|
checkedIcon={<EditOutlinedIcon htmlColor="white" />}
|
||||||
checked={editItem.writeable}
|
checked={editItem.writeable}
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
name="writeable"
|
name="writeable"
|
||||||
@@ -191,7 +214,7 @@ const CustomEntitiesDialog = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="device_id"
|
name="device_id"
|
||||||
label={LL.ID_OF(LL.DEVICE())}
|
label={LL.ID_OF(LL.DEVICE())}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
@@ -211,7 +234,7 @@ const CustomEntitiesDialog = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="type_id"
|
name="type_id"
|
||||||
label={LL.ID_OF(LL.TYPE(1))}
|
label={LL.ID_OF(LL.TYPE(1))}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
@@ -231,7 +254,7 @@ const CustomEntitiesDialog = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="offset"
|
name="offset"
|
||||||
label={LL.OFFSET()}
|
label={LL.OFFSET()}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
@@ -323,7 +346,7 @@ const CustomEntitiesDialog = ({
|
|||||||
editItem.device_id !== '0' && (
|
editItem.device_id !== '0' && (
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="factor"
|
name="factor"
|
||||||
label={LL.BYTES()}
|
label={LL.BYTES()}
|
||||||
value={numberValue(editItem.factor as number)}
|
value={numberValue(editItem.factor as number)}
|
||||||
@@ -341,7 +364,7 @@ const CustomEntitiesDialog = ({
|
|||||||
{editItem.value_type === DeviceValueType.BOOL && (
|
{editItem.value_type === DeviceValueType.BOOL && (
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="factor"
|
name="factor"
|
||||||
label={LL.BITMASK()}
|
label={LL.BITMASK()}
|
||||||
value={editItem.factor as string}
|
value={editItem.factor as string}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
Link,
|
Link,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
@@ -125,13 +125,22 @@ const Customizations = () => {
|
|||||||
|
|
||||||
const setOriginalSettings = (data: DeviceEntity[]) => {
|
const setOriginalSettings = (data: DeviceEntity[]) => {
|
||||||
setDeviceEntities(
|
setDeviceEntities(
|
||||||
data.map((de) => ({
|
data.map((de) => {
|
||||||
...de,
|
const result: DeviceEntity = {
|
||||||
o_m: de.m,
|
...de,
|
||||||
o_cn: de.cn,
|
o_m: de.m
|
||||||
o_mi: de.mi,
|
};
|
||||||
o_ma: de.ma
|
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);
|
setSelectedDevice(-1);
|
||||||
setSelectedDeviceTypeNameURL('');
|
setSelectedDeviceTypeNameURL('');
|
||||||
} else {
|
} else {
|
||||||
setSelectedDeviceTypeNameURL(devices.devices[index].url || '');
|
const device = devices.devices[index];
|
||||||
setSelectedDeviceName(devices.devices[index].n);
|
if (device) {
|
||||||
|
setSelectedDeviceTypeNameURL(device.url || '');
|
||||||
|
setSelectedDeviceName(device.n);
|
||||||
|
}
|
||||||
setNumChanges(0);
|
setNumChanges(0);
|
||||||
setRestartNeeded(false);
|
setRestartNeeded(false);
|
||||||
}
|
}
|
||||||
@@ -306,7 +318,7 @@ const Customizations = () => {
|
|||||||
|
|
||||||
const filter_entity = (de: DeviceEntity) =>
|
const filter_entity = (de: DeviceEntity) =>
|
||||||
(de.m & selectedFilters || !selectedFilters) &&
|
(de.m & selectedFilters || !selectedFilters) &&
|
||||||
formatName(de, true).includes(search);
|
formatName(de, true).toLowerCase().includes(search.toLowerCase());
|
||||||
|
|
||||||
const maskDisabled = (set: boolean) => {
|
const maskDisabled = (set: boolean) => {
|
||||||
setDeviceEntities(
|
setDeviceEntities(
|
||||||
@@ -396,14 +408,20 @@ const Customizations = () => {
|
|||||||
await sendCustomizationEntities({
|
await sendCustomizationEntities({
|
||||||
id: selectedDevice,
|
id: selectedDevice,
|
||||||
entity_ids: masked_entities
|
entity_ids: masked_entities
|
||||||
}).catch((error: Error) => {
|
})
|
||||||
if (error.message === 'Reboot required') {
|
.then(() => {
|
||||||
setRestartNeeded(true);
|
toast.success(LL.CUSTOMIZATIONS_SAVED());
|
||||||
} else {
|
})
|
||||||
toast.error(error.message);
|
.catch((error: Error) => {
|
||||||
}
|
if (error.message === 'Reboot required') {
|
||||||
});
|
setRestartNeeded(true);
|
||||||
setOriginalSettings(deviceEntities);
|
} else {
|
||||||
|
toast.error(error.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setOriginalSettings(deviceEntities);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -545,7 +563,7 @@ const Customizations = () => {
|
|||||||
size="small"
|
size="small"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
value={getMaskString(selectedFilters)}
|
value={getMaskString(selectedFilters)}
|
||||||
onChange={(event, mask: string[]) => {
|
onChange={(_, mask: string[]) => {
|
||||||
setSelectedFilters(getMaskNumber(mask));
|
setSelectedFilters(getMaskNumber(mask));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
TextField,
|
TextField,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
@@ -54,7 +54,10 @@ const CustomizationsDialog = ({
|
|||||||
}
|
}
|
||||||
}, [open, selectedItem]);
|
}, [open, selectedItem]);
|
||||||
|
|
||||||
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
|
const handleClose = (
|
||||||
|
_event: React.SyntheticEvent,
|
||||||
|
reason: 'backdropClick' | 'escapeKeyDown'
|
||||||
|
) => {
|
||||||
if (reason !== 'backdropClick') {
|
if (reason !== 'backdropClick') {
|
||||||
onClose();
|
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 { IconContext } from 'react-icons/lib';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
ToggleButton,
|
ToggleButton,
|
||||||
ToggleButtonGroup,
|
ToggleButtonGroup,
|
||||||
|
Tooltip,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import { deviceValueItemValidation } from './validators';
|
import { deviceValueItemValidation } from './validators';
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = memo(() => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
|
|
||||||
@@ -76,35 +77,40 @@ const Dashboard = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
const deviceValueDialogSave = useCallback(
|
||||||
if (!selectedDashboardItem) {
|
async (devicevalue: DeviceValue) => {
|
||||||
return;
|
if (!selectedDashboardItem) {
|
||||||
}
|
return;
|
||||||
const id = selectedDashboardItem.parentNode.id; // this is the parent ID
|
}
|
||||||
await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
|
const id = selectedDashboardItem.parentNode.id; // this is the parent ID
|
||||||
.then(() => {
|
await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
|
||||||
toast.success(LL.WRITE_CMD_SENT());
|
.then(() => {
|
||||||
})
|
toast.success(LL.WRITE_CMD_SENT());
|
||||||
.catch((error: Error) => {
|
})
|
||||||
toast.error(error.message);
|
.catch((error: Error) => {
|
||||||
})
|
toast.error(error.message);
|
||||||
.finally(() => {
|
})
|
||||||
setDeviceValueDialogOpen(false);
|
.finally(() => {
|
||||||
setSelectedDashboardItem(undefined);
|
setDeviceValueDialogOpen(false);
|
||||||
});
|
setSelectedDashboardItem(undefined);
|
||||||
};
|
});
|
||||||
|
},
|
||||||
|
[selectedDashboardItem, sendDeviceValue, LL]
|
||||||
|
);
|
||||||
|
|
||||||
const dashboard_theme = useTheme({
|
const dashboard_theme = useMemo(
|
||||||
Table: `
|
() =>
|
||||||
|
useTheme({
|
||||||
|
Table: `
|
||||||
--data-table-library_grid-template-columns: minmax(80px, auto) 120px 32px;
|
--data-table-library_grid-template-columns: minmax(80px, auto) 120px 32px;
|
||||||
`,
|
`,
|
||||||
BaseRow: `
|
BaseRow: `
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
.td {
|
.td {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
Row: `
|
Row: `
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #1e1e1e;
|
background-color: #1e1e1e;
|
||||||
&:nth-of-type(odd) .td {
|
&:nth-of-type(odd) .td {
|
||||||
@@ -114,7 +120,7 @@ const Dashboard = () => {
|
|||||||
background-color: #177ac9;
|
background-color: #177ac9;
|
||||||
},
|
},
|
||||||
`,
|
`,
|
||||||
BaseCell: `
|
BaseCell: `
|
||||||
&:nth-of-type(2) {
|
&:nth-of-type(2) {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@@ -122,12 +128,14 @@ const Dashboard = () => {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
});
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const tree = useTree(
|
const tree = useTree(
|
||||||
{ nodes: data.nodes },
|
{ nodes: data.nodes },
|
||||||
{
|
{
|
||||||
onChange: undefined // not used but needed
|
onChange: () => {} // not used but needed
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
treeIcon: {
|
treeIcon: {
|
||||||
@@ -156,65 +164,82 @@ const Dashboard = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const nodeIds = useMemo(
|
||||||
|
() => data.nodes.map((item: DashboardItem) => item.id),
|
||||||
|
[data.nodes]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
showAll
|
showAll
|
||||||
? tree.fns.onAddAll(data.nodes.map((item: DashboardItem) => item.id)) // expand tree
|
? tree.fns.onAddAll(nodeIds) // expand tree
|
||||||
: tree.fns.onRemoveAll(); // collapse tree
|
: tree.fns.onRemoveAll(); // collapse tree
|
||||||
}, [parentNodes]);
|
}, [parentNodes]);
|
||||||
|
|
||||||
const showType = (n?: string, t?: number) => {
|
const showType = useCallback(
|
||||||
// if we have a name show it
|
(n?: string, t?: number) => {
|
||||||
if (n) {
|
// if we have a name show it
|
||||||
return n;
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
if (t) {
|
||||||
return '';
|
// otherwise pick translation based on type
|
||||||
};
|
switch (t) {
|
||||||
|
case DeviceType.CUSTOM:
|
||||||
const showName = (di: DashboardItem) => {
|
return LL.CUSTOM_ENTITIES(0);
|
||||||
if (di.id < 100) {
|
case DeviceType.ANALOGSENSOR:
|
||||||
// if its a device (parent node) and has entities
|
return LL.ANALOG_SENSORS();
|
||||||
if (di.nodes?.length) {
|
case DeviceType.TEMPERATURESENSOR:
|
||||||
return (
|
return LL.TEMP_SENSORS();
|
||||||
<span style={{ fontWeight: 'bold', fontSize: '14px' }}>
|
case DeviceType.SCHEDULER:
|
||||||
<DeviceIcon type_id={di.t ?? 0} />
|
return LL.SCHEDULER();
|
||||||
{showType(di.n, di.t)}
|
default:
|
||||||
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</span>
|
break;
|
||||||
</span>
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
return '';
|
||||||
if (di.dv) {
|
},
|
||||||
return <span>{di.dv.id.slice(2)}</span>;
|
[LL]
|
||||||
}
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const hasMask = (id: string, mask: number) =>
|
const showName = useCallback(
|
||||||
(parseInt(id.slice(0, 2), 16) & mask) === mask;
|
(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) => {
|
const hasMask = useCallback(
|
||||||
if (me.admin && di.dv?.c) {
|
(id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask,
|
||||||
setSelectedDashboardItem(di);
|
[]
|
||||||
setDeviceValueDialogOpen(true);
|
);
|
||||||
}
|
|
||||||
};
|
const editDashboardValue = useCallback(
|
||||||
|
(di: DashboardItem) => {
|
||||||
|
if (me.admin && di.dv?.c) {
|
||||||
|
setSelectedDashboardItem(di);
|
||||||
|
setDeviceValueDialogOpen(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[me.admin]
|
||||||
|
);
|
||||||
|
|
||||||
const handleShowAll = (
|
const handleShowAll = (
|
||||||
event: React.MouseEvent<HTMLElement>,
|
_event: React.MouseEvent<HTMLElement>,
|
||||||
toggle: boolean | null
|
toggle: boolean | null
|
||||||
) => {
|
) => {
|
||||||
if (toggle !== 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 = () => {
|
const renderContent = () => {
|
||||||
if (!data) {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{!data.connected && (
|
{!data.connected && (
|
||||||
@@ -257,37 +285,44 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
{data.nodes.length > 0 && (
|
{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
|
<Box
|
||||||
padding={1}
|
display="flex"
|
||||||
justifyContent="center"
|
justifyContent="flex-end"
|
||||||
flexDirection="column"
|
flexWrap="nowrap"
|
||||||
sx={{
|
whiteSpace="nowrap"
|
||||||
borderRadius: 1,
|
|
||||||
border: '1px solid grey'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
|
<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
|
<IconContext.Provider
|
||||||
value={{
|
value={{
|
||||||
color: 'lightblue',
|
color: 'lightblue',
|
||||||
@@ -375,6 +410,6 @@ const Dashboard = () => {
|
|||||||
)}
|
)}
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default Dashboard;
|
export default Dashboard;
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { IconContext } from 'react-icons';
|
import { IconContext } from 'react-icons';
|
||||||
@@ -31,7 +33,7 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
List,
|
List,
|
||||||
@@ -75,7 +77,7 @@ import { DeviceEntityMask, DeviceType, DeviceValueUOM_s } from './types';
|
|||||||
import type { Device, DeviceValue } from './types';
|
import type { Device, DeviceValue } from './types';
|
||||||
import { deviceValueItemValidation } from './validators';
|
import { deviceValueItemValidation } from './validators';
|
||||||
|
|
||||||
const Devices = () => {
|
const Devices = memo(() => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
|
|
||||||
@@ -141,11 +143,13 @@ const Devices = () => {
|
|||||||
return left + (right - left < 400 ? 0 : 200);
|
return left + (right - left < 400 ? 0 : 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
const common_theme = useTheme({
|
const common_theme = useMemo(
|
||||||
BaseRow: `
|
() =>
|
||||||
|
useTheme({
|
||||||
|
BaseRow: `
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
`,
|
`,
|
||||||
HeaderRow: `
|
HeaderRow: `
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
color: #90CAF9;
|
color: #90CAF9;
|
||||||
@@ -153,7 +157,7 @@ const Devices = () => {
|
|||||||
border-bottom: 1px solid #565656;
|
border-bottom: 1px solid #565656;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
Row: `
|
Row: `
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #1E1E1E;
|
background-color: #1E1E1E;
|
||||||
.td {
|
.td {
|
||||||
@@ -163,30 +167,47 @@ const Devices = () => {
|
|||||||
background-color: #177ac9;
|
background-color: #177ac9;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
});
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const device_theme = useTheme([
|
const device_theme = useMemo(
|
||||||
common_theme,
|
() =>
|
||||||
{
|
useTheme([
|
||||||
Table: `
|
common_theme,
|
||||||
|
{
|
||||||
|
BaseRow: `
|
||||||
|
font-size: 15px;
|
||||||
|
.td {
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Table: `
|
||||||
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 130px;
|
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 130px;
|
||||||
`,
|
`,
|
||||||
HeaderRow: `
|
HeaderRow: `
|
||||||
.th {
|
.th {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
`,
|
`,
|
||||||
Row: `
|
Row: `
|
||||||
font-weight: bold;
|
&:nth-of-type(odd) .td {
|
||||||
|
background-color: #303030;
|
||||||
|
},
|
||||||
&:hover .td {
|
&:hover .td {
|
||||||
background-color: #177ac9;
|
background-color: #177ac9;
|
||||||
|
},
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
]);
|
]),
|
||||||
|
[common_theme]
|
||||||
|
);
|
||||||
|
|
||||||
const data_theme = useTheme([
|
const data_theme = useMemo(
|
||||||
common_theme,
|
() =>
|
||||||
{
|
useTheme([
|
||||||
Table: `
|
common_theme,
|
||||||
|
{
|
||||||
|
Table: `
|
||||||
--data-table-library_grid-template-columns: minmax(200px, auto) minmax(150px, auto) 40px;
|
--data-table-library_grid-template-columns: minmax(200px, auto) minmax(150px, auto) 40px;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
@@ -195,12 +216,12 @@ const Devices = () => {
|
|||||||
display:none;
|
display:none;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
BaseRow: `
|
BaseRow: `
|
||||||
.td {
|
.td {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
BaseCell: `
|
BaseCell: `
|
||||||
&:nth-of-type(1) {
|
&:nth-of-type(1) {
|
||||||
border-left: 1px solid #177ac9;
|
border-left: 1px solid #177ac9;
|
||||||
},
|
},
|
||||||
@@ -211,12 +232,12 @@ const Devices = () => {
|
|||||||
border-right: 1px solid #177ac9;
|
border-right: 1px solid #177ac9;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
HeaderRow: `
|
HeaderRow: `
|
||||||
.th {
|
.th {
|
||||||
border-top: 1px solid #565656;
|
border-top: 1px solid #565656;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
Row: `
|
Row: `
|
||||||
&:nth-of-type(odd) .td {
|
&:nth-of-type(odd) .td {
|
||||||
background-color: #303030;
|
background-color: #303030;
|
||||||
},
|
},
|
||||||
@@ -224,8 +245,10 @@ const Devices = () => {
|
|||||||
background-color: #177ac9;
|
background-color: #177ac9;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
]);
|
]),
|
||||||
|
[common_theme]
|
||||||
|
);
|
||||||
|
|
||||||
const getSortIcon = (state: State, sortKey: unknown) => {
|
const getSortIcon = (state: State, sortKey: unknown) => {
|
||||||
if (state.sortKey === sortKey && state.reverse) {
|
if (state.sortKey === sortKey && state.reverse) {
|
||||||
@@ -324,18 +347,23 @@ const Devices = () => {
|
|||||||
return sc;
|
return sc;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasMask = (id: string, mask: number) =>
|
const hasMask = useCallback(
|
||||||
(parseInt(id.slice(0, 2), 16) & mask) === mask;
|
(id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask,
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const handleDownloadCsv = () => {
|
const handleDownloadCsv = () => {
|
||||||
const deviceIndex = coreData.devices.findIndex(
|
const deviceIndex = coreData.devices.findIndex(
|
||||||
(d) => d.id === device_select.state.id
|
(d: Device) => d.id === device_select.state.id
|
||||||
);
|
);
|
||||||
if (deviceIndex === -1) {
|
if (deviceIndex === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const filename =
|
const selectedDevice = coreData.devices[deviceIndex];
|
||||||
coreData.devices[deviceIndex].tn + '_' + coreData.devices[deviceIndex].n;
|
if (!selectedDevice) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filename = selectedDevice.tn + '_' + selectedDevice.n;
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -350,7 +378,7 @@ const Devices = () => {
|
|||||||
{
|
{
|
||||||
accessor: (dv: DeviceValue) =>
|
accessor: (dv: DeviceValue) =>
|
||||||
dv.u !== undefined && DeviceValueUOM_s[dv.u]
|
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'
|
name: 'UoM'
|
||||||
},
|
},
|
||||||
@@ -373,7 +401,9 @@ const Devices = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const data = onlyFav
|
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;
|
: deviceData.nodes;
|
||||||
|
|
||||||
const csvData = data.reduce(
|
const csvData = data.reduce(
|
||||||
@@ -433,10 +463,14 @@ const Devices = () => {
|
|||||||
const renderDeviceDetails = () => {
|
const renderDeviceDetails = () => {
|
||||||
if (showDeviceInfo) {
|
if (showDeviceInfo) {
|
||||||
const deviceIndex = coreData.devices.findIndex(
|
const deviceIndex = coreData.devices.findIndex(
|
||||||
(d) => d.id === device_select.state.id
|
(d: Device) => d.id === device_select.state.id
|
||||||
);
|
);
|
||||||
if (deviceIndex === -1) {
|
if (deviceIndex === -1) {
|
||||||
return;
|
return null;
|
||||||
|
}
|
||||||
|
const deviceDetails = coreData.devices[deviceIndex];
|
||||||
|
if (!deviceDetails) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -449,47 +483,35 @@ const Devices = () => {
|
|||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<List dense={true}>
|
<List dense={true}>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText primary={LL.TYPE(0)} secondary={deviceDetails.tn} />
|
||||||
primary={LL.TYPE(0)}
|
|
||||||
secondary={coreData.devices[deviceIndex].tn}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText primary={LL.NAME(0)} secondary={deviceDetails.n} />
|
||||||
primary={LL.NAME(0)}
|
|
||||||
secondary={coreData.devices[deviceIndex].n}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{coreData.devices[deviceIndex].t !== DeviceType.CUSTOM && (
|
{deviceDetails.t !== DeviceType.CUSTOM && (
|
||||||
<>
|
<>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText primary={LL.BRAND()} secondary={deviceDetails.b} />
|
||||||
primary={LL.BRAND()}
|
|
||||||
secondary={coreData.devices[deviceIndex].b}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={LL.ID_OF(LL.DEVICE())}
|
primary={LL.ID_OF(LL.DEVICE())}
|
||||||
secondary={
|
secondary={
|
||||||
'0x' +
|
'0x' +
|
||||||
(
|
('00' + deviceDetails.d.toString(16).toUpperCase()).slice(-2)
|
||||||
'00' +
|
|
||||||
coreData.devices[deviceIndex].d.toString(16).toUpperCase()
|
|
||||||
).slice(-2)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={LL.ID_OF(LL.PRODUCT())}
|
primary={LL.ID_OF(LL.PRODUCT())}
|
||||||
secondary={coreData.devices[deviceIndex].p}
|
secondary={deviceDetails.p}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={LL.VERSION()}
|
primary={LL.VERSION()}
|
||||||
secondary={coreData.devices[deviceIndex].v}
|
secondary={deviceDetails.v}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</>
|
</>
|
||||||
@@ -508,59 +530,62 @@ const Devices = () => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCoreData = () => (
|
const renderCoreData = () => (
|
||||||
<>
|
<>
|
||||||
<IconContext.Provider
|
<Box justifyContent="center" flexDirection="column">
|
||||||
value={{
|
<IconContext.Provider
|
||||||
color: 'lightblue',
|
value={{
|
||||||
size: '18',
|
color: 'lightblue',
|
||||||
style: { verticalAlign: 'middle' }
|
size: '18',
|
||||||
}}
|
style: { verticalAlign: 'middle' }
|
||||||
>
|
}}
|
||||||
{!coreData.connected && (
|
>
|
||||||
<MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />
|
{!coreData.connected && (
|
||||||
)}
|
<MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />
|
||||||
|
)}
|
||||||
|
|
||||||
{coreData.connected && (
|
{coreData.connected && (
|
||||||
<Table
|
<Table
|
||||||
data={{ nodes: coreData.devices }}
|
data={{ nodes: coreData.devices }}
|
||||||
select={device_select}
|
select={device_select}
|
||||||
theme={device_theme}
|
theme={device_theme}
|
||||||
layout={{ custom: true }}
|
layout={{ custom: true }}
|
||||||
>
|
>
|
||||||
{(tableList: Device[]) => (
|
{(tableList: Device[]) => (
|
||||||
<>
|
<>
|
||||||
<Header>
|
<Header>
|
||||||
<HeaderRow>
|
<HeaderRow>
|
||||||
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
||||||
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
|
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
|
||||||
</HeaderRow>
|
</HeaderRow>
|
||||||
</Header>
|
</Header>
|
||||||
<Body>
|
<Body>
|
||||||
{tableList.length === 0 && (
|
{tableList.length === 0 && (
|
||||||
<CircularProgress sx={{ margin: 1 }} size={18} />
|
<CircularProgress sx={{ margin: 1 }} size={18} />
|
||||||
)}
|
)}
|
||||||
{tableList.map((device: Device) => (
|
{tableList.map((device: Device) => (
|
||||||
<Row key={device.id} item={device}>
|
<Row key={device.id} item={device}>
|
||||||
<Cell>
|
<Cell>
|
||||||
<DeviceIcon type_id={device.t} />
|
<DeviceIcon type_id={device.t} />
|
||||||
|
|
||||||
{device.n}
|
{device.n}
|
||||||
<span style={{ color: 'lightblue' }}>
|
<span style={{ color: 'lightblue' }}>
|
||||||
({device.e})
|
({device.e})
|
||||||
</span>
|
</span>
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell stiff>{device.tn}</Cell>
|
<Cell stiff>{device.tn}</Cell>
|
||||||
</Row>
|
</Row>
|
||||||
))}
|
))}
|
||||||
</Body>
|
</Body>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Table>
|
</Table>
|
||||||
)}
|
)}
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>
|
||||||
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -576,41 +601,54 @@ const Devices = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const showDeviceValue = (dv: DeviceValue) => {
|
const showDeviceValue = useCallback((dv: DeviceValue) => {
|
||||||
setSelectedDeviceValue(dv);
|
setSelectedDeviceValue(dv);
|
||||||
setDeviceValueDialogOpen(true);
|
setDeviceValueDialogOpen(true);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const renderNameCell = (dv: DeviceValue) => (
|
const renderNameCell = useCallback(
|
||||||
<>
|
(dv: DeviceValue) => (
|
||||||
{dv.id.slice(2)}
|
<>
|
||||||
{hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) && (
|
{dv.id.slice(2)}
|
||||||
<StarIcon color="primary" sx={{ fontSize: 12 }} />
|
{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_READONLY) && (
|
||||||
)}
|
<EditOffOutlinedIcon color="primary" sx={{ fontSize: 12 }} />
|
||||||
{hasMask(dv.id, DeviceEntityMask.DV_API_MQTT_EXCLUDE) && (
|
)}
|
||||||
<CommentsDisabledOutlinedIcon color="primary" sx={{ fontSize: 12 }} />
|
{hasMask(dv.id, DeviceEntityMask.DV_API_MQTT_EXCLUDE) && (
|
||||||
)}
|
<CommentsDisabledOutlinedIcon color="primary" sx={{ fontSize: 12 }} />
|
||||||
</>
|
)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
[hasMask]
|
||||||
);
|
);
|
||||||
|
|
||||||
const shown_data = onlyFav
|
const shown_data = useMemo(() => {
|
||||||
? deviceData.nodes.filter(
|
if (onlyFav) {
|
||||||
(dv) =>
|
return deviceData.nodes.filter(
|
||||||
|
(dv: DeviceValue) =>
|
||||||
hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) &&
|
hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) &&
|
||||||
dv.id.slice(2).includes(search)
|
dv.id.slice(2).toLowerCase().includes(search.toLowerCase())
|
||||||
)
|
);
|
||||||
: deviceData.nodes.filter((dv) => dv.id.slice(2).includes(search));
|
}
|
||||||
|
return deviceData.nodes.filter((dv: DeviceValue) =>
|
||||||
|
dv.id.slice(2).toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
}, [deviceData.nodes, onlyFav, search]);
|
||||||
|
|
||||||
const deviceIndex = coreData.devices.findIndex(
|
const deviceIndex = coreData.devices.findIndex(
|
||||||
(d) => d.id === device_select.state.id
|
(d: Device) => d.id === device_select.state.id
|
||||||
);
|
);
|
||||||
if (deviceIndex === -1) {
|
if (deviceIndex === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const deviceInfo = coreData.devices[deviceIndex];
|
||||||
|
if (!deviceInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, height] = size;
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -621,15 +659,15 @@ const Devices = () => {
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
top: 64,
|
top: 64,
|
||||||
zIndex: 'modal',
|
zIndex: 'modal',
|
||||||
maxHeight: () => size[1] - 126,
|
maxHeight: () => (height || 0) - 126,
|
||||||
border: '1px solid #177ac9'
|
border: '1px solid #177ac9'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ p: 1 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Grid container justifyContent="space-between">
|
<Grid container justifyContent="space-between">
|
||||||
<Typography noWrap variant="subtitle1" color="warning.main">
|
<Typography noWrap variant="subtitle1" color="warning.main">
|
||||||
{coreData.devices[deviceIndex].n} (
|
{deviceInfo.n} (
|
||||||
{coreData.devices[deviceIndex].tn})
|
{deviceInfo.tn})
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid justifyContent="flex-end">
|
<Grid justifyContent="flex-end">
|
||||||
<ButtonTooltip title={LL.CLOSE()}>
|
<ButtonTooltip title={LL.CLOSE()}>
|
||||||
@@ -699,7 +737,7 @@ const Devices = () => {
|
|||||||
' ' +
|
' ' +
|
||||||
shown_data.length +
|
shown_data.length +
|
||||||
'/' +
|
'/' +
|
||||||
coreData.devices[deviceIndex].e +
|
deviceInfo.e +
|
||||||
' ' +
|
' ' +
|
||||||
LL.ENTITIES(shown_data.length)}
|
LL.ENTITIES(shown_data.length)}
|
||||||
</span>
|
</span>
|
||||||
@@ -790,6 +828,6 @@ const Devices = () => {
|
|||||||
)}
|
)}
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default Devices;
|
export default Devices;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
@@ -120,7 +120,7 @@ const DevicesDialog = ({
|
|||||||
{editItem.l ? (
|
{editItem.l ? (
|
||||||
<TextField
|
<TextField
|
||||||
name="v"
|
name="v"
|
||||||
label={LL.VALUE(0)}
|
// label={LL.VALUE(0)}
|
||||||
value={editItem.v}
|
value={editItem.v}
|
||||||
disabled={!writeable}
|
disabled={!writeable}
|
||||||
sx={{ width: '30ch' }}
|
sx={{ width: '30ch' }}
|
||||||
@@ -135,7 +135,7 @@ const DevicesDialog = ({
|
|||||||
</TextField>
|
</TextField>
|
||||||
) : editItem.s || editItem.u !== DeviceValueUOM.NONE ? (
|
) : editItem.s || editItem.u !== DeviceValueUOM.NONE ? (
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="v"
|
name="v"
|
||||||
label={LL.VALUE(0)}
|
label={LL.VALUE(0)}
|
||||||
value={numberValue(Math.round((editItem.v as number) * 10) / 10)}
|
value={numberValue(Math.round((editItem.v as number) * 10) / 10)}
|
||||||
@@ -159,7 +159,7 @@ const DevicesDialog = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="v"
|
name="v"
|
||||||
label={LL.VALUE(0)}
|
label={LL.VALUE(0)}
|
||||||
value={editItem.v}
|
value={editItem.v}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => {
|
|||||||
size="small"
|
size="small"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
value={getMaskString(de.m)}
|
value={getMaskString(de.m)}
|
||||||
onChange={(event, mask: string[]) => {
|
onChange={(_event, mask: string[]) => {
|
||||||
de.m = getMaskNumber(mask);
|
de.m = getMaskNumber(mask);
|
||||||
if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) {
|
if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) {
|
||||||
de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE;
|
de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE;
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ const Help = () => {
|
|||||||
|
|
||||||
useRequest(() => callAction({ action: 'getCustomSupport' })).onSuccess((event) => {
|
useRequest(() => callAction({ action: 'getCustomSupport' })).onSuccess((event) => {
|
||||||
if (event && event.data && Object.keys(event.data).length !== 0) {
|
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) {
|
if (data.img_url) {
|
||||||
setCustomSupportIMG(data.img_url);
|
setCustomSupportIMG(data.img_url);
|
||||||
}
|
}
|
||||||
@@ -59,7 +60,7 @@ const Help = () => {
|
|||||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||||
})
|
})
|
||||||
.onError((error) => {
|
.onError((error) => {
|
||||||
toast.error(error.message);
|
toast.error(String(error.error?.message || 'An error occurred'));
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -72,7 +73,7 @@ const Help = () => {
|
|||||||
divider={<Divider orientation="vertical" flexItem />}
|
divider={<Divider orientation="vertical" flexItem />}
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
border: '2px solid grey',
|
border: '1px solid lightblue',
|
||||||
justifyContent: 'space-evenly',
|
justifyContent: 'space-evenly',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}}
|
}}
|
||||||
@@ -98,7 +99,7 @@ const Help = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{me.admin && (
|
{me.admin && (
|
||||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
<List>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
component="a"
|
component="a"
|
||||||
|
|||||||
@@ -133,13 +133,15 @@ const Modules = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const saveModules = async () => {
|
const saveModules = async () => {
|
||||||
await updateModules({
|
await Promise.all(
|
||||||
modules: modules.map((condensed_mi) => ({
|
modules.map((condensed_mi: ModuleItem) =>
|
||||||
key: condensed_mi.key,
|
updateModules({
|
||||||
enabled: condensed_mi.enabled,
|
key: condensed_mi.key,
|
||||||
license: condensed_mi.license
|
enabled: condensed_mi.enabled,
|
||||||
}))
|
license: condensed_mi.license
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success(LL.MODULES_UPDATED());
|
toast.success(LL.MODULES_UPDATED());
|
||||||
})
|
})
|
||||||
@@ -154,7 +156,9 @@ const Modules = () => {
|
|||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (!modules) {
|
if (!modules) {
|
||||||
return <FormLoader onRetry={fetchModules} errorMessage={error?.message} />;
|
return (
|
||||||
|
<FormLoader onRetry={fetchModules} errorMessage={error?.message || ''} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modules.length === 0) {
|
if (modules.length === 0) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
TextField
|
TextField
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
import { readSchedule, writeSchedule } from '../../api/app';
|
import { readSchedule, writeSchedule } from '../../api/app';
|
||||||
import SettingsSchedulerDialog from './SchedulerDialog';
|
import SettingsSchedulerDialog from './SchedulerDialog';
|
||||||
@@ -73,6 +74,12 @@ const Scheduler = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
if (numChanges === 0) {
|
||||||
|
void fetchSchedule();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const formatter = new Intl.DateTimeFormat(locale, {
|
const formatter = new Intl.DateTimeFormat(locale, {
|
||||||
weekday: 'short',
|
weekday: 'short',
|
||||||
@@ -128,8 +135,8 @@ const Scheduler = () => {
|
|||||||
const saveSchedule = async () => {
|
const saveSchedule = async () => {
|
||||||
await updateSchedule({
|
await updateSchedule({
|
||||||
schedule: schedule
|
schedule: schedule
|
||||||
.filter((si) => !si.deleted)
|
.filter((si: ScheduleItem) => !si.deleted)
|
||||||
.map((condensed_si) => ({
|
.map((condensed_si: ScheduleItem) => ({
|
||||||
id: condensed_si.id,
|
id: condensed_si.id,
|
||||||
active: condensed_si.active,
|
active: condensed_si.active,
|
||||||
flags: condensed_si.flags,
|
flags: condensed_si.flags,
|
||||||
@@ -205,7 +212,9 @@ const Scheduler = () => {
|
|||||||
|
|
||||||
const renderSchedule = () => {
|
const renderSchedule = () => {
|
||||||
if (!schedule) {
|
if (!schedule) {
|
||||||
return <FormLoader onRetry={fetchSchedule} errorMessage={error?.message} />;
|
return (
|
||||||
|
<FormLoader onRetry={fetchSchedule} errorMessage={error?.message || ''} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dayBox = (si: ScheduleItem, flag: number) => (
|
const dayBox = (si: ScheduleItem, flag: number) => (
|
||||||
@@ -244,8 +253,8 @@ const Scheduler = () => {
|
|||||||
<Table
|
<Table
|
||||||
data={{
|
data={{
|
||||||
nodes: schedule
|
nodes: schedule
|
||||||
.filter((si) => !si.deleted)
|
.filter((si: ScheduleItem) => !si.deleted)
|
||||||
.sort((a, b) => a.flags - b.flags)
|
.sort((a: ScheduleItem, b: ScheduleItem) => a.flags - b.flags)
|
||||||
}}
|
}}
|
||||||
theme={schedule_theme}
|
theme={schedule_theme}
|
||||||
layout={{ custom: true }}
|
layout={{ custom: true }}
|
||||||
@@ -329,7 +338,7 @@ const Scheduler = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box mt={1} display="flex" flexWrap="wrap">
|
<Box display="flex" flexWrap="wrap">
|
||||||
<Box flexGrow={1}>
|
<Box flexGrow={1}>
|
||||||
{numChanges !== 0 && (
|
{numChanges !== 0 && (
|
||||||
<ButtonRow>
|
<ButtonRow>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
TextField,
|
TextField,
|
||||||
ToggleButton,
|
ToggleButton,
|
||||||
ToggleButtonGroup,
|
ToggleButtonGroup,
|
||||||
@@ -144,7 +144,10 @@ const SchedulerDialog = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
|
const handleClose = (
|
||||||
|
_event: React.SyntheticEvent,
|
||||||
|
reason: 'backdropClick' | 'escapeKeyDown'
|
||||||
|
) => {
|
||||||
if (reason !== 'backdropClick') {
|
if (reason !== 'backdropClick') {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
@@ -325,7 +328,7 @@ const SchedulerDialog = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="cmd"
|
name="cmd"
|
||||||
label={LL.COMMAND(0)}
|
label={LL.COMMAND(0)}
|
||||||
multiline
|
multiline
|
||||||
@@ -344,7 +347,7 @@ const SchedulerDialog = ({
|
|||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
/>
|
/>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="name"
|
name="name"
|
||||||
label={LL.NAME(0) + ' (' + LL.OPTIONAL() + ')'}
|
label={LL.NAME(0) + ' (' + LL.OPTIONAL() + ')'}
|
||||||
value={editItem.name}
|
value={editItem.name}
|
||||||
|
|||||||
@@ -105,8 +105,6 @@ const Sensors = () => {
|
|||||||
color: #90CAF9;
|
color: #90CAF9;
|
||||||
.th {
|
.th {
|
||||||
border-bottom: 1px solid #565656;
|
border-bottom: 1px solid #565656;
|
||||||
}
|
|
||||||
.th {
|
|
||||||
height: 36px;
|
height: 36px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -439,7 +437,8 @@ const Sensors = () => {
|
|||||||
<Cell>{a.n}</Cell>
|
<Cell>{a.n}</Cell>
|
||||||
<Cell stiff>{AnalogTypeNames[a.t]} </Cell>
|
<Cell stiff>{AnalogTypeNames[a.t]} </Cell>
|
||||||
{(a.t === AnalogType.DIGITAL_OUT && a.g !== 25 && a.g !== 26) ||
|
{(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.v ? LL.ON() : LL.OFF()}</Cell>
|
||||||
) : (
|
) : (
|
||||||
<Cell stiff>{a.t ? formatValue(a.v, a.u) : ''}</Cell>
|
<Cell stiff>{a.t ? formatValue(a.v, a.u) : ''}</Cell>
|
||||||
@@ -490,7 +489,7 @@ const Sensors = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{sensorData?.analog_enabled === true && me.admin && (
|
{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
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
@@ -57,7 +57,10 @@ const SensorsAnalogDialog = ({
|
|||||||
}
|
}
|
||||||
}, [open, selectedItem]);
|
}, [open, selectedItem]);
|
||||||
|
|
||||||
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
|
const handleClose = (
|
||||||
|
_event: React.SyntheticEvent,
|
||||||
|
reason: 'backdropClick' | 'escapeKeyDown'
|
||||||
|
) => {
|
||||||
if (reason !== 'backdropClick') {
|
if (reason !== 'backdropClick') {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
@@ -88,7 +91,7 @@ const SensorsAnalogDialog = ({
|
|||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="g"
|
name="g"
|
||||||
label="GPIO"
|
label="GPIO"
|
||||||
sx={{ width: '11ch' }}
|
sx={{ width: '11ch' }}
|
||||||
@@ -107,7 +110,7 @@ const SensorsAnalogDialog = ({
|
|||||||
)}
|
)}
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="n"
|
name="n"
|
||||||
label={LL.NAME(0)}
|
label={LL.NAME(0)}
|
||||||
value={editItem.n}
|
value={editItem.n}
|
||||||
@@ -132,7 +135,9 @@ const SensorsAnalogDialog = ({
|
|||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
</Grid>
|
</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>
|
<Grid>
|
||||||
<TextField
|
<TextField
|
||||||
name="u"
|
name="u"
|
||||||
@@ -171,6 +176,27 @@ const SensorsAnalogDialog = ({
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</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 && (
|
{editItem.t === AnalogType.COUNTER && (
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -187,6 +213,19 @@ const SensorsAnalogDialog = ({
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</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 && (
|
{editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && (
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -314,6 +353,42 @@ const SensorsAnalogDialog = ({
|
|||||||
</Grid>
|
</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>
|
</Grid>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
TextField,
|
TextField,
|
||||||
Typography
|
Typography
|
||||||
@@ -52,7 +52,10 @@ const SensorsTemperatureDialog = ({
|
|||||||
}
|
}
|
||||||
}, [open, selectedItem]);
|
}, [open, selectedItem]);
|
||||||
|
|
||||||
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
|
const handleClose = (
|
||||||
|
_event: React.SyntheticEvent,
|
||||||
|
reason: 'backdropClick' | 'escapeKeyDown'
|
||||||
|
) => {
|
||||||
if (reason !== 'backdropClick') {
|
if (reason !== 'backdropClick') {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
@@ -82,7 +85,7 @@ const SensorsTemperatureDialog = ({
|
|||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="n"
|
name="n"
|
||||||
label={LL.NAME(0)}
|
label={LL.NAME(0)}
|
||||||
value={editItem.n}
|
value={editItem.n}
|
||||||
|
|||||||
@@ -188,7 +188,8 @@ export enum DeviceValueUOM {
|
|||||||
VOLTS,
|
VOLTS,
|
||||||
MBAR,
|
MBAR,
|
||||||
LH,
|
LH,
|
||||||
CTKWH
|
CTKWH,
|
||||||
|
HZ
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeviceValueUOM_s = [
|
export const DeviceValueUOM_s = [
|
||||||
@@ -218,7 +219,8 @@ export const DeviceValueUOM_s = [
|
|||||||
'V',
|
'V',
|
||||||
'mbar',
|
'mbar',
|
||||||
'l/h',
|
'l/h',
|
||||||
'ct/kWh'
|
'ct/kWh',
|
||||||
|
'Hz'
|
||||||
];
|
];
|
||||||
|
|
||||||
export enum AnalogType {
|
export enum AnalogType {
|
||||||
@@ -232,20 +234,32 @@ export enum AnalogType {
|
|||||||
DIGITAL_OUT = 6,
|
DIGITAL_OUT = 6,
|
||||||
PWM_0 = 7,
|
PWM_0 = 7,
|
||||||
PWM_1 = 8,
|
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 = [
|
export const AnalogTypeNames = [
|
||||||
'(disabled)',
|
'(disabled)',
|
||||||
'Digital In',
|
'Digital In',
|
||||||
'Counter',
|
'Counter',
|
||||||
'ADC',
|
'ADC In',
|
||||||
'Timer',
|
'Timer',
|
||||||
'Rate',
|
'Rate',
|
||||||
'Digital Out',
|
'Digital Out',
|
||||||
'PWM 0',
|
'PWM 0',
|
||||||
'PWM 1',
|
'PWM 1',
|
||||||
'PWM 2'
|
'PWM 2',
|
||||||
|
'NTC Temp.',
|
||||||
|
'RGB Led',
|
||||||
|
'Pulse',
|
||||||
|
'Freq 0',
|
||||||
|
'Freq 1',
|
||||||
|
'Freq 2'
|
||||||
];
|
];
|
||||||
|
|
||||||
type BoardProfiles = Record<string, string>;
|
type BoardProfiles = Record<string, string>;
|
||||||
@@ -255,6 +269,7 @@ export const BOARD_PROFILES: BoardProfiles = {
|
|||||||
S32S3: 'BBQKees Gateway S3',
|
S32S3: 'BBQKees Gateway S3',
|
||||||
E32: 'BBQKees Gateway E32',
|
E32: 'BBQKees Gateway E32',
|
||||||
E32V2: 'BBQKees Gateway E32 V2',
|
E32V2: 'BBQKees Gateway E32 V2',
|
||||||
|
E32V2_2: 'BBQKees Gateway E32 V2.2',
|
||||||
NODEMCU: 'NodeMCU 32S',
|
NODEMCU: 'NodeMCU 32S',
|
||||||
'MH-ET': 'MH-ET Live D1 Mini',
|
'MH-ET': 'MH-ET Live D1 Mini',
|
||||||
LOLIN: 'Lolin D32',
|
LOLIN: 'Lolin D32',
|
||||||
@@ -380,6 +395,7 @@ export interface EntityItem {
|
|||||||
value_type: number;
|
value_type: number;
|
||||||
value?: unknown;
|
value?: unknown;
|
||||||
writeable: boolean;
|
writeable: boolean;
|
||||||
|
hide: boolean;
|
||||||
deleted?: boolean;
|
deleted?: boolean;
|
||||||
o_id?: number;
|
o_id?: number;
|
||||||
o_ram?: number;
|
o_ram?: number;
|
||||||
@@ -393,6 +409,7 @@ export interface EntityItem {
|
|||||||
o_deleted?: boolean;
|
o_deleted?: boolean;
|
||||||
o_writeable?: boolean;
|
o_writeable?: boolean;
|
||||||
o_value?: unknown;
|
o_value?: unknown;
|
||||||
|
o_hide?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Entities {
|
export interface Entities {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type {
|
|||||||
|
|
||||||
export const GPIO_VALIDATOR = {
|
export const GPIO_VALIDATOR = {
|
||||||
validator(
|
validator(
|
||||||
rule: InternalRuleItem,
|
_rule: InternalRuleItem,
|
||||||
value: number,
|
value: number,
|
||||||
callback: (error?: string) => void
|
callback: (error?: string) => void
|
||||||
) {
|
) {
|
||||||
@@ -36,7 +36,7 @@ export const GPIO_VALIDATOR = {
|
|||||||
|
|
||||||
export const GPIO_VALIDATORR = {
|
export const GPIO_VALIDATORR = {
|
||||||
validator(
|
validator(
|
||||||
rule: InternalRuleItem,
|
_rule: InternalRuleItem,
|
||||||
value: number,
|
value: number,
|
||||||
callback: (error?: string) => void
|
callback: (error?: string) => void
|
||||||
) {
|
) {
|
||||||
@@ -60,7 +60,7 @@ export const GPIO_VALIDATORR = {
|
|||||||
|
|
||||||
export const GPIO_VALIDATORC3 = {
|
export const GPIO_VALIDATORC3 = {
|
||||||
validator(
|
validator(
|
||||||
rule: InternalRuleItem,
|
_rule: InternalRuleItem,
|
||||||
value: number,
|
value: number,
|
||||||
callback: (error?: string) => void
|
callback: (error?: string) => void
|
||||||
) {
|
) {
|
||||||
@@ -74,7 +74,7 @@ export const GPIO_VALIDATORC3 = {
|
|||||||
|
|
||||||
export const GPIO_VALIDATORS2 = {
|
export const GPIO_VALIDATORS2 = {
|
||||||
validator(
|
validator(
|
||||||
rule: InternalRuleItem,
|
_rule: InternalRuleItem,
|
||||||
value: number,
|
value: number,
|
||||||
callback: (error?: string) => void
|
callback: (error?: string) => void
|
||||||
) {
|
) {
|
||||||
@@ -94,7 +94,7 @@ export const GPIO_VALIDATORS2 = {
|
|||||||
|
|
||||||
export const GPIO_VALIDATORS3 = {
|
export const GPIO_VALIDATORS3 = {
|
||||||
validator(
|
validator(
|
||||||
rule: InternalRuleItem,
|
_rule: InternalRuleItem,
|
||||||
value: number,
|
value: number,
|
||||||
callback: (error?: string) => void
|
callback: (error?: string) => void
|
||||||
) {
|
) {
|
||||||
@@ -279,7 +279,7 @@ export const createSettingsValidator = (settings: Settings) =>
|
|||||||
|
|
||||||
export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) => ({
|
export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) => ({
|
||||||
validator(
|
validator(
|
||||||
rule: InternalRuleItem,
|
_rule: InternalRuleItem,
|
||||||
name: string,
|
name: string,
|
||||||
callback: (error?: string) => void
|
callback: (error?: string) => void
|
||||||
) {
|
) {
|
||||||
@@ -324,7 +324,7 @@ export const uniqueCustomNameValidator = (
|
|||||||
o_name?: string
|
o_name?: string
|
||||||
) => ({
|
) => ({
|
||||||
validator(
|
validator(
|
||||||
rule: InternalRuleItem,
|
_rule: InternalRuleItem,
|
||||||
name: string,
|
name: string,
|
||||||
callback: (error?: string) => void
|
callback: (error?: string) => void
|
||||||
) {
|
) {
|
||||||
@@ -353,7 +353,7 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte
|
|||||||
device_id: [
|
device_id: [
|
||||||
{
|
{
|
||||||
validator(
|
validator(
|
||||||
rule: InternalRuleItem,
|
_rule: InternalRuleItem,
|
||||||
value: string,
|
value: string,
|
||||||
callback: (error?: string) => void
|
callback: (error?: string) => void
|
||||||
) {
|
) {
|
||||||
@@ -367,7 +367,7 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte
|
|||||||
type_id: [
|
type_id: [
|
||||||
{
|
{
|
||||||
validator(
|
validator(
|
||||||
rule: InternalRuleItem,
|
_rule: InternalRuleItem,
|
||||||
value: string,
|
value: string,
|
||||||
callback: (error?: string) => void
|
callback: (error?: string) => void
|
||||||
) {
|
) {
|
||||||
@@ -389,7 +389,7 @@ export const uniqueTemperatureNameValidator = (
|
|||||||
sensors: TemperatureSensor[],
|
sensors: TemperatureSensor[],
|
||||||
o_name?: string
|
o_name?: string
|
||||||
) => ({
|
) => ({
|
||||||
validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
validator(_rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
||||||
if (
|
if (
|
||||||
(o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) &&
|
(o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) &&
|
||||||
n !== '' &&
|
n !== '' &&
|
||||||
@@ -419,7 +419,7 @@ export const temperatureSensorItemValidation = (
|
|||||||
|
|
||||||
export const isGPIOUniqueValidator = (sensors: AnalogSensor[]) => ({
|
export const isGPIOUniqueValidator = (sensors: AnalogSensor[]) => ({
|
||||||
validator(
|
validator(
|
||||||
rule: InternalRuleItem,
|
_rule: InternalRuleItem,
|
||||||
gpio: number,
|
gpio: number,
|
||||||
callback: (error?: string) => void
|
callback: (error?: string) => void
|
||||||
) {
|
) {
|
||||||
@@ -435,7 +435,7 @@ export const uniqueAnalogNameValidator = (
|
|||||||
sensors: AnalogSensor[],
|
sensors: AnalogSensor[],
|
||||||
o_name?: string
|
o_name?: string
|
||||||
) => ({
|
) => ({
|
||||||
validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
validator(_rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
|
||||||
if (
|
if (
|
||||||
(o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) &&
|
(o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) &&
|
||||||
n !== '' &&
|
n !== '' &&
|
||||||
@@ -482,7 +482,7 @@ export const deviceValueItemValidation = (dv: DeviceValue) =>
|
|||||||
{ required: true, message: 'Value is required' },
|
{ required: true, message: 'Value is required' },
|
||||||
{
|
{
|
||||||
validator(
|
validator(
|
||||||
rule: InternalRuleItem,
|
_rule: InternalRuleItem,
|
||||||
value: unknown,
|
value: unknown,
|
||||||
callback: (error?: string) => void
|
callback: (error?: string) => void
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ const APSettings = () => {
|
|||||||
origData,
|
origData,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
setDirtyFlags,
|
setDirtyFlags,
|
||||||
updateDataValue
|
updateDataValue as (value: unknown) => void
|
||||||
);
|
);
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateAndSubmit = async () => {
|
const validateAndSubmit = async () => {
|
||||||
@@ -80,7 +80,7 @@ const APSettings = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="provision_mode"
|
name="provision_mode"
|
||||||
label={LL.AP_PROVIDE() + '...'}
|
label={LL.AP_PROVIDE() + '...'}
|
||||||
value={data.provision_mode}
|
value={data.provision_mode}
|
||||||
@@ -103,7 +103,7 @@ const APSettings = () => {
|
|||||||
{isAPEnabled(data) && (
|
{isAPEnabled(data) && (
|
||||||
<>
|
<>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="ssid"
|
name="ssid"
|
||||||
label={LL.ACCESS_POINT(2) + ' SSID'}
|
label={LL.ACCESS_POINT(2) + ' SSID'}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -113,7 +113,7 @@ const APSettings = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
<ValidatedPasswordField
|
<ValidatedPasswordField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="password"
|
name="password"
|
||||||
label={LL.ACCESS_POINT(2) + ' ' + LL.PASSWORD()}
|
label={LL.ACCESS_POINT(2) + ' ' + LL.PASSWORD()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -123,7 +123,7 @@ const APSettings = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="channel"
|
name="channel"
|
||||||
label={LL.AP_PREFERRED_CHANNEL()}
|
label={LL.AP_PREFERRED_CHANNEL()}
|
||||||
value={numberValue(data.channel)}
|
value={numberValue(data.channel)}
|
||||||
@@ -151,7 +151,7 @@ const APSettings = () => {
|
|||||||
label={LL.AP_HIDE_SSID()}
|
label={LL.AP_HIDE_SSID()}
|
||||||
/>
|
/>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="max_clients"
|
name="max_clients"
|
||||||
label={LL.AP_MAX_CLIENTS()}
|
label={LL.AP_MAX_CLIENTS()}
|
||||||
value={numberValue(data.max_clients)}
|
value={numberValue(data.max_clients)}
|
||||||
@@ -169,7 +169,7 @@ const APSettings = () => {
|
|||||||
))}
|
))}
|
||||||
</ValidatedTextField>
|
</ValidatedTextField>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="local_ip"
|
name="local_ip"
|
||||||
label={LL.AP_LOCAL_IP()}
|
label={LL.AP_LOCAL_IP()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -179,7 +179,7 @@ const APSettings = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="gateway_ip"
|
name="gateway_ip"
|
||||||
label={LL.NETWORK_GATEWAY()}
|
label={LL.NETWORK_GATEWAY()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -189,7 +189,7 @@ const APSettings = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="subnet_mask"
|
name="subnet_mask"
|
||||||
label={LL.NETWORK_SUBNET()}
|
label={LL.NETWORK_SUBNET()}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Divider,
|
Divider,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
@@ -75,7 +75,7 @@ const ApplicationSettings = () => {
|
|||||||
origData,
|
origData,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
setDirtyFlags,
|
setDirtyFlags,
|
||||||
updateDataValue
|
updateDataValue as (value: unknown) => void
|
||||||
);
|
);
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
@@ -135,7 +135,7 @@ const ApplicationSettings = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data || !hardwareData) {
|
if (!data || !hardwareData) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateAndSubmit = async () => {
|
const validateAndSubmit = async () => {
|
||||||
@@ -219,7 +219,7 @@ const ApplicationSettings = () => {
|
|||||||
<Grid container spacing={2} rowSpacing={0}>
|
<Grid container spacing={2} rowSpacing={0}>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="modbus_max_clients"
|
name="modbus_max_clients"
|
||||||
label={LL.AP_MAX_CLIENTS()}
|
label={LL.AP_MAX_CLIENTS()}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -231,7 +231,7 @@ const ApplicationSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="modbus_port"
|
name="modbus_port"
|
||||||
label="Port"
|
label="Port"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -243,7 +243,7 @@ const ApplicationSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="modbus_timeout"
|
name="modbus_timeout"
|
||||||
label="Timeout"
|
label="Timeout"
|
||||||
slotProps={{
|
slotProps={{
|
||||||
@@ -273,7 +273,7 @@ const ApplicationSettings = () => {
|
|||||||
<Grid container spacing={2} rowSpacing={0}>
|
<Grid container spacing={2} rowSpacing={0}>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="syslog_host"
|
name="syslog_host"
|
||||||
label="Host"
|
label="Host"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -284,7 +284,7 @@ const ApplicationSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="syslog_port"
|
name="syslog_port"
|
||||||
label="Port"
|
label="Port"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -315,7 +315,7 @@ const ApplicationSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="syslog_mark_interval"
|
name="syslog_mark_interval"
|
||||||
label={LL.MARK_INTERVAL()}
|
label={LL.MARK_INTERVAL()}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
@@ -485,7 +485,7 @@ const ApplicationSettings = () => {
|
|||||||
<Grid container spacing={2} rowSpacing={0}>
|
<Grid container spacing={2} rowSpacing={0}>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="rx_gpio"
|
name="rx_gpio"
|
||||||
label={LL.GPIO_OF('Rx')}
|
label={LL.GPIO_OF('Rx')}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -498,7 +498,7 @@ const ApplicationSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="tx_gpio"
|
name="tx_gpio"
|
||||||
label={LL.GPIO_OF('Tx')}
|
label={LL.GPIO_OF('Tx')}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -511,7 +511,7 @@ const ApplicationSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="pbutton_gpio"
|
name="pbutton_gpio"
|
||||||
label={LL.GPIO_OF(LL.BUTTON())}
|
label={LL.GPIO_OF(LL.BUTTON())}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -524,7 +524,7 @@ const ApplicationSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="dallas_gpio"
|
name="dallas_gpio"
|
||||||
label={
|
label={
|
||||||
LL.GPIO_OF(LL.TEMPERATURE()) + ' (0=' + LL.DISABLED(1) + ')'
|
LL.GPIO_OF(LL.TEMPERATURE()) + ' (0=' + LL.DISABLED(1) + ')'
|
||||||
@@ -539,7 +539,7 @@ const ApplicationSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="led_gpio"
|
name="led_gpio"
|
||||||
label={LL.GPIO_OF('LED') + ' (0=' + LL.DISABLED(1) + ')'}
|
label={LL.GPIO_OF('LED') + ' (0=' + LL.DISABLED(1) + ')'}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -554,7 +554,7 @@ const ApplicationSettings = () => {
|
|||||||
<Grid>
|
<Grid>
|
||||||
<TextField
|
<TextField
|
||||||
name="led_type"
|
name="led_type"
|
||||||
label={'LED ' + LL.TYPE()}
|
label={'LED ' + LL.TYPE(0)}
|
||||||
value={data.led_type}
|
value={data.led_type}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -743,7 +743,7 @@ const ApplicationSettings = () => {
|
|||||||
{data.remote_timeout_en && (
|
{data.remote_timeout_en && (
|
||||||
<Box mt={2}>
|
<Box mt={2}>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="remote_timeout"
|
name="remote_timeout"
|
||||||
label={LL.REMOTE_TIMEOUT()}
|
label={LL.REMOTE_TIMEOUT()}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
@@ -783,7 +783,7 @@ const ApplicationSettings = () => {
|
|||||||
{data.shower_timer && (
|
{data.shower_timer && (
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="shower_min_duration"
|
name="shower_min_duration"
|
||||||
label={LL.MIN_DURATION()}
|
label={LL.MIN_DURATION()}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
@@ -801,7 +801,7 @@ const ApplicationSettings = () => {
|
|||||||
<>
|
<>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="shower_alert_trigger"
|
name="shower_alert_trigger"
|
||||||
label={LL.TRIGGER_TIME()}
|
label={LL.TRIGGER_TIME()}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
@@ -817,7 +817,7 @@ const ApplicationSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="shower_alert_coldshot"
|
name="shower_alert_coldshot"
|
||||||
label={LL.COLD_SHOT_DURATION()}
|
label={LL.COLD_SHOT_DURATION()}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
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 * as SystemApi from 'api/system';
|
||||||
import { API, callAction } from 'api/app';
|
import { API, callAction } from 'api/app';
|
||||||
@@ -35,7 +35,7 @@ const DownloadUpload = () => {
|
|||||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||||
})
|
})
|
||||||
.onError((error) => {
|
.onError((error) => {
|
||||||
toast.error(error.message);
|
toast.error(String(error.error?.message || 'An error occurred'));
|
||||||
});
|
});
|
||||||
|
|
||||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||||
@@ -57,7 +57,7 @@ const DownloadUpload = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import WarningIcon from '@mui/icons-material/Warning';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
@@ -56,7 +56,7 @@ const MqttSettings = () => {
|
|||||||
origData,
|
origData,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
setDirtyFlags,
|
setDirtyFlags,
|
||||||
updateDataValue
|
updateDataValue as (value: unknown) => void
|
||||||
);
|
);
|
||||||
|
|
||||||
const SecondsInputProps = {
|
const SecondsInputProps = {
|
||||||
@@ -65,7 +65,7 @@ const MqttSettings = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateAndSubmit = async () => {
|
const validateAndSubmit = async () => {
|
||||||
@@ -93,7 +93,7 @@ const MqttSettings = () => {
|
|||||||
<Grid container spacing={2} rowSpacing={0}>
|
<Grid container spacing={2} rowSpacing={0}>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="host"
|
name="host"
|
||||||
label={LL.ADDRESS_OF(LL.BROKER())}
|
label={LL.ADDRESS_OF(LL.BROKER())}
|
||||||
multiline
|
multiline
|
||||||
@@ -105,7 +105,7 @@ const MqttSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="port"
|
name="port"
|
||||||
label="Port"
|
label="Port"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -117,7 +117,7 @@ const MqttSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="base"
|
name="base"
|
||||||
label={LL.BASE_TOPIC()}
|
label={LL.BASE_TOPIC()}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -158,7 +158,7 @@ const MqttSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="keep_alive"
|
name="keep_alive"
|
||||||
label="Keep Alive"
|
label="Keep Alive"
|
||||||
slotProps={{
|
slotProps={{
|
||||||
@@ -254,109 +254,107 @@ const MqttSettings = () => {
|
|||||||
}
|
}
|
||||||
label={LL.MQTT_RESPONSE()}
|
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>
|
<Grid>
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
name="publish_single"
|
name="publish_single2cmd"
|
||||||
checked={data.publish_single}
|
checked={data.publish_single2cmd}
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={LL.MQTT_PUBLISH_TEXT_1()}
|
label={LL.MQTT_PUBLISH_TEXT_2()}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</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>
|
<Grid>
|
||||||
<BlockFormControlLabel
|
<TextField
|
||||||
control={
|
name="discovery_type"
|
||||||
<Checkbox
|
label={LL.MQTT_PUBLISH_TEXT_5()}
|
||||||
name="publish_single2cmd"
|
value={data.discovery_type}
|
||||||
checked={data.publish_single2cmd}
|
variant="outlined"
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
/>
|
margin="normal"
|
||||||
}
|
select
|
||||||
label={LL.MQTT_PUBLISH_TEXT_2()}
|
>
|
||||||
|
<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>
|
||||||
)}
|
<Grid>
|
||||||
</Grid>
|
<TextField
|
||||||
)}
|
name="entity_format"
|
||||||
{!data.publish_single && (
|
label={LL.MQTT_ENTITY_FORMAT()}
|
||||||
<Grid container spacing={2} rowSpacing={0}>
|
value={data.entity_format}
|
||||||
<Grid>
|
variant="outlined"
|
||||||
<BlockFormControlLabel
|
onChange={updateFormValue}
|
||||||
control={
|
margin="normal"
|
||||||
<Checkbox
|
select
|
||||||
name="ha_enabled"
|
>
|
||||||
checked={data.ha_enabled}
|
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
|
||||||
onChange={updateFormValue}
|
<MenuItem value={3}>
|
||||||
/>
|
{LL.MQTT_ENTITY_FORMAT_1()} (v3.6)
|
||||||
}
|
</MenuItem>
|
||||||
label={LL.MQTT_PUBLISH_TEXT_3()}
|
<MenuItem value={4}>
|
||||||
/>
|
{LL.MQTT_ENTITY_FORMAT_2()} (v3.6)
|
||||||
</Grid>
|
</MenuItem>
|
||||||
{data.ha_enabled && (
|
<MenuItem value={1}>{LL.MQTT_ENTITY_FORMAT_1()}</MenuItem>
|
||||||
<Grid container spacing={2} rowSpacing={0}>
|
<MenuItem value={2}>{LL.MQTT_ENTITY_FORMAT_2()}</MenuItem>
|
||||||
<Grid>
|
</TextField>
|
||||||
<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>
|
</Grid>
|
||||||
)}
|
</Grid>
|
||||||
</Grid>
|
)}
|
||||||
)}
|
</Grid>
|
||||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||||
{LL.MQTT_PUBLISH_INTERVALS()} (0=auto)
|
{LL.MQTT_PUBLISH_INTERVALS()} (0=auto)
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={2} rowSpacing={0}>
|
<Grid container spacing={2} rowSpacing={0}>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="publish_time_heartbeat"
|
name="publish_time_heartbeat"
|
||||||
label="Heartbeat"
|
label="Heartbeat"
|
||||||
slotProps={{
|
slotProps={{
|
||||||
@@ -442,7 +440,7 @@ const MqttSettings = () => {
|
|||||||
<Grid>
|
<Grid>
|
||||||
<TextField
|
<TextField
|
||||||
name="publish_time_sensor"
|
name="publish_time_sensor"
|
||||||
label={LL.TEMP_SENSORS()}
|
label={LL.SENSORS()}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={numberValue(data.publish_time_sensor)}
|
value={numberValue(data.publish_time_sensor)}
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
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 * as NTPApi from 'api/ntp';
|
||||||
import { readNTPSettings } from 'api/ntp';
|
import { readNTPSettings } from 'api/ntp';
|
||||||
|
|
||||||
|
import { dialogStyle } from 'CustomTheme';
|
||||||
|
import { useRequest } from 'alova/client';
|
||||||
import { updateState } from 'alova/client';
|
import { updateState } from 'alova/client';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
import {
|
import {
|
||||||
@@ -19,8 +34,8 @@ import {
|
|||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { NTPSettingsType } from 'types';
|
import type { NTPSettingsType, Time } from 'types';
|
||||||
import { updateValueDirty, useRest } from 'utils';
|
import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { validate } from 'validators';
|
||||||
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
||||||
|
|
||||||
@@ -46,18 +61,101 @@ const NTPSettings = () => {
|
|||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle('NTP');
|
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(
|
const updateFormValue = updateValueDirty(
|
||||||
origData,
|
origData,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
setDirtyFlags,
|
setDirtyFlags,
|
||||||
updateDataValue
|
updateDataValue as (value: unknown) => void
|
||||||
);
|
);
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
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 = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateAndSubmit = async () => {
|
const validateAndSubmit = async () => {
|
||||||
@@ -92,7 +190,7 @@ const NTPSettings = () => {
|
|||||||
label={LL.ENABLE_NTP()}
|
label={LL.ENABLE_NTP()}
|
||||||
/>
|
/>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="server"
|
name="server"
|
||||||
label={LL.NTP_SERVER()}
|
label={LL.NTP_SERVER()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -102,7 +200,7 @@ const NTPSettings = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="tz_label"
|
name="tz_label"
|
||||||
label={LL.TIME_ZONE()}
|
label={LL.TIME_ZONE()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -115,6 +213,25 @@ const NTPSettings = () => {
|
|||||||
<MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
|
<MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
|
||||||
{timeZoneSelectItems()}
|
{timeZoneSelectItems()}
|
||||||
</ValidatedTextField>
|
</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 && (
|
{dirtyFlags && dirtyFlags.length !== 0 && (
|
||||||
<ButtonRow>
|
<ButtonRow>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
Divider,
|
||||||
List
|
List
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
@@ -74,9 +75,9 @@ const Settings = () => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
||||||
const content = () => (
|
return (
|
||||||
<>
|
<SectionContent>
|
||||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
<List>
|
||||||
<ListMenuItem
|
<ListMenuItem
|
||||||
icon={TuneIcon}
|
icon={TuneIcon}
|
||||||
bgcolor="#134ba2"
|
bgcolor="#134ba2"
|
||||||
@@ -143,7 +144,15 @@ const Settings = () => {
|
|||||||
|
|
||||||
{renderFactoryResetDialog()}
|
{renderFactoryResetDialog()}
|
||||||
|
|
||||||
<Box mt={2} display="flex" flexWrap="wrap">
|
<Divider />
|
||||||
|
|
||||||
|
<Box
|
||||||
|
mt={2}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="flex-end"
|
||||||
|
flexWrap="nowrap"
|
||||||
|
whiteSpace="nowrap"
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
startIcon={<SettingsBackupRestoreIcon />}
|
startIcon={<SettingsBackupRestoreIcon />}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -153,10 +162,8 @@ const Settings = () => {
|
|||||||
{LL.FACTORY_RESET()}
|
{LL.FACTORY_RESET()}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</SectionContent>
|
||||||
);
|
);
|
||||||
|
|
||||||
return <SectionContent>{content()}</SectionContent>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Settings;
|
export default Settings;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const Network = () => {
|
|||||||
],
|
],
|
||||||
useLocation()
|
useLocation()
|
||||||
);
|
);
|
||||||
const routerTab = matchedRoutes?.[0].route.path || false;
|
const routerTab = matchedRoutes?.[0]?.route.path || false;
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ const Network = () => {
|
|||||||
return (
|
return (
|
||||||
<WiFiConnectionContext.Provider
|
<WiFiConnectionContext.Provider
|
||||||
value={{
|
value={{
|
||||||
selectedNetwork,
|
...(selectedNetwork && { selectedNetwork }),
|
||||||
selectNetwork,
|
selectNetwork,
|
||||||
deselectNetwork
|
deselectNetwork
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const NetworkSettings = () => {
|
|||||||
origData,
|
origData,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
setDirtyFlags,
|
setDirtyFlags,
|
||||||
updateDataValue
|
updateDataValue as (value: unknown) => void
|
||||||
);
|
);
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
@@ -113,7 +113,7 @@ const NetworkSettings = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateAndSubmit = async () => {
|
const validateAndSubmit = async () => {
|
||||||
@@ -172,7 +172,7 @@ const NetworkSettings = () => {
|
|||||||
</List>
|
</List>
|
||||||
) : (
|
) : (
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="ssid"
|
name="ssid"
|
||||||
label={'SSID (' + LL.NETWORK_BLANK_SSID() + ')'}
|
label={'SSID (' + LL.NETWORK_BLANK_SSID() + ')'}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -183,7 +183,7 @@ const NetworkSettings = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="bssid"
|
name="bssid"
|
||||||
label={'BSSID (' + LL.NETWORK_BLANK_BSSID() + ')'}
|
label={'BSSID (' + LL.NETWORK_BLANK_BSSID() + ')'}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -194,7 +194,7 @@ const NetworkSettings = () => {
|
|||||||
/>
|
/>
|
||||||
{(!selectedNetwork || !isNetworkOpen(selectedNetwork)) && (
|
{(!selectedNetwork || !isNetworkOpen(selectedNetwork)) && (
|
||||||
<ValidatedPasswordField
|
<ValidatedPasswordField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="password"
|
name="password"
|
||||||
label={LL.PASSWORD()}
|
label={LL.PASSWORD()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -251,7 +251,7 @@ const NetworkSettings = () => {
|
|||||||
{LL.GENERAL_OPTIONS()}
|
{LL.GENERAL_OPTIONS()}
|
||||||
</Typography>
|
</Typography>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="hostname"
|
name="hostname"
|
||||||
label={LL.HOSTNAME()}
|
label={LL.HOSTNAME()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -304,7 +304,7 @@ const NetworkSettings = () => {
|
|||||||
{data.static_ip_config && (
|
{data.static_ip_config && (
|
||||||
<>
|
<>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="local_ip"
|
name="local_ip"
|
||||||
label={LL.AP_LOCAL_IP()}
|
label={LL.AP_LOCAL_IP()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -314,7 +314,7 @@ const NetworkSettings = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="gateway_ip"
|
name="gateway_ip"
|
||||||
label={LL.NETWORK_GATEWAY()}
|
label={LL.NETWORK_GATEWAY()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -324,7 +324,7 @@ const NetworkSettings = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="subnet_mask"
|
name="subnet_mask"
|
||||||
label={LL.NETWORK_SUBNET()}
|
label={LL.NETWORK_SUBNET()}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -334,7 +334,7 @@ const NetworkSettings = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="dns_ip_1"
|
name="dns_ip_1"
|
||||||
label="DNS #1"
|
label="DNS #1"
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -344,7 +344,7 @@ const NetworkSettings = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="dns_ip_2"
|
name="dns_ip_2"
|
||||||
label="DNS #2"
|
label="DNS #2"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@@ -50,9 +50,7 @@ const WiFiNetworkScanner = () => {
|
|||||||
|
|
||||||
const renderNetworkScanner = () => {
|
const renderNetworkScanner = () => {
|
||||||
if (!networkList) {
|
if (!networkList) {
|
||||||
return (
|
return <FormLoader errorMessage={errorMessage || ''} />;
|
||||||
<FormLoader message={LL.SCANNING() + '...'} errorMessage={errorMessage} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return <WiFiNetworkSelector networkList={networkList} />;
|
return <WiFiNetworkSelector networkList={networkList} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ const ManageUsers = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const noAdminConfigured = () => !data.users.find((u) => u.admin);
|
const noAdminConfigured = () => !data.users.find((u) => u.admin);
|
||||||
@@ -260,15 +260,20 @@ const ManageUsers = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<GenerateToken username={generatingToken} onClose={closeGenerateToken} />
|
<GenerateToken
|
||||||
<User
|
username={generatingToken || ''}
|
||||||
user={user}
|
onClose={closeGenerateToken}
|
||||||
setUser={setUser}
|
|
||||||
creating={creating}
|
|
||||||
onDoneEditing={doneEditingUser}
|
|
||||||
onCancelEditing={cancelEditingUser}
|
|
||||||
validator={createUserValidator(data.users, creating)}
|
|
||||||
/>
|
/>
|
||||||
|
{user && (
|
||||||
|
<User
|
||||||
|
user={user}
|
||||||
|
setUser={setUser}
|
||||||
|
creating={creating}
|
||||||
|
onDoneEditing={doneEditingUser}
|
||||||
|
onCancelEditing={cancelEditingUser}
|
||||||
|
validator={createUserValidator(data.users, creating)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const Security = () => {
|
|||||||
],
|
],
|
||||||
useLocation()
|
useLocation()
|
||||||
);
|
);
|
||||||
const routerTab = matchedRoutes?.[0].route.path || false;
|
const routerTab = matchedRoutes?.[0]?.route.path || false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -47,12 +47,12 @@ const SecuritySettings = () => {
|
|||||||
origData,
|
origData,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
setDirtyFlags,
|
setDirtyFlags,
|
||||||
updateDataValue
|
updateDataValue as (value: unknown) => void
|
||||||
);
|
);
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateAndSubmit = async () => {
|
const validateAndSubmit = async () => {
|
||||||
@@ -69,7 +69,7 @@ const SecuritySettings = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ValidatedPasswordField
|
<ValidatedPasswordField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="jwt_secret"
|
name="jwt_secret"
|
||||||
label={LL.SU_PASSWORD()}
|
label={LL.SU_PASSWORD()}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ const User: FC<UserFormProps> = ({
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="username"
|
name="username"
|
||||||
label={LL.USERNAME(1)}
|
label={LL.USERNAME(1)}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -93,7 +93,7 @@ const User: FC<UserFormProps> = ({
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
<ValidatedPasswordField
|
<ValidatedPasswordField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="password"
|
name="password"
|
||||||
label={LL.PASSWORD()}
|
label={LL.PASSWORD()}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const APStatus = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ const SystemActivity = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const showName = (id: number) => {
|
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]();
|
return LL.STATUS_NAMES[name]();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ const SystemActivity = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const HardwareStatus = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ const MqttStatus = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderConnectionStatus = () => (
|
const renderConnectionStatus = () => (
|
||||||
|
|||||||
@@ -1,40 +1,27 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
|
||||||
import DnsIcon from '@mui/icons-material/Dns';
|
import DnsIcon from '@mui/icons-material/Dns';
|
||||||
import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle';
|
import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle';
|
||||||
import UpdateIcon from '@mui/icons-material/Update';
|
import UpdateIcon from '@mui/icons-material/Update';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Dialog,
|
|
||||||
DialogActions,
|
|
||||||
DialogContent,
|
|
||||||
DialogTitle,
|
|
||||||
Divider,
|
Divider,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
TextField,
|
|
||||||
Typography,
|
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import type { Theme } from '@mui/material';
|
import type { Theme } from '@mui/material';
|
||||||
|
|
||||||
import * as NTPApi from 'api/ntp';
|
import * as NTPApi from 'api/ntp';
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
|
||||||
import { useRequest } from 'alova/client';
|
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 { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { NTPStatusType, Time } from 'types';
|
import type { NTPStatusType } from 'types';
|
||||||
import { NTPSyncStatus } from 'types';
|
import { NTPSyncStatus } from 'types';
|
||||||
import { useInterval } from 'utils';
|
import { useInterval } from 'utils';
|
||||||
import { formatDateTime, formatLocalDateTime } from 'utils';
|
import { formatDateTime } from 'utils';
|
||||||
|
|
||||||
const NTPStatus = () => {
|
const NTPStatus = () => {
|
||||||
const { data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
|
const { data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
|
||||||
@@ -43,24 +30,11 @@ const NTPStatus = () => {
|
|||||||
void loadData();
|
void loadData();
|
||||||
});
|
});
|
||||||
|
|
||||||
const [localTime, setLocalTime] = useState<string>('');
|
|
||||||
const [settingTime, setSettingTime] = useState<boolean>(false);
|
|
||||||
const [processing, setProcessing] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle('NTP');
|
useLayoutTitle('NTP');
|
||||||
|
|
||||||
const { send: updateTime } = useRequest(
|
|
||||||
(local_time: Time) => NTPApi.updateTime(local_time),
|
|
||||||
{
|
|
||||||
immediate: false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
NTPApi.updateTime;
|
NTPApi.updateTime;
|
||||||
|
|
||||||
const isNtpActive = ({ status }: NTPStatusType) =>
|
|
||||||
status === NTPSyncStatus.NTP_ACTIVE;
|
|
||||||
const isNtpEnabled = ({ status }: NTPStatusType) =>
|
const isNtpEnabled = ({ status }: NTPStatusType) =>
|
||||||
status !== NTPSyncStatus.NTP_DISABLED;
|
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 theme = useTheme();
|
||||||
|
|
||||||
const ntpStatus = ({ status }: NTPStatusType) => {
|
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 = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -219,23 +121,6 @@ const NTPStatus = () => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider variant="inset" component="li" />
|
<Divider variant="inset" component="li" />
|
||||||
</List>
|
</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 = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -248,12 +248,12 @@ const SystemStatus = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data || !LL) {
|
if (!data || !LL) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
<List>
|
||||||
<ListMenuItem
|
<ListMenuItem
|
||||||
icon={BuildIcon}
|
icon={BuildIcon}
|
||||||
bgcolor="#72caf9"
|
bgcolor="#72caf9"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
@@ -31,13 +31,14 @@ import type { LogEntry, LogSettings } from 'types';
|
|||||||
import { LogLevel } from 'types';
|
import { LogLevel } from 'types';
|
||||||
import { updateValueDirty, useRest } from 'utils';
|
import { updateValueDirty, useRest } from 'utils';
|
||||||
|
|
||||||
const TextColors = {
|
const TextColors: Record<LogLevel, string> = {
|
||||||
[LogLevel.ERROR]: '#ff0000', // red
|
[LogLevel.ERROR]: '#ff0000', // red
|
||||||
[LogLevel.WARNING]: '#ff0000', // red
|
[LogLevel.WARNING]: '#ff0000', // red
|
||||||
[LogLevel.NOTICE]: '#ffffff', // white
|
[LogLevel.NOTICE]: '#ffffff', // white
|
||||||
[LogLevel.INFO]: '#ffcc00', // yellow
|
[LogLevel.INFO]: '#ffcc00', // yellow
|
||||||
[LogLevel.DEBUG]: '#00ffff', // cyan
|
[LogLevel.DEBUG]: '#00ffff', // cyan
|
||||||
[LogLevel.TRACE]: '#00ffff' // cyan
|
[LogLevel.TRACE]: '#00ffff', // cyan
|
||||||
|
[LogLevel.ALL]: '#ffffff' // white
|
||||||
};
|
};
|
||||||
|
|
||||||
const LogEntryLine = styled('span')(
|
const LogEntryLine = styled('span')(
|
||||||
@@ -109,7 +110,7 @@ const SystemLog = () => {
|
|||||||
origData,
|
origData,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
setDirtyFlags,
|
setDirtyFlags,
|
||||||
updateDataValue
|
updateDataValue as (value: unknown) => void
|
||||||
);
|
);
|
||||||
|
|
||||||
useSSE(fetchLogES, {
|
useSSE(fetchLogES, {
|
||||||
@@ -190,7 +191,7 @@ const SystemLog = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const SystemMonitor = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.onError((error) => {
|
.onError((error) => {
|
||||||
setErrorMessage(error.message);
|
setErrorMessage(String(error.error?.message || 'An error occurred'));
|
||||||
});
|
});
|
||||||
|
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
@@ -97,7 +97,7 @@ const SystemMonitor = () => {
|
|||||||
color="error"
|
color="error"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
>
|
>
|
||||||
{LL.RESET(0)}
|
{LL.RESTART()}
|
||||||
</Button>
|
</Button>
|
||||||
</MessageBox>
|
</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 { toast } from 'react-toastify';
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import CheckIcon from '@mui/icons-material/Done';
|
import CheckIcon from '@mui/icons-material/Done';
|
||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||||
@@ -15,8 +16,13 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Grid2 as Grid,
|
Grid,
|
||||||
|
IconButton,
|
||||||
Link,
|
Link,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableRow,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
@@ -36,247 +42,403 @@ import {
|
|||||||
} from 'components';
|
} from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import 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 Version = () => {
|
||||||
const { LL, locale } = useI18nContext();
|
const { LL, locale } = useI18nContext();
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
|
|
||||||
|
// State management
|
||||||
const [restarting, setRestarting] = useState<boolean>(false);
|
const [restarting, setRestarting] = useState<boolean>(false);
|
||||||
const [openInstallDialog, setOpenInstallDialog] = useState<boolean>(false);
|
const [openInstallDialog, setOpenInstallDialog] = useState<boolean>(false);
|
||||||
const [usingDevVersion, setUsingDevVersion] = 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 [internetLive, setInternetLive] = useState<boolean>(false);
|
||||||
const [downloadOnly, setDownloadOnly] = 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/';
|
// API calls with optimized error handling
|
||||||
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';
|
|
||||||
|
|
||||||
const { send: sendCheckUpgrade } = useRequest(
|
const { send: sendCheckUpgrade } = useRequest(
|
||||||
(versions: string) => callAction({ action: 'checkUpgrade', param: versions }),
|
(versions: string) => callAction({ action: 'checkUpgrade', param: versions }),
|
||||||
{
|
{ immediate: false }
|
||||||
immediate: false
|
|
||||||
}
|
|
||||||
).onSuccess((event) => {
|
).onSuccess((event) => {
|
||||||
const data = event.data as { emsesp_version: string; upgradeable: boolean };
|
const data = event.data as UpgradeCheckData;
|
||||||
setUpgradeAvailable(data.upgradeable);
|
setDevUpgradeAvailable(data.dev_upgradeable);
|
||||||
|
setStableUpgradeAvailable(data.stable_upgradeable);
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: data,
|
data,
|
||||||
send: loadData,
|
send: loadData,
|
||||||
error
|
error
|
||||||
} = useRequest(SystemApi.readSystemStatus).onSuccess((event) => {
|
} = useRequest(SystemApi.readSystemStatus).onSuccess((event) => {
|
||||||
// older version of EMS-ESP on 4MB boards, can't use OTA because of SSL support in HttpClient
|
const systemData = event.data as VersionData;
|
||||||
if (event.data.arduino_version.startsWith('Tasmota')) {
|
if (systemData.arduino_version.startsWith('Tasmota')) {
|
||||||
setDownloadOnly(true);
|
setDownloadOnly(true);
|
||||||
}
|
}
|
||||||
setUsingDevVersion(event.data.emsesp_version.includes('dev'));
|
setUsingDevVersion(systemData.emsesp_version.includes('dev'));
|
||||||
});
|
});
|
||||||
|
|
||||||
const { send: sendUploadURL } = useRequest(
|
const { send: sendUploadURL } = useRequest(
|
||||||
(url: string) => callAction({ action: 'uploadURL', param: url }),
|
(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: latestVersion } = useRequest(getStableVersion);
|
||||||
const { data: latestDevVersion } = useRequest(getDevVersion);
|
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), {
|
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||||
immediate: false
|
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);
|
setRestarting(true);
|
||||||
await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch(
|
await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch(
|
||||||
(error: Error) => {
|
(error: Error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
}, [sendAPI]);
|
||||||
|
|
||||||
const getBinURL = () => {
|
const installFirmwareURL = useCallback(
|
||||||
if (!internetLive) {
|
async (url: string) => {
|
||||||
return '';
|
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 =
|
}, [latestVersion, latestDevVersion, sendCheckUpgrade]);
|
||||||
'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);
|
|
||||||
};
|
|
||||||
|
|
||||||
useLayoutTitle('EMS-ESP Firmware');
|
useLayoutTitle('EMS-ESP Firmware');
|
||||||
|
|
||||||
const renderInstallDialog = () => (
|
// Memoized button rendering logic
|
||||||
<Dialog
|
const showButtons = useCallback(
|
||||||
sx={dialogStyle}
|
(showingDev: boolean) => {
|
||||||
open={openInstallDialog}
|
const choice = showingDev
|
||||||
onClose={() => closeInstallDialog()}
|
? !usingDevVersion
|
||||||
>
|
? LL.SWITCH_RELEASE_TYPE(LL.DEVELOPMENT())
|
||||||
<DialogTitle>
|
: devUpgradeAvailable
|
||||||
{LL.INSTALL() +
|
? LL.UPDATE_AVAILABLE()
|
||||||
' ' +
|
: undefined
|
||||||
(usingDevVersion ? LL.DEVELOPMENT() : LL.STABLE()) +
|
: usingDevVersion
|
||||||
' Firmware'}
|
? LL.SWITCH_RELEASE_TYPE(LL.STABLE())
|
||||||
</DialogTitle>
|
: stableUpgradeAvailable
|
||||||
<DialogContent dividers>
|
? LL.UPDATE_AVAILABLE()
|
||||||
<Typography mb={2}>
|
: undefined;
|
||||||
{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>
|
|
||||||
);
|
|
||||||
|
|
||||||
const showFirmwareDialog = (useDevVersion?: boolean) => {
|
if (!choice) {
|
||||||
setUsingDevVersion(useDevVersion || usingDevVersion);
|
return (
|
||||||
setOpenInstallDialog(true);
|
<>
|
||||||
};
|
<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 = () => {
|
if (!me.admin) return null;
|
||||||
setOpenInstallDialog(false);
|
|
||||||
setUsingDevVersion(data.emsesp_version.includes('dev'));
|
|
||||||
};
|
|
||||||
|
|
||||||
const showButtons = (showDev?: boolean) => {
|
|
||||||
if (!me.admin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadOnly) {
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
sx={{ ml: 2 }}
|
sx={{ ml: 2 }}
|
||||||
startIcon={<DownloadIcon />}
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={() => setOpenInstallDialog(false)}
|
color={choice === LL.UPDATE_AVAILABLE() ? 'success' : 'warning'}
|
||||||
color="warning"
|
|
||||||
size="small"
|
size="small"
|
||||||
|
onClick={() => showFirmwareDialog(showingDev)}
|
||||||
>
|
>
|
||||||
<Link underline="none" target="_blank" href={getBinURL()} color="warning">
|
{choice}
|
||||||
{LL.DOWNLOAD(1)}
|
|
||||||
</Link>
|
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
[
|
||||||
|
usingDevVersion,
|
||||||
|
devUpgradeAvailable,
|
||||||
|
stableUpgradeAvailable,
|
||||||
|
me.admin,
|
||||||
|
LL,
|
||||||
|
showFirmwareDialog
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
const content = useMemo(() => {
|
||||||
<Button
|
|
||||||
sx={{ ml: 2 }}
|
|
||||||
variant="outlined"
|
|
||||||
color="warning"
|
|
||||||
size="small"
|
|
||||||
onClick={() => showFirmwareDialog()}
|
|
||||||
>
|
|
||||||
{upgradeAvailable || (!usingDevVersion && showDev)
|
|
||||||
? LL.UPGRADE()
|
|
||||||
: LL.REINSTALL()}
|
|
||||||
…
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const content = () => {
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDev = data.emsesp_version.includes('dev');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box p={2} border="1px solid grey" borderRadius={2}>
|
<Box p={2} border="1px solid grey" borderRadius={2}>
|
||||||
@@ -312,9 +474,27 @@ const Version = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 8, md: 10 }}>
|
<Grid size={{ xs: 8, md: 10 }}>
|
||||||
<Typography>
|
<Typography>
|
||||||
{getPlatform()}
|
{platform}
|
||||||
<Typography variant="caption">
|
<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>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -386,16 +566,11 @@ const Version = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 8, md: 10 }}>
|
<Grid size={{ xs: 8, md: 10 }}>
|
||||||
<Typography>
|
<Typography>
|
||||||
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
|
{latestVersion?.name}
|
||||||
{latestVersion.name}
|
<IconButton onClick={() => setShowVersionInfo(1)}>
|
||||||
</Link>
|
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
||||||
{latestVersion.published_at && (
|
</IconButton>
|
||||||
<Typography component="span" variant="caption">
|
{showButtons(false)}
|
||||||
(
|
|
||||||
{formatTimeAgo(new Date(latestVersion.published_at))})
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{!usingDevVersion && showButtons(false)}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
@@ -404,37 +579,14 @@ const Version = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 8, md: 10 }}>
|
<Grid size={{ xs: 8, md: 10 }}>
|
||||||
<Typography>
|
<Typography>
|
||||||
<Link target="_blank" href={DEV_RELNOTES_URL} color="primary">
|
{latestDevVersion?.name}
|
||||||
{latestDevVersion.name}
|
<IconButton onClick={() => setShowVersionInfo(2)}>
|
||||||
</Link>
|
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
||||||
{latestDevVersion.published_at && (
|
</IconButton>
|
||||||
<Typography component="span" variant="caption">
|
|
||||||
(
|
|
||||||
{formatTimeAgo(new Date(latestDevVersion.published_at))})
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{showButtons(true)}
|
{showButtons(true)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</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">
|
<Typography mt={2} color="warning">
|
||||||
@@ -444,7 +596,25 @@ const Version = () => {
|
|||||||
)}
|
)}
|
||||||
{me.admin && (
|
{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">
|
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||||
{LL.UPLOAD()}
|
{LL.UPLOAD()}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -454,11 +624,30 @@ const Version = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}, [
|
||||||
|
data,
|
||||||
|
error,
|
||||||
|
loadData,
|
||||||
|
LL,
|
||||||
|
platform,
|
||||||
|
isDev,
|
||||||
|
internetLive,
|
||||||
|
latestVersion,
|
||||||
|
latestDevVersion,
|
||||||
|
showVersionInfo,
|
||||||
|
locale,
|
||||||
|
openInstallDialog,
|
||||||
|
fetchDevVersion,
|
||||||
|
downloadOnly,
|
||||||
|
me.admin,
|
||||||
|
showButtons,
|
||||||
|
handleVersionInfoClose,
|
||||||
|
closeInstallDialog,
|
||||||
|
installFirmwareURL,
|
||||||
|
doRestart
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return <SectionContent>{restarting ? <SystemMonitor /> : content}</SectionContent>;
|
||||||
<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 { Box } from '@mui/material';
|
||||||
import type { BoxProps } from '@mui/material';
|
import type { BoxProps } from '@mui/material';
|
||||||
|
|
||||||
const ButtonRow: FC<BoxProps> = ({ children, ...rest }) => (
|
const ButtonRow = memo<BoxProps>(({ children, ...rest }) => (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
'& button, & a, & .MuiCard-root': {
|
'& button, & a, & .MuiCard-root': {
|
||||||
mt: 2,
|
mt: 2,
|
||||||
mx: 0.6,
|
mx: 0.6,
|
||||||
'&:last-child': {
|
'&:last-child': { mr: 0 },
|
||||||
mr: 0
|
'&:first-of-type': { ml: 0 }
|
||||||
},
|
|
||||||
'&:first-of-type': {
|
|
||||||
ml: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
));
|
||||||
|
|
||||||
|
ButtonRow.displayName = 'ButtonRow';
|
||||||
|
|
||||||
export default 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) => (
|
export const ButtonTooltip = ({ children, ...props }: TooltipProps) => (
|
||||||
<Tooltip {...props} placement="top" arrow classes={{ popper: className }} />
|
<Tooltip {...props}>{children}</Tooltip>
|
||||||
))(({ 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 default ButtonTooltip;
|
export default ButtonTooltip;
|
||||||
|
|||||||
@@ -1,30 +1,20 @@
|
|||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { Divider, Paper } from '@mui/material';
|
import { Paper } from '@mui/material';
|
||||||
|
|
||||||
import type { RequiredChildrenProps } from 'utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
|
||||||
interface SectionContentProps extends RequiredChildrenProps {
|
interface SectionContentProps extends RequiredChildrenProps {
|
||||||
title?: string;
|
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SectionContent: FC<SectionContentProps> = (props) => {
|
const SectionContent: FC<SectionContentProps> = (props) => {
|
||||||
const { children, title, id } = props;
|
const { children, id } = props;
|
||||||
return (
|
return (
|
||||||
<Paper id={id} sx={{ p: 2, m: 2 }}>
|
<Paper
|
||||||
{title && (
|
id={id}
|
||||||
<Divider
|
sx={{ p: 1.5, m: 1.5, borderRadius: 3, border: '1px solid rgb(65, 65, 65)' }}
|
||||||
sx={{
|
>
|
||||||
pb: 2,
|
|
||||||
borderColor: 'primary.main',
|
|
||||||
fontSize: 20,
|
|
||||||
color: 'primary.main'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Divider>
|
|
||||||
)}
|
|
||||||
{children}
|
{children}
|
||||||
</Paper>
|
</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 './inputs';
|
||||||
export * from './layout';
|
export * from './layout';
|
||||||
export * from './loading';
|
export * from './loading';
|
||||||
export * from './routing';
|
export * from './routing';
|
||||||
export * from './upload';
|
export * from './upload';
|
||||||
export { default as SectionContent } from './SectionContent';
|
|
||||||
export { default as ButtonRow } from './ButtonRow';
|
// Specific routing exports
|
||||||
export { default as MessageBox } from './MessageBox';
|
|
||||||
export { default as BlockNavigation } from './routing/BlockNavigation';
|
export { default as BlockNavigation } from './routing/BlockNavigation';
|
||||||
export { default as ButtonTooltip } from './ButtonTooltip';
|
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ const ValidatedTextField: FC<ValidatedTextFieldProps> = ({
|
|||||||
fieldErrors,
|
fieldErrors,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const errors = fieldErrors && fieldErrors[rest.name];
|
const errors = fieldErrors?.[rest.name];
|
||||||
const renderErrors = () =>
|
|
||||||
errors &&
|
|
||||||
errors.map((e) => <FormHelperText key={e.message}>{e.message}</FormHelperText>);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TextField error={!!errors} {...rest} />
|
<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 type { FC } from 'react';
|
||||||
import { useLocation } from 'react-router';
|
import { useLocation } from 'react-router';
|
||||||
|
|
||||||
@@ -13,22 +13,26 @@ import { LayoutContext } from './context';
|
|||||||
|
|
||||||
export const DRAWER_WIDTH = 210;
|
export const DRAWER_WIDTH = 210;
|
||||||
|
|
||||||
const Layout: FC<RequiredChildrenProps> = ({ children }) => {
|
const Layout: FC<RequiredChildrenProps> = memo(({ children }) => {
|
||||||
const [mobileOpen, setMobileOpen] = useState(false);
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
const [title, setTitle] = useState(PROJECT_NAME);
|
const [title, setTitle] = useState(PROJECT_NAME);
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const handleDrawerToggle = () => {
|
// Memoize drawer toggle handler to prevent unnecessary re-renders
|
||||||
setMobileOpen(!mobileOpen);
|
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
|
// Memoize context value to prevent unnecessary re-renders
|
||||||
const obj = useMemo(() => ({ title, setTitle }), [title]);
|
const contextValue = useMemo(() => ({ title, setTitle }), [title]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutContext.Provider value={obj}>
|
<LayoutContext.Provider value={contextValue}>
|
||||||
<LayoutAppBar title={title} onToggleDrawer={handleDrawerToggle} />
|
<LayoutAppBar title={title} onToggleDrawer={handleDrawerToggle} />
|
||||||
<LayoutDrawer mobileOpen={mobileOpen} onClose={handleDrawerToggle} />
|
<LayoutDrawer mobileOpen={mobileOpen} onClose={handleDrawerToggle} />
|
||||||
<Box component="main" sx={{ marginLeft: { md: `${DRAWER_WIDTH}px` } }}>
|
<Box component="main" sx={{ marginLeft: { md: `${DRAWER_WIDTH}px` } }}>
|
||||||
@@ -37,6 +41,6 @@ const Layout: FC<RequiredChildrenProps> = ({ children }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</LayoutContext.Provider>
|
</LayoutContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default Layout;
|
export default Layout;
|
||||||
|
|||||||
@@ -73,19 +73,6 @@ const LayoutMenu = () => {
|
|||||||
>
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={LL.MODULES()}
|
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 }}
|
sx={{ my: 0 }}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
primary: {
|
primary: {
|
||||||
|
|||||||
@@ -23,11 +23,52 @@ const LayoutMenuItem = ({
|
|||||||
const selected = routeMatches(to, pathname);
|
const selected = routeMatches(to, pathname);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItemButton component={Link} to={to} disabled={disabled} selected={selected}>
|
<ListItemButton
|
||||||
<ListItemIcon sx={{ color: selected ? '#90caf9' : '#9e9e9e' }}>
|
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 />
|
<Icon />
|
||||||
</ListItemIcon>
|
</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}
|
{label}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
|||||||
@@ -58,12 +58,22 @@ const LayoutMenuItem = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListItemButton component={Link} to={to}>
|
<ListItemButton component={Link} to={to}>
|
||||||
<RenderIcon icon={icon} bgcolor={bgcolor} label={label} text={text} />
|
<RenderIcon
|
||||||
|
icon={icon}
|
||||||
|
{...(bgcolor && { bgcolor })}
|
||||||
|
label={label}
|
||||||
|
text={text}
|
||||||
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
) : (
|
) : (
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<RenderIcon icon={icon} bgcolor={bgcolor} label={label} text={text} />
|
<RenderIcon
|
||||||
|
icon={icon}
|
||||||
|
{...(bgcolor && { bgcolor })}
|
||||||
|
label={label}
|
||||||
|
text={text}
|
||||||
|
/>
|
||||||
</ListItem>
|
</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 LoadingSpinner } from './LoadingSpinner';
|
||||||
export { default as FormLoader } from './FormLoader';
|
export { default as FormLoader } from './FormLoader';
|
||||||
|
export { default as LazyLoader } from './LazyLoader';
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Path } from 'react-router';
|
import type { Path } from 'react-router';
|
||||||
|
|
||||||
import type * as H from 'history';
|
|
||||||
import { jwtDecode } from 'jwt-decode';
|
import { jwtDecode } from 'jwt-decode';
|
||||||
import type { Me, SignInRequest, SignInResponse } from 'types';
|
import type { Me, SignInRequest, SignInResponse } from 'types';
|
||||||
|
|
||||||
@@ -18,7 +17,7 @@ export function getStorage() {
|
|||||||
return localStorage || sessionStorage;
|
return localStorage || sessionStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function storeLoginRedirect(location?: H.Location) {
|
export function storeLoginRedirect(location?: { pathname: string; search: string }) {
|
||||||
if (location) {
|
if (location) {
|
||||||
getStorage().setItem(SIGN_IN_PATHNAME, location.pathname);
|
getStorage().setItem(SIGN_IN_PATHNAME, location.pathname);
|
||||||
getStorage().setItem(SIGN_IN_SEARCH, location.search);
|
getStorage().setItem(SIGN_IN_SEARCH, location.search);
|
||||||
@@ -36,7 +35,7 @@ export function fetchLoginRedirect(): Partial<Path> {
|
|||||||
clearLoginRedirect();
|
clearLoginRedirect();
|
||||||
return {
|
return {
|
||||||
pathname: signInPathname || `/dashboard`,
|
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
|
// 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 CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||||
import UploadIcon from '@mui/icons-material/Upload';
|
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 { 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 [file, setFile] = useState<File>();
|
||||||
const [dragged, setDragged] = useState(false);
|
const [dragged, setDragged] = useState(false);
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
@@ -28,14 +71,17 @@ const DragNdrop = ({ text, onFileSelected }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!e.target.files) {
|
if (!e.target.files || e.target.files.length === 0) {
|
||||||
return;
|
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
|
e.target.value = ''; // this is to allow the same file to be selected again
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDrop = (event) => {
|
const handleDrop = (event: DragEvent<HTMLDivElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const droppedFiles = event.dataTransfer.files;
|
const droppedFiles = event.dataTransfer.files;
|
||||||
if (droppedFiles.length > 0) {
|
if (droppedFiles.length > 0) {
|
||||||
@@ -43,38 +89,40 @@ const DragNdrop = ({ text, onFileSelected }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFile = (event) => {
|
const handleRemoveFile = (event: MouseEvent<HTMLButtonElement>) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setFile(undefined);
|
setFile(undefined);
|
||||||
setDragged(false);
|
setDragged(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadClick = (event) => {
|
const handleUploadClick = (event: MouseEvent<HTMLButtonElement>) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
onFileSelected(file);
|
if (file) {
|
||||||
|
onFileSelected(file);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBrowseClick = () => {
|
const handleBrowseClick = () => {
|
||||||
inputRef.current?.click();
|
inputRef.current?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragOver = (event) => {
|
const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
|
||||||
event.preventDefault(); // prevent file from being opened
|
event.preventDefault(); // prevent file from being opened
|
||||||
setDragged(true);
|
setDragged(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<DocumentUploader
|
||||||
className={`document-uploader ${file || dragged ? 'active' : ''}`}
|
active={!!(file || dragged)}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
onDragLeave={() => setDragged(false)}
|
onDragLeave={() => setDragged(false)}
|
||||||
onClick={handleBrowseClick}
|
onClick={handleBrowseClick}
|
||||||
>
|
>
|
||||||
<div className="upload-info">
|
<UploadInfo>
|
||||||
<CloudUploadIcon sx={{ marginRight: 4 }} color="primary" fontSize="large" />
|
<CloudUploadIcon sx={{ marginRight: 4 }} color="primary" fontSize="large" />
|
||||||
<p>{text}</p>
|
<Typography>{text}</Typography>
|
||||||
</div>
|
</UploadInfo>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
@@ -88,9 +136,9 @@ const DragNdrop = ({ text, onFileSelected }) => {
|
|||||||
|
|
||||||
{file && (
|
{file && (
|
||||||
<>
|
<>
|
||||||
<div className="file-info">
|
<FileInfo>
|
||||||
<p>{file.name}</p>
|
<FileName>{file.name}</FileName>
|
||||||
</div>
|
</FileInfo>
|
||||||
<Box>
|
<Box>
|
||||||
<Button
|
<Button
|
||||||
startIcon={<CancelIcon />}
|
startIcon={<CancelIcon />}
|
||||||
@@ -112,7 +160,7 @@ const DragNdrop = ({ text, onFileSelected }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</DocumentUploader>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ import { useI18nContext } from 'i18n/i18n-react';
|
|||||||
import DragNdrop from './DragNdrop';
|
import DragNdrop from './DragNdrop';
|
||||||
import { LinearProgressWithLabel } from './LinearProgressWithLabel';
|
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 [md5, setMd5] = useState<string>();
|
||||||
const [file, setFile] = useState<File>();
|
const [file, setFile] = useState<File>();
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
@@ -25,8 +30,8 @@ const SingleUpload = ({ text, doRestart }) => {
|
|||||||
} = useRequest(SystemApi.uploadFile, {
|
} = useRequest(SystemApi.uploadFile, {
|
||||||
immediate: false
|
immediate: false
|
||||||
}).onSuccess(({ data }) => {
|
}).onSuccess(({ data }) => {
|
||||||
if (data) {
|
if (data && typeof data === 'object' && 'md5' in data) {
|
||||||
setMd5(data.md5 as string);
|
setMd5((data as { md5: string }).md5);
|
||||||
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
|
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
|
||||||
setFile(undefined);
|
setFile(undefined);
|
||||||
} else {
|
} else {
|
||||||
@@ -34,16 +39,19 @@ const SingleUpload = ({ text, doRestart }) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(async () => {
|
useEffect(() => {
|
||||||
if (file) {
|
const uploadFile = async () => {
|
||||||
await sendUpload(file).catch((error: Error) => {
|
if (file) {
|
||||||
if (error.message === 'The user aborted a request') {
|
await sendUpload(file).catch((error: Error) => {
|
||||||
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
|
if (error.message.includes('The user aborted a request')) {
|
||||||
} else {
|
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
|
||||||
toast.warning('Invalid file extension or incompatible bin file');
|
} else {
|
||||||
}
|
toast.warning('Invalid file extension or incompatible bin file');
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void uploadFile();
|
||||||
}, [file]);
|
}, [file]);
|
||||||
|
|
||||||
return (
|
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
|
// cache object to prevent re-renders
|
||||||
const obj = useMemo(
|
const obj = useMemo(
|
||||||
() => ({ signIn, signOut, me, refresh }),
|
() => ({
|
||||||
|
signIn,
|
||||||
|
signOut,
|
||||||
|
refresh,
|
||||||
|
...(me && { me })
|
||||||
|
}),
|
||||||
[signIn, signOut, me, refresh]
|
[signIn, signOut, me, refresh]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ const cz: Translation = {
|
|||||||
UPLOAD: 'Nahrát',
|
UPLOAD: 'Nahrát',
|
||||||
DOWNLOAD: '{{S|s|s}}táhnout',
|
DOWNLOAD: '{{S|s|s}}táhnout',
|
||||||
INSTALL: 'Instalovat',
|
INSTALL: 'Instalovat',
|
||||||
|
REINSTALL: 'Znovu instalovat',
|
||||||
ABORTED: 'přerušeno',
|
ABORTED: 'přerušeno',
|
||||||
FAILED: 'neúspěšné',
|
FAILED: 'neúspěšné',
|
||||||
SUCCESSFUL: 'úspěšné',
|
SUCCESSFUL: 'úspěšné',
|
||||||
@@ -187,7 +188,7 @@ const cz: Translation = {
|
|||||||
COMPACT: 'Kompaktní',
|
COMPACT: 'Kompaktní',
|
||||||
DOWNLOAD_SETTINGS_TEXT: 'Vytvořte zálohu svého nastavení a konfigurace',
|
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_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',
|
ERROR: 'Neočekávaná chyba, zkuste to prosím znovu',
|
||||||
TIME_SET: 'Čas nastaven',
|
TIME_SET: 'Čas nastaven',
|
||||||
MANAGE_USERS: 'Spravovat uživatele',
|
MANAGE_USERS: 'Spravovat uživatele',
|
||||||
@@ -331,28 +332,28 @@ const cz: Translation = {
|
|||||||
ALLVALUES: 'Všechny hodnoty',
|
ALLVALUES: 'Všechny hodnoty',
|
||||||
SPECIAL_FUNCTIONS: 'Speciální funkce',
|
SPECIAL_FUNCTIONS: 'Speciální funkce',
|
||||||
WAIT_FIRMWARE: 'Firmware se nahrává a instaluje',
|
WAIT_FIRMWARE: 'Firmware se nahrává a instaluje',
|
||||||
INSTALL_VERSION: 'Tímto se instalovat verze {0}. Jste si jistí?',
|
INSTALL_VERSION: 'Tímto se {0} verze {1}. Jste si jistí?',
|
||||||
UPGRADE_AVAILABLE: 'Je k dispozici aktualizace firmwaru!',
|
UPDATE_AVAILABLE: 'aktualizace dostupná',
|
||||||
LATEST_VERSION: 'Používáte nejnovější verzi firmwaru',
|
LATEST_VERSION: 'Používáte nejnovější verzi {0}firmwaru',
|
||||||
PLEASE_WAIT: 'Prosím čekejte',
|
PLEASE_WAIT: 'Prosím čekejte',
|
||||||
RESTARTING_PRE: 'Inicializace',
|
RESTARTING_PRE: 'Inicializace',
|
||||||
RESTARTING_POST: 'Příprava',
|
RESTARTING_POST: 'Příprava',
|
||||||
AUTO_SCROLL: 'Automatické rolování',
|
AUTO_SCROLL: 'Automatické rolování',
|
||||||
DASHBOARD: 'Dashboard',
|
DASHBOARD: 'Dashboard',
|
||||||
DEVELOPER_MODE: 'Režim vývojáře',
|
DEVELOPER_MODE: 'Režim vývojáře',
|
||||||
BYTES: 'Bytes', // TODO translate
|
BYTES: 'Bajty',
|
||||||
BITMASK: 'Bit Mask',// TODO translate
|
BITMASK: 'Bit Mask',
|
||||||
DUPLICATE: 'Duplikát',
|
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.',
|
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_1: 'Nebyly nalezeny žádné oblíbené entity. Použijte modul',
|
||||||
NO_DATA_2: 'modul sloužící k jejich výběru.',
|
NO_DATA_2: 'pro jejich výběr.',
|
||||||
NO_DATA_3: 'Pro zobrazení všech dostupných entit navštivte',
|
NO_DATA_3: 'Pro zobrazení všech dostupných entit navštivte stránku',
|
||||||
THIS_VERSION: 'Tato verze',
|
THIS_VERSION: 'Tato verze',
|
||||||
PLATFORM: 'Platforma',
|
PLATFORM: 'Platforma',
|
||||||
RELEASE_TYPE: 'Typ sestavení',
|
RELEASE_TYPE: 'Typ sestavení',
|
||||||
REINSTALL: 'Přeinstalovat',
|
|
||||||
INTERNET_CONNECTION_REQUIRED: 'Pro automatickou kontrolu a instalaci aktualizací je třeba internetové připojení',
|
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;
|
export default cz;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const de: Translation = {
|
|||||||
CANCEL: 'Abbrechen',
|
CANCEL: 'Abbrechen',
|
||||||
RESET: 'Zurücksetzen',
|
RESET: 'Zurücksetzen',
|
||||||
APPLY_CHANGES: 'Änderungen anwenden ({0})',
|
APPLY_CHANGES: 'Änderungen anwenden ({0})',
|
||||||
UPDATE: 'Update',
|
UPDATE: 'Aktualisieren',
|
||||||
EXECUTE: 'Ausführen',
|
EXECUTE: 'Ausführen',
|
||||||
REMOVE: 'Entfernen',
|
REMOVE: 'Entfernen',
|
||||||
PROBLEM_UPDATING: 'Problem beim Aktualisieren',
|
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_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',
|
HELP_INFORMATION_4: 'Bitte laden Sie die Systemdetails und hängen Sie sie an das Support-Issue an',
|
||||||
UPLOAD: 'Hochladen',
|
UPLOAD: 'Hochladen',
|
||||||
DOWNLOAD: '{{H|h|h}}erunterladen',
|
DOWNLOAD: '{{Herunterladen|heruntergeladen|}}',
|
||||||
INSTALL: 'Installieren',
|
INSTALL: 'Installieren',
|
||||||
|
REINSTALL: 'Neu installieren',
|
||||||
ABORTED: 'abgebrochen',
|
ABORTED: 'abgebrochen',
|
||||||
FAILED: 'gescheitert',
|
FAILED: 'gescheitert',
|
||||||
SUCCESSFUL: 'erfolgreich',
|
SUCCESSFUL: 'erfolgreich',
|
||||||
@@ -331,9 +332,9 @@ const de: Translation = {
|
|||||||
ALLVALUES: 'Alle Werte',
|
ALLVALUES: 'Alle Werte',
|
||||||
SPECIAL_FUNCTIONS: 'Sonderfunktionen',
|
SPECIAL_FUNCTIONS: 'Sonderfunktionen',
|
||||||
WAIT_FIRMWARE: 'Die Firmware wird hochgeladen und installiert.',
|
WAIT_FIRMWARE: 'Die Firmware wird hochgeladen und installiert.',
|
||||||
INSTALL_VERSION: 'Dadurch wird die Version {0} heruntergeladen. Sind Sie sicher?',
|
INSTALL_VERSION: 'Dadurch wird die Version {1} {0}. Sind Sie sicher?',
|
||||||
UPGRADE_AVAILABLE: 'Es ist ein Firmware-Upgrade verfügbar.',
|
UPDATE_AVAILABLE: 'Firmware-Update verfügbar',
|
||||||
LATEST_VERSION: 'Sie verwenden die neueste Firmware-Version',
|
LATEST_VERSION: 'Sie verwenden die neueste {0} Firmware-Version',
|
||||||
PLEASE_WAIT: 'Bitte warten',
|
PLEASE_WAIT: 'Bitte warten',
|
||||||
RESTARTING_PRE: 'Initialisierung',
|
RESTARTING_PRE: 'Initialisierung',
|
||||||
RESTARTING_POST: 'Vorbereitung',
|
RESTARTING_POST: 'Vorbereitung',
|
||||||
@@ -343,7 +344,6 @@ const de: Translation = {
|
|||||||
BYTES: 'Bytes',
|
BYTES: 'Bytes',
|
||||||
BITMASK: 'Bit Maske',
|
BITMASK: 'Bit Maske',
|
||||||
DUPLICATE: 'Kopieren',
|
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.',
|
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_1: 'Keine favorisierten EMS-Entitäten gefunden! Verwenden Sie das Modul',
|
||||||
NO_DATA_2: ', um sie zu markieren.',
|
NO_DATA_2: ', um sie zu markieren.',
|
||||||
@@ -351,8 +351,9 @@ const de: Translation = {
|
|||||||
THIS_VERSION: 'Diese Version',
|
THIS_VERSION: 'Diese Version',
|
||||||
PLATFORM: 'Plattform',
|
PLATFORM: 'Plattform',
|
||||||
RELEASE_TYPE: 'Release Typ',
|
RELEASE_TYPE: 'Release Typ',
|
||||||
REINSTALL: 'Neu installieren',
|
INTERNET_CONNECTION_REQUIRED: 'Für die automatische Versionsprüfung und Aktualisierung ist eine Internetverbindung erforderlich',
|
||||||
INTERNET_CONNECTION_REQUIRED: 'Internetverbindung erforderlich für automatische Version-Überprüfung und -Aktualisierung',
|
SWITCH_RELEASE_TYPE: 'Zum {0}-Release wechseln',
|
||||||
|
FIRMWARE_VERSION_INFO: 'Firmware-Versionsinformation'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default de;
|
export default de;
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ const en: Translation = {
|
|||||||
UPLOAD: 'Upload',
|
UPLOAD: 'Upload',
|
||||||
DOWNLOAD: '{{D|d|d}}ownload',
|
DOWNLOAD: '{{D|d|d}}ownload',
|
||||||
INSTALL: 'Install',
|
INSTALL: 'Install',
|
||||||
|
REINSTALL: 'Reinstall',
|
||||||
ABORTED: 'aborted',
|
ABORTED: 'aborted',
|
||||||
FAILED: 'failed',
|
FAILED: 'failed',
|
||||||
SUCCESSFUL: 'successful',
|
SUCCESSFUL: 'successful',
|
||||||
@@ -179,10 +180,10 @@ const en: Translation = {
|
|||||||
DEVELOPMENT: 'Development',
|
DEVELOPMENT: 'Development',
|
||||||
UPTIME: 'System Uptime',
|
UPTIME: 'System Uptime',
|
||||||
FREE_MEMORY: 'Free Memory',
|
FREE_MEMORY: 'Free Memory',
|
||||||
PSRAM: 'PSRAM (Size / Free)',
|
PSRAM: 'PSRAM (size / free)',
|
||||||
FLASH: 'Flash Chip (Size , Speed)',
|
FLASH: 'Flash Chip (size , speed)',
|
||||||
APPSIZE: 'Application (Partition: Used / Free)',
|
APPSIZE: 'Application (partition: used / free)',
|
||||||
FILESYSTEM: 'File System (Used / Free)',
|
FILESYSTEM: 'File System (used / free)',
|
||||||
BUFFER_SIZE: 'Max Buffer Size',
|
BUFFER_SIZE: 'Max Buffer Size',
|
||||||
COMPACT: 'Compact',
|
COMPACT: 'Compact',
|
||||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
|
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
|
||||||
@@ -331,9 +332,9 @@ const en: Translation = {
|
|||||||
ALLVALUES: 'All Values',
|
ALLVALUES: 'All Values',
|
||||||
SPECIAL_FUNCTIONS: 'Special Functions',
|
SPECIAL_FUNCTIONS: 'Special Functions',
|
||||||
WAIT_FIRMWARE: 'Firmware is uploading and installing',
|
WAIT_FIRMWARE: 'Firmware is uploading and installing',
|
||||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?',
|
INSTALL_VERSION: 'This will {0} version {1}. Are you sure?',
|
||||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!',
|
UPDATE_AVAILABLE: 'update available',
|
||||||
LATEST_VERSION: 'You are using the latest firmware version',
|
LATEST_VERSION: 'You are using the latest {0} firmware version',
|
||||||
PLEASE_WAIT: 'Please wait',
|
PLEASE_WAIT: 'Please wait',
|
||||||
RESTARTING_PRE: 'Initializing',
|
RESTARTING_PRE: 'Initializing',
|
||||||
RESTARTING_POST: 'Preparing',
|
RESTARTING_POST: 'Preparing',
|
||||||
@@ -344,15 +345,15 @@ const en: Translation = {
|
|||||||
BYTES: 'Bytes',
|
BYTES: 'Bytes',
|
||||||
BITMASK: 'Bit Mask',
|
BITMASK: 'Bit Mask',
|
||||||
DUPLICATE: 'Duplicate',
|
DUPLICATE: 'Duplicate',
|
||||||
UPGRADE: 'Upgrade',
|
|
||||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the',
|
NO_DATA_1: 'No favorite EMS entities found yet. Use the',
|
||||||
NO_DATA_2: 'module to mark them.',
|
NO_DATA_2: 'module to mark them.',
|
||||||
NO_DATA_3: 'To see all available entities go to',
|
NO_DATA_3: 'To see all available entities go to',
|
||||||
THIS_VERSION: 'This Version',
|
THIS_VERSION: 'This Version',
|
||||||
PLATFORM: 'Platform',
|
PLATFORM: 'Platform',
|
||||||
RELEASE_TYPE: 'Release Type',
|
RELEASE_TYPE: 'Release Type',
|
||||||
REINSTALL: 'Re-install',
|
|
||||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
|
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;
|
export default en;
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ const fr: Translation = {
|
|||||||
CHANGE_VALUE: 'Changer la valeur',
|
CHANGE_VALUE: 'Changer la valeur',
|
||||||
CANCEL: 'Annuler',
|
CANCEL: 'Annuler',
|
||||||
RESET: 'Réinitialiser',
|
RESET: 'Réinitialiser',
|
||||||
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
APPLY_CHANGES: 'Appliquer les changements ({0})',
|
||||||
UPDATE: 'Update', // TODO translate
|
UPDATE: 'Update',
|
||||||
EXECUTE: 'Execute', // TODO translate
|
EXECUTE: 'Execute',
|
||||||
REMOVE: 'Enlever',
|
REMOVE: 'Enlever',
|
||||||
PROBLEM_UPDATING: 'Problème lors de la mise à jour',
|
PROBLEM_UPDATING: 'Problème lors de la mise à jour',
|
||||||
PROBLEM_LOADING: 'Problème lors du chargement',
|
PROBLEM_LOADING: 'Problème lors du chargement',
|
||||||
@@ -66,13 +66,13 @@ const fr: Translation = {
|
|||||||
TEMP_SENSOR: 'Capteur de température',
|
TEMP_SENSOR: 'Capteur de température',
|
||||||
TEMP_SENSORS: 'Capteurs de température',
|
TEMP_SENSORS: 'Capteurs de température',
|
||||||
WRITE_CMD_SENT: 'Envoyer la commande sent',
|
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...',
|
EMS_BUS_SCANNING: 'Scan des appareils EMS...',
|
||||||
CONNECTED: 'Connecté',
|
CONNECTED: 'Connecté',
|
||||||
TX_ISSUES: 'Problèmes de transmission (Tx) - Essayez un autre mode Tx',
|
TX_ISSUES: 'Problèmes de transmission (Tx) - Essayez un autre mode Tx',
|
||||||
DISCONNECTED: 'Déconnecté',
|
DISCONNECTED: 'Déconnecté',
|
||||||
EMS_SCAN: 'Etes-vous sûr de vouloir lancer un scan complet du bus EMS ?',
|
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',
|
EMS_DEVICE: 'Appareils EMS',
|
||||||
SUCCESS: 'SUCCÈS',
|
SUCCESS: 'SUCCÈS',
|
||||||
FAIL: 'ÉCHEC',
|
FAIL: 'ÉCHEC',
|
||||||
@@ -114,9 +114,9 @@ const fr: Translation = {
|
|||||||
BYPASS_TOKEN: "Contourner l'autorisation du jeton d'accès sur les appels API",
|
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)',
|
READONLY: 'Activer le mode lecture uniquement (bloque toutes les commandes EMS sortantes en écriture Tx)',
|
||||||
UNDERCLOCK_CPU: 'Underclock du CPU',
|
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: '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',
|
MIN_DURATION: 'Wait time',
|
||||||
ENABLE_SHOWER_TIMER: 'Activer la minuterie de la douche',
|
ENABLE_SHOWER_TIMER: 'Activer la minuterie de la douche',
|
||||||
ENABLE_SHOWER_ALERT: 'Activer les alertes de durée de 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_3: "désactiver l'action d'écriture",
|
||||||
CUSTOMIZATIONS_HELP_4: "exclure de MQTT et de l'API",
|
CUSTOMIZATIONS_HELP_4: "exclure de MQTT et de l'API",
|
||||||
CUSTOMIZATIONS_HELP_5: 'cacher des appareils',
|
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',
|
SELECT_DEVICE: 'Sélectionnez un appareil',
|
||||||
SET_ALL: 'tout régler',
|
SET_ALL: 'tout régler',
|
||||||
OPTIONS: 'Options',
|
OPTIONS: 'Options',
|
||||||
@@ -162,20 +162,21 @@ const fr: Translation = {
|
|||||||
UPLOAD: 'Upload',
|
UPLOAD: 'Upload',
|
||||||
DOWNLOAD: '{{D|d|d}}ownload',
|
DOWNLOAD: '{{D|d|d}}ownload',
|
||||||
INSTALL: 'Installer',
|
INSTALL: 'Installer',
|
||||||
|
REINSTALL: 'Réinstaller',
|
||||||
ABORTED: 'annulé',
|
ABORTED: 'annulé',
|
||||||
FAILED: 'échoué',
|
FAILED: 'échoué',
|
||||||
SUCCESSFUL: 'réussi',
|
SUCCESSFUL: 'réussi',
|
||||||
SYSTEM: 'Système',
|
SYSTEM: 'Système',
|
||||||
LOG_OF: '{0} Log',
|
LOG_OF: '{0} Log',
|
||||||
STATUS_OF: 'Statut {0}',
|
STATUS_OF: 'Statut {0}',
|
||||||
DOWNLOAD_UPLOAD: 'Download/Upload', // TODO translate
|
DOWNLOAD_UPLOAD: 'Télécharger/Mettre à jour',
|
||||||
CLOSE: 'Fermer',
|
CLOSE: 'Fermer',
|
||||||
USE: 'Utiliser',
|
USE: 'Utiliser',
|
||||||
FACTORY_RESET: 'Réinitialisation',
|
FACTORY_RESET: 'Réinitialisation',
|
||||||
SYSTEM_FACTORY_TEXT: "L'appareil a été réinitialisé et va maintenant redémarrer",
|
SYSTEM_FACTORY_TEXT: "L'appareil a été réinitialisé et va maintenant redémarrer",
|
||||||
SYSTEM_FACTORY_TEXT_DIALOG: "Êtes-vous sûr de vouloir réinitialiser l'appareil à ses paramètres d'usine ?",
|
SYSTEM_FACTORY_TEXT_DIALOG: "Êtes-vous sûr de vouloir réinitialiser l'appareil à ses paramètres d'usine ?",
|
||||||
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
|
AVAILABLE_VERSION: 'Versions disponibles',
|
||||||
STABLE: 'Stable', // TODO translate
|
STABLE: 'Stable',
|
||||||
DEVELOPMENT: 'Développement',
|
DEVELOPMENT: 'Développement',
|
||||||
UPTIME: 'Durée de fonctionnement du système',
|
UPTIME: 'Durée de fonctionnement du système',
|
||||||
FREE_MEMORY: 'Libre Memory',
|
FREE_MEMORY: 'Libre Memory',
|
||||||
@@ -185,9 +186,9 @@ const fr: Translation = {
|
|||||||
FILESYSTEM: 'File System (Utilisée / Libre)',
|
FILESYSTEM: 'File System (Utilisée / Libre)',
|
||||||
BUFFER_SIZE: 'Max taille du buffer',
|
BUFFER_SIZE: 'Max taille du buffer',
|
||||||
COMPACT: 'Compact',
|
COMPACT: 'Compact',
|
||||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
|
DOWNLOAD_SETTINGS_TEXT: 'Créer une sauvegarde de vos paramètres et configurations',
|
||||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
|
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', // TODO translate
|
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
|
||||||
ERROR: 'Erreur inattendue, veuillez réessayer',
|
ERROR: 'Erreur inattendue, veuillez réessayer',
|
||||||
TIME_SET: 'Time set',
|
TIME_SET: 'Time set',
|
||||||
MANAGE_USERS: 'Gérer les utilisateurs',
|
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_2: 'Publier vers des topics de commande (ioBroker)',
|
||||||
MQTT_PUBLISH_TEXT_3: 'Activer la découverte MQTT',
|
MQTT_PUBLISH_TEXT_3: 'Activer la découverte MQTT',
|
||||||
MQTT_PUBLISH_TEXT_4: 'Préfixe pour les topics découverte',
|
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_PUBLISH_INTERVALS: 'Intervalles de publication',
|
||||||
MQTT_INT_BOILER: 'Chaudières et pompes à chaleur',
|
MQTT_INT_BOILER: 'Chaudières et pompes à chaleur',
|
||||||
MQTT_INT_THERMOSTATS: 'Thermostats',
|
MQTT_INT_THERMOSTATS: 'Thermostats',
|
||||||
@@ -226,10 +227,10 @@ const fr: Translation = {
|
|||||||
MQTT_INT_WATER: 'Modules eau',
|
MQTT_INT_WATER: 'Modules eau',
|
||||||
MQTT_QUEUE: 'Queue MQTT',
|
MQTT_QUEUE: 'Queue MQTT',
|
||||||
DEFAULT: 'Défaut',
|
DEFAULT: 'Défaut',
|
||||||
MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate
|
MQTT_ENTITY_FORMAT: 'Format de l\'ID de l\'entité',
|
||||||
MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)', // TODO translate
|
MQTT_ENTITY_FORMAT_0: 'Instance unique, nom long (v3.4)',
|
||||||
MQTT_ENTITY_FORMAT_1: 'Single instance, short name', // TODO translate
|
MQTT_ENTITY_FORMAT_1: 'Instance unique, nom court',
|
||||||
MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name', // TODO translate
|
MQTT_ENTITY_FORMAT_2: 'Instances multiples, nom court',
|
||||||
MQTT_CLEAN_SESSION: 'Flag Clean Session',
|
MQTT_CLEAN_SESSION: 'Flag Clean Session',
|
||||||
MQTT_RETAIN_FLAG: 'Toujours activer le Retain Flag',
|
MQTT_RETAIN_FLAG: 'Toujours activer le Retain Flag',
|
||||||
INACTIVE: 'Inactif',
|
INACTIVE: 'Inactif',
|
||||||
@@ -260,7 +261,7 @@ const fr: Translation = {
|
|||||||
NETWORK_SCANNER: 'Scan réseau',
|
NETWORK_SCANNER: 'Scan réseau',
|
||||||
NETWORK_NO_WIFI: 'Pas de réseau WiFi trouvé',
|
NETWORK_NO_WIFI: 'Pas de réseau WiFi trouvé',
|
||||||
NETWORK_BLANK_SSID: 'laisser vide pour désactiver le WiFi',
|
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',
|
TX_POWER: 'Puissance Tx',
|
||||||
HOSTNAME: "Nom d'hôte",
|
HOSTNAME: "Nom d'hôte",
|
||||||
NETWORK_DISABLE_SLEEP: 'Désactiver le mode veille du WiFi',
|
NETWORK_DISABLE_SLEEP: 'Désactiver le mode veille du WiFi',
|
||||||
@@ -280,79 +281,79 @@ const fr: Translation = {
|
|||||||
ENTITY: 'entité',
|
ENTITY: 'entité',
|
||||||
MIN: 'min',
|
MIN: 'min',
|
||||||
MAX: 'max',
|
MAX: 'max',
|
||||||
BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate
|
BLOCK_NAVIGATE_1: 'Vous avez des modifications non enregistrées',
|
||||||
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
|
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: 'Stay', // TODO translate
|
STAY: 'Rester',
|
||||||
LEAVE: 'Leave', // TODO translate
|
LEAVE: 'Quitter',
|
||||||
SCHEDULER: 'Scheduler', // TODO translate
|
SCHEDULER: 'Scheduler',
|
||||||
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_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: 'Use 00:00 to trigger once on start-up', // TODO translate
|
SCHEDULER_HELP_2: 'Utiliser 00:00 pour déclencher une fois au démarrage',
|
||||||
SCHEDULE: 'Schedule', // TODO translate
|
SCHEDULE: 'Programme',
|
||||||
TIME: 'Time', // TODO translate
|
TIME: 'Temps',
|
||||||
TIMER: 'Timer', // TODO translate
|
TIMER: 'Minuteur',
|
||||||
ONCHANGE: 'Sur le changement', // TODO translate
|
ONCHANGE: 'Sur le changement',
|
||||||
CONDITION: 'Condition', // TODO translate
|
CONDITION: 'Condition',
|
||||||
IMMEDIATE: 'Immédiate', // TODO translate
|
IMMEDIATE: 'Immédiat',
|
||||||
SCHEDULE_UPDATED: 'Schedule updated', // TODO translate
|
SCHEDULE_UPDATED: 'Programme mis à jour',
|
||||||
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
SCHEDULE_TIMER_1: 'au démarrage',
|
||||||
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
SCHEDULE_TIMER_2: 'toutes les minutes',
|
||||||
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
SCHEDULE_TIMER_3: 'toutes les heures',
|
||||||
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
|
CUSTOM_ENTITIES: 'Entités personnalisées',
|
||||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
|
ENTITIES_HELP_1: 'Récupérer les entités personnalisées du bus EMS',
|
||||||
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
ENTITIES_UPDATED: 'Entités mises à jour',
|
||||||
WRITEABLE: 'Writeable', // TODO translate
|
WRITEABLE: 'Écriture',
|
||||||
SHOWING: 'Showing', // TODO translate
|
SHOWING: 'Affichage',
|
||||||
SEARCH: 'Search', // TODO translate
|
SEARCH: 'Rechercher',
|
||||||
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
|
CERT: 'Certificat racine TLS (laisser vide pour l\'insecurité)',
|
||||||
ENABLE_TLS: 'Activer TLS',
|
ENABLE_TLS: 'Activer TLS',
|
||||||
ON: 'On', // TODO translate
|
ON: 'On',
|
||||||
OFF: 'Off', // TODO translate
|
OFF: 'Off',
|
||||||
POLARITY: 'Polarity', // TODO translate
|
POLARITY: 'Polarity',
|
||||||
ACTIVEHIGH: 'Active High', // TODO translate
|
ACTIVEHIGH: 'Actif haut',
|
||||||
ACTIVELOW: 'Active Low', // TODO translate
|
ACTIVELOW: 'Actif bas',
|
||||||
UNCHANGED: 'Unchanged', // TODO translate
|
UNCHANGED: 'Inchangé',
|
||||||
ALWAYS: 'Always', // TODO translate
|
ALWAYS: 'Toujours',
|
||||||
ACTIVITY: 'Activity', // TODO translate
|
ACTIVITY: 'Activité',
|
||||||
CONFIGURE: 'Configure {0}', // TODO translate
|
CONFIGURE: 'Configurer {0}',
|
||||||
SYSTEM_MEMORY: 'System Memory', // TODO translate
|
SYSTEM_MEMORY: 'Mémoire système',
|
||||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
|
APPLICATION_SETTINGS_1: 'Modifier les paramètres de l\'application EMS-ESP',
|
||||||
SECURITY_1: 'Add or remove users', // TODO translate
|
SECURITY_1: 'Ajouter ou supprimer des utilisateurs',
|
||||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
|
DOWNLOAD_UPLOAD_1: 'Télécharger et mettre à jour les paramètres et le firmware',
|
||||||
MODULES: 'Module', // TODO translate
|
MODULES: 'Module',
|
||||||
MODULES_1: 'Activer ou désactiver les modules externes',
|
MODULES_1: 'Activer ou désactiver les modules externes',
|
||||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
MODULES_UPDATED: 'Modules mis à jour',
|
||||||
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
|
MODULES_DESCRIPTION: 'Cliquer sur le module pour activer ou désactiver les modules EMS-ESP',
|
||||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
MODULES_NONE: 'Aucun module externe détecté',
|
||||||
RENAME: 'Rename', // TODO translate
|
RENAME: 'Renommer',
|
||||||
ENABLE_MODBUS: 'Activer Modbus',
|
ENABLE_MODBUS: 'Activer Modbus',
|
||||||
VIEW_LOG: 'View log to diagnose issues', // TODO translate
|
VIEW_LOG: 'Voir le journal pour diagnostiquer les problèmes',
|
||||||
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
|
UPLOAD_DRAG: 'glisser-déposer un fichier ici ou cliquer pour en sélectionner un',
|
||||||
SERVICES: 'Services', // TODO translate
|
SERVICES: 'Services',
|
||||||
ALLVALUES: 'All Values', // TODO translate
|
ALLVALUES: 'Toutes les valeurs',
|
||||||
SPECIAL_FUNCTIONS: 'Special Functions',
|
SPECIAL_FUNCTIONS: 'Fonctions spéciales',
|
||||||
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
|
WAIT_FIRMWARE: 'Firmware en cours de téléchargement et d\'installation',
|
||||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
|
INSTALL_VERSION: 'Cela va {0} la version {1}. Êtes-vous sûr ?',
|
||||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
|
UPDATE_AVAILABLE: 'mise à jour disponible',
|
||||||
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
|
LATEST_VERSION: 'Vous utilisez la dernière version {0} du firmware',
|
||||||
PLEASE_WAIT: 'Please wait', // TODO translate
|
PLEASE_WAIT: 'Veuillez patienter',
|
||||||
RESTARTING_PRE: 'Initializing', // TODO translate
|
RESTARTING_PRE: 'Initialisation',
|
||||||
RESTARTING_POST: 'Preparing', // TODO translate
|
RESTARTING_POST: 'Préparation',
|
||||||
AUTO_SCROLL: 'Auto Scroll', // TODO translate
|
AUTO_SCROLL: 'Défilement automatique',
|
||||||
DASHBOARD: 'Dashboard', // TODO translate
|
DASHBOARD: 'Tableau de bord',
|
||||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
DEVELOPER_MODE: 'Mode développeur',
|
||||||
BYTES: 'Bytes', // TODO translate
|
BYTES: 'Octets',
|
||||||
BITMASK: 'Bit Mask',// TODO translate
|
BITMASK: 'Masque de bits',
|
||||||
DUPLICATE: 'Duplicate', // TODO translate
|
DUPLICATE: 'Dupliquer',
|
||||||
UPGRADE: 'Upgrade', // TODO translate
|
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.',
|
||||||
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: 'Aucune entité EMS favorite trouvée. Utilisez le',
|
||||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
|
NO_DATA_2: 'module pour les marquer.',
|
||||||
NO_DATA_2: 'module to mark them.', // TODO translate
|
NO_DATA_3: 'Pour voir toutes les entités disponibles, aller à',
|
||||||
NO_DATA_3: 'To see all available entities go to', // TODO translate
|
THIS_VERSION: 'Cette version',
|
||||||
THIS_VERSION: 'This Version', // TODO translate
|
PLATFORM: 'Plateforme',
|
||||||
PLATFORM: 'Platform', // TODO translate
|
RELEASE_TYPE: 'Type de version',
|
||||||
RELEASE_TYPE: 'Release Type', // TODO translate
|
INTERNET_CONNECTION_REQUIRED: 'Connexion Internet requise pour la vérification automatique des versions et la mise à niveau',
|
||||||
REINSTALL: 'Re-install', // TODO translate
|
SWITCH_RELEASE_TYPE: 'Passer à la version {0}',
|
||||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
|
FIRMWARE_VERSION_INFO: 'Informations sur la version du firmware'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default fr;
|
export default fr;
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const it: Translation = {
|
|||||||
TX_ISSUES: 'Problema di Tx - prova una modalità differente',
|
TX_ISSUES: 'Problema di Tx - prova una modalità differente',
|
||||||
DISCONNECTED: 'Disconnesso',
|
DISCONNECTED: 'Disconnesso',
|
||||||
EMS_SCAN: 'Sei sicuro di voler iniziare una scansione completa del bus EMS ?',
|
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 ',
|
EMS_DEVICE: 'Dispositivo EMS ',
|
||||||
SUCCESS: 'SUCCESSO',
|
SUCCESS: 'SUCCESSO',
|
||||||
FAIL: 'FALLITO',
|
FAIL: 'FALLITO',
|
||||||
@@ -115,7 +115,7 @@ const it: Translation = {
|
|||||||
READONLY: 'Abilita modalità sola-lettura (blocca tutti i comandi di scrittura EMS Tx in uscita)',
|
READONLY: 'Abilita modalità sola-lettura (blocca tutti i comandi di scrittura EMS Tx in uscita)',
|
||||||
UNDERCLOCK_CPU: 'Abbassa velocità della CPU',
|
UNDERCLOCK_CPU: 'Abbassa velocità della CPU',
|
||||||
REMOTE_TIMEOUT: 'Remote timeout',
|
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',
|
HEATINGOFF: 'Avviamento caldaia con riscaldamento forzato spento',
|
||||||
MIN_DURATION: 'Wait time',
|
MIN_DURATION: 'Wait time',
|
||||||
ENABLE_SHOWER_TIMER: 'Abilita timer doccia',
|
ENABLE_SHOWER_TIMER: 'Abilita timer doccia',
|
||||||
@@ -162,6 +162,7 @@ const it: Translation = {
|
|||||||
UPLOAD: 'Carica',
|
UPLOAD: 'Carica',
|
||||||
DOWNLOAD: 'Scarica',
|
DOWNLOAD: 'Scarica',
|
||||||
INSTALL: 'Installare {0}',
|
INSTALL: 'Installare {0}',
|
||||||
|
REINSTALL: 'Riavviare',
|
||||||
ABORTED: 'Annullato',
|
ABORTED: 'Annullato',
|
||||||
FAILED: 'Fallito',
|
FAILED: 'Fallito',
|
||||||
SUCCESSFUL: 'Riuscito',
|
SUCCESSFUL: 'Riuscito',
|
||||||
@@ -174,8 +175,8 @@ const it: Translation = {
|
|||||||
FACTORY_RESET: 'Impostazioni di fabbrica',
|
FACTORY_RESET: 'Impostazioni di fabbrica',
|
||||||
SYSTEM_FACTORY_TEXT: 'Il dispositivo è stato ripristinato alle impostazioni di fabbrica e ora verrà riavviato',
|
SYSTEM_FACTORY_TEXT: 'Il dispositivo è stato ripristinato alle impostazioni di fabbrica e ora verrà riavviato',
|
||||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Sei sicuro di voler ripristinare il dispositivo alle impostazioni di fabbrica??',
|
SYSTEM_FACTORY_TEXT_DIALOG: 'Sei sicuro di voler ripristinare il dispositivo alle impostazioni di fabbrica??',
|
||||||
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
|
AVAILABLE_VERSION: 'Versioni disponibili',
|
||||||
STABLE: 'Stable', // TODO translate
|
STABLE: 'Stabile',
|
||||||
DEVELOPMENT: 'Sviluppo',
|
DEVELOPMENT: 'Sviluppo',
|
||||||
UPTIME: 'Tempo di attività del sistema',
|
UPTIME: 'Tempo di attività del sistema',
|
||||||
FREE_MEMORY: 'Free Memory',
|
FREE_MEMORY: 'Free Memory',
|
||||||
@@ -184,10 +185,10 @@ const it: Translation = {
|
|||||||
APPSIZE: 'Applicazione (Partizione: Usata / Libera)',
|
APPSIZE: 'Applicazione (Partizione: Usata / Libera)',
|
||||||
FILESYSTEM: 'Memoria Sistema (Usata / Libera)',
|
FILESYSTEM: 'Memoria Sistema (Usata / Libera)',
|
||||||
BUFFER_SIZE: 'Max Buffer Size',
|
BUFFER_SIZE: 'Max Buffer Size',
|
||||||
COMPACT: 'Compact',
|
COMPACT: 'Compatto',
|
||||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
|
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
|
||||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
|
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)',
|
||||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
|
||||||
ERROR: 'Errore Inaspettato, prego tenta ancora',
|
ERROR: 'Errore Inaspettato, prego tenta ancora',
|
||||||
TIME_SET: 'Imposta Ora',
|
TIME_SET: 'Imposta Ora',
|
||||||
MANAGE_USERS: 'Gestione Utenti',
|
MANAGE_USERS: 'Gestione Utenti',
|
||||||
@@ -260,7 +261,7 @@ const it: Translation = {
|
|||||||
NETWORK_SCANNER: 'Scansione Rete',
|
NETWORK_SCANNER: 'Scansione Rete',
|
||||||
NETWORK_NO_WIFI: 'Nessuana rete WiFi trovata',
|
NETWORK_NO_WIFI: 'Nessuana rete WiFi trovata',
|
||||||
NETWORK_BLANK_SSID: 'lasciare vuoto per disattivare WiFi',
|
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',
|
TX_POWER: 'Potenza Tx',
|
||||||
HOSTNAME: 'Nome ospite',
|
HOSTNAME: 'Nome ospite',
|
||||||
NETWORK_DISABLE_SLEEP: 'Disabilita la modalità sospensione Wi-Fi',
|
NETWORK_DISABLE_SLEEP: 'Disabilita la modalità sospensione Wi-Fi',
|
||||||
@@ -303,56 +304,56 @@ const it: Translation = {
|
|||||||
WRITEABLE: 'Scrivibile',
|
WRITEABLE: 'Scrivibile',
|
||||||
SHOWING: 'Visualizza',
|
SHOWING: 'Visualizza',
|
||||||
SEARCH: 'Ricerca',
|
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',
|
ENABLE_TLS: 'Abilita TLS',
|
||||||
ON: 'On', // TODO translate
|
ON: 'On',
|
||||||
OFF: 'Off', // TODO translate
|
OFF: 'Off',
|
||||||
POLARITY: 'Polarity', // TODO translate
|
POLARITY: 'Polarity',
|
||||||
ACTIVEHIGH: 'Active High', // TODO translate
|
ACTIVEHIGH: 'Active High',
|
||||||
ACTIVELOW: 'Active Low', // TODO translate
|
ACTIVELOW: 'Active Low',
|
||||||
UNCHANGED: 'Unchanged', // TODO translate
|
UNCHANGED: 'Unchanged',
|
||||||
ALWAYS: 'Always', // TODO translate
|
ALWAYS: 'Always',
|
||||||
ACTIVITY: 'Activity', // TODO translate
|
ACTIVITY: 'Activity',
|
||||||
CONFIGURE: 'Configure {0}', // TODO translate
|
CONFIGURE: 'Configure {0}',
|
||||||
SYSTEM_MEMORY: 'System Memory', // TODO translate
|
SYSTEM_MEMORY: 'System Memory',
|
||||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
|
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings',
|
||||||
SECURITY_1: 'Add or remove users', // TODO translate
|
SECURITY_1: 'Add or remove users',
|
||||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
|
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware',
|
||||||
MODULES: 'Module', // TODO translate
|
MODULES: 'Module',
|
||||||
MODULES_1: 'Attiva o disattiva i moduli esterni', // TODO translate
|
MODULES_1: 'Attiva o disattiva i moduli esterni',
|
||||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
MODULES_UPDATED: 'Modules updated',
|
||||||
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
|
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules',
|
||||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
MODULES_NONE: 'No external modules detected',
|
||||||
RENAME: 'Rename', // TODO translate
|
RENAME: 'Rename',
|
||||||
ENABLE_MODBUS: 'Abilita Modbus',
|
ENABLE_MODBUS: 'Abilita Modbus',
|
||||||
VIEW_LOG: 'View log to diagnose issues', // TODO translate
|
VIEW_LOG: 'Visualizza log per diagnosticare problemi',
|
||||||
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
|
UPLOAD_DRAG: 'trascina e rilascia un file qui o clicca per selezionare uno',
|
||||||
SERVICES: 'Services', // TODO translate
|
SERVICES: 'Servizi',
|
||||||
ALLVALUES: 'All Values', // TODO translate
|
ALLVALUES: 'Tutti i valori',
|
||||||
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
|
SPECIAL_FUNCTIONS: 'Funzioni speciali',
|
||||||
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
|
WAIT_FIRMWARE: 'Firmware è in upload e installazione',
|
||||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
|
INSTALL_VERSION: 'Questo installerà la versione {1} {0}. Sei sicuro?',
|
||||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
|
UPDATE_AVAILABLE: 'aggiornamento disponibile',
|
||||||
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
|
LATEST_VERSION: 'Stai usando la versione più recente del firmware {0}',
|
||||||
PLEASE_WAIT: 'Please wait', // TODO translate
|
PLEASE_WAIT: 'Attendere',
|
||||||
RESTARTING_PRE: 'Initializing', // TODO translate
|
RESTARTING_PRE: 'Inizializzazione',
|
||||||
RESTARTING_POST: 'Preparing', // TODO translate
|
RESTARTING_POST: 'Preparazione',
|
||||||
AUTO_SCROLL: 'Auto Scroll', // TODO translate
|
AUTO_SCROLL: 'Scorrimento automatico',
|
||||||
DASHBOARD: 'Dashboard', // TODO translate
|
DASHBOARD: 'Pannello di controllo',
|
||||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
DEVELOPER_MODE: 'Modalità sviluppatore',
|
||||||
BYTES: 'Bytes', // TODO translate
|
BYTES: 'Byte',
|
||||||
BITMASK: 'Bit Mask',// TODO translate
|
BITMASK: 'Bitmask',
|
||||||
DUPLICATE: 'Duplicate', // TODO translate
|
DUPLICATE: 'Duplicato',
|
||||||
UPGRADE: 'Upgrade', // TODO translate
|
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.',
|
||||||
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: 'Nessuna entità EMS preferita trovata. Usa il',
|
||||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
|
NO_DATA_2: 'modulo per marcarle.',
|
||||||
NO_DATA_2: 'module to mark them.', // TODO translate
|
NO_DATA_3: 'Per vedere tutte le entità disponibili vai a',
|
||||||
NO_DATA_3: 'To see all available entities go to', // TODO translate
|
THIS_VERSION: 'Questa versione',
|
||||||
THIS_VERSION: 'This Version', // TODO translate
|
PLATFORM: 'Piattaforma',
|
||||||
PLATFORM: 'Platform', // TODO translate
|
RELEASE_TYPE: 'Tipo di rilascio',
|
||||||
RELEASE_TYPE: 'Release Type', // TODO translate
|
INTERNET_CONNECTION_REQUIRED: 'Connessione internet richiesta per il controllo automatico delle versioni e l\'aggiornamento',
|
||||||
REINSTALL: 'Re-install', // TODO translate
|
SWITCH_RELEASE_TYPE: 'Cambia in {0} rilascio',
|
||||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
|
FIRMWARE_VERSION_INFO: 'Informazioni sulla versione del firmware'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default it;
|
export default it;
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ const nl: Translation = {
|
|||||||
UPLOAD: 'Upload',
|
UPLOAD: 'Upload',
|
||||||
DOWNLOAD: '{{D|d|d}}ownload',
|
DOWNLOAD: '{{D|d|d}}ownload',
|
||||||
INSTALL: 'Installeren',
|
INSTALL: 'Installeren',
|
||||||
|
REINSTALL: 'Opnieuw installeren',
|
||||||
ABORTED: 'afgebroken',
|
ABORTED: 'afgebroken',
|
||||||
FAILED: 'mislukt',
|
FAILED: 'mislukt',
|
||||||
SUCCESSFUL: 'successvol',
|
SUCCESSFUL: 'successvol',
|
||||||
@@ -178,12 +179,12 @@ const nl: Translation = {
|
|||||||
STABLE: 'Stable',
|
STABLE: 'Stable',
|
||||||
DEVELOPMENT: 'Development',
|
DEVELOPMENT: 'Development',
|
||||||
UPTIME: 'Systeem Uptime',
|
UPTIME: 'Systeem Uptime',
|
||||||
FREE_MEMORY: 'Free Memory',
|
FREE_MEMORY: 'Vrij geheugen',
|
||||||
PSRAM: 'PSRAM (Size / Free)',
|
PSRAM: 'PSRAM (grootte / vrij)',
|
||||||
FLASH: 'Flash Chip (Size , Speed)',
|
FLASH: 'Flash Chip (grootte , snelheid)',
|
||||||
APPSIZE: 'Application (Partition: Used / Free)',
|
APPSIZE: 'Applicatie (partition: gebruikt / vrij)',
|
||||||
FILESYSTEM: 'File System (Used / Free)',
|
FILESYSTEM: 'Bestandssysteem (gebruikt / vrij)',
|
||||||
BUFFER_SIZE: 'Max Buffer Size',
|
BUFFER_SIZE: 'Max buffer grootte',
|
||||||
COMPACT: 'Compact',
|
COMPACT: 'Compact',
|
||||||
DOWNLOAD_SETTINGS_TEXT: 'Maak een back-up van uw configuratie en instellingen',
|
DOWNLOAD_SETTINGS_TEXT: 'Maak een back-up van uw configuratie en instellingen',
|
||||||
UPLOAD_TEXT: 'Upload een nieuw firmwarebestand (.bin) of een back-upbestand (.json)',
|
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_MIXER: 'Mixer Modules',
|
||||||
MQTT_INT_WATER: 'Water Modules',
|
MQTT_INT_WATER: 'Water Modules',
|
||||||
MQTT_QUEUE: 'MQTT Queue',
|
MQTT_QUEUE: 'MQTT Queue',
|
||||||
DEFAULT: 'Default',
|
DEFAULT: 'Standaard',
|
||||||
MQTT_ENTITY_FORMAT: 'Entity ID formaat',
|
MQTT_ENTITY_FORMAT: 'Entity ID formaat',
|
||||||
MQTT_ENTITY_FORMAT_0: 'Eén instantie, lange naam (v3.4)',
|
MQTT_ENTITY_FORMAT_0: 'Eén instantie, lange naam (v3.4)',
|
||||||
MQTT_ENTITY_FORMAT_1: 'Eén instantie, korte naam',
|
MQTT_ENTITY_FORMAT_1: 'Eén instantie, korte naam',
|
||||||
@@ -249,20 +250,20 @@ const nl: Translation = {
|
|||||||
AP_PROVIDE_TEXT_3: 'nooit',
|
AP_PROVIDE_TEXT_3: 'nooit',
|
||||||
AP_PREFERRED_CHANNEL: 'Voorkeurskanaal',
|
AP_PREFERRED_CHANNEL: 'Voorkeurskanaal',
|
||||||
AP_HIDE_SSID: 'SSID verbergen',
|
AP_HIDE_SSID: 'SSID verbergen',
|
||||||
AP_CLIENTS: 'AP Clients',
|
AP_CLIENTS: 'AP Gebruikers',
|
||||||
AP_MAX_CLIENTS: 'Max Clients',
|
AP_MAX_CLIENTS: 'Max Gebruikers',
|
||||||
AP_LOCAL_IP: 'Local IP',
|
AP_LOCAL_IP: 'Lokale IP',
|
||||||
NETWORK_SCAN: 'Scan WiFi Networken',
|
NETWORK_SCAN: 'Scan WiFi Netwerken',
|
||||||
IDLE: 'Idle',
|
IDLE: 'Inactief',
|
||||||
LOST: 'Verloren',
|
LOST: 'Verloren',
|
||||||
SCANNING: 'Scannen',
|
SCANNING: 'Scannen',
|
||||||
SCAN_AGAIN: 'Opnieuw scannen',
|
SCAN_AGAIN: 'Opnieuw scannen',
|
||||||
NETWORK_SCANNER: 'Netwerk Scanner',
|
NETWORK_SCANNER: 'Netwerk Scannen',
|
||||||
NETWORK_NO_WIFI: 'Geen WiFi networken gevonden',
|
NETWORK_NO_WIFI: 'Geen WiFi netwerken gevonden',
|
||||||
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen',
|
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen',
|
||||||
NETWORK_BLANK_BSSID: 'laat leeg om alleen SSID te bebruiken',
|
NETWORK_BLANK_BSSID: 'laat leeg om alleen SSID te bebruiken',
|
||||||
TX_POWER: 'Tx Vermogen',
|
TX_POWER: 'Tx Vermogen',
|
||||||
HOSTNAME: 'Hostname',
|
HOSTNAME: 'Hostnaam',
|
||||||
NETWORK_DISABLE_SLEEP: 'WiFi Sleep Mode uitzetten',
|
NETWORK_DISABLE_SLEEP: 'WiFi Sleep Mode uitzetten',
|
||||||
NETWORK_LOW_BAND: 'Lagere WiFi bandbreedte gebruiken',
|
NETWORK_LOW_BAND: 'Lagere WiFi bandbreedte gebruiken',
|
||||||
NETWORK_USE_DNS: 'Activeer mDNS Service',
|
NETWORK_USE_DNS: 'Activeer mDNS Service',
|
||||||
@@ -272,9 +273,9 @@ const nl: Translation = {
|
|||||||
NETWORK_GATEWAY: 'Gateway',
|
NETWORK_GATEWAY: 'Gateway',
|
||||||
NETWORK_SUBNET: 'Subnetmasker',
|
NETWORK_SUBNET: 'Subnetmasker',
|
||||||
NETWORK_DNS: 'DNS Servers',
|
NETWORK_DNS: 'DNS Servers',
|
||||||
ADDRESS_OF: '{0} Address',
|
ADDRESS_OF: '{0} Adres',
|
||||||
ADMINISTRATOR: 'Administrator',
|
ADMINISTRATOR: 'Beheerder',
|
||||||
GUEST: 'Gast',
|
GUEST: 'Bezoeker',
|
||||||
NEW: 'Nieuwe',
|
NEW: 'Nieuwe',
|
||||||
NEW_NAME_OF: 'Hernoem {0}',
|
NEW_NAME_OF: 'Hernoem {0}',
|
||||||
ENTITY: 'Entiteit',
|
ENTITY: 'Entiteit',
|
||||||
@@ -320,7 +321,7 @@ const nl: Translation = {
|
|||||||
DOWNLOAD_UPLOAD_1: 'Download en upload instellingen en firmware',
|
DOWNLOAD_UPLOAD_1: 'Download en upload instellingen en firmware',
|
||||||
MODULES: 'Module',
|
MODULES: 'Module',
|
||||||
MODULES_1: 'Externe modules activeren of deactiveren',
|
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_DESCRIPTION: 'Klik op de module om EMS-ESP library modules te activeren of te deactiveren',
|
||||||
MODULES_NONE: 'Geen externe modules gedetecteerd',
|
MODULES_NONE: 'Geen externe modules gedetecteerd',
|
||||||
RENAME: 'Hernoemen',
|
RENAME: 'Hernoemen',
|
||||||
@@ -331,19 +332,18 @@ const nl: Translation = {
|
|||||||
ALLVALUES: 'All waarden',
|
ALLVALUES: 'All waarden',
|
||||||
SPECIAL_FUNCTIONS: 'Speciale functies',
|
SPECIAL_FUNCTIONS: 'Speciale functies',
|
||||||
WAIT_FIRMWARE: 'Firmware wordt geüpload en geïnstalleerd',
|
WAIT_FIRMWARE: 'Firmware wordt geüpload en geïnstalleerd',
|
||||||
INSTALL_VERSION: 'Hiermee wordt versie {0} geïnstalleerd. Weet je het zeker?',
|
INSTALL_VERSION: 'Hiermee wordt versie {1} {0}. Weet je het zeker?',
|
||||||
UPGRADE_AVAILABLE: 'Er is een firmware-upgrade beschikbaar!',
|
UPDATE_AVAILABLE: 'update beschikbaar',
|
||||||
LATEST_VERSION: 'U gebruikt de nieuwste firmwareversie',
|
LATEST_VERSION: 'U gebruikt de nieuwste {0} firmwareversie',
|
||||||
PLEASE_WAIT: 'Een ogenblik geduld',
|
PLEASE_WAIT: 'Een ogenblik geduld',
|
||||||
RESTARTING_PRE: 'Initialiseren',
|
RESTARTING_PRE: 'Initialiseren',
|
||||||
RESTARTING_POST: 'Voorbereiding',
|
RESTARTING_POST: 'Voorbereiding',
|
||||||
AUTO_SCROLL: 'Automatisch Scrollen',
|
AUTO_SCROLL: 'Automatisch Scrollen',
|
||||||
DASHBOARD: 'Dashboard',
|
DASHBOARD: 'Dashboard',
|
||||||
DEVELOPER_MODE: 'Ontwikkelaarsmodus',
|
DEVELOPER_MODE: 'Ontwikkelaarsmodus',
|
||||||
BYTES: 'Bytes', // TODO translate
|
BYTES: 'Bytes',
|
||||||
BITMASK: 'Bit Mask',// TODO translate
|
BITMASK: 'Bit Mask',
|
||||||
DUPLICATE: 'Duplicaat',
|
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.',
|
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_1: 'Er zijn nog geen favoriete EMS-entiteiten gevonden. Gebruik de',
|
||||||
NO_DATA_2: 'module om ze te markeren.',
|
NO_DATA_2: 'module om ze te markeren.',
|
||||||
@@ -351,8 +351,9 @@ const nl: Translation = {
|
|||||||
THIS_VERSION: 'Deze Versie',
|
THIS_VERSION: 'Deze Versie',
|
||||||
PLATFORM: 'Platform',
|
PLATFORM: 'Platform',
|
||||||
RELEASE_TYPE: 'Release Typ',
|
RELEASE_TYPE: 'Release Typ',
|
||||||
REINSTALL: 'Opnieuw Installeren',
|
|
||||||
INTERNET_CONNECTION_REQUIRED: 'Internetverbinding vereist voor automatische versiecontrole en -upgrade',
|
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;
|
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