mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-10 09:49:54 +03:00
Compare commits
1160 Commits
v3.7.0
...
2b2c86ba5a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b2c86ba5a | ||
|
|
2b2217e8ce | ||
|
|
08d3e8bab6 | ||
|
|
ded3552873 | ||
|
|
9a7893e99f | ||
|
|
307ef4e285 | ||
|
|
f75deb3505 | ||
|
|
6281f9cfe1 | ||
|
|
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 | ||
|
|
f3873e330d | ||
|
|
be0aba1b59 | ||
|
|
321187d0d2 | ||
|
|
1e5bc9dcc5 | ||
|
|
f769bbbe42 | ||
|
|
cf93081252 | ||
|
|
30fca2a190 | ||
|
|
b7654c7e33 | ||
|
|
a3964102c9 | ||
|
|
727d9bdf2e | ||
|
|
4471c1fad6 | ||
|
|
4de56af85a | ||
|
|
b11f9be5e7 | ||
|
|
272bb84a16 | ||
|
|
c0bffe4ccf | ||
|
|
92c32b1abb | ||
|
|
35cf87d155 | ||
|
|
4c7b8a1db1 | ||
|
|
29990001b2 | ||
|
|
add9d12aca | ||
|
|
a5b1742bca | ||
|
|
676b164ce8 | ||
|
|
bb9315777a | ||
|
|
f11b9ee420 | ||
|
|
3ba0bb80e7 | ||
|
|
0850737208 | ||
|
|
c851b14d35 | ||
|
|
e62bb101e2 | ||
|
|
4a0680ee4d | ||
|
|
89ccaf1e26 | ||
|
|
eeef5a862a | ||
|
|
558f43813b | ||
|
|
c59657ac3b | ||
|
|
381e8de617 | ||
|
|
1ebbdbe557 | ||
|
|
2751aca37a | ||
|
|
acebc03d69 | ||
|
|
a7a3207cca | ||
|
|
f5acf6c0a4 | ||
|
|
d47b3b9276 | ||
|
|
5c60ea9c15 | ||
|
|
aec887373e | ||
|
|
4e5ad82cff | ||
|
|
7971d3bb93 | ||
|
|
549f7e30db | ||
|
|
a5879d1995 | ||
|
|
9e5d78a29c | ||
|
|
0191a95416 | ||
|
|
2276490c67 | ||
|
|
d60e42a483 | ||
|
|
7b5b5bb5ee | ||
|
|
aef4c2245d | ||
|
|
62f460163f | ||
|
|
7ddc2ccdad | ||
|
|
2437976aed | ||
|
|
b1b8a128d5 | ||
|
|
b9a0744433 | ||
|
|
7049aa8892 | ||
|
|
dbd6d38176 | ||
|
|
52b9e4650e | ||
|
|
4a3cb8c525 | ||
|
|
3680fad909 | ||
|
|
c80ec368a9 | ||
|
|
9fbb28ee33 | ||
|
|
92ff32f8ba | ||
|
|
e974967903 | ||
|
|
3752247d1c | ||
|
|
82e470a425 | ||
|
|
a67de57249 | ||
|
|
9157da3e0b | ||
|
|
fe5cab66fb | ||
|
|
3402b6d2dd | ||
|
|
1255381420 | ||
|
|
a8f17ec1b3 | ||
|
|
05904d9de9 | ||
|
|
89ade2e107 | ||
|
|
ca5d631dfc | ||
|
|
de7bd38850 | ||
|
|
aea9a4429d | ||
|
|
9f3d3c7a44 | ||
|
|
d7b59c2613 | ||
|
|
269c68b945 | ||
|
|
fc32eb7f12 | ||
|
|
56efb15938 | ||
|
|
53c1f4ebb6 | ||
|
|
0b1f87b6ce | ||
|
|
6371a0d298 | ||
|
|
757044dde9 | ||
|
|
0abdd9e2c2 | ||
|
|
b7548476ed | ||
|
|
0fee98c4c2 | ||
|
|
e3354b2a49 | ||
|
|
d06ea9590e | ||
|
|
e52f988261 | ||
|
|
a6b89cee58 | ||
|
|
f840543f0f | ||
|
|
c696974644 | ||
|
|
3ebb0225c8 | ||
|
|
2508a30b79 | ||
|
|
056febda6c | ||
|
|
28c523af24 | ||
|
|
09b8dcdb10 | ||
|
|
3daf1145e0 | ||
|
|
da77d52585 | ||
|
|
c628b4b5ac | ||
|
|
eea32f3027 | ||
|
|
92e5433165 | ||
|
|
af41979fd1 | ||
|
|
fd63428e6c | ||
|
|
a82b32ce97 | ||
|
|
c3ee1991a9 | ||
|
|
1f04c1fc2c | ||
|
|
f071fd842b | ||
|
|
b924d07447 | ||
|
|
87de5d1670 | ||
|
|
4173d4d42a | ||
|
|
402f5600d3 | ||
|
|
268c582a86 | ||
|
|
346bcb977f | ||
|
|
4cf251f613 | ||
|
|
1db093e6f4 | ||
|
|
10ee8ef120 | ||
|
|
b8a7c2750c | ||
|
|
654c9c39ae | ||
|
|
c365055825 | ||
|
|
7a6670db0d | ||
|
|
0a46bcc14f | ||
|
|
ef9d889e3d | ||
|
|
a33d116bf2 | ||
|
|
fa4dcdcc6b | ||
|
|
5f11bb3bb0 | ||
|
|
fdc3b19170 | ||
|
|
e15f9dec5a | ||
|
|
f8c279edb0 | ||
|
|
bf21097745 | ||
|
|
b6d2ab6adb | ||
|
|
1811558069 | ||
|
|
d899b58284 | ||
|
|
9e8faaab64 | ||
|
|
5b1266a638 | ||
|
|
52552de300 | ||
|
|
11fd81f25f | ||
|
|
a8b93dd571 | ||
|
|
9b91faba10 | ||
|
|
9b4ea2af94 | ||
|
|
c5a5b3b20c | ||
|
|
68fceaab87 | ||
|
|
6b3cb00917 | ||
|
|
00beb2a039 | ||
|
|
0927a2b44b | ||
|
|
b00f5dd8c0 | ||
|
|
367260b143 | ||
|
|
38efb0de67 | ||
|
|
df92e9253f | ||
|
|
afc2afbac0 | ||
|
|
7b4cd5c4d1 | ||
|
|
06fa541931 | ||
|
|
7f5ab93644 | ||
|
|
c2e90e2710 | ||
|
|
fdf7ab1aa7 | ||
|
|
e7334a2492 | ||
|
|
847c3ac2cd | ||
|
|
cce5c0d09e | ||
|
|
77330c99f4 | ||
|
|
a4c07b8472 | ||
|
|
5dd644fe1c | ||
|
|
fc53917512 | ||
|
|
8284627b1d | ||
|
|
f6f0c326ef | ||
|
|
2780a5f148 | ||
|
|
4785f78f28 | ||
|
|
3720ac4e2f | ||
|
|
5d3695f72c | ||
|
|
62dcfb3244 | ||
|
|
64de164e9d | ||
|
|
aa065aa24e | ||
|
|
bba0b76e3f | ||
|
|
2623178a00 | ||
|
|
6ab3ce8b02 | ||
|
|
b193b45f1f | ||
|
|
324f9d0686 | ||
|
|
e74af57c16 | ||
|
|
74b935c27f | ||
|
|
f6964e360b | ||
|
|
e9441c40aa | ||
|
|
9da73ba04f | ||
|
|
2cd60883d2 | ||
|
|
6a0a3ea440 | ||
|
|
4a71059c7c | ||
|
|
bed0f3674b | ||
|
|
7837bc1e30 | ||
|
|
81d42c21e5 | ||
|
|
5777d7cb0a | ||
|
|
9689f0a7da | ||
|
|
64a1e00501 | ||
|
|
fa630b9e64 | ||
|
|
761ee9e7d0 | ||
|
|
000a73156a | ||
|
|
086aef6ef2 | ||
|
|
94f822d39f | ||
|
|
ea4c1f92d3 | ||
|
|
eb9e72e257 | ||
|
|
1d8e32ec74 | ||
|
|
6cf4985a04 | ||
|
|
3c44f89804 | ||
|
|
993fa9fead | ||
|
|
bab44d58ff | ||
|
|
5bfb65fa91 | ||
|
|
197f943d63 | ||
|
|
b6f932e019 | ||
|
|
6bbd7d6333 | ||
|
|
98708f2293 | ||
|
|
df75e86bf4 | ||
|
|
92041d5d3f | ||
|
|
ed9e7f9e2f | ||
|
|
f9062463fd | ||
|
|
88b39be386 | ||
|
|
5f13fcf744 | ||
|
|
e6e0e9700f | ||
|
|
1e1dfccbee | ||
|
|
628f845b16 | ||
|
|
2def5a237a | ||
|
|
d6e66982fd | ||
|
|
a88f9070b9 | ||
|
|
e430ecf85c | ||
|
|
13dfc87c57 | ||
|
|
60ac6aa4ba | ||
|
|
5a37517f08 | ||
|
|
30b7b6db34 | ||
|
|
2df67c2d64 | ||
|
|
fd81dc9f5b | ||
|
|
ef3862fff0 | ||
|
|
06bb5324f2 | ||
|
|
38abe96fe1 | ||
|
|
5f1fa3f59b | ||
|
|
ba0f77e41b | ||
|
|
e28794c77a | ||
|
|
3dd67acc8d | ||
|
|
971e2869a2 | ||
|
|
83a49c1814 | ||
|
|
11e398ad49 | ||
|
|
4037e1db5a | ||
|
|
8bf97c067f | ||
|
|
f23e92bd2e | ||
|
|
c8d22f72b3 | ||
|
|
4514ae2210 | ||
|
|
52adf6749e | ||
|
|
47599e632a | ||
|
|
d527f4662d | ||
|
|
29016b6342 | ||
|
|
87210f947c | ||
|
|
fcf8432325 | ||
|
|
a41ded6673 | ||
|
|
7598290c5f | ||
|
|
eed676f521 | ||
|
|
6e9bc2d47a | ||
|
|
21acd900e5 | ||
|
|
48ab1abf39 | ||
|
|
b6053e9a7c | ||
|
|
65e0ae174a | ||
|
|
b2ffe8fe49 | ||
|
|
19a3632c04 | ||
|
|
5325904050 | ||
|
|
944f94997b | ||
|
|
1bb7d2f150 | ||
|
|
20455fdc00 |
132
.github/CODE_OF_CONDUCT.md
vendored
Normal file
132
.github/CODE_OF_CONDUCT.md
vendored
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
|
||||||
|
at [https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -21,7 +21,7 @@ _Make sure your have performed every step and checked the applicable boxes befor
|
|||||||
|
|
||||||
- [ ] Searched the issue in [issues](https://github.com/emsesp/EMS-ESP32/issues)
|
- [ ] Searched the issue in [issues](https://github.com/emsesp/EMS-ESP32/issues)
|
||||||
- [ ] Searched the issue in [discussions](https://github.com/emsesp/EMS-ESP32/discussions)
|
- [ ] Searched the issue in [discussions](https://github.com/emsesp/EMS-ESP32/discussions)
|
||||||
- [ ] Searched the issue in the [docs](https://emsesp.org/Troubleshooting/)
|
- [ ] Searched the issue in the [docs](https://docs.emsesp.org/Troubleshooting/)
|
||||||
- [ ] Searched the issue in the [chat](https://discord.gg/3J3GgnzpyT)
|
- [ ] Searched the issue in the [chat](https://discord.gg/3J3GgnzpyT)
|
||||||
- [ ] Provide the System information in the area below, taken from `http://<IP>/api/system`
|
- [ ] Provide the System information in the area below, taken from `http://<IP>/api/system`
|
||||||
|
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: EMS-ESP Docs
|
- name: EMS-ESP Docs
|
||||||
url: https://emsesp.org
|
url: https://docs.emsesp.org
|
||||||
about: All the information related to EMS-ESP.
|
about: All the information related to EMS-ESP.
|
||||||
- name: EMS-ESP Discussions and Support
|
- name: EMS-ESP Discussions and Support
|
||||||
url: https://github.com/emsesp/EMS-ESP32/discussions
|
url: https://github.com/emsesp/EMS-ESP32/discussions
|
||||||
|
|||||||
1
.github/SUPPORT.md
vendored
Normal file
1
.github/SUPPORT.md
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Support
|
||||||
@@ -1,55 +1,64 @@
|
|||||||
name: 'pre-release'
|
name: 'Build dev release'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
|
paths:
|
||||||
|
- 'src/emsesp_version.h'
|
||||||
branches:
|
branches:
|
||||||
- 'dev'
|
- 'dev'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-release:
|
pre-release:
|
||||||
name: 'Automatic pre-release build'
|
name: 'Build Dev Release'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
|
- name: Install python 3.13
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.13'
|
||||||
|
|
||||||
|
- name: Install Node.js 22
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Enable Corepack
|
- name: Enable Corepack
|
||||||
run: corepack enable
|
run: corepack enable pnpm
|
||||||
|
|
||||||
- name: Install python 3.11
|
- name: Get the EMS-ESP version
|
||||||
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
|
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 the WebUI
|
||||||
run: |
|
run: |
|
||||||
cd interface
|
cd interface
|
||||||
yarn install
|
pnpm install
|
||||||
yarn typesafe-i18n --no-watch
|
pnpm typesafe-i18n --no-watch
|
||||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||||
yarn build
|
pnpm build
|
||||||
yarn webUI
|
pnpm webUI
|
||||||
|
|
||||||
- name: Build all PIO target environments from default_envs
|
- name: Build all PIO target environments
|
||||||
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'
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: 'github-releases-to-discord'
|
name: 'Publish releases to discord'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
|
|||||||
37
.github/workflows/pr_check.yml
vendored
Normal file
37
.github/workflows/pr_check.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: 'Pre-check on PR'
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
branches: dev
|
||||||
|
paths:
|
||||||
|
- '**.c'
|
||||||
|
- '**.cpp'
|
||||||
|
- '**.h'
|
||||||
|
- '**.hpp'
|
||||||
|
- '**.json'
|
||||||
|
- '**.py'
|
||||||
|
- '**.md'
|
||||||
|
- '.github/workflows/pr_check.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pre-release:
|
||||||
|
name: 'Automatic pre-release build'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install python 3.11
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install PlatformIO
|
||||||
|
run: |
|
||||||
|
pip install wheel
|
||||||
|
pip install -U platformio
|
||||||
|
|
||||||
|
- name: Build native
|
||||||
|
run: |
|
||||||
|
platformio run -e native
|
||||||
15
.github/workflows/sonar_check.yml
vendored
15
.github/workflows/sonar_check.yml
vendored
@@ -20,15 +20,12 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
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
|
||||||
|
|
||||||
- name: Run build-wrapper
|
|
||||||
run: build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make all
|
run: build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make all
|
||||||
|
- name: SonarQube Scan
|
||||||
- name: Run sonar-scanner
|
uses: SonarSource/sonarqube-scan-action@master
|
||||||
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"
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
name: 'tagged-release'
|
name: 'Build stable release'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -8,42 +11,46 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tagged-release:
|
tagged-release:
|
||||||
name: 'Tagged Release'
|
name: 'Build Stable Release'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
|
- name: Install python 3.13
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.13'
|
||||||
|
|
||||||
|
- name: Install Node.js 22
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Enable Corepack
|
- name: Enable Corepack
|
||||||
run: corepack enable
|
run: corepack enable pnpm
|
||||||
|
|
||||||
- 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
|
- 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 the WebUI
|
||||||
run: |
|
run: |
|
||||||
cd interface
|
cd interface
|
||||||
yarn install
|
pnpm install
|
||||||
yarn typesafe-i18n --no-watch
|
pnpm typesafe-i18n --no-watch
|
||||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||||
yarn build
|
pnpm build
|
||||||
yarn webUI
|
pnpm webUI
|
||||||
|
|
||||||
- name: Build all PIO target environments from default_envs
|
- name: Build all PIO target environments
|
||||||
run: |
|
run: |
|
||||||
platformio run
|
platformio run
|
||||||
|
env:
|
||||||
|
NO_BUILD_WEBUI: true
|
||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
uses: emsesp/action-automatic-releases@v1.0.0
|
uses: emsesp/action-automatic-releases@v1.0.0
|
||||||
23
.github/workflows/stale_issues.yml
vendored
Normal file
23
.github/workflows/stale_issues.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: "Mark or close stale issues and PRs"
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 1 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
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"
|
||||||
54
.github/workflows/test_release.yml
vendored
54
.github/workflows/test_release.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: 'test-release'
|
name: 'Build test release'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -6,41 +6,60 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'dev2'
|
- 'dev2'
|
||||||
|
|
||||||
|
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: Enable Corepack
|
- name: Install python 3.13
|
||||||
run: corepack enable
|
uses: actions/setup-python@v5
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.13'
|
||||||
- name: Use Node.js 20.x
|
|
||||||
|
- name: Install Node.js 22
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: 22
|
||||||
- name: Get EMS-ESP source code and version
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Enable Corepack
|
||||||
|
run: corepack enable pnpm
|
||||||
|
|
||||||
|
- name: Get the EMS-ESP 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
|
||||||
- name: Build WebUI
|
python -m pip install intelhex
|
||||||
|
|
||||||
|
- name: Build the WebUI
|
||||||
run: |
|
run: |
|
||||||
cd interface
|
cd interface
|
||||||
yarn install
|
pnpm install
|
||||||
yarn typesafe-i18n --no-watch
|
pnpm typesafe-i18n --no-watch
|
||||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||||
yarn build
|
pnpm build
|
||||||
yarn webUI
|
pnpm webUI
|
||||||
- name: Build all target environments from default_envs
|
|
||||||
|
- name: Build all target environments
|
||||||
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'
|
||||||
uses: emsesp/action-automatic-releases@v1.0.0
|
uses: emsesp/action-automatic-releases@v1.0.0
|
||||||
@@ -52,3 +71,4 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
CHANGELOG_LATEST.md
|
CHANGELOG_LATEST.md
|
||||||
./build/firmware/*.*
|
./build/firmware/*.*
|
||||||
|
|
||||||
|
|||||||
22
.gitignore
vendored
22
.gitignore
vendored
@@ -12,17 +12,15 @@ cppcheck.out.xml
|
|||||||
# platformio
|
# platformio
|
||||||
.pio
|
.pio
|
||||||
pio_local.ini
|
pio_local.ini
|
||||||
*_old
|
|
||||||
|
|
||||||
# OS specific
|
# OS specific
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*Thumbs.db
|
*Thumbs.db
|
||||||
|
|
||||||
# web specfic
|
# web specific
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
/data/www
|
/data/www
|
||||||
/lib/framework/WWWData.h
|
|
||||||
/interface/build
|
/interface/build
|
||||||
node_modules
|
node_modules
|
||||||
/interface/.eslintcache
|
/interface/.eslintcache
|
||||||
@@ -30,16 +28,10 @@ stats.html
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
.pnp.*
|
.pnp.*
|
||||||
*/.yarn/*
|
|
||||||
!.yarn/patches
|
|
||||||
!.yarn/plugins
|
|
||||||
!.yarn/releases
|
|
||||||
!.yarn/sdks
|
|
||||||
!.yarn/versions
|
|
||||||
yarn.lock
|
|
||||||
analyse.html
|
analyse.html
|
||||||
interface/vite.config.ts.timestamp*
|
interface/vite.config.ts.timestamp*
|
||||||
*.local
|
*.local
|
||||||
|
src/ESP32React/WWWData.h
|
||||||
|
|
||||||
# i18n generated files
|
# i18n generated files
|
||||||
interface/src/i18n/i18n-react.tsx
|
interface/src/i18n/i18n-react.tsx
|
||||||
@@ -71,4 +63,12 @@ words-found-verbose.txt
|
|||||||
|
|
||||||
# sonarlint
|
# sonarlint
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
package.json
|
|
||||||
|
# pioarduino + hybrid
|
||||||
|
managed_components
|
||||||
|
dependencies.lock
|
||||||
|
CMakeLists.txt
|
||||||
|
.dummy/*
|
||||||
|
logs/*
|
||||||
|
sdkconfig.*
|
||||||
|
sdkconfig_tasmota_esp32
|
||||||
|
|||||||
6
.markdownlint.json
Normal file
6
.markdownlint.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"MD033": false,
|
||||||
|
"MD013": false,
|
||||||
|
"MD045": false,
|
||||||
|
"MD041": false
|
||||||
|
}
|
||||||
158
CHANGELOG.md
158
CHANGELOG.md
@@ -5,6 +5,164 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [3.7.2] 22 March 2025
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- change enum_heatingtype for remote control [#2268](https://github.com/emsesp/EMS-ESP32/issues/2268)
|
||||||
|
- system service commands [#2182](https://github.com/emsesp/EMS-ESP32/issues/2182)
|
||||||
|
- read 0x02A5 for thermostat CT200 [#2277](https://github.com/emsesp/EMS-ESP32/issues/2277)
|
||||||
|
- add "duplicate" option to Custom Entities [#2266](https://github.com/emsesp/EMS-ESP32/discussion/2266)
|
||||||
|
- mask bits for bool custom entities
|
||||||
|
- thermostat `reduce threshold` [#2288](https://github.com/emsesp/EMS-ESP32/issues/2288)
|
||||||
|
- thermostat `absent` [#1957](https://github.com/emsesp/EMS-ESP32/issues/1957)
|
||||||
|
- CR11 thermostat [#2295](https://github.com/emsesp/EMS-ESP32/issues/2295)
|
||||||
|
- Show ESP32's CPU temp in Hardware Status
|
||||||
|
- vacation mode for the CR50 [#2403](https://github.com/emsesp/EMS-ESP32/issues/2403)
|
||||||
|
- new Console command "set admin password" to set WebUI admin password
|
||||||
|
- support nested conditions in scheduler [#2451](https://github.com/emsesp/EMS-ESP32/issues/2451)
|
||||||
|
- allow mixed case in scheduler expressions [#2457](https://github.com/emsesp/EMS-ESP32/issues/2457)
|
||||||
|
- Suprapur-o [#2470](https://github.com/emsesp/EMS-ESP32/issues/2470)
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- long numbers of custom entities [#2267](https://github.com/emsesp/EMS-ESP32/issues/2267)
|
||||||
|
- modbus command path to `api/` [#2276](https://github.com/emsesp/EMS-ESP32/issues/2276)
|
||||||
|
- info command for devices without entity-commands [#2274](https://github.com/emsesp/EMS-ESP32/issues/2274)
|
||||||
|
- CW100 settings telegram 0x241 [#2290](https://github.com/emsesp/EMS-ESP32/issues/2290)
|
||||||
|
- modbus signed 8bit values [#2294](https://github.com/emsesp/EMS-ESP32/issues/2294)
|
||||||
|
- thermostat date [#2313](https://github.com/emsesp/EMS-ESP32/issues/2313)
|
||||||
|
- Updated unknown compressor stati "enum_hpactivity" [#2311](https://github.com/emsesp/EMS-ESP32/pull/2311)
|
||||||
|
- Underline Tab headers in WebUI
|
||||||
|
- console unit tests fixed due to changed shell output
|
||||||
|
- tx-queue overflow in some heatpump systems [#2455](https://github.com/emsesp/EMS-ESP32/issues/2455)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- show operation in pretty telegram between src and dst [#2263](https://github.com/emsesp/EMS-ESP32/discussions/2263)
|
||||||
|
- update eModbus to 1.7.2 [#2254](https://github.com/emsesp/EMS-ESP32/issues/2254)
|
||||||
|
- modbus timeout default to 300 sec, change setting from ms to sec [#2254](https://github.com/emsesp/EMS-ESP32/issues/2254)
|
||||||
|
- update AsyncTCP and ESPAsyncWebServer to latest versions
|
||||||
|
- update Arduino pio platform to 3.10.0 and optimized flash using build flags
|
||||||
|
- Version checker in WebUI improved
|
||||||
|
- rename `remoteseltemp` to `cooltemp` [#2456](https://github.com/emsesp/EMS-ESP32/issues/2456)
|
||||||
|
|
||||||
|
## [3.7.1] 29 November 2024
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- include HA "unit_of_meas", "stat_cla" and "dev_cla" attributes for Number sensors [#2149](https://github.com/emsesp/EMS-ESP32/issues/2149)
|
||||||
|
- Bosch CS6800i AW - Silent Mode + Electrical Power Reduction (HP) [#2147](https://github.com/emsesp/EMS-ESP32/issues/2147)
|
||||||
|
- `/api/system/showeralert` and `/api/system/showertimer` [#2182](https://github.com/emsesp/EMS-ESP32/issues/2182)
|
||||||
|
- MX400 [#2198](https://github.com/emsesp/EMS-ESP32/issues/2198)
|
||||||
|
- SM200 values [#2212](https://github.com/emsesp/EMS-ESP32/discussions/2212)
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- Modbus integration in 3.7.0 missing offset [#2148](https://github.com/emsesp/EMS-ESP32/issues/2148)
|
||||||
|
- fix changing TZ in NTPsettings without clearing enable+server, added DST support [#2142](https://github.com/emsesp/EMS-ESP32/issues/2142)
|
||||||
|
- Support MQTT Discovery (AD) with Domoticz [#2177](https://github.com/emsesp/EMS-ESP32/issues/2177)
|
||||||
|
- wwExtra (dhw extra) changed from temperature reading to number
|
||||||
|
- auxheaterstatus [#2192](https://github.com/emsesp/EMS-ESP32/issues/2192)
|
||||||
|
- lastCode character check [#2189](https://github.com/emsesp/EMS-ESP32/issues/2189)
|
||||||
|
- reading too many telegram parts
|
||||||
|
- heatpump cost UOMs [#2188](https://github.com/emsesp/EMS-ESP32/issues/2188)
|
||||||
|
- analog dac output and inputs on dac pins [#2201](https://github.com/emsesp/EMS-ESP32/discussions/2201)
|
||||||
|
- api memory leak [#2216](https://github.com/emsesp/EMS-ESP32/issues/2216)
|
||||||
|
- modbus multiple mixers [#2229](https://github.com/emsesp/EMS-ESP32/issues/2229)
|
||||||
|
- Last Will (LWT) not set on MQTT Connect [#2247](https://github.com/emsesp/EMS-ESP32/issues/2247)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- name of wwstarts2 [#2217](https://github.com/emsesp/EMS-ESP32/discussions/2217)
|
||||||
|
|
||||||
|
## [3.7.0] 27 October 2024
|
||||||
|
|
||||||
|
## **IMPORTANT! BREAKING CHANGES with 3.6.5**
|
||||||
|
|
||||||
|
- "ww" and "wwc" has been renamed to "dhw". It is nested JSON object in both the MQTT and API outputs. The old prefix has also been removed from MQTT topics ([#1634](https://github.com/emsesp/EMS-ESP32/issues/1634)). This will impact historical data in home automation systems like Home Assistant and IOBroker. To preserve the current value of dhw energy (was previously nrgww) refer to this issue [#1938](https://github.com/emsesp/EMS-ESP32/issues/1938).
|
||||||
|
- dhw entities from the MM100/SM100 have been moved under a new Device called 'water'.
|
||||||
|
- The automatically generated temperature sensor ID has replaced dashes (`-`) with underscores (`_`) to be compatible with Home Assistant.
|
||||||
|
- `api/system/info` has it's JSON key names changed to camelCase syntax.
|
||||||
|
|
||||||
|
For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- some more entities for dhw with SM100 module
|
||||||
|
- thermostat second dhw circuit [#1634](https://github.com/emsesp/EMS-ESP32/issues/1634)
|
||||||
|
- remote thermostat emulation for RC100H, RC200 and FB10 [#1287](https://github.com/emsesp/EMS-ESP32/discussions/1287), [#1602](https://github.com/emsesp/EMS-ESP32/discussions/1602), [#1551](https://github.com/emsesp/EMS-ESP32/discussions/1551)
|
||||||
|
- heatpump dhw stop temperatures [#1624](https://github.com/emsesp/EMS-ESP32/issues/1624)
|
||||||
|
- reset history [#1695](https://github.com/emsesp/EMS-ESP32/issues/1695)
|
||||||
|
- heatpump entities `fan` and `shutdown` [#1690](https://github.com/emsesp/EMS-ESP32/discussions/1690)
|
||||||
|
- mqtt HA-mode 3 for v3.6 compatible HA entities, set on update v3.6->v3.7
|
||||||
|
- HP input states [#1723](https://github.com/emsesp/EMS-ESP32/discussions/1723)
|
||||||
|
- holiday settings for rego 3000 [#1735](https://github.com/emsesp/EMS-ESP32/issues/1735)
|
||||||
|
- Added scripts for OTA (scripts/upload.py and upload_cli.py) [#1738](https://github.com/emsesp/EMS-ESP32/issues/1738)
|
||||||
|
- timeout for remote thermostat emulation [#1680](https://github.com/emsesp/EMS-ESP32/discussions/1680), [#1774](https://github.com/emsesp/EMS-ESP32/issues/1774)
|
||||||
|
- CR120 thermostat as own model() [#1779](https://github.com/emsesp/EMS-ESP32/discussions/1779)
|
||||||
|
- modules - external linkable module library [#1778](https://github.com/emsesp/EMS-ESP32/issues/1778)
|
||||||
|
- scheduler onChange and Conditions [#1806](https://github.com/emsesp/EMS-ESP32/issues/1806)
|
||||||
|
- make remote control timeout editable [#1774](https://github.com/emsesp/EMS-ESP32/issues/1774)
|
||||||
|
- added extra pump characteristics (mode and pressure for EMS+) by @SLTKA [#1802](https://github.com/emsesp/EMS-ESP32/pull/1802)
|
||||||
|
- allow device name to be customized [#1174](https://github.com/emsesp/EMS-ESP32/issues/1174)
|
||||||
|
- Modbus support by @mheyse [#1744](https://github.com/emsesp/EMS-ESP32/issues/1744)
|
||||||
|
- System Message command [#1854](https://github.com/emsesp/EMS-ESP32/issues/1854)
|
||||||
|
- scheduler can use web get/post for values and commands [#1806](https://github.com/emsesp/EMS-ESP32/issues/1806)
|
||||||
|
- RT800 remote emulation [#1867](https://github.com/emsesp/EMS-ESP32/issues/1867)
|
||||||
|
- RC310 cooling parameters [#1857](https://github.com/emsesp/EMS-ESP32/issues/1857)
|
||||||
|
- command `api/device/entities` [#1897](https://github.com/emsesp/EMS-ESP32/issues/1897)
|
||||||
|
- switchprogmode [#1903](https://github.com/emsesp/EMS-ESP32/discussions/1903)
|
||||||
|
- autodetect and download firmware upgrades via the WebUI
|
||||||
|
- command 'show log' that lists out the current weblog buffer, showing last messages.
|
||||||
|
- default web log buffer to 25 lines for ESP32s with no PSRAM
|
||||||
|
- try and determine correct board profile if none is set during boot
|
||||||
|
- auto Scroll in WebLog UI - reduced delay so incoming logs are faster
|
||||||
|
- uploading custom support info, shown to Guest users in Help page [#2054](https://github.com/emsesp/EMS-ESP32/issues/2054)
|
||||||
|
- feature: Dashboard showing all data (favorites, sensors, custom) [#1958](https://github.com/emsesp/EMS-ESP32/issues/1958)
|
||||||
|
- entity for low-temperature boilers pump start temp (pumpOnTemp) #2088 [#2088](https://github.com/emsesp/EMS-ESP32/issues/2088)
|
||||||
|
- internal ESP32 temperature sensor on the S3 [#2077](https://github.com/emsesp/EMS-ESP32/issues/2077)
|
||||||
|
- MQTT status topic (used in connect and last will) set to Retain [#2086](https://github.com/emsesp/EMS-ESP32/discussions/2086)
|
||||||
|
- Czech language [2096](https://github.com/emsesp/EMS-ESP32/issues/2096)
|
||||||
|
- Developer Mode and send EMS Read Commands from WebUI [#2116](https://github.com/emsesp/EMS-ESP32/issues/2116)
|
||||||
|
- Scheduler functions [#2115](https://github.com/emsesp/EMS-ESP32/issues/2115)
|
||||||
|
- Set device custom name from telegram 0x01 [#2073](https://github.com/emsesp/EMS-ESP32/issues/2073)
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- remote thermostat emulation for RC200 on Rego2000/3000 thermostats [#1691](https://github.com/emsesp/EMS-ESP32/discussions/1691)
|
||||||
|
- log shows data for F7/F9 requests
|
||||||
|
- Detection of LittleFS for factory setting wasn't working
|
||||||
|
- Check for bad GPIOs with Ethernet before the ethernet is initialized
|
||||||
|
- Show values with factor 50 on webUI [#2064](https://github.com/emsesp/EMS-ESP32/issues/2064)
|
||||||
|
- Rendering of values between -1 and 0
|
||||||
|
- Value for 32bit times not-set [#2109](https://github.com/emsesp/EMS-ESP32/issues/2109)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- use flag for BC400 compatible thermostats, manage different mode settings
|
||||||
|
- use factory partition for 16M flash
|
||||||
|
- store digital out states to nvs
|
||||||
|
- Refresh UI - moving settings to one location [#1665](https://github.com/emsesp/EMS-ESP32/issues/1665)
|
||||||
|
- rename DeviceValueTypes, add UINT32 for custom entities
|
||||||
|
- dynamic register dhw circuits for thermostat
|
||||||
|
- removed OTA feature [#1738](https://github.com/emsesp/EMS-ESP32/issues/1738)
|
||||||
|
- added shower min duration [#1801](https://github.com/emsesp/EMS-ESP32/issues/1801)
|
||||||
|
- Include TXT file along with the generated CSV for Device Data export/download
|
||||||
|
- thermostat/remotetemp as command [#1835](https://github.com/emsesp/EMS-ESP32/discussions/1835)
|
||||||
|
- temperaturesensor id notation with underscore [#1794](https://github.com/emsesp/EMS-ESP32/discussions/1794)
|
||||||
|
- Change key-names in JSON to be compliant and consistent [#1860](https://github.com/emsesp/EMS-ESP32/issues/1860)
|
||||||
|
- Updates to webUI [#1920](https://github.com/emsesp/EMS-ESP32/issues/1920)
|
||||||
|
- Correct firmware naming #1933 [#1933](https://github.com/emsesp/EMS-ESP32/issues/1933)
|
||||||
|
- Don't start Serial console if not connected to a Serial port. Will initiate manually after a CTRL-C/CTRL-S
|
||||||
|
- WebLog UI matches color schema of the terminal console correctly
|
||||||
|
- Updated Web libraries, ArduinoJson
|
||||||
|
- Help page doesn't show detailed tech info if the user is not 'admin' role [#2054](https://github.com/emsesp/EMS-ESP32/issues/2054)
|
||||||
|
- removed system command `allvalues` and moved to an action called `export`
|
||||||
|
- Show ems-esp internal devices in device list of system/info
|
||||||
|
- Scheduler and mqtt run async on systems with psram
|
||||||
|
- Show IPv6 address type (local/global/ula) in log
|
||||||
|
|
||||||
## [3.6.5] March 23 2024
|
## [3.6.5] March 23 2024
|
||||||
|
|
||||||
## **IMPORTANT! BREAKING CHANGES**
|
## **IMPORTANT! BREAKING CHANGES**
|
||||||
|
|||||||
@@ -1,88 +1,56 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [3.7.0]
|
For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
|
||||||
|
|
||||||
## **IMPORTANT! BREAKING CHANGES with 3.6.5**
|
## [3.7.3]
|
||||||
|
|
||||||
- "ww" and "wwc" has been renamed to "dhw". It is nested JSON object in both the MQTT and API outputs. The old prefix has also been removed from MQTT topics ([#1634](https://github.com/emsesp/EMS-ESP32/issues/1634)). This will impact historical data in home automation systems like Home Assistant and IOBroker. To preserve the current value of dhw energy (was previously nrgww) refer to this issue [#1938](https://github.com/emsesp/EMS-ESP32/issues/1938).
|
|
||||||
- dhw entities from the MM100/SM100 have been moved under a new Device called 'water'.
|
|
||||||
- The automatically generated temperature sensor ID has replaced dashes (`-`) with underscores (`_`) to be compatible with Home Assistant.
|
|
||||||
- `api/system/info` has it's JSON key names changed to camelCase syntax.
|
|
||||||
|
|
||||||
For more details go to [www.emsesp.org](https://www.emsesp.org/).
|
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
- some more entities for dhw with SM100 module
|
- analogsensor types: NTC and RGB-Led
|
||||||
- thermostat second dhw circuit [#1634](https://github.com/emsesp/EMS-ESP32/issues/1634)
|
- Flag for HMC310 [#2465](https://github.com/emsesp/EMS-ESP32/issues/2465)
|
||||||
- remote thermostat emulation for RC100H, RC200 and FB10 [#1287](https://github.com/emsesp/EMS-ESP32/discussions/1287), [#1602](https://github.com/emsesp/EMS-ESP32/discussions/1602), [#1551](https://github.com/emsesp/EMS-ESP32/discussions/1551)
|
- boiler auxheatersource [#2489](https://github.com/emsesp/EMS-ESP32/discussions/2489)
|
||||||
- heatpump dhw stop temperatures [#1624](https://github.com/emsesp/EMS-ESP32/issues/1624)
|
- thermostat last error for RC100/300 [#2501](https://github.com/emsesp/EMS-ESP32/issues/2501)
|
||||||
- reset history [#1695](https://github.com/emsesp/EMS-ESP32/issues/1695)
|
- boiler 0xC6 telegram [#1963](https://github.com/emsesp/EMS-ESP32/issues/1963)
|
||||||
- heatpump entities `fan` and `shutdown` [#1690](https://github.com/emsesp/EMS-ESP32/discussions/1690)
|
- CS6800i changes [#2448](https://github.com/emsesp/EMS-ESP32/issues/2448), [#2449](https://github.com/emsesp/EMS-ESP32/issues/2449)
|
||||||
- mqtt HA-mode 3 for v3.6 compatible HA entities, set on update v3.6->v3.7
|
- charging pump [#2544](https://github.com/emsesp/EMS-ESP32/issues/2544)
|
||||||
- HP input states [#1723](https://github.com/emsesp/EMS-ESP32/discussions/1723)
|
- hybrid CSH5800iG [#2569](https://github.com/emsesp/EMS-ESP32/issues/2569)
|
||||||
- holiday settings for rego 3000 [#1735](https://github.com/emsesp/EMS-ESP32/issues/1735)
|
- add EMS Device details to Home Assistant MQTT Discovery
|
||||||
- Added scripts for OTA (scripts/upload.py and upload_cli.py) [#1738](https://github.com/emsesp/EMS-ESP32/issues/1738)
|
- disinfection command [#2601](https://github.com/emsesp/EMS-ESP32/issues/2601)
|
||||||
- timeout for remote thermostat emulation [#1680](https://github.com/emsesp/EMS-ESP32/discussions/1680), [#1774](https://github.com/emsesp/EMS-ESP32/issues/1774)
|
- added new board profile for upcoming BBQKees E32V2.2
|
||||||
- CR120 thermostat as own model() [#1779](https://github.com/emsesp/EMS-ESP32/discussions/1779)
|
- set differential pressure entity in Mixer device
|
||||||
- modules - external linkable module library [#1778](https://github.com/emsesp/EMS-ESP32/issues/1778)
|
- set set climate action cooling/heating in HA [#2583](https://github.com/emsesp/EMS-ESP32/issues/2583)
|
||||||
- scheduler onChange and Conditions [#1806](https://github.com/emsesp/EMS-ESP32/issues/1806)
|
- Internal sensors of E32V2_2
|
||||||
- make remote control timeout editable [#1774](https://github.com/emsesp/EMS-ESP32/issues/1774)
|
- FW200 display options [#2610](https://github.com/emsesp/EMS-ESP32/discussions/2610)
|
||||||
- added extra pump characteristics (mode and pressure for EMS+) by @SLTKA [#1802](https://github.com/emsesp/EMS-ESP32/pull/1802)
|
- CR11 mode settings OFF/MANUAL depends on selTemp [#2437](https://github.com/emsesp/EMS-ESP32/issues/2437)
|
||||||
- allow device name to be customized [#1174](https://github.com/emsesp/EMS-ESP32/issues/1174)
|
- Fuse settings for BBQKees boards
|
||||||
- Modbus support by @mheyse [#1744](https://github.com/emsesp/EMS-ESP32/issues/1744)
|
- Analogsensors for pulse output [#2624](https://github.com/emsesp/EMS-ESP32/discussions/2624)
|
||||||
- System Message command [#1854](https://github.com/emsesp/EMS-ESP32/issues/1854)
|
- Analogsensors frequency input [#2631](https://github.com/emsesp/EMS-ESP32/discussions/2631)
|
||||||
- scheduler can use web get/post for values and commands [#1806](https://github.com/emsesp/EMS-ESP32/issues/1806)
|
- SRC plus thermostats [#2636](https://github.com/emsesp/EMS-ESP32/issues/2636)
|
||||||
- RT800 remote emulation [#1867](https://github.com/emsesp/EMS-ESP32/issues/1867)
|
- Greenstar 2000 [#2645](https://github.com/emsesp/EMS-ESP32/issues/2645)
|
||||||
- RC310 cooling parameters [#1857](https://github.com/emsesp/EMS-ESP32/issues/1857)
|
- RC3xx `dhw modetype` [#2659](https://github.com/emsesp/EMS-ESP32/discussions/2659)
|
||||||
- command `api/device/entities` [#1897](https://github.com/emsesp/EMS-ESP32/issues/1897)
|
|
||||||
- switchprogmode [#1903](https://github.com/emsesp/EMS-ESP32/discussions/1903)
|
|
||||||
- autodetect and download firmware upgrades via the WebUI
|
|
||||||
- command 'show log' that lists out the current weblog buffer, showing last messages.
|
|
||||||
- default web log buffer to 25 lines for ESP32s with no PSRAM
|
|
||||||
- try and determine correct board profile if none is set during boot
|
|
||||||
- auto Scroll in WebLog UI - reduced delay so incoming logs are faster
|
|
||||||
- uploading custom support info, shown to Guest users in Help page [#2054](https://github.com/emsesp/EMS-ESP32/issues/2054)
|
|
||||||
- feature: Dashboard showing all data (favorites, sensors, custom) [#1958](https://github.com/emsesp/EMS-ESP32/issues/1958)
|
|
||||||
- entity for low-temperature boilers pump start temp (pumpOnTemp) #2088 [#2088](https://github.com/emsesp/EMS-ESP32/issues/2088)
|
|
||||||
- internal ESP32 temperature sensor on the S3 [#2077](https://github.com/emsesp/EMS-ESP32/issues/2077)
|
|
||||||
- MQTT status topic (used in connect and last will) set to Retain [#2086](https://github.com/emsesp/EMS-ESP32/discussions/2086)
|
|
||||||
- Czech language [2096](https://github.com/emsesp/EMS-ESP32/issues/2096)
|
|
||||||
- Developer Mode and send EMS Read Commands from WebUI [#2116](https://github.com/emsesp/EMS-ESP32/issues/2116)
|
|
||||||
- Scheduler functions [#2115](https://github.com/emsesp/EMS-ESP32/issues/2115)
|
|
||||||
- Set device custom name from telegram 0x01 [#2073](https://github.com/emsesp/EMS-ESP32/issues/2073)
|
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
- remote thermostat emulation for RC200 on Rego2000/3000 thermostats [#1691](https://github.com/emsesp/EMS-ESP32/discussions/1691)
|
- dhw/switchtime [#2490](https://github.com/emsesp/EMS-ESP32/issues/2490)
|
||||||
- log shows data for F7/F9 requests
|
- switch to secure mqtt [#2492](https://github.com/emsesp/EMS-ESP32/issues/2492)
|
||||||
- Detection of LittleFS for factory setting wasn't working
|
- update link buttons [#2497](https://github.com/emsesp/EMS-ESP32/issues/2497)
|
||||||
- Check for bad GPIOs with Ethernet before the ethernet is initialized
|
- refresh scheduler states [#2502](https://github.com/emsesp/EMS-ESP32/discussions/2502)
|
||||||
- Show values with factor 50 on webUI [#2064](https://github.com/emsesp/EMS-ESP32/issues/2064)
|
- also rebuild HA config on mqtt connect for scheduler, custom and shower
|
||||||
- Rendering of values between -1 and 0
|
- FB100 controls the hc, not the master [#2510](https://github.com/emsesp/EMS-ESP32/issues/2510)
|
||||||
- Value for 32bit times not-set [#2109](https://github.com/emsesp/EMS-ESP32/issues/2109)
|
- 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
|
## Changed
|
||||||
|
|
||||||
- use flag for BC400 compatible thermostats, manage different mode settings
|
- show console log with ISO date/time [#2533](https://github.com/emsesp/EMS-ESP32/discussions/2533)
|
||||||
- use factory partition for 16M flash
|
- remove ESP32 CPU temperature
|
||||||
- store digital out states to nvs
|
- updated core libraries like AsyncTCP, AsyncWebServer and Modbus
|
||||||
- Refresh UI - moving settings to one location [#1665](https://github.com/emsesp/EMS-ESP32/issues/1665)
|
- remove command `scan deep`
|
||||||
- rename DeviceValueTypes, add UINT32 for custom entities
|
- ignore repeated `forceheatingoff` commands [#2641](https://github.com/emsesp/EMS-ESP32/discussions/2641)
|
||||||
- dynamic register dhw circuits for thermostat
|
|
||||||
- removed OTA feature [#1738](https://github.com/emsesp/EMS-ESP32/issues/1738)
|
|
||||||
- added shower min duration [#1801](https://github.com/emsesp/EMS-ESP32/issues/1801)
|
|
||||||
- Include TXT file along with the generated CSV for Device Data export/download
|
|
||||||
- thermostat/remotetemp as command [#1835](https://github.com/emsesp/EMS-ESP32/discussions/1835)
|
|
||||||
- temperaturesensor id notation with underscore [#1794](https://github.com/emsesp/EMS-ESP32/discussions/1794)
|
|
||||||
- Change key-names in JSON to be compliant and consistent [#1860](https://github.com/emsesp/EMS-ESP32/issues/1860)
|
|
||||||
- Updates to webUI [#1920](https://github.com/emsesp/EMS-ESP32/issues/1920)
|
|
||||||
- Correct firmware naming #1933 [#1933](https://github.com/emsesp/EMS-ESP32/issues/1933)
|
|
||||||
- Don't start Serial console if not connected to a Serial port. Will initiate manually after a CTRL-C/CTRL-S
|
|
||||||
- WebLog UI matches color schema of the terminal console correctly
|
|
||||||
- Updated Web libraries, ArduinoJson
|
|
||||||
- Help page doesn't show detailed tech info if the user is not 'admin' role [#2054](https://github.com/emsesp/EMS-ESP32/issues/2054)
|
|
||||||
- removed system command `allvalues` and moved to an action called `export`
|
|
||||||
- Show ems-esp internal devices in device list of system/info
|
|
||||||
- Scheduler and mqtt run async on systems with psram
|
|
||||||
- Show IPv6 address type (local/global/ula) in log
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Everybody is welcome and invited to contribute to the EMS-ESP Project by:
|
|||||||
|
|
||||||
- providing Pull Requests (Features, Fixes, suggestions)
|
- providing Pull Requests (Features, Fixes, suggestions)
|
||||||
- testing new released features and report issues on your EMS equipment
|
- testing new released features and report issues on your EMS equipment
|
||||||
- contributing to missing [documentation](https://emsesp.org)
|
- contributing to missing [documentation](https://docs.emsesp.org)
|
||||||
|
|
||||||
This document describes rules that are in effect for this repository, meant for handling issues by contributors in the issue tracker and PRs.
|
This document describes rules that are in effect for this repository, meant for handling issues by contributors in the issue tracker and PRs.
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
76
Makefile
76
Makefile
@@ -1,10 +1,37 @@
|
|||||||
#
|
#
|
||||||
# GNUMakefile for EMS-ESP
|
# GNUMakefile for EMS-ESP
|
||||||
# This is mainly used to generate the .o files for SonarQube analysis
|
|
||||||
#
|
#
|
||||||
|
|
||||||
# NUMJOBS=${NUMJOBS:-" -j10 "}
|
_mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
|
||||||
# MAKEFLAGS+="j "
|
I := $(patsubst %/,%,$(dir $(_mkfile_path)))
|
||||||
|
|
||||||
|
ifneq ($(words $(MAKECMDGOALS)),1)
|
||||||
|
.DEFAULT_GOAL = all
|
||||||
|
%:
|
||||||
|
@$(MAKE) $@ --no-print-directory -rRf $(firstword $(MAKEFILE_LIST))
|
||||||
|
else
|
||||||
|
ifndef ECHO
|
||||||
|
T := $(shell $(MAKE) $(MAKECMDGOALS) --no-print-directory \
|
||||||
|
-nrRf $(firstword $(MAKEFILE_LIST)) \
|
||||||
|
ECHO="COUNTTHIS" | grep -c "COUNTTHIS")
|
||||||
|
N := x
|
||||||
|
C = $(words $N)$(eval N := x $N)
|
||||||
|
ECHO = python3 $(I)/scripts/echo_progress.py --stepno=$C --nsteps=$T
|
||||||
|
endif
|
||||||
|
|
||||||
|
# determine number of parallel compiles based on OS
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
ifeq ($(UNAME_S),Linux)
|
||||||
|
EXTRA_CPPFLAGS = -D LINUX
|
||||||
|
JOBS ?= $(shell nproc)
|
||||||
|
endif
|
||||||
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
EXTRA_CPPFLAGS = -D OSX -Wno-tautological-constant-out-of-range-compare
|
||||||
|
JOBS ?= $(shell sysctl -n hw.ncpu)
|
||||||
|
endif
|
||||||
|
MAKEFLAGS += -j $(JOBS) -l $(JOBS)
|
||||||
|
|
||||||
|
# $(info Number of jobs: $(JOBS))
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# Project Structure
|
# Project Structure
|
||||||
@@ -15,26 +42,20 @@
|
|||||||
# INCLUDES is a list of directories containing header files
|
# INCLUDES is a list of directories containing header files
|
||||||
# LIBRARIES is a list of directories containing libraries, this must be the top level containing include and lib
|
# LIBRARIES is a list of directories containing libraries, this must be the top level containing include and lib
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
|
|
||||||
#TARGET := $(notdir $(CURDIR))
|
|
||||||
TARGET := emsesp
|
TARGET := emsesp
|
||||||
BUILD := build
|
BUILD := build
|
||||||
SOURCES := src src/* lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src lib/PButton lib/semver lib/espMqttClient/src lib/espMqttClient/src/*
|
SOURCES := src/core src/devices src/web src/test lib_standalone lib/semver lib/espMqttClient/src lib/espMqttClient/src/* lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/PButton
|
||||||
INCLUDES := src lib_standalone lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/semver lib/* src/devices
|
INCLUDES := src/core src/devices src/web src/test lib/* lib_standalone lib/semver lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src
|
||||||
LIBRARIES :=
|
LIBRARIES :=
|
||||||
|
|
||||||
CPPCHECK = cppcheck
|
CPPCHECK = cppcheck
|
||||||
# CHECKFLAGS = -q --force --std=c++17
|
CHECKFLAGS = -q --force --std=gnu++17
|
||||||
CHECKFLAGS = -q --force --std=c++11
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# Languages Standard
|
# Languages Standard
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
C_STANDARD := -std=c17
|
C_STANDARD := -std=c17
|
||||||
CXX_STANDARD := -std=gnu++14
|
CXX_STANDARD := -std=gnu++17
|
||||||
|
|
||||||
# C_STANDARD := -std=c11
|
|
||||||
# CXX_STANDARD := -std=c++11
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# Defined Symbols
|
# Defined Symbols
|
||||||
@@ -43,7 +64,7 @@ DEFINES += -DARDUINOJSON_ENABLE -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSO
|
|||||||
DEFINES += -DEMSESP_STANDALONE -DEMSESP_TEST -DEMSESP_DEBUG -DEMC_RX_BUFFER_SIZE=1500
|
DEFINES += -DEMSESP_STANDALONE -DEMSESP_TEST -DEMSESP_DEBUG -DEMC_RX_BUFFER_SIZE=1500
|
||||||
DEFINES += $(ARGS)
|
DEFINES += $(ARGS)
|
||||||
|
|
||||||
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.0-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.3-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32S3\"
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# Sources & Files
|
# Sources & Files
|
||||||
@@ -77,14 +98,16 @@ CXX := /usr/bin/g++
|
|||||||
# LDFLAGS Linker Flags
|
# LDFLAGS Linker Flags
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
|
CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
|
||||||
CPPFLAGS += -ggdb
|
CPPFLAGS += -ggdb -g3 -MMD
|
||||||
CPPFLAGS += -g3
|
CPPFLAGS += -flto=auto -fno-lto
|
||||||
CPPFLAGS += -Os
|
CPPFLAGS += -Wall -Wextra -Werror -Wswitch-enum
|
||||||
|
CPPFLAGS += -Wno-unused-parameter -Wno-missing-braces -Wno-vla-cxx-extension
|
||||||
|
|
||||||
|
CPPFLAGS += $(EXTRA_CPPFLAGS)
|
||||||
|
|
||||||
CFLAGS += $(CPPFLAGS)
|
CFLAGS += $(CPPFLAGS)
|
||||||
CFLAGS += -Wall -Wextra -Werror -Wswitch-enum
|
CXXFLAGS += $(CPPFLAGS)
|
||||||
CFLAGS += -Wno-tautological-constant-out-of-range-compare -Wno-unused-parameter -Wno-inconsistent-missing-override -Wno-missing-braces -Wno-unused-lambda-capture -Wno-sign-compare
|
LDFLAGS =
|
||||||
CXXFLAGS += $(CFLAGS) -MMD
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# Compiler & Linker Commands
|
# Compiler & Linker Commands
|
||||||
@@ -125,23 +148,27 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
|
|||||||
.SILENT: $(OUTPUT)
|
.SILENT: $(OUTPUT)
|
||||||
|
|
||||||
all: $(OUTPUT)
|
all: $(OUTPUT)
|
||||||
|
@$(ECHO) Build complete.
|
||||||
|
|
||||||
$(OUTPUT): $(OBJS)
|
$(OUTPUT): $(OBJS)
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
|
@$(ECHO) Linking $@
|
||||||
$(LINK.o)
|
$(LINK.o)
|
||||||
$(SYMBOLS.out)
|
$(SYMBOLS.out)
|
||||||
|
|
||||||
$(BUILD)/%.o: %.c
|
$(BUILD)/%.o: %.c
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
$(COMPILE.c)
|
@$(ECHO) Compiling $@
|
||||||
|
@$(COMPILE.c)
|
||||||
|
|
||||||
$(BUILD)/%.o: %.cpp
|
$(BUILD)/%.o: %.cpp
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
$(COMPILE.cpp)
|
@$(ECHO) Compiling $@
|
||||||
|
@$(COMPILE.cpp)
|
||||||
|
|
||||||
$(BUILD)/%.o: %.s
|
$(BUILD)/%.o: %.s
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
$(COMPILE.s)
|
@$(COMPILE.s)
|
||||||
|
|
||||||
cppcheck: $(SOURCES)
|
cppcheck: $(SOURCES)
|
||||||
$(CPPCHECK) $(CHECKFLAGS) $^
|
$(CPPCHECK) $(CHECKFLAGS) $^
|
||||||
@@ -150,6 +177,7 @@ run: $(OUTPUT)
|
|||||||
@$<
|
@$<
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@$(RM) -rf $(BUILD) $(OUTPUT)
|
@$(RM) -rf $(BUILD) $(OUTPUT)
|
||||||
|
|
||||||
@@ -158,3 +186,5 @@ help:
|
|||||||
@echo $(OUTPUT)
|
@echo $(OUTPUT)
|
||||||
|
|
||||||
-include $(DEPS)
|
-include $(DEPS)
|
||||||
|
|
||||||
|
endif
|
||||||
|
|||||||
58
README.md
58
README.md
@@ -1,4 +1,30 @@
|
|||||||
# 
|
<div align="center">
|
||||||
|
<p align="center">
|
||||||
|
<a href="#">
|
||||||
|
<img src="https://raw.githubusercontent.com/emsesp/EMS-ESP32/dev/media/favicon/android-chrome-512x512.png" height="100px" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 align="center">EMS-ESP</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://emsesp.org">
|
||||||
|
<img src="https://img.shields.io/badge/Website-0077b5?style=for-the-badge&logo=googlehome&logoColor=white" alt="Website" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/emsesp/EMS-ESP32/blob/dev/CONTRIBUTING.md">
|
||||||
|
<img src="https://img.shields.io/badge/Contribute-ff4785?style=for-the-badge&logo=git&logoColor=white" alt="Contribute" />
|
||||||
|
</a>
|
||||||
|
<a href="https://docs.emsesp.org">
|
||||||
|
<img src="https://img.shields.io/badge/Documentation-0077b5?style=for-the-badge&logo=googledocs&logoColor=white" alt="Guides" />
|
||||||
|
</a>
|
||||||
|
<a href="https://discord.gg/3J3GgnzpyT">
|
||||||
|
<img src="https://img.shields.io/badge/Discord-7289da?style=for-the-badge&logo=discord&logoColor=white" alt="Discord" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md">
|
||||||
|
<img src="https://img.shields.io/badge/Changelog-6c5ce7?style=for-the-badge&logo=git&logoColor=white" alt="Changelog" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
[](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md)
|
[](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md)
|
||||||
[](https://github.com/emsesp/EMS-ESP32/commits/main)
|
[](https://github.com/emsesp/EMS-ESP32/commits/main)
|
||||||
@@ -9,14 +35,14 @@
|
|||||||
[](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.
|
||||||
|
|
||||||
It requires a small circuit to interface with the EMS bus which can be purchased from <https://bbqkees-electronics.nl> or custom built.
|
It requires a small circuit to interface with the EMS bus which can be purchased from <https://bbqkees-electronics.nl> or custom built.
|
||||||
|
|
||||||
## **Key Features**
|
## 📦 **Key Features**
|
||||||
|
|
||||||
- Compatible with EMS, EMS+, EMS2, EMS Plus, Logamatic EMS, Junkers 2-wire, Heatronic 3 and 4
|
- Compatible with EMS, EMS+, EMS2, EMS Plus, Logamatic EMS, Junkers 2-wire, Heatronic 3 and 4
|
||||||
- Supporting over 120 different EMS compatible devices such as thermostats, boilers, heat pumps, mixing units, solar modules, connect modules, ventilation units, switches and more
|
- Supporting over 120 different EMS compatible devices such as thermostats, boilers, heat pumps, mixing units, solar modules, connect modules, ventilation units, switches and more
|
||||||
@@ -32,43 +58,39 @@ It requires a small circuit to interface with the EMS bus which can be purchased
|
|||||||
- A powerful Scheduler to automate tasks and trigger events based data changes
|
- A powerful Scheduler to automate tasks and trigger events based data changes
|
||||||
- A Notification service to alert you of important events
|
- A Notification service to alert you of important events
|
||||||
|
|
||||||
## **Installing**
|
## 🚀 **Installing**
|
||||||
|
|
||||||
For a quick install of the latest stable release go to [https://install.emsesp.org](https://install.emsesp.org). For other methods of installing and upgrading, and switching over to the development version go to [this section](https://emsesp.org/Getting-Started/#first-time-install) in 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.
|
||||||
|
|
||||||
If you're upgrading a BBQKees Electronics EMS Gateway and unsure which firmware to use, please refer to the [this overview](https://emsesp.org/Getting-Started/#bbqkees-electronics-ems-gateway).
|
## 📋 **Documentation**
|
||||||
|
|
||||||
## **Documentation**
|
Visit [emsesp.org](https://docs.emsesp.org) for more details on how to install and configure EMS-ESP. There is also a collection of Frequently Asked Questions and Troubleshooting tips with example customizations from the community.
|
||||||
|
|
||||||
Visit [emsesp.org](https://emsesp.org) for more details on how to install and configure EMS-ESP. There is also a collection of Frequently Asked Questions and Troubleshooting tips with example customizations from the community.
|
## 💬 **Getting Support**
|
||||||
|
|
||||||
## **Getting Support**
|
|
||||||
|
|
||||||
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://emsesp.org/Support/) on how to submit a bug report or feature request.
|
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.
|
||||||
|
|
||||||
## **Live Demo**
|
## 🎥 **Live Demo**
|
||||||
|
|
||||||
For a live demo go to [demo.emsesp.org](https://demo.emsesp.org). Pick a language from the sign on page and log in with any username or password. Note not all features are operational as it's based on static data.
|
For a live demo go to [demo.emsesp.org](https://demo.emsesp.org). Pick a language from the sign on page and log in with any username or password. Note not all features are operational as it's based on static data.
|
||||||
|
|
||||||
## **Contributors**
|
## 💖 **Contributors**
|
||||||
|
|
||||||
EMS-ESP is a project created by [proddy](https://github.com/proddy) and owned and maintained by both [proddy](https://github.com/proddy) and [MichaelDvP](https://github.com/MichaelDvP) with support from [BBQKees Electronics](https://bbqkees-electronics.nl).
|
EMS-ESP is a project created by [proddy](https://github.com/proddy) and owned and maintained by both [proddy](https://github.com/proddy) and [MichaelDvP](https://github.com/MichaelDvP) with support from [BBQKees Electronics](https://bbqkees-electronics.nl).
|
||||||
|
|
||||||
You can contact us using [this form](https://emsesp.org/Contact/).
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
## **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) 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 and TCP backends
|
||||||
|
|
||||||
## **License**
|
## 📜 **License**
|
||||||
|
|
||||||
This program is licensed under GPL-3.0
|
This program is licensed under GPL-3.0
|
||||||
|
|
||||||
|
|||||||
45
boards/c3_mini_4M.json
Normal file
45
boards/c3_mini_4M.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "esp32c3_out.ld"
|
||||||
|
},
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": [
|
||||||
|
"-DTASMOTA_SDK",
|
||||||
|
"-DARDUINO_LOLIN_C3_MINI",
|
||||||
|
"-DARDUINO_USB_MODE=1",
|
||||||
|
"-DARDUINO_USB_CDC_ON_BOOT=1"
|
||||||
|
],
|
||||||
|
"f_cpu": "160000000L",
|
||||||
|
"f_flash": "80000000L",
|
||||||
|
"flash_mode": "qio",
|
||||||
|
"hwids": [
|
||||||
|
[
|
||||||
|
"0X303A",
|
||||||
|
"0x1001"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"mcu": "esp32c3",
|
||||||
|
"variant": "lolin_c3_mini"
|
||||||
|
},
|
||||||
|
"connectivity": [
|
||||||
|
"wifi"
|
||||||
|
],
|
||||||
|
"debug": {
|
||||||
|
"openocd_target": "esp32c3.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino",
|
||||||
|
"espidf"
|
||||||
|
],
|
||||||
|
"name": "WEMOS LOLIN C3 Mini",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "4MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 4194304,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 460800
|
||||||
|
},
|
||||||
|
"url": "https://www.wemos.cc/en/latest/c3/c3_mini.html",
|
||||||
|
"vendor": "WEMOS"
|
||||||
|
}
|
||||||
34
boards/esp32dev.json
Normal file
34
boards/esp32dev.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"core": "esp32",
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "40000000L",
|
||||||
|
"flash_mode": "dio",
|
||||||
|
"mcu": "esp32",
|
||||||
|
"variant": "esp32"
|
||||||
|
},
|
||||||
|
"connectivity": [
|
||||||
|
"wifi",
|
||||||
|
"ethernet"
|
||||||
|
],
|
||||||
|
"debug": {
|
||||||
|
"openocd_board": "esp32.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino",
|
||||||
|
"espidf"
|
||||||
|
],
|
||||||
|
"name": "Espressif ESP32 Dev Module",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "16MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 16777216,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 460800
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"speed": 230400
|
||||||
|
},
|
||||||
|
"url": "https://en.wikipedia.org/wiki/ESP32",
|
||||||
|
"vendor": "Espressif"
|
||||||
|
}
|
||||||
47
boards/s2_4M_P.json
Normal file
47
boards/s2_4M_P.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "esp32s2_out.ld"
|
||||||
|
},
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": [
|
||||||
|
"-DBOARD_HAS_PSRAM",
|
||||||
|
"-DTASMOTA_SDK",
|
||||||
|
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||||
|
"-DARDUINO_USB_MODE=0"
|
||||||
|
],
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "80000000L",
|
||||||
|
"flash_mode": "dio",
|
||||||
|
"hwids": [
|
||||||
|
[
|
||||||
|
"0X303A",
|
||||||
|
"0x80C2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"mcu": "esp32s2",
|
||||||
|
"variant": "lolin_s2_mini"
|
||||||
|
},
|
||||||
|
"connectivity": [
|
||||||
|
"wifi"
|
||||||
|
],
|
||||||
|
"debug": {
|
||||||
|
"openocd_target": "esp32s2.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino",
|
||||||
|
"espidf"
|
||||||
|
],
|
||||||
|
"name": "WEMOS LOLIN S2 Mini",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "4MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 4194304,
|
||||||
|
"use_1200bps_touch": true,
|
||||||
|
"wait_for_upload_port": true,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 921600
|
||||||
|
},
|
||||||
|
"url": "https://www.wemos.cc/en/latest/s2/s2_mini.html",
|
||||||
|
"vendor": "WEMOS"
|
||||||
|
}
|
||||||
44
boards/s3_16M_P.json
Normal file
44
boards/s3_16M_P.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "esp32s3_out.ld",
|
||||||
|
"memory_type": "qio_opi"
|
||||||
|
},
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": [
|
||||||
|
"-DBOARD_HAS_PSRAM",
|
||||||
|
"-DARDUINO_USB_MODE=1",
|
||||||
|
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||||
|
"-DARDUINO_RUNNING_CORE=1",
|
||||||
|
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||||
|
],
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "80000000L",
|
||||||
|
"flash_mode": "qio",
|
||||||
|
"mcu": "esp32s3",
|
||||||
|
"variant": "esp32s3"
|
||||||
|
},
|
||||||
|
"connectivity": [
|
||||||
|
"wifi"
|
||||||
|
],
|
||||||
|
"debug": {
|
||||||
|
"openocd_target": "esp32s3.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino",
|
||||||
|
"espidf"
|
||||||
|
],
|
||||||
|
"name": "Espressif ESP32-S3 16M Flash OPI PSRAM, 4608KB Code/OTA, 2MB FS",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "16MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 16777216,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 460800
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"speed": 230400
|
||||||
|
},
|
||||||
|
"url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/",
|
||||||
|
"vendor": "Espressif"
|
||||||
|
}
|
||||||
37
boards/s3_32M_P.json
Normal file
37
boards/s3_32M_P.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino":{
|
||||||
|
"memory_type": "opi_opi"
|
||||||
|
},
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": "-DBOARD_HAS_PSRAM",
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "80000000L",
|
||||||
|
"flash_mode": "opi",
|
||||||
|
"mcu": "esp32s3",
|
||||||
|
"variant": "esp32s3"
|
||||||
|
},
|
||||||
|
"connectivity": [
|
||||||
|
"wifi"
|
||||||
|
],
|
||||||
|
"debug": {
|
||||||
|
"openocd_target": "esp32s3.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino",
|
||||||
|
"espidf"
|
||||||
|
],
|
||||||
|
"name": "Espressif ESP32-S3 32M Flash OPI PSRAM, 4608KB Code/OTA, 2MB FS",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "32MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 16777216,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 460800
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"speed": 230400
|
||||||
|
},
|
||||||
|
"url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/",
|
||||||
|
"vendor": "Espressif"
|
||||||
|
}
|
||||||
35
boards/s_16M.json
Normal file
35
boards/s_16M.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": "-DTASMOTA_SDK",
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "40000000L",
|
||||||
|
"flash_mode": "dio",
|
||||||
|
"mcu": "esp32",
|
||||||
|
"variant": "esp32"
|
||||||
|
},
|
||||||
|
"connectivity": [
|
||||||
|
"wifi",
|
||||||
|
"ethernet"
|
||||||
|
],
|
||||||
|
"debug": {
|
||||||
|
"openocd_target": "esp32.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino",
|
||||||
|
"espidf"
|
||||||
|
],
|
||||||
|
"name": "Espressif ESP32 16M Flash, 4608KB Code/OTA, 2MB FS",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "16MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 16777216,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 460800
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"speed": 230400
|
||||||
|
},
|
||||||
|
"url": "https://en.wikipedia.org/wiki/ESP32",
|
||||||
|
"vendor": "Espressif"
|
||||||
|
}
|
||||||
35
boards/s_16M_P.json
Normal file
35
boards/s_16M_P.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": "-DBOARD_HAS_PSRAM",
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "80000000L",
|
||||||
|
"flash_mode": "dio",
|
||||||
|
"mcu": "esp32",
|
||||||
|
"variant": "esp32"
|
||||||
|
},
|
||||||
|
"connectivity": [
|
||||||
|
"wifi",
|
||||||
|
"ethernet"
|
||||||
|
],
|
||||||
|
"debug": {
|
||||||
|
"openocd_target": "esp32.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino",
|
||||||
|
"espidf"
|
||||||
|
],
|
||||||
|
"name": "Espressif ESP32 16M Flash DIO PSRAM, 4608KB Code/OTA, 2MB FS",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "16MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 16777216,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 460800
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"speed": 230400
|
||||||
|
},
|
||||||
|
"url": "https://en.wikipedia.org/wiki/ESP32",
|
||||||
|
"vendor": "Espressif"
|
||||||
|
}
|
||||||
34
boards/s_4M.json
Normal file
34
boards/s_4M.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": "-DTASMOTA_SDK",
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "40000000L",
|
||||||
|
"flash_mode": "dio",
|
||||||
|
"mcu": "esp32",
|
||||||
|
"variant": "esp32"
|
||||||
|
},
|
||||||
|
"connectivity": [
|
||||||
|
"wifi"
|
||||||
|
],
|
||||||
|
"debug": {
|
||||||
|
"openocd_target": "esp32.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino",
|
||||||
|
"espidf"
|
||||||
|
],
|
||||||
|
"name": "Tasmota ESP32 4M Flash, 4608KB Code/OTA, 2MB FS",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "4MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 4194304,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 460800
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"speed": 230400
|
||||||
|
},
|
||||||
|
"url": "https://en.wikipedia.org/wiki/ESP32",
|
||||||
|
"vendor": "Espressif"
|
||||||
|
}
|
||||||
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"
|
||||||
|
}
|
||||||
28
cspell.json
28
cspell.json
@@ -9,5 +9,31 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dictionaries": ["project-words"],
|
"dictionaries": ["project-words"],
|
||||||
"ignorePaths": ["node_modules", "compile_commands.json", "WWWData.h", "**/venv/**", "lib/eModbus", "lib/ESPAsyncWebServer", "lib/espMqttClient", "analyse.html", "dist", "**/*.csv", "locale_translations.h", "TZ.tsx", "**/*.txt","build/**", "**/i18n/**", "/project-words.txt"]
|
"ignorePaths": [
|
||||||
|
"node_modules",
|
||||||
|
"compile_commands.json",
|
||||||
|
"WWWData.h", "**/venv/**",
|
||||||
|
"lib/eModbus",
|
||||||
|
"lib/ESPAsyncWebServer",
|
||||||
|
"lib/espMqttClient",
|
||||||
|
"analyse.html",
|
||||||
|
"dist",
|
||||||
|
"**/*.csv",
|
||||||
|
"**/*.md",
|
||||||
|
"**/*.py",
|
||||||
|
"locale_translations.h",
|
||||||
|
"TZ.tsx",
|
||||||
|
"**/*.txt",
|
||||||
|
"build/**",
|
||||||
|
"**/i18n/**",
|
||||||
|
"/project-words.txt",
|
||||||
|
"Makefile",
|
||||||
|
"**/*.ini",
|
||||||
|
"**/*.json",
|
||||||
|
"src/core/modbus_entity_parameters.hpp",
|
||||||
|
"sdkconfig.*",
|
||||||
|
"managed_components/**",
|
||||||
|
"pnpm-*.yaml",
|
||||||
|
"vite.config.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
85
data/pre_load.json
Normal file
85
data/pre_load.json
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"type": "settings",
|
||||||
|
"Network": {
|
||||||
|
"ssid": "my_wifi_ssid",
|
||||||
|
"bssid": "",
|
||||||
|
"password": "my_wifi_password",
|
||||||
|
"hostname": "ems-esp"
|
||||||
|
},
|
||||||
|
"AP": {
|
||||||
|
"provision_mode": 2,
|
||||||
|
"ssid": "ems-esp",
|
||||||
|
"password": "ems-esp-neo",
|
||||||
|
"channel": 1,
|
||||||
|
"ssid_hidden": false,
|
||||||
|
"max_clients": 4,
|
||||||
|
"local_ip": "192.168.4.1",
|
||||||
|
"gateway_ip": "192.168.4.1",
|
||||||
|
"subnet_mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
"MQTT": {
|
||||||
|
"enableTLS": false,
|
||||||
|
"rootCA": "",
|
||||||
|
"enabled": false,
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 1883,
|
||||||
|
"base": "ems-esp",
|
||||||
|
"username": "username",
|
||||||
|
"password": "password",
|
||||||
|
"client_id": "ems-esp",
|
||||||
|
"entity_format": 1,
|
||||||
|
"publish_time_boiler": 10,
|
||||||
|
"publish_time_thermostat": 10,
|
||||||
|
"publish_time_solar": 10,
|
||||||
|
"publish_time_mixer": 10,
|
||||||
|
"publish_time_water": 10,
|
||||||
|
"publish_time_other": 60,
|
||||||
|
"publish_time_sensor": 10,
|
||||||
|
"publish_time_heartbeat": 60,
|
||||||
|
"mqtt_qos": 0,
|
||||||
|
"mqtt_retain": false,
|
||||||
|
"ha_enabled": false,
|
||||||
|
"nested_format": 1,
|
||||||
|
"discovery_prefix": "homeassistant",
|
||||||
|
"discovery_type": 0,
|
||||||
|
"publish_single": false,
|
||||||
|
"publish_single2cmd": false,
|
||||||
|
"send_response": false
|
||||||
|
},
|
||||||
|
"NTP": {
|
||||||
|
"enabled": true,
|
||||||
|
"server": "time.google.com",
|
||||||
|
"tz_label": "Europe/Amsterdam",
|
||||||
|
"tz_format": "CET-1CEST,M3.5.0,M10.5.0/3"
|
||||||
|
},
|
||||||
|
"Security": {
|
||||||
|
"jwt_secret": "ems-esp-neo",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin",
|
||||||
|
"admin": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "guest",
|
||||||
|
"password": "guest",
|
||||||
|
"admin": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Settings": {
|
||||||
|
"board_profile": "S3",
|
||||||
|
"locale": "en",
|
||||||
|
"tx_mode": 1,
|
||||||
|
"ems_bus_id": 11,
|
||||||
|
"boiler_heatingoff": false,
|
||||||
|
"hide_led": true,
|
||||||
|
"telnet_enabled": true,
|
||||||
|
"notoken_api": false,
|
||||||
|
"analog_enabled": true,
|
||||||
|
"fahrenheit": false,
|
||||||
|
"bool_format": 1,
|
||||||
|
"bool_dashboard": 1,
|
||||||
|
"enum_format": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
0
docs/dump_telegrams.csv
Normal file
0
docs/dump_telegrams.csv
Normal file
|
|
@@ -1,212 +0,0 @@
|
|||||||
telegram_type_id,name,is_fetched
|
|
||||||
0x04,UBAFactory,fetched
|
|
||||||
0x06,RCTime,
|
|
||||||
0x0A,EasyMonitor,fetched
|
|
||||||
0x10,UBAErrorMessage1,
|
|
||||||
0x11,UBAErrorMessage2,
|
|
||||||
0x12,RCErrorMessage,
|
|
||||||
0x13,RCErrorMessage2,
|
|
||||||
0x14,UBATotalUptime,fetched
|
|
||||||
0x15,UBAMaintenanceData,
|
|
||||||
0x16,UBAParameters,fetched
|
|
||||||
0x18,UBAMonitorFast,
|
|
||||||
0x19,UBAMonitorSlow,
|
|
||||||
0x1A,UBASetPoints,
|
|
||||||
0x1C,UBAMaintenanceStatus,
|
|
||||||
0x1E,WM10TempMessage,
|
|
||||||
0x23,JunkersSetMixer,fetched
|
|
||||||
0x26,UBASettingsWW,fetched
|
|
||||||
0x28,WeatherComp,fetched
|
|
||||||
0x2A,MC110Status,
|
|
||||||
0x2E,Meters,
|
|
||||||
0x33,UBAParameterWW,fetched
|
|
||||||
0x34,UBAMonitorWW,
|
|
||||||
0x35,UBAFlags,
|
|
||||||
0x37,WWSettings,fetched
|
|
||||||
0x38,WWTimer,fetched
|
|
||||||
0x39,WWCircTimer,fetched
|
|
||||||
0x3A,RC30WWSettings,fetched
|
|
||||||
0x3B,Energy,
|
|
||||||
0x3D,RC35Set,
|
|
||||||
0x3E,RC35Monitor,
|
|
||||||
0x3F,RC35Timer,
|
|
||||||
0x40,RC30Temp,
|
|
||||||
0x41,RC30Monitor,
|
|
||||||
0x42,RC35Timer2,
|
|
||||||
0x47,RC35Set,
|
|
||||||
0x48,RC35Monitor,
|
|
||||||
0x49,RC35Timer,
|
|
||||||
0x4C,RC35Timer2,
|
|
||||||
0x51,RC35Set,
|
|
||||||
0x52,RC35Monitor,
|
|
||||||
0x53,RC35Timer,
|
|
||||||
0x56,RC35Timer2,
|
|
||||||
0x5B,RC35Set,
|
|
||||||
0x5C,RC35Monitor,
|
|
||||||
0x5D,RC35Timer,
|
|
||||||
0x60,RC35Timer2,
|
|
||||||
0x96,SM10Config,fetched
|
|
||||||
0x97,SM10Monitor,
|
|
||||||
0x9C,WM10MonitorMessage,
|
|
||||||
0x9D,WM10SetMessage,
|
|
||||||
0xA2,RCError,
|
|
||||||
0xA3,RCOutdoorTemp,
|
|
||||||
0xA5,IBASettings,fetched
|
|
||||||
0xA7,RC30Set,
|
|
||||||
0xA9,RC30Vacation,fetched
|
|
||||||
0xAA,MMConfigMessage,fetched
|
|
||||||
0xAB,MMStatusMessage,
|
|
||||||
0xAC,MMSetMessage,
|
|
||||||
0xAF,RC20Remote,
|
|
||||||
0xB0,RC10Set,
|
|
||||||
0xB1,RC10Monitor,
|
|
||||||
0xBB,HybridSettings,fetched
|
|
||||||
0xBF,ErrorMessage,
|
|
||||||
0xC2,UBAErrorMessage3,
|
|
||||||
0xD1,UBAOutdoorTemp,
|
|
||||||
0xE3,UBAMonitorSlowPlus2,
|
|
||||||
0xE4,UBAMonitorFastPlus,
|
|
||||||
0xE5,UBAMonitorSlowPlus,
|
|
||||||
0xE6,UBAParametersPlus,fetched
|
|
||||||
0xE9,UBAMonitorWWPlus,
|
|
||||||
0xEA,UBAParameterWWPlus,fetched
|
|
||||||
0x0101,ISM1Set,fetched
|
|
||||||
0x0103,ISM1StatusMessage,fetched
|
|
||||||
0x0104,ISM2StatusMessage,
|
|
||||||
0x010C,IPMStatusMessage,
|
|
||||||
0x011E,IPMTempMessage,
|
|
||||||
0x0165,JunkersSet,
|
|
||||||
0x0166,JunkersSet,
|
|
||||||
0x0167,JunkersSet,
|
|
||||||
0x0168,JunkersSet,
|
|
||||||
0x016F,JunkersMonitor,
|
|
||||||
0x0170,JunkersMonitor,
|
|
||||||
0x0171,JunkersMonitor,
|
|
||||||
0x0172,JunkersMonitor,
|
|
||||||
0x0179,JunkersSet,
|
|
||||||
0x017A,JunkersSet,
|
|
||||||
0x017B,JunkersSet,
|
|
||||||
0x017C,JunkersSet,
|
|
||||||
0x01D3,JunkersDhw,fetched
|
|
||||||
0x023A,RC300OutdoorTemp,fetched
|
|
||||||
0x023E,PVSettings,fetched
|
|
||||||
0x0240,RC300Settings,fetched
|
|
||||||
0x0267,RC300Floordry,
|
|
||||||
0x0269,RC300Holiday1,fetched
|
|
||||||
0x0291,HPMode,fetched
|
|
||||||
0x0292,HPMode,fetched
|
|
||||||
0x0293,HPMode,fetched
|
|
||||||
0x0294,HPMode,fetched
|
|
||||||
0x029B,RC300Curves,
|
|
||||||
0x029C,RC300Curves,
|
|
||||||
0x029D,RC300Curves,
|
|
||||||
0x029E,RC300Curves,
|
|
||||||
0x029F,RC300Curves,
|
|
||||||
0x02A0,RC300Curves,
|
|
||||||
0x02A1,RC300Curves,
|
|
||||||
0x02A2,RC300Curves,
|
|
||||||
0x02A5,RC300Monitor,
|
|
||||||
0x02A6,RC300Monitor,
|
|
||||||
0x02A7,RC300Monitor,
|
|
||||||
0x02A8,RC300Monitor,
|
|
||||||
0x02A9,RC300Monitor,
|
|
||||||
0x02AA,RC300Monitor,
|
|
||||||
0x02AB,RC300Monitor,
|
|
||||||
0x02AC,RC300Monitor,
|
|
||||||
0x02AF,RC300Summer,
|
|
||||||
0x02B0,RC300Summer,
|
|
||||||
0x02B1,RC300Summer,
|
|
||||||
0x02B2,RC300Summer,
|
|
||||||
0x02B3,RC300Summer,
|
|
||||||
0x02B4,RC300Summer,
|
|
||||||
0x02B5,RC300Summer,
|
|
||||||
0x02B6,RC300Summer,
|
|
||||||
0x02B9,RC300Set,
|
|
||||||
0x02BA,RC300Set,
|
|
||||||
0x02BB,RC300Set,
|
|
||||||
0x02BC,RC300Set,
|
|
||||||
0x02BD,RC300Set,
|
|
||||||
0x02BE,RC300Set,
|
|
||||||
0x02BF,RC300Set,
|
|
||||||
0x02C0,RC300Set,
|
|
||||||
0x02CC,RC300Set2,
|
|
||||||
0x02CD,MMPLUSConfigMessage,fetched
|
|
||||||
0x02CE,RC300Set2,
|
|
||||||
0x02D0,RC300Set2,
|
|
||||||
0x02D2,RC300Set2,
|
|
||||||
0x02D5,MMPLUSConfigMessage,fetched
|
|
||||||
0x02D6,HPPump2,fetched
|
|
||||||
0x02D7,MMPLUSStatusMessage,
|
|
||||||
0x02DF,MMPLUSStatusMessage,
|
|
||||||
0x02F5,RC300WWmode,fetched
|
|
||||||
0x02F6,RC300WW2mode,fetched
|
|
||||||
0x031B,RC300WWtemp,fetched
|
|
||||||
0x031D,RC300WWmode2,
|
|
||||||
0x031E,RC300WWmode2,
|
|
||||||
0x0358,SM100SystemConfig,fetched
|
|
||||||
0x035A,SM100CircuitConfig,fetched
|
|
||||||
0x035C,SM100HeatAssist,fetched
|
|
||||||
0x035D,SM100Circuit2Config,fetched
|
|
||||||
0x035F,SM100Config1,fetched
|
|
||||||
0x0361,SM100Differential,fetched
|
|
||||||
0x0362,SM100Monitor,
|
|
||||||
0x0363,SM100Monitor2,
|
|
||||||
0x0364,SM100Status,
|
|
||||||
0x0366,SM100Config,
|
|
||||||
0x036A,SM100Status2,
|
|
||||||
0x0380,SM100CollectorConfig,fetched
|
|
||||||
0x038E,SM100Energy,fetched
|
|
||||||
0x0391,SM100Time,fetched
|
|
||||||
0x0467,HPSet,
|
|
||||||
0x0468,HPSet,
|
|
||||||
0x0469,HPSet,
|
|
||||||
0x046A,HPSet,
|
|
||||||
0x0471,RC300Summer2,
|
|
||||||
0x0472,RC300Summer2,
|
|
||||||
0x0473,RC300Summer2,
|
|
||||||
0x0474,RC300Summer2,
|
|
||||||
0x0475,RC300Summer2,
|
|
||||||
0x0476,RC300Summer2,
|
|
||||||
0x0477,RC300Summer2,
|
|
||||||
0x0478,RC300Summer2,
|
|
||||||
0x047B,HP2,
|
|
||||||
0x0484,HPSilentMode,fetched
|
|
||||||
0x0485,HpCooling,fetched
|
|
||||||
0x0486,HpInConfig,fetched
|
|
||||||
0x0488,HPValve,fetched
|
|
||||||
0x048A,HpPool,fetched
|
|
||||||
0x048B,HPPumps,fetched
|
|
||||||
0x048D,HpPower,fetched
|
|
||||||
0x048F,HpTemperatures,
|
|
||||||
0x0491,HPAdditionalHeater,fetched
|
|
||||||
0x0492,HpHeaterConfig,fetched
|
|
||||||
0x0494,UBAEnergySupplied,
|
|
||||||
0x0495,UBAInformation,
|
|
||||||
0x0499,HPDhwSettings,fetched
|
|
||||||
0x049C,HPSettings2,fetched
|
|
||||||
0x049D,HPSettings3,fetched
|
|
||||||
0x04A2,HpInput,fetched
|
|
||||||
0x04A5,HPFan,fetched
|
|
||||||
0x04A7,HPPowerLimit,fetched
|
|
||||||
0x04AA,HPPower2,fetched
|
|
||||||
0x04AE,HPEnergy,fetched
|
|
||||||
0x04AF,HPMeters,fetched
|
|
||||||
0x056B,VentilationMode,fetched
|
|
||||||
0x0583,VentilationMonitor,
|
|
||||||
0x0585,Blowerspeed,
|
|
||||||
0x0587,Bypass,
|
|
||||||
0x05BA,HpPoolStatus,fetched
|
|
||||||
0x05D9,Airquality,
|
|
||||||
0x0772,HIUSettings,
|
|
||||||
0x0779,HIUMonitor,
|
|
||||||
0x0935,EM100SetMessage,fetched
|
|
||||||
0x0936,EM100OutMessage,
|
|
||||||
0x0937,EM100TempMessage,
|
|
||||||
0x0938,EM100InputMessage,
|
|
||||||
0x0939,EM100MonitorMessage,
|
|
||||||
0x093A,EM100ConfigMessage,
|
|
||||||
0x0998,HPSettings,fetched
|
|
||||||
0x0999,HPFunctionTest,fetched
|
|
||||||
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
|
||||||
@@ -1 +0,0 @@
|
|||||||
nodeLinker: node-modules
|
|
||||||
@@ -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.0",
|
"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,60 +8,60 @@
|
|||||||
"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\" \"npm: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\" \"npm: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"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alova/adapter-xhr": "2.0.9",
|
"@alova/adapter-xhr": "2.2.1",
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/icons-material": "^6.1.5",
|
"@mui/icons-material": "^7.3.4",
|
||||||
"@mui/material": "^6.1.5",
|
"@mui/material": "^7.3.4",
|
||||||
"@table-library/react-table-library": "4.1.7",
|
"@table-library/react-table-library": "4.1.15",
|
||||||
"alova": "3.1.1",
|
"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.19",
|
||||||
"preact": "^10.24.3",
|
"mime-types": "^3.0.1",
|
||||||
"react": "^18.3.1",
|
"preact": "^10.27.2",
|
||||||
"react-dom": "^18.3.1",
|
"react": "^19.2.0",
|
||||||
"react-icons": "^5.3.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-router-dom": "^6.27.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-toastify": "^10.0.6",
|
"react-router": "^7.9.4",
|
||||||
|
"react-toastify": "^11.0.5",
|
||||||
"typesafe-i18n": "^5.26.2",
|
"typesafe-i18n": "^5.26.2",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.26.0",
|
"@babel/core": "^7.28.4",
|
||||||
"@eslint/js": "^9.13.0",
|
"@eslint/js": "^9.38.0",
|
||||||
"@preact/compat": "^18.3.1",
|
"@preact/compat": "^18.3.1",
|
||||||
"@preact/preset-vite": "^2.9.1",
|
"@preact/preset-vite": "^2.10.2",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||||
"@types/formidable": "^3",
|
"@types/node": "^24.9.1",
|
||||||
"@types/node": "^22.8.1",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@types/react-dom": "^18.3.1",
|
"concurrently": "^9.2.1",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"eslint": "^9.38.0",
|
||||||
"concurrently": "^9.0.1",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint": "^9.13.0",
|
"prettier": "^3.6.2",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"rollup-plugin-visualizer": "^6.0.5",
|
||||||
"formidable": "^3.5.2",
|
"terser": "^5.44.0",
|
||||||
"prettier": "^3.3.3",
|
"typescript-eslint": "^8.46.2",
|
||||||
"rollup-plugin-visualizer": "^5.12.0",
|
"vite": "^7.1.11",
|
||||||
"terser": "^5.36.0",
|
|
||||||
"typescript-eslint": "8.11.0",
|
|
||||||
"vite": "^5.4.10",
|
|
||||||
"vite-plugin-imagemin": "^0.6.1",
|
"vite-plugin-imagemin": "^0.6.1",
|
||||||
"vite-tsconfig-paths": "^5.0.1"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.5.1"
|
"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';
|
||||||
@@ -12,70 +13,82 @@ import zlib from 'zlib';
|
|||||||
|
|
||||||
const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n';
|
const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n';
|
||||||
const INDENT = ' ';
|
const INDENT = ' ';
|
||||||
const outputPath = '../lib/framework/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));
|
||||||
|
|||||||
@@ -13,8 +13,9 @@
|
|||||||
local('Roboto'),
|
local('Roboto'),
|
||||||
local('Roboto-Regular'),
|
local('Roboto-Regular'),
|
||||||
url(../fonts/re.woff2) format('woff2');
|
url(../fonts/re.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131,
|
unicode-range:
|
||||||
U+0141-0144, U+0152-0153, U+015A-015B, U+015E-015F, U+0179-017C, U+02BB-02BC,
|
U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131, U+0141-0144,
|
||||||
U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
|
U+0152-0153, U+015A-015B, U+015E-015F, U+0179-017C, U+02BB-02BC, U+02C6, U+02DA,
|
||||||
U+2212, U+2215, U+FEFF, U+FFFD;
|
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
|
||||||
|
U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,44 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Slide, ToastContainer } from 'react-toastify';
|
import { ToastContainer, Zoom } from 'react-toastify';
|
||||||
import 'react-toastify/dist/ReactToastify.min.css';
|
|
||||||
|
|
||||||
import AppRouting from 'AppRouting';
|
import AppRouting from 'AppRouting';
|
||||||
import CustomTheme from 'CustomTheme';
|
import CustomTheme from 'CustomTheme';
|
||||||
import TypesafeI18n from 'i18n/i18n-react';
|
import TypesafeI18n from 'i18n/i18n-react';
|
||||||
import { detectLocale } from 'i18n/i18n-util';
|
import type { Locales } from 'i18n/i18n-types';
|
||||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||||
import { localStorageDetector } from 'typesafe-i18n/detectors';
|
import { detectLocale, navigatorDetector } from 'typesafe-i18n/detectors';
|
||||||
|
|
||||||
const detectedLocale = detectLocale(localStorageDetector);
|
const availableLocales = [
|
||||||
|
'de',
|
||||||
|
'en',
|
||||||
|
'it',
|
||||||
|
'fr',
|
||||||
|
'nl',
|
||||||
|
'no',
|
||||||
|
'pl',
|
||||||
|
'sk',
|
||||||
|
'sv',
|
||||||
|
'tr',
|
||||||
|
'cz'
|
||||||
|
];
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [wasLoaded, setWasLoaded] = useState(false);
|
const [wasLoaded, setWasLoaded] = useState(false);
|
||||||
|
const [locale, setLocale] = useState<Locales>('en');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true));
|
// determine locale, take from session if set other default to browser language
|
||||||
|
const browserLocale = detectLocale('en', availableLocales, navigatorDetector);
|
||||||
|
const newLocale = (localStorage.getItem('lang') || browserLocale) as Locales;
|
||||||
|
localStorage.setItem('lang', newLocale);
|
||||||
|
setLocale(newLocale);
|
||||||
|
void loadLocaleAsync(newLocale).then(() => setWasLoaded(true));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!wasLoaded) return null;
|
if (!wasLoaded) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypesafeI18n locale={detectedLocale}>
|
<TypesafeI18n locale={locale}>
|
||||||
<CustomTheme>
|
<CustomTheme>
|
||||||
<AppRouting />
|
<AppRouting />
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
@@ -29,14 +46,17 @@ const App = () => {
|
|||||||
autoClose={3000}
|
autoClose={3000}
|
||||||
hideProgressBar={false}
|
hideProgressBar={false}
|
||||||
newestOnTop={false}
|
newestOnTop={false}
|
||||||
closeOnClick={true}
|
closeOnClick
|
||||||
rtl={false}
|
rtl={false}
|
||||||
pauseOnFocusLoss={false}
|
pauseOnFocusLoss
|
||||||
draggable={false}
|
draggable={false}
|
||||||
pauseOnHover={false}
|
pauseOnHover={false}
|
||||||
transition={Slide}
|
transition={Zoom}
|
||||||
closeButton={false}
|
closeButton={false}
|
||||||
theme="light"
|
theme="dark"
|
||||||
|
toastStyle={{
|
||||||
|
border: '1px solid #177ac9'
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</CustomTheme>
|
</CustomTheme>
|
||||||
</TypesafeI18n>
|
</TypesafeI18n>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import AuthenticatedRouting from 'AuthenticatedRouting';
|
import AuthenticatedRouting from 'AuthenticatedRouting';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router';
|
||||||
|
|
||||||
import CustomEntities from 'app/main/CustomEntities';
|
import CustomEntities from 'app/main/CustomEntities';
|
||||||
import Customizations from 'app/main/Customizations';
|
import Customizations from 'app/main/Customizations';
|
||||||
@@ -15,7 +15,6 @@ import DownloadUpload from 'app/settings/DownloadUpload';
|
|||||||
import MqttSettings from 'app/settings/MqttSettings';
|
import MqttSettings from 'app/settings/MqttSettings';
|
||||||
import NTPSettings from 'app/settings/NTPSettings';
|
import NTPSettings from 'app/settings/NTPSettings';
|
||||||
import Settings from 'app/settings/Settings';
|
import Settings from 'app/settings/Settings';
|
||||||
import Version from 'app/settings/Version';
|
|
||||||
import Network from 'app/settings/network/Network';
|
import Network from 'app/settings/network/Network';
|
||||||
import Security from 'app/settings/security/Security';
|
import Security from 'app/settings/security/Security';
|
||||||
import APStatus from 'app/status/APStatus';
|
import APStatus from 'app/status/APStatus';
|
||||||
@@ -26,6 +25,7 @@ import NTPStatus from 'app/status/NTPStatus';
|
|||||||
import NetworkStatus from 'app/status/NetworkStatus';
|
import NetworkStatus from 'app/status/NetworkStatus';
|
||||||
import Status from 'app/status/Status';
|
import Status from 'app/status/Status';
|
||||||
import SystemLog from 'app/status/SystemLog';
|
import SystemLog from 'app/status/SystemLog';
|
||||||
|
import Version from 'app/status/Version';
|
||||||
import { Layout } from 'components';
|
import { Layout } from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
@@ -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 />} />
|
||||||
@@ -48,17 +47,17 @@ const AuthenticatedRouting = () => {
|
|||||||
<Route path="/status/ntp" element={<NTPStatus />} />
|
<Route path="/status/ntp" element={<NTPStatus />} />
|
||||||
<Route path="/status/ap" element={<APStatus />} />
|
<Route path="/status/ap" element={<APStatus />} />
|
||||||
<Route path="/status/network" element={<NetworkStatus />} />
|
<Route path="/status/network" element={<NetworkStatus />} />
|
||||||
|
<Route path="/status/version" element={<Version />} />
|
||||||
|
|
||||||
{me.admin && (
|
{me.admin && (
|
||||||
<>
|
<>
|
||||||
<Route path="/settings" element={<Settings />} />
|
<Route path="/settings" element={<Settings />} />
|
||||||
<Route path="/settings/version" element={<Version />} />
|
|
||||||
<Route path="/settings/application" element={<ApplicationSettings />} />
|
<Route path="/settings/application" element={<ApplicationSettings />} />
|
||||||
<Route path="/settings/mqtt" element={<MqttSettings />} />
|
<Route path="/settings/mqtt" element={<MqttSettings />} />
|
||||||
<Route path="/settings/ntp" element={<NTPSettings />} />
|
<Route path="/settings/ntp" element={<NTPSettings />} />
|
||||||
<Route path="/settings/ap" element={<APSettings />} />
|
<Route path="/settings/ap" element={<APSettings />} />
|
||||||
<Route path="/settings/modules" element={<Modules />} />
|
<Route path="/settings/modules" element={<Modules />} />
|
||||||
<Route path="/settings/upload" element={<DownloadUpload />} />
|
<Route path="/settings/downloadUpload" element={<DownloadUpload />} />
|
||||||
|
|
||||||
<Route path="/settings/network/*" element={<Network />} />
|
<Route path="/settings/network/*" element={<Network />} />
|
||||||
<Route path="/settings/security/*" element={<Security />} />
|
<Route path="/settings/security/*" element={<Security />} />
|
||||||
@@ -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,11 +1,7 @@
|
|||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { CssBaseline } from '@mui/material';
|
import { CssBaseline, ThemeProvider, responsiveFontSizes } from '@mui/material';
|
||||||
import {
|
import { createTheme } from '@mui/material/styles';
|
||||||
ThemeProvider,
|
|
||||||
createTheme,
|
|
||||||
responsiveFontSizes
|
|
||||||
} from '@mui/material/styles';
|
|
||||||
|
|
||||||
import type { RequiredChildrenProps } from 'utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
Action,
|
Action,
|
||||||
Activity,
|
Activity,
|
||||||
CoreData,
|
CoreData,
|
||||||
DashboardItem,
|
DashboardData,
|
||||||
DeviceData,
|
DeviceData,
|
||||||
DeviceEntity,
|
DeviceEntity,
|
||||||
Entities,
|
Entities,
|
||||||
@@ -22,7 +22,7 @@ import type {
|
|||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
export const readDashboard = () =>
|
export const readDashboard = () =>
|
||||||
alovaInstance.Get<DashboardItem[]>('/rest/dashboardData', {
|
alovaInstance.Get<DashboardData>('/rest/dashboardData', {
|
||||||
responseType: 'arraybuffer' // uses msgpack
|
responseType: 'arraybuffer' // uses msgpack
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -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
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const alovaInstance = createAlova({
|
|||||||
method.config.headers.Authorization =
|
method.config.headers.Authorization =
|
||||||
'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
||||||
}
|
}
|
||||||
// for simulating vrey slow networks
|
// for simulating very slow networks
|
||||||
// return new Promise((resolve) => {
|
// return new Promise((resolve) => {
|
||||||
// const random = 3000 + Math.random() * 2000;
|
// const random = 3000 + Math.random() * 2000;
|
||||||
// setTimeout(resolve, Math.floor(random));
|
// setTimeout(resolve, Math.floor(random));
|
||||||
@@ -57,7 +57,10 @@ export const alovaInstance = createAlova({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const alovaInstanceGH = createAlova({
|
export const alovaInstanceGH = createAlova({
|
||||||
baseURL: 'https://api.github.com/repos/emsesp/EMS-ESP32/releases',
|
baseURL:
|
||||||
|
process.env.NODE_ENV === 'development'
|
||||||
|
? '/gh'
|
||||||
|
: 'https://api.github.com/repos/emsesp/EMS-ESP32/releases',
|
||||||
statesHook: ReactHook,
|
statesHook: ReactHook,
|
||||||
requestAdapter: xhrRequestAdapter()
|
requestAdapter: xhrRequestAdapter()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { LogSettings, SystemStatus } from 'types';
|
|||||||
|
|
||||||
import { alovaInstance, alovaInstanceGH } from './endpoints';
|
import { alovaInstance, alovaInstanceGH } from './endpoints';
|
||||||
|
|
||||||
// systemStatus - also used to ping in Restart monitor for pinging
|
// systemStatus - also used to ping in System Monitor for pinging
|
||||||
export const readSystemStatus = () =>
|
export const readSystemStatus = () =>
|
||||||
alovaInstance.Get<SystemStatus>('/rest/systemStatus');
|
alovaInstance.Get<SystemStatus>('/rest/systemStatus');
|
||||||
|
|
||||||
@@ -14,16 +14,25 @@ export const updateLogSettings = (data: LogSettings) =>
|
|||||||
export const fetchLogES = () => alovaInstance.Get('/es/log');
|
export const fetchLogES = () => alovaInstance.Get('/es/log');
|
||||||
|
|
||||||
// Get versions from GitHub
|
// Get versions from GitHub
|
||||||
|
// cache for 10 minutes to stop getting the IP blocked by GitHub
|
||||||
export const getStableVersion = () =>
|
export const getStableVersion = () =>
|
||||||
alovaInstanceGH.Get('latest', {
|
alovaInstanceGH.Get('latest', {
|
||||||
transform(response: { data: { name: string } }) {
|
cacheFor: 60 * 10 * 1000,
|
||||||
return response.data.name.substring(1);
|
transform(response: { data: { name: string; published_at: string } }) {
|
||||||
|
return {
|
||||||
|
name: response.data.name.substring(1),
|
||||||
|
published_at: response.data.published_at
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
export const getDevVersion = () =>
|
export const getDevVersion = () =>
|
||||||
alovaInstanceGH.Get('tags/latest', {
|
alovaInstanceGH.Get('tags/latest', {
|
||||||
transform(response: { data: { name: string } }) {
|
cacheFor: 60 * 10 * 1000,
|
||||||
return response.data.name.split(/\s+/).splice(-1)[0].substring(1);
|
transform(response: { data: { name: string; published_at: string } }) {
|
||||||
|
return {
|
||||||
|
name: response.data.name.split(/\s+/).splice(-1)[0]?.substring(1) || '',
|
||||||
|
published_at: response.data.published_at
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,37 @@
|
|||||||
let decoder;
|
// @ts-nocheck - Optimized MessagePack unpacking library for EMS-ESP32
|
||||||
|
let decoder,
|
||||||
|
src,
|
||||||
|
srcEnd,
|
||||||
|
position = 0,
|
||||||
|
strings = [],
|
||||||
|
stringPosition = 0,
|
||||||
|
currentUnpackr = {},
|
||||||
|
currentStructures,
|
||||||
|
srcString,
|
||||||
|
srcStringStart = 0,
|
||||||
|
srcStringEnd = 0,
|
||||||
|
bundledStrings,
|
||||||
|
referenceMap,
|
||||||
|
dataView;
|
||||||
|
const EMPTY_ARRAY = [],
|
||||||
|
currentExtensions = [];
|
||||||
|
const defaultOptions = { useRecords: false, mapsAsObjects: true };
|
||||||
try {
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useBlocker } from 'react-router-dom';
|
import { useBlocker } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
@@ -57,7 +57,7 @@ const CustomEntities = () => {
|
|||||||
if (!dialogOpen && !numChanges) {
|
if (!dialogOpen && !numChanges) {
|
||||||
void fetchEntities();
|
void fetchEntities();
|
||||||
}
|
}
|
||||||
}, 3000);
|
});
|
||||||
|
|
||||||
const { send: writeEntities } = useRequest(
|
const { send: writeEntities } = useRequest(
|
||||||
(data: Entities) => writeCustomEntities(data),
|
(data: Entities) => writeCustomEntities(data),
|
||||||
@@ -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 || '')
|
||||||
);
|
);
|
||||||
@@ -83,7 +84,7 @@ const CustomEntities = () => {
|
|||||||
|
|
||||||
const entity_theme = useTheme({
|
const entity_theme = useTheme({
|
||||||
Table: `
|
Table: `
|
||||||
--data-table-library_grid-template-columns: repeat(1, minmax(60px, 1fr)) minmax(80px, auto) 80px 80px 80px 90px;
|
--data-table-library_grid-template-columns: repeat(1, minmax(60px, 1fr)) minmax(80px, auto) 80px 80px 80px 120px;
|
||||||
`,
|
`,
|
||||||
BaseRow: `
|
BaseRow: `
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -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
|
||||||
}))
|
}))
|
||||||
@@ -195,6 +197,26 @@ const CustomEntities = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDialogDup = (item: EntityItem) => {
|
||||||
|
setCreating(true);
|
||||||
|
setSelectedEntityItem({
|
||||||
|
id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100),
|
||||||
|
name: item.name + '_',
|
||||||
|
ram: item.ram,
|
||||||
|
device_id: item.device_id,
|
||||||
|
type_id: item.type_id,
|
||||||
|
offset: item.offset,
|
||||||
|
factor: item.factor,
|
||||||
|
uom: item.uom,
|
||||||
|
value_type: item.value_type,
|
||||||
|
writeable: item.writeable,
|
||||||
|
deleted: false,
|
||||||
|
hide: item.hide,
|
||||||
|
value: item.value
|
||||||
|
});
|
||||||
|
setDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const addEntityItem = () => {
|
const addEntityItem = () => {
|
||||||
setCreating(true);
|
setCreating(true);
|
||||||
setSelectedEntityItem({
|
setSelectedEntityItem({
|
||||||
@@ -209,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);
|
||||||
@@ -220,7 +243,7 @@ const CustomEntities = () => {
|
|||||||
: typeof value === 'number'
|
: typeof value === 'number'
|
||||||
? new Intl.NumberFormat().format(value) +
|
? new Intl.NumberFormat().format(value) +
|
||||||
(uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom])
|
(uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom])
|
||||||
: (value as string);
|
: (value as string) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showHex(value: number, digit: number) {
|
function showHex(value: number, digit: number) {
|
||||||
@@ -229,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 }}
|
||||||
@@ -296,6 +321,7 @@ const CustomEntities = () => {
|
|||||||
creating={creating}
|
creating={creating}
|
||||||
onClose={onDialogClose}
|
onClose={onDialogClose}
|
||||||
onSave={onDialogSave}
|
onSave={onDialogSave}
|
||||||
|
onDup={onDialogDup}
|
||||||
selectedItem={selectedEntityItem}
|
selectedItem={selectedEntityItem}
|
||||||
validator={entityItemValidation(entities, selectedEntityItem)}
|
validator={entityItemValidation(entities, selectedEntityItem)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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,11 +16,11 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField
|
TextField
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import type Schema from 'async-validator';
|
import type Schema from 'async-validator';
|
||||||
@@ -34,6 +38,7 @@ interface CustomEntitiesDialogProps {
|
|||||||
creating: boolean;
|
creating: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSave: (ei: EntityItem) => void;
|
onSave: (ei: EntityItem) => void;
|
||||||
|
onDup: (ei: EntityItem) => void;
|
||||||
selectedItem: EntityItem;
|
selectedItem: EntityItem;
|
||||||
validator: Schema;
|
validator: Schema;
|
||||||
}
|
}
|
||||||
@@ -43,6 +48,7 @@ const CustomEntitiesDialog = ({
|
|||||||
creating,
|
creating,
|
||||||
onClose,
|
onClose,
|
||||||
onSave,
|
onSave,
|
||||||
|
onDup,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
validator
|
validator
|
||||||
}: CustomEntitiesDialogProps) => {
|
}: CustomEntitiesDialogProps) => {
|
||||||
@@ -59,12 +65,19 @@ const CustomEntitiesDialog = ({
|
|||||||
setEditItem({
|
setEditItem({
|
||||||
...selectedItem,
|
...selectedItem,
|
||||||
device_id: selectedItem.device_id.toString(16).toUpperCase(),
|
device_id: selectedItem.device_id.toString(16).toUpperCase(),
|
||||||
type_id: selectedItem.type_id.toString(16).toUpperCase()
|
type_id: selectedItem.type_id.toString(16).toUpperCase(),
|
||||||
|
factor:
|
||||||
|
selectedItem.value_type === DeviceValueType.BOOL
|
||||||
|
? selectedItem.factor.toString(16).toUpperCase()
|
||||||
|
: selectedItem.factor
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [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();
|
||||||
}
|
}
|
||||||
@@ -80,6 +93,12 @@ const CustomEntitiesDialog = ({
|
|||||||
if (typeof editItem.type_id === 'string') {
|
if (typeof editItem.type_id === 'string') {
|
||||||
editItem.type_id = parseInt(editItem.type_id, 16);
|
editItem.type_id = parseInt(editItem.type_id, 16);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
editItem.value_type === DeviceValueType.BOOL &&
|
||||||
|
typeof editItem.factor === 'string'
|
||||||
|
) {
|
||||||
|
editItem.factor = parseInt(editItem.factor, 16);
|
||||||
|
}
|
||||||
onSave(editItem);
|
onSave(editItem);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors(error as ValidateFieldsError);
|
||||||
@@ -91,6 +110,10 @@ const CustomEntitiesDialog = ({
|
|||||||
onSave(editItem);
|
onSave(editItem);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dup = () => {
|
||||||
|
onDup(editItem);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
|
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
@@ -103,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}
|
||||||
@@ -112,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"
|
||||||
@@ -128,6 +165,7 @@ const CustomEntitiesDialog = ({
|
|||||||
</TextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
{editItem.ram === 1 && (
|
{editItem.ram === 1 && (
|
||||||
|
<>
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextField
|
<TextField
|
||||||
name="value"
|
name="value"
|
||||||
@@ -140,13 +178,32 @@ const CustomEntitiesDialog = ({
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<TextField
|
||||||
|
name="uom"
|
||||||
|
label={LL.UNIT()}
|
||||||
|
value={editItem.uom}
|
||||||
|
margin="normal"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
select
|
||||||
|
>
|
||||||
|
{DeviceValueUOM_s.map((val, i) => (
|
||||||
|
<MenuItem key={val} value={i}>
|
||||||
|
{val}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{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"
|
||||||
@@ -157,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"
|
||||||
@@ -177,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"
|
||||||
@@ -197,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"
|
||||||
@@ -255,7 +312,7 @@ const CustomEntitiesDialog = ({
|
|||||||
<TextField
|
<TextField
|
||||||
name="factor"
|
name="factor"
|
||||||
label={LL.FACTOR()}
|
label={LL.FACTOR()}
|
||||||
value={numberValue(editItem.factor)}
|
value={numberValue(editItem.factor as number)}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
sx={{ width: '11ch' }}
|
sx={{ width: '11ch' }}
|
||||||
@@ -289,15 +346,41 @@ const CustomEntitiesDialog = ({
|
|||||||
editItem.device_id !== '0' && (
|
editItem.device_id !== '0' && (
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="factor"
|
name="factor"
|
||||||
label="Bytes"
|
label={LL.BYTES()}
|
||||||
value={numberValue(editItem.factor)}
|
value={numberValue(editItem.factor as number)}
|
||||||
sx={{ width: '11ch' }}
|
sx={{ width: '11ch' }}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
type="number"
|
type="number"
|
||||||
|
slotProps={{
|
||||||
|
htmlInput: { step: '1', min: '1', max: '255' }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
{editItem.value_type === DeviceValueType.BOOL && (
|
||||||
|
<Grid>
|
||||||
|
<ValidatedTextField
|
||||||
|
fieldErrors={fieldErrors || {}}
|
||||||
|
name="factor"
|
||||||
|
label={LL.BITMASK()}
|
||||||
|
value={editItem.factor as string}
|
||||||
|
sx={{ width: '11ch' }}
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
margin="normal"
|
||||||
|
type="string"
|
||||||
|
slotProps={{
|
||||||
|
input: {
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">0x</InputAdornment>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
htmlInput: { style: { textTransform: 'uppercase' } }
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
@@ -316,6 +399,15 @@ const CustomEntitiesDialog = ({
|
|||||||
>
|
>
|
||||||
{LL.REMOVE()}
|
{LL.REMOVE()}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{ ml: 1 }}
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
onClick={dup}
|
||||||
|
>
|
||||||
|
{LL.DUPLICATE()}
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useBlocker, useLocation } from 'react-router-dom';
|
import { useBlocker, useLocation } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
Link,
|
Link,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
@@ -24,7 +25,6 @@ import {
|
|||||||
ToggleButtonGroup,
|
ToggleButtonGroup,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
@@ -38,7 +38,7 @@ import {
|
|||||||
import { useTheme } from '@table-library/react-table-library/theme';
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import RestartMonitor from 'app/status/RestartMonitor';
|
import SystemMonitor from 'app/status/SystemMonitor';
|
||||||
import {
|
import {
|
||||||
BlockNavigation,
|
BlockNavigation,
|
||||||
ButtonRow,
|
ButtonRow,
|
||||||
@@ -125,13 +125,22 @@ const Customizations = () => {
|
|||||||
|
|
||||||
const setOriginalSettings = (data: DeviceEntity[]) => {
|
const setOriginalSettings = (data: DeviceEntity[]) => {
|
||||||
setDeviceEntities(
|
setDeviceEntities(
|
||||||
data.map((de) => ({
|
data.map((de) => {
|
||||||
|
const result: DeviceEntity = {
|
||||||
...de,
|
...de,
|
||||||
o_m: de.m,
|
o_m: de.m
|
||||||
o_cn: de.cn,
|
};
|
||||||
o_mi: de.mi,
|
if (de.cn !== undefined) {
|
||||||
o_ma: de.ma
|
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) => {
|
})
|
||||||
|
.then(() => {
|
||||||
|
toast.success(LL.CUSTOMIZATIONS_SAVED());
|
||||||
|
})
|
||||||
|
.catch((error: Error) => {
|
||||||
if (error.message === 'Reboot required') {
|
if (error.message === 'Reboot required') {
|
||||||
setRestartNeeded(true);
|
setRestartNeeded(true);
|
||||||
} else {
|
} else {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.finally(() => {
|
||||||
setOriginalSettings(deviceEntities);
|
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));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -593,7 +611,7 @@ const Customizations = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Typography variant="subtitle2" color="primary">
|
<Typography variant="subtitle2" color="grey">
|
||||||
{LL.SHOWING()} {shown_data.length}/{deviceEntities.length}
|
{LL.SHOWING()} {shown_data.length}/{deviceEntities.length}
|
||||||
{LL.ENTITIES(deviceEntities.length)}
|
{LL.ENTITIES(deviceEntities.length)}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -737,7 +755,7 @@ const Customizations = () => {
|
|||||||
return (
|
return (
|
||||||
<SectionContent>
|
<SectionContent>
|
||||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||||
{restarting ? <RestartMonitor /> : renderContent()}
|
{restarting ? <SystemMonitor /> : renderContent()}
|
||||||
{selectedDeviceEntity && (
|
{selectedDeviceEntity && (
|
||||||
<SettingsCustomizationsDialog
|
<SettingsCustomizationsDialog
|
||||||
open={dialogOpen}
|
open={dialogOpen}
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
Grid,
|
||||||
TextField,
|
TextField,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
@@ -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,10 +1,12 @@
|
|||||||
import { useContext, useEffect, useState } from 'react';
|
import { 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 { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
||||||
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
|
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
|
||||||
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
|
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
|
||||||
import {
|
import {
|
||||||
@@ -12,16 +14,20 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
ToggleButton,
|
ToggleButton,
|
||||||
ToggleButtonGroup,
|
ToggleButtonGroup,
|
||||||
Tooltip,
|
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import { Body, Cell, Row, Table } from '@table-library/react-table-library/table';
|
import { Body, Cell, Row, Table } from '@table-library/react-table-library/table';
|
||||||
import { useTheme } from '@table-library/react-table-library/theme';
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
import { CellTree, useTree } from '@table-library/react-table-library/tree';
|
import { CellTree, useTree } from '@table-library/react-table-library/tree';
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
import {
|
||||||
|
ButtonTooltip,
|
||||||
|
FormLoader,
|
||||||
|
MessageBox,
|
||||||
|
SectionContent,
|
||||||
|
useLayoutTitle
|
||||||
|
} from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { useInterval, usePersistState } from 'utils';
|
import { useInterval, usePersistState } from 'utils';
|
||||||
@@ -54,13 +60,12 @@ const Dashboard = () => {
|
|||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
send: fetchDashboard,
|
send: fetchDashboard,
|
||||||
error,
|
error
|
||||||
loading
|
|
||||||
} = useRequest(readDashboard, {
|
} = useRequest(readDashboard, {
|
||||||
initialData: []
|
initialData: { connected: true, nodes: [] }
|
||||||
}).onSuccess((event) => {
|
}).onSuccess((event) => {
|
||||||
if (event.data.length !== parentNodes) {
|
if (event.data.nodes.length !== parentNodes) {
|
||||||
setParentNodes(event.data.length); // count number of parents/devices
|
setParentNodes(event.data.nodes.length); // count number of parents/devices
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,11 +76,12 @@ const Dashboard = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
const deviceValueDialogSave = useCallback(
|
||||||
|
async (devicevalue: DeviceValue) => {
|
||||||
if (!selectedDashboardItem) {
|
if (!selectedDashboardItem) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = selectedDashboardItem.parentNode.id; // this is the parent ID
|
const id = selectedDashboardItem.id; // this is the parent ID
|
||||||
await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
|
await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success(LL.WRITE_CMD_SENT());
|
toast.success(LL.WRITE_CMD_SENT());
|
||||||
@@ -87,9 +93,13 @@ const Dashboard = () => {
|
|||||||
setDeviceValueDialogOpen(false);
|
setDeviceValueDialogOpen(false);
|
||||||
setSelectedDashboardItem(undefined);
|
setSelectedDashboardItem(undefined);
|
||||||
});
|
});
|
||||||
};
|
},
|
||||||
|
[selectedDashboardItem, sendDeviceValue, LL]
|
||||||
|
);
|
||||||
|
|
||||||
const dashboard_theme = useTheme({
|
const dashboard_theme = useMemo(
|
||||||
|
() =>
|
||||||
|
useTheme({
|
||||||
Table: `
|
Table: `
|
||||||
--data-table-library_grid-template-columns: minmax(80px, auto) 120px 32px;
|
--data-table-library_grid-template-columns: minmax(80px, auto) 120px 32px;
|
||||||
`,
|
`,
|
||||||
@@ -107,7 +117,7 @@ const Dashboard = () => {
|
|||||||
},
|
},
|
||||||
&:hover .td {
|
&:hover .td {
|
||||||
background-color: #177ac9;
|
background-color: #177ac9;
|
||||||
}
|
},
|
||||||
`,
|
`,
|
||||||
BaseCell: `
|
BaseCell: `
|
||||||
&:nth-of-type(2) {
|
&:nth-of-type(2) {
|
||||||
@@ -117,12 +127,14 @@ const Dashboard = () => {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
});
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const tree = useTree(
|
const tree = useTree(
|
||||||
{ nodes: data },
|
{ nodes: data.nodes },
|
||||||
{
|
{
|
||||||
onChange: undefined // not used but needed
|
onChange: () => {} // not used but needed
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
treeIcon: {
|
treeIcon: {
|
||||||
@@ -149,15 +161,16 @@ const Dashboard = () => {
|
|||||||
if (!deviceValueDialogOpen) {
|
if (!deviceValueDialogOpen) {
|
||||||
void fetchDashboard();
|
void fetchDashboard();
|
||||||
}
|
}
|
||||||
}, 3000);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
showAll
|
showAll
|
||||||
? tree.fns.onAddAll(data.map((item: DashboardItem) => item.id)) // expand tree
|
? tree.fns.onAddAll(data.nodes.map((item: DashboardItem) => item.id)) // expand tree
|
||||||
: tree.fns.onRemoveAll(); // collapse tree
|
: tree.fns.onRemoveAll(); // collapse tree
|
||||||
}, [parentNodes]);
|
}, [parentNodes]);
|
||||||
|
|
||||||
const showType = (n?: string, t?: number) => {
|
const showType = useCallback(
|
||||||
|
(n?: string, t?: number) => {
|
||||||
// if we have a name show it
|
// if we have a name show it
|
||||||
if (n) {
|
if (n) {
|
||||||
return n;
|
return n;
|
||||||
@@ -178,40 +191,44 @@ const Dashboard = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
};
|
},
|
||||||
|
[LL]
|
||||||
|
);
|
||||||
|
|
||||||
const showName = (di: DashboardItem) => {
|
const showName = (di: DashboardItem) => {
|
||||||
if (di.id < 100) {
|
if (di.id < 100) {
|
||||||
// if its a device (parent node) and has entities
|
// if its a device (parent node) and has entities
|
||||||
if (di.nodes?.length) {
|
if (di.nodes?.length) {
|
||||||
return (
|
return (
|
||||||
<>
|
<span style={{ fontWeight: 'bold', fontSize: '14px' }}>
|
||||||
<span style="font-size: 14px">
|
|
||||||
<DeviceIcon type_id={di.t ?? 0} />
|
<DeviceIcon type_id={di.t ?? 0} />
|
||||||
{showType(di.n, di.t)}
|
{showType(di.n, di.t)}
|
||||||
</span>
|
|
||||||
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</span>
|
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</span>
|
||||||
</>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (di.dv) {
|
if (di.dv) {
|
||||||
return <span style="color:lightgrey">{di.dv.id.slice(2)}</span>;
|
return <span>{di.dv.id.slice(2)}</span>;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasMask = (id: string, mask: number) =>
|
const hasMask = (id: string, mask: number) =>
|
||||||
(parseInt(id.slice(0, 2), 16) & mask) === mask;
|
(parseInt(id.slice(0, 2), 16) & mask) === mask;
|
||||||
|
|
||||||
const editDashboardValue = (di: DashboardItem) => {
|
const editDashboardValue = useCallback(
|
||||||
|
(di: DashboardItem) => {
|
||||||
if (me.admin && di.dv?.c) {
|
if (me.admin && di.dv?.c) {
|
||||||
setSelectedDashboardItem(di);
|
setSelectedDashboardItem(di);
|
||||||
setDeviceValueDialogOpen(true);
|
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) {
|
||||||
@@ -222,26 +239,40 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
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 (
|
||||||
<>
|
<>
|
||||||
<Box
|
{!data.connected && (
|
||||||
sx={{
|
<MessageBox mb={2} level="error" message={LL.EMS_BUS_WARNING()} />
|
||||||
backgroundColor: 'black',
|
)}
|
||||||
pt: 1,
|
|
||||||
pl: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Grid container spacing={0} justifyContent="flex-start">
|
|
||||||
<Grid size={11}>
|
|
||||||
<Typography mb={2} variant="body1" color="warning">
|
|
||||||
{LL.DASHBOARD_1()}.
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid size={1} alignItems="end">
|
{data.connected && data.nodes.length > 0 && !hasFavEntities && (
|
||||||
|
<MessageBox mb={2} level="warning">
|
||||||
|
<Typography>
|
||||||
|
{LL.NO_DATA_1()}
|
||||||
|
<Link to="/customizations" style={{ color: 'white' }}>
|
||||||
|
{LL.CUSTOMIZATIONS()}
|
||||||
|
</Link>
|
||||||
|
{LL.NO_DATA_2()}
|
||||||
|
{LL.NO_DATA_3()}
|
||||||
|
<Link to="/devices" style={{ color: 'white' }}>
|
||||||
|
{LL.DEVICES()}
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Typography>
|
||||||
|
</MessageBox>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data.nodes.length > 0 && (
|
||||||
|
<>
|
||||||
<ToggleButtonGroup
|
<ToggleButtonGroup
|
||||||
color="primary"
|
color="primary"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -249,16 +280,20 @@ const Dashboard = () => {
|
|||||||
exclusive
|
exclusive
|
||||||
onChange={handleShowAll}
|
onChange={handleShowAll}
|
||||||
>
|
>
|
||||||
|
<ButtonTooltip title={LL.ALLVALUES()} arrow>
|
||||||
<ToggleButton value={true}>
|
<ToggleButton value={true}>
|
||||||
<UnfoldMoreIcon sx={{ fontSize: 14 }} />
|
<UnfoldMoreIcon sx={{ fontSize: 18 }} />
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
</ButtonTooltip>
|
||||||
|
<ButtonTooltip title={LL.COMPACT()} arrow>
|
||||||
<ToggleButton value={false}>
|
<ToggleButton value={false}>
|
||||||
<UnfoldLessIcon sx={{ fontSize: 14 }} />
|
<UnfoldLessIcon sx={{ fontSize: 18 }} />
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
</ButtonTooltip>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
</Grid>
|
<ButtonTooltip title={LL.DASHBOARD_1()} arrow>
|
||||||
</Grid>
|
<HelpOutlineIcon color="primary" sx={{ ml: 1, fontSize: 20 }} />
|
||||||
</Box>
|
</ButtonTooltip>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
padding={1}
|
padding={1}
|
||||||
@@ -272,17 +307,12 @@ const Dashboard = () => {
|
|||||||
<IconContext.Provider
|
<IconContext.Provider
|
||||||
value={{
|
value={{
|
||||||
color: 'lightblue',
|
color: 'lightblue',
|
||||||
size: '16',
|
size: '18',
|
||||||
style: { verticalAlign: 'middle' }
|
style: { verticalAlign: 'middle' }
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!loading && data.length === 0 ? (
|
|
||||||
<Typography variant="subtitle2" color="secondary">
|
|
||||||
{LL.NO_DATA()}
|
|
||||||
</Typography>
|
|
||||||
) : (
|
|
||||||
<Table
|
<Table
|
||||||
data={{ nodes: data }}
|
data={{ nodes: data.nodes }}
|
||||||
theme={dashboard_theme}
|
theme={dashboard_theme}
|
||||||
layout={{ custom: true }}
|
layout={{ custom: true }}
|
||||||
tree={tree}
|
tree={tree}
|
||||||
@@ -299,21 +329,20 @@ const Dashboard = () => {
|
|||||||
<>
|
<>
|
||||||
<Cell>{showName(di)}</Cell>
|
<Cell>{showName(di)}</Cell>
|
||||||
<Cell>
|
<Cell>
|
||||||
<Tooltip
|
<ButtonTooltip
|
||||||
placement="left"
|
|
||||||
title={formatValue(LL, di.dv?.v, di.dv?.u)}
|
title={formatValue(LL, di.dv?.v, di.dv?.u)}
|
||||||
arrow
|
|
||||||
>
|
>
|
||||||
<span style={{ color: 'lightgrey' }}>
|
<span>{formatValue(LL, di.dv?.v, di.dv?.u)}</span>
|
||||||
{formatValue(LL, di.dv?.v, di.dv?.u)}
|
</ButtonTooltip>
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
</Cell>
|
</Cell>
|
||||||
|
|
||||||
<Cell>
|
<Cell>
|
||||||
{me.admin &&
|
{me.admin &&
|
||||||
di.dv?.c &&
|
di.dv?.c &&
|
||||||
!hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && (
|
!hasMask(
|
||||||
|
di.dv.id,
|
||||||
|
DeviceEntityMask.DV_READONLY
|
||||||
|
) && (
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => editDashboardValue(di)}
|
onClick={() => editDashboardValue(di)}
|
||||||
@@ -338,10 +367,11 @@ const Dashboard = () => {
|
|||||||
</Body>
|
</Body>
|
||||||
)}
|
)}
|
||||||
</Table>
|
</Table>
|
||||||
)}
|
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,19 +6,20 @@ import {
|
|||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { IconContext } from 'react-icons';
|
import { IconContext } from 'react-icons';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||||
|
import ConstructionIcon from '@mui/icons-material/Construction';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
|
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
|
||||||
import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
|
|
||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
import HighlightOffIcon from '@mui/icons-material/HighlightOff';
|
import HighlightOffIcon from '@mui/icons-material/HighlightOff';
|
||||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||||
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||||
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
|
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
|
||||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
import StarIcon from '@mui/icons-material/Star';
|
import StarIcon from '@mui/icons-material/Star';
|
||||||
import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined';
|
import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined';
|
||||||
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
||||||
@@ -30,17 +31,16 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
Tooltip,
|
TextField,
|
||||||
type TooltipProps,
|
ToggleButton,
|
||||||
Typography,
|
Typography
|
||||||
styled,
|
|
||||||
tooltipClasses
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import { useRowSelect } from '@table-library/react-table-library/select';
|
import { useRowSelect } from '@table-library/react-table-library/select';
|
||||||
import { SortToggleType, useSort } from '@table-library/react-table-library/sort';
|
import { SortToggleType, useSort } from '@table-library/react-table-library/sort';
|
||||||
@@ -57,7 +57,12 @@ import { useTheme } from '@table-library/react-table-library/theme';
|
|||||||
import type { Action, State } from '@table-library/react-table-library/types/common';
|
import type { Action, State } from '@table-library/react-table-library/types/common';
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { MessageBox, SectionContent, useLayoutTitle } from 'components';
|
import {
|
||||||
|
ButtonTooltip,
|
||||||
|
MessageBox,
|
||||||
|
SectionContent,
|
||||||
|
useLayoutTitle
|
||||||
|
} from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { useInterval } from 'utils';
|
import { useInterval } from 'utils';
|
||||||
@@ -80,6 +85,7 @@ const Devices = () => {
|
|||||||
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false);
|
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false);
|
||||||
const [showDeviceInfo, setShowDeviceInfo] = useState(false);
|
const [showDeviceInfo, setShowDeviceInfo] = useState(false);
|
||||||
const [selectedDevice, setSelectedDevice] = useState<number>();
|
const [selectedDevice, setSelectedDevice] = useState<number>();
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -155,7 +161,6 @@ const Devices = () => {
|
|||||||
}
|
}
|
||||||
&.tr.tr-body.row-select.row-select-single-selected {
|
&.tr.tr-body.row-select.row-select-single-selected {
|
||||||
background-color: #177ac9;
|
background-color: #177ac9;
|
||||||
font-weight: normal;
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
});
|
});
|
||||||
@@ -169,9 +174,9 @@ const Devices = () => {
|
|||||||
HeaderRow: `
|
HeaderRow: `
|
||||||
.th {
|
.th {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
height: 36px;
|
|
||||||
`,
|
`,
|
||||||
Row: `
|
Row: `
|
||||||
|
font-weight: bold;
|
||||||
&:hover .td {
|
&:hover .td {
|
||||||
background-color: #177ac9;
|
background-color: #177ac9;
|
||||||
`
|
`
|
||||||
@@ -222,20 +227,6 @@ const Devices = () => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const ButtonTooltip = styled(({ className, ...props }: TooltipProps) => (
|
|
||||||
<Tooltip {...props} arrow classes={{ popper: className }} />
|
|
||||||
))(({ theme }) => ({
|
|
||||||
[`& .${tooltipClasses.arrow}`]: {
|
|
||||||
color: theme.palette.success.main
|
|
||||||
},
|
|
||||||
[`& .${tooltipClasses.tooltip}`]: {
|
|
||||||
backgroundColor: theme.palette.success.main,
|
|
||||||
color: 'rgba(0, 0, 0, 0.87)',
|
|
||||||
boxShadow: theme.shadows[1],
|
|
||||||
fontSize: 10
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const getSortIcon = (state: State, sortKey: unknown) => {
|
const getSortIcon = (state: State, sortKey: unknown) => {
|
||||||
if (state.sortKey === sortKey && state.reverse) {
|
if (state.sortKey === sortKey && state.reverse) {
|
||||||
return <KeyboardArrowDownOutlinedIcon />;
|
return <KeyboardArrowDownOutlinedIcon />;
|
||||||
@@ -285,6 +276,7 @@ const Devices = () => {
|
|||||||
|
|
||||||
const resetDeviceSelect = () => {
|
const resetDeviceSelect = () => {
|
||||||
device_select.fns.onRemoveAll();
|
device_select.fns.onRemoveAll();
|
||||||
|
setSearch('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const escFunction = useCallback(
|
const escFunction = useCallback(
|
||||||
@@ -307,9 +299,9 @@ const Devices = () => {
|
|||||||
|
|
||||||
const customize = () => {
|
const customize = () => {
|
||||||
if (selectedDevice === 99) {
|
if (selectedDevice === 99) {
|
||||||
navigate('/customentities');
|
void navigate('/customentities');
|
||||||
} else {
|
} else {
|
||||||
navigate('/customizations', { state: selectedDevice });
|
void navigate('/customizations', { state: selectedDevice });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -337,13 +329,16 @@ const Devices = () => {
|
|||||||
|
|
||||||
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 = [
|
||||||
{
|
{
|
||||||
@@ -358,7 +353,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'
|
||||||
},
|
},
|
||||||
@@ -381,7 +376,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(
|
||||||
@@ -420,7 +417,7 @@ const Devices = () => {
|
|||||||
if (!deviceValueDialogOpen) {
|
if (!deviceValueDialogOpen) {
|
||||||
selectedDevice ? void sendDeviceData(selectedDevice) : void sendCoreData();
|
selectedDevice ? void sendDeviceData(selectedDevice) : void sendCoreData();
|
||||||
}
|
}
|
||||||
}, 3000);
|
});
|
||||||
|
|
||||||
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
||||||
const id = Number(device_select.state.id);
|
const id = Number(device_select.state.id);
|
||||||
@@ -441,10 +438,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 (
|
||||||
@@ -457,47 +458,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>
|
||||||
</>
|
</>
|
||||||
@@ -516,6 +505,7 @@ const Devices = () => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCoreData = () => (
|
const renderCoreData = () => (
|
||||||
@@ -574,7 +564,9 @@ const Devices = () => {
|
|||||||
|
|
||||||
const deviceValueDialogClose = () => {
|
const deviceValueDialogClose = () => {
|
||||||
setDeviceValueDialogOpen(false);
|
setDeviceValueDialogOpen(false);
|
||||||
|
if (selectedDevice !== undefined) {
|
||||||
void sendDeviceData(selectedDevice);
|
void sendDeviceData(selectedDevice);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderDeviceData = () => {
|
const renderDeviceData = () => {
|
||||||
@@ -603,16 +595,27 @@ const Devices = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const shown_data = onlyFav
|
const shown_data = onlyFav
|
||||||
? deviceData.nodes.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE))
|
? deviceData.nodes.filter(
|
||||||
: deviceData.nodes;
|
(dv: DeviceValue) =>
|
||||||
|
hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) &&
|
||||||
|
dv.id.slice(2).toLowerCase().includes(search.toLowerCase())
|
||||||
|
)
|
||||||
|
: deviceData.nodes.filter((dv: DeviceValue) =>
|
||||||
|
dv.id.slice(2).toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
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={{
|
||||||
@@ -623,26 +626,44 @@ 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={{ border: '1px solid #177ac9' }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography noWrap variant="subtitle1" color="warning.main" sx={{ ml: 1 }}>
|
|
||||||
{coreData.devices[deviceIndex].n} (
|
|
||||||
{coreData.devices[deviceIndex].tn})
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Grid container justifyContent="space-between">
|
<Grid container justifyContent="space-between">
|
||||||
<Typography sx={{ ml: 1 }} variant="subtitle2" color="grey">
|
<Typography noWrap variant="subtitle1" color="warning.main">
|
||||||
{LL.SHOWING() +
|
{deviceInfo.n} (
|
||||||
' ' +
|
{deviceInfo.tn})
|
||||||
shown_data.length +
|
</Typography>
|
||||||
'/' +
|
<Grid justifyContent="flex-end">
|
||||||
coreData.devices[deviceIndex].e +
|
<ButtonTooltip title={LL.CLOSE()}>
|
||||||
' ' +
|
<IconButton onClick={resetDeviceSelect}>
|
||||||
LL.ENTITIES(shown_data.length)}
|
<HighlightOffIcon color="primary" sx={{ fontSize: 18 }} />
|
||||||
<ButtonTooltip title="Info">
|
</IconButton>
|
||||||
|
</ButtonTooltip>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ width: '22ch' }}
|
||||||
|
placeholder={LL.SEARCH()}
|
||||||
|
onChange={(event) => {
|
||||||
|
setSearch(event.target.value);
|
||||||
|
}}
|
||||||
|
slotProps={{
|
||||||
|
input: {
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<SearchIcon color="primary" sx={{ fontSize: 16 }} />
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ButtonTooltip title={LL.DEVICE_DETAILS()}>
|
||||||
<IconButton onClick={() => setShowDeviceInfo(true)}>
|
<IconButton onClick={() => setShowDeviceInfo(true)}>
|
||||||
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -650,7 +671,7 @@ const Devices = () => {
|
|||||||
{me.admin && (
|
{me.admin && (
|
||||||
<ButtonTooltip title={LL.CUSTOMIZATIONS()}>
|
<ButtonTooltip title={LL.CUSTOMIZATIONS()}>
|
||||||
<IconButton onClick={customize}>
|
<IconButton onClick={customize}>
|
||||||
<FormatListNumberedIcon color="primary" sx={{ fontSize: 18 }} />
|
<ConstructionIcon color="primary" sx={{ fontSize: 18 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</ButtonTooltip>
|
</ButtonTooltip>
|
||||||
)}
|
)}
|
||||||
@@ -659,24 +680,34 @@ const Devices = () => {
|
|||||||
<DownloadIcon color="primary" sx={{ fontSize: 18 }} />
|
<DownloadIcon color="primary" sx={{ fontSize: 18 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</ButtonTooltip>
|
</ButtonTooltip>
|
||||||
|
|
||||||
<ButtonTooltip title={LL.FAVORITES()}>
|
<ButtonTooltip title={LL.FAVORITES()}>
|
||||||
<IconButton onClick={() => setOnlyFav(!onlyFav)}>
|
<ToggleButton
|
||||||
|
value="1"
|
||||||
|
size="small"
|
||||||
|
selected={onlyFav}
|
||||||
|
onChange={() => {
|
||||||
|
setOnlyFav(!onlyFav);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{onlyFav ? (
|
{onlyFav ? (
|
||||||
<StarIcon color="primary" sx={{ fontSize: 18 }} />
|
<StarIcon color="primary" sx={{ fontSize: 18 }} />
|
||||||
) : (
|
) : (
|
||||||
<StarBorderOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
<StarBorderOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
||||||
)}
|
)}{' '}
|
||||||
</IconButton>
|
</ToggleButton>
|
||||||
</ButtonTooltip>
|
</ButtonTooltip>
|
||||||
</Typography>
|
|
||||||
<Grid justifyContent="flex-end">
|
<span style={{ color: 'grey', fontSize: '12px' }}>
|
||||||
<ButtonTooltip title={LL.CANCEL()}>
|
|
||||||
<IconButton onClick={resetDeviceSelect}>
|
{LL.SHOWING() +
|
||||||
<HighlightOffIcon color="primary" sx={{ fontSize: 18 }} />
|
' ' +
|
||||||
</IconButton>
|
shown_data.length +
|
||||||
</ButtonTooltip>
|
'/' +
|
||||||
</Grid>
|
deviceInfo.e +
|
||||||
</Grid>
|
' ' +
|
||||||
|
LL.ENTITIES(shown_data.length)}
|
||||||
|
</span>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import type Schema from 'async-validator';
|
import type Schema from 'async-validator';
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -39,9 +39,10 @@ const Help = () => {
|
|||||||
const [customSupportHTML, setCustomSupportHTML] = useState<string | null>(null);
|
const [customSupportHTML, setCustomSupportHTML] = useState<string | null>(null);
|
||||||
const [notFound, setNotFound] = useState<boolean>(false);
|
const [notFound, setNotFound] = useState<boolean>(false);
|
||||||
|
|
||||||
useRequest(() => callAction({ action: 'customSupport' })).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);
|
||||||
}
|
}
|
||||||
@@ -51,20 +52,6 @@ const Help = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// const { send: sendExportAllValues } = useRequest(
|
|
||||||
// () => callAction({ action: 'export', param: 'allvalues' }),
|
|
||||||
// {
|
|
||||||
// immediate: false
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// .onSuccess((event) => {
|
|
||||||
// saveFile(event.data, 'allvalues', '.txt');
|
|
||||||
// toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
|
||||||
// })
|
|
||||||
// .onError((error) => {
|
|
||||||
// toast.error(error.message);
|
|
||||||
// });
|
|
||||||
|
|
||||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||||
immediate: false
|
immediate: false
|
||||||
})
|
})
|
||||||
@@ -73,11 +60,12 @@ 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 (
|
||||||
<SectionContent>
|
<SectionContent>
|
||||||
|
{customSupportHTML && (
|
||||||
<Stack
|
<Stack
|
||||||
padding={1}
|
padding={1}
|
||||||
mb={2}
|
mb={2}
|
||||||
@@ -91,11 +79,7 @@ const Help = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
{customSupportHTML ? (
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: customSupportHTML }} />
|
<div dangerouslySetInnerHTML={{ __html: customSupportHTML }} />
|
||||||
) : (
|
|
||||||
LL.HELP_INFORMATION_5()
|
|
||||||
)}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
@@ -107,15 +91,22 @@ const Help = () => {
|
|||||||
src={
|
src={
|
||||||
notFound
|
notFound
|
||||||
? ''
|
? ''
|
||||||
: customSupportIMG || 'https://emsesp.org/_media/images/installer.jpeg'
|
: customSupportIMG ||
|
||||||
|
'https://docs.emsesp.org/_media/images/installer.jpeg'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
{me.admin && (
|
{me.admin && (
|
||||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemButton component="a" href="https://emsesp.org">
|
<ListItemButton
|
||||||
|
component="a"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
href="https://docs.emsesp.org"
|
||||||
|
>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar sx={{ bgcolor: '#72caf9' }}>
|
<Avatar sx={{ bgcolor: '#72caf9' }}>
|
||||||
<MenuBookIcon />
|
<MenuBookIcon />
|
||||||
@@ -126,7 +117,12 @@ const Help = () => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemButton component="a" href="https://discord.gg/3J3GgnzpyT">
|
<ListItemButton
|
||||||
|
component="a"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
href="https://discord.gg/3J3GgnzpyT"
|
||||||
|
>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar sx={{ bgcolor: '#72caf9' }}>
|
<Avatar sx={{ bgcolor: '#72caf9' }}>
|
||||||
<CommentIcon />
|
<CommentIcon />
|
||||||
@@ -139,6 +135,8 @@ const Help = () => {
|
|||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
component="a"
|
component="a"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
href="https://github.com/emsesp/EMS-ESP32/issues/new/choose"
|
href="https://github.com/emsesp/EMS-ESP32/issues/new/choose"
|
||||||
>
|
>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
@@ -166,21 +164,16 @@ const Help = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* <Button
|
|
||||||
sx={{ ml: 2 }}
|
|
||||||
startIcon={<DownloadIcon />}
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => sendExportAllValues()}
|
|
||||||
>
|
|
||||||
{LL.DOWNLOAD(1)} {LL.ALLVALUES()}
|
|
||||||
</Button> */}
|
|
||||||
|
|
||||||
<Divider sx={{ mt: 4 }} />
|
<Divider sx={{ mt: 4 }} />
|
||||||
|
|
||||||
<Typography color="white" variant="subtitle1" align="center" mt={1}>
|
<Typography color="white" variant="subtitle1" align="center" mt={1}>
|
||||||
©
|
©
|
||||||
<Link target="_blank" href="https://emsesp.org" color="primary">
|
<Link
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
href="https://emsesp.org"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
{'emsesp.org'}
|
{'emsesp.org'}
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useBlocker } from 'react-router-dom';
|
import { useBlocker } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@@ -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) =>
|
||||||
|
updateModules({
|
||||||
key: condensed_mi.key,
|
key: condensed_mi.key,
|
||||||
enabled: condensed_mi.enabled,
|
enabled: condensed_mi.enabled,
|
||||||
license: condensed_mi.license
|
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,9 +10,9 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
Grid,
|
||||||
TextField
|
TextField
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import { BlockFormControlLabel } from 'components';
|
import { BlockFormControlLabel } from 'components';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useBlocker } from 'react-router-dom';
|
import { useBlocker } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
Grid,
|
||||||
TextField,
|
TextField,
|
||||||
ToggleButton,
|
ToggleButton,
|
||||||
ToggleButtonGroup,
|
ToggleButtonGroup,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import type Schema from 'async-validator';
|
import type Schema from 'async-validator';
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ const Sensors = () => {
|
|||||||
if (!temperatureDialogOpen && !analogDialogOpen) {
|
if (!temperatureDialogOpen && !analogDialogOpen) {
|
||||||
void fetchSensorData();
|
void fetchSensorData();
|
||||||
}
|
}
|
||||||
}, 3000);
|
});
|
||||||
|
|
||||||
const common_theme = useTheme({
|
const common_theme = useTheme({
|
||||||
BaseRow: `
|
BaseRow: `
|
||||||
@@ -438,7 +438,9 @@ const Sensors = () => {
|
|||||||
<Cell stiff>{a.g}</Cell>
|
<Cell stiff>{a.g}</Cell>
|
||||||
<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.t === AnalogType.DIGITAL_IN ? (
|
{(a.t === AnalogType.DIGITAL_OUT && a.g !== 25 && a.g !== 26) ||
|
||||||
|
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>
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import type Schema from 'async-validator';
|
import type Schema from 'async-validator';
|
||||||
@@ -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,11 +9,11 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
TextField,
|
TextField,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import type Schema from 'async-validator';
|
import type Schema from 'async-validator';
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -34,7 +34,12 @@ export function formatValue(
|
|||||||
if (value === undefined || typeof value === 'boolean') {
|
if (value === undefined || typeof value === 'boolean') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return value as string;
|
return (
|
||||||
|
(value as string) +
|
||||||
|
(value === '' || uom === undefined || uom === 0
|
||||||
|
? ''
|
||||||
|
: ' ' + DeviceValueUOM_s[uom])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (uom) {
|
switch (uom) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export interface Settings {
|
|||||||
dallas_gpio: number;
|
dallas_gpio: number;
|
||||||
dallas_parasite: boolean;
|
dallas_parasite: boolean;
|
||||||
led_gpio: number;
|
led_gpio: number;
|
||||||
|
led_type: number;
|
||||||
hide_led: boolean;
|
hide_led: boolean;
|
||||||
low_clock: boolean;
|
low_clock: boolean;
|
||||||
notoken_api: boolean;
|
notoken_api: boolean;
|
||||||
@@ -71,7 +72,7 @@ export interface Device {
|
|||||||
d: number; // deviceid
|
d: number; // deviceid
|
||||||
p: number; // productid
|
p: number; // productid
|
||||||
v: string; // version
|
v: string; // version
|
||||||
e: number; // entities
|
e: number; // total number of entities
|
||||||
url?: string; // lowercase type name used in API URL
|
url?: string; // lowercase type name used in API URL
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +124,11 @@ export interface DashboardItem {
|
|||||||
nodes?: DashboardItem[]; // children nodes, optional
|
nodes?: DashboardItem[]; // children nodes, optional
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DashboardData {
|
||||||
|
connected: boolean; // true if connected to EMS bus
|
||||||
|
nodes: DashboardItem[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface DeviceValue {
|
export interface DeviceValue {
|
||||||
id: string; // index, contains mask+name
|
id: string; // index, contains mask+name
|
||||||
v?: unknown; // value, Number, String or Boolean - can be undefined
|
v?: unknown; // value, Number, String or Boolean - can be undefined
|
||||||
@@ -181,7 +187,9 @@ export enum DeviceValueUOM {
|
|||||||
K,
|
K,
|
||||||
VOLTS,
|
VOLTS,
|
||||||
MBAR,
|
MBAR,
|
||||||
LH
|
LH,
|
||||||
|
CTKWH,
|
||||||
|
HZ
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeviceValueUOM_s = [
|
export const DeviceValueUOM_s = [
|
||||||
@@ -210,7 +218,9 @@ export const DeviceValueUOM_s = [
|
|||||||
'K',
|
'K',
|
||||||
'V',
|
'V',
|
||||||
'mbar',
|
'mbar',
|
||||||
'l/h'
|
'l/h',
|
||||||
|
'ct/kWh',
|
||||||
|
'Hz'
|
||||||
];
|
];
|
||||||
|
|
||||||
export enum AnalogType {
|
export enum AnalogType {
|
||||||
@@ -224,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>;
|
||||||
@@ -247,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',
|
||||||
@@ -260,6 +283,7 @@ export const BOARD_PROFILES: BoardProfiles = {
|
|||||||
export interface BoardProfile {
|
export interface BoardProfile {
|
||||||
board_profile: string;
|
board_profile: string;
|
||||||
led_gpio: number;
|
led_gpio: number;
|
||||||
|
led_type: number;
|
||||||
dallas_gpio: number;
|
dallas_gpio: number;
|
||||||
rx_gpio: number;
|
rx_gpio: number;
|
||||||
tx_gpio: number;
|
tx_gpio: number;
|
||||||
@@ -366,11 +390,12 @@ export interface EntityItem {
|
|||||||
device_id: number | string;
|
device_id: number | string;
|
||||||
type_id: number | string;
|
type_id: number | string;
|
||||||
offset: number;
|
offset: number;
|
||||||
factor: number;
|
factor: number | string;
|
||||||
uom: number;
|
uom: number;
|
||||||
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;
|
||||||
@@ -378,12 +403,13 @@ export interface EntityItem {
|
|||||||
o_device_id?: number | string;
|
o_device_id?: number | string;
|
||||||
o_type_id?: number | string;
|
o_type_id?: number | string;
|
||||||
o_offset?: number;
|
o_offset?: number;
|
||||||
o_factor?: number;
|
o_factor?: number | string;
|
||||||
o_uom?: number;
|
o_uom?: number;
|
||||||
o_value_type?: number;
|
o_value_type?: number;
|
||||||
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
|
||||||
) {
|
) {
|
||||||
@@ -382,17 +382,14 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte
|
|||||||
{ required: true, message: 'Offset is required' },
|
{ required: true, message: 'Offset is required' },
|
||||||
{ type: 'number', min: 0, max: 255, message: 'Must be between 0 and 255' }
|
{ type: 'number', min: 0, max: 255, message: 'Must be between 0 and 255' }
|
||||||
],
|
],
|
||||||
factor: [
|
factor: [{ required: true, message: 'is required' }]
|
||||||
{ required: true, message: 'Bytes is required' },
|
|
||||||
{ type: 'number', min: 1, max: 255, message: 'Must be between 1 and 255' }
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const uniqueTemperatureNameValidator = (
|
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 !== '' &&
|
||||||
@@ -422,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
|
||||||
) {
|
) {
|
||||||
@@ -438,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 !== '' &&
|
||||||
@@ -485,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
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const APSettings = () => {
|
|||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
useLayoutTitle(LL.SETTINGS_OF(LL.ACCESS_POINT(0)));
|
useLayoutTitle(LL.ACCESS_POINT(0));
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
@@ -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,17 +9,17 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Divider,
|
Divider,
|
||||||
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import { readSystemStatus } from 'api/system';
|
import { readSystemStatus } from 'api/system';
|
||||||
|
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import RestartMonitor from 'app/status/RestartMonitor';
|
import SystemMonitor from 'app/status/SystemMonitor';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
@@ -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>();
|
||||||
@@ -121,14 +121,11 @@ const ApplicationSettings = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useLayoutTitle(LL.SETTINGS_OF(LL.APPLICATION()));
|
useLayoutTitle(LL.APPLICATION());
|
||||||
|
|
||||||
const SecondsInputProps = {
|
const SecondsInputProps = {
|
||||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||||
};
|
};
|
||||||
const MilliSecondsInputProps = {
|
|
||||||
endAdornment: <InputAdornment position="end">ms</InputAdornment>
|
|
||||||
};
|
|
||||||
const MinutesInputProps = {
|
const MinutesInputProps = {
|
||||||
endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment>
|
endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment>
|
||||||
};
|
};
|
||||||
@@ -138,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 () => {
|
||||||
@@ -207,13 +204,22 @@ const ApplicationSettings = () => {
|
|||||||
disabled={!hardwareData.psram}
|
disabled={!hardwareData.psram}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={LL.ENABLE_MODBUS()}
|
label={
|
||||||
|
<Typography color={!hardwareData.psram ? 'grey' : 'default'}>
|
||||||
|
{LL.ENABLE_MODBUS()}
|
||||||
|
{!hardwareData.psram && (
|
||||||
|
<Typography variant="caption">
|
||||||
|
({LL.IS_REQUIRED('PSRAM')})
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{data.modbus_enabled && (
|
{data.modbus_enabled && (
|
||||||
<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"
|
||||||
@@ -225,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"
|
||||||
@@ -237,11 +243,11 @@ const ApplicationSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="modbus_timeout"
|
name="modbus_timeout"
|
||||||
label="Timeout"
|
label="Timeout"
|
||||||
slotProps={{
|
slotProps={{
|
||||||
input: MilliSecondsInputProps
|
input: SecondsInputProps
|
||||||
}}
|
}}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={numberValue(data.modbus_timeout)}
|
value={numberValue(data.modbus_timeout)}
|
||||||
@@ -267,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"
|
||||||
@@ -278,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"
|
||||||
@@ -309,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={{
|
||||||
@@ -479,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
|
||||||
@@ -492,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
|
||||||
@@ -505,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
|
||||||
@@ -518,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) + ')'
|
||||||
@@ -533,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
|
||||||
@@ -544,6 +550,23 @@ const ApplicationSettings = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{data.led_gpio !== 0 && (
|
||||||
|
<Grid>
|
||||||
|
<TextField
|
||||||
|
name="led_type"
|
||||||
|
label={'LED ' + LL.TYPE(0)}
|
||||||
|
value={data.led_type}
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
margin="normal"
|
||||||
|
select
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>LED</MenuItem>
|
||||||
|
<MenuItem value={1}>RGB-LED</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextField
|
<TextField
|
||||||
name="phy_type"
|
name="phy_type"
|
||||||
@@ -720,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={{
|
||||||
@@ -760,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={{
|
||||||
@@ -778,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={{
|
||||||
@@ -794,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={{
|
||||||
@@ -853,7 +876,7 @@ const ApplicationSettings = () => {
|
|||||||
return (
|
return (
|
||||||
<SectionContent>
|
<SectionContent>
|
||||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||||
{restarting ? <RestartMonitor /> : content()}
|
{restarting ? <SystemMonitor /> : content()}
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ 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, Typography } from '@mui/material';
|
import { Box, Button, Grid, Typography } from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import * as SystemApi from 'api/system';
|
import * as SystemApi from 'api/system';
|
||||||
import { API, callAction } from 'api/app';
|
import { API, callAction } from 'api/app';
|
||||||
|
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import type { APIcall } from 'app/main/types';
|
import type { APIcall } from 'app/main/types';
|
||||||
import RestartMonitor from 'app/status/RestartMonitor';
|
import SystemMonitor from 'app/status/SystemMonitor';
|
||||||
import {
|
import {
|
||||||
FormLoader,
|
FormLoader,
|
||||||
SectionContent,
|
SectionContent,
|
||||||
@@ -36,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), {
|
||||||
@@ -58,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 (
|
||||||
@@ -109,6 +108,15 @@ const DownloadUpload = () => {
|
|||||||
{LL.SCHEDULE(0)}
|
{LL.SCHEDULE(0)}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Button
|
||||||
|
sx={{ ml: 2, mt: 2 }}
|
||||||
|
startIcon={<DownloadIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => sendExportData('allvalues')}
|
||||||
|
>
|
||||||
|
{LL.ALLVALUES()}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||||
{LL.UPLOAD()}
|
{LL.UPLOAD()}
|
||||||
@@ -118,13 +126,13 @@ const DownloadUpload = () => {
|
|||||||
<Typography variant="body1">{LL.UPLOAD_TEXT()}.</Typography>
|
<Typography variant="body1">{LL.UPLOAD_TEXT()}.</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<SingleUpload doRestart={doRestart} />
|
<SingleUpload text={LL.UPLOAD_DRAG()} doRestart={doRestart} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
|
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import WarningIcon from '@mui/icons-material/Warning';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import * as MqttApi from 'api/mqtt';
|
import * as MqttApi from 'api/mqtt';
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ const MqttSettings = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.SETTINGS_OF('MQTT'));
|
useLayoutTitle('MQTT');
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
@@ -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,7 +254,6 @@ 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>
|
<Grid>
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
@@ -263,6 +262,7 @@ const MqttSettings = () => {
|
|||||||
name="publish_single"
|
name="publish_single"
|
||||||
checked={data.publish_single}
|
checked={data.publish_single}
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
|
disabled={data.ha_enabled}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={LL.MQTT_PUBLISH_TEXT_1()}
|
label={LL.MQTT_PUBLISH_TEXT_1()}
|
||||||
@@ -283,8 +283,6 @@ const MqttSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
|
||||||
{!data.publish_single && (
|
|
||||||
<Grid container spacing={2} rowSpacing={0}>
|
<Grid container spacing={2} rowSpacing={0}>
|
||||||
<Grid>
|
<Grid>
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
@@ -293,6 +291,7 @@ const MqttSettings = () => {
|
|||||||
name="ha_enabled"
|
name="ha_enabled"
|
||||||
checked={data.ha_enabled}
|
checked={data.ha_enabled}
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
|
disabled={data.publish_single}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={LL.MQTT_PUBLISH_TEXT_3()}
|
label={LL.MQTT_PUBLISH_TEXT_3()}
|
||||||
@@ -349,14 +348,13 @@ const MqttSettings = () => {
|
|||||||
</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';
|
||||||
|
|
||||||
@@ -44,20 +59,103 @@ const NTPSettings = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.SETTINGS_OF('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 () => {
|
||||||
@@ -71,12 +169,12 @@ const NTPSettings = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
updateFormValue(event);
|
|
||||||
void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({
|
void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({
|
||||||
...settings,
|
...settings,
|
||||||
tz_label: event.target.value,
|
tz_label: event.target.value,
|
||||||
tz_format: TIME_ZONES[event.target.value]
|
tz_format: TIME_ZONES[event.target.value]
|
||||||
}));
|
}));
|
||||||
|
updateFormValue(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||||
import BuildIcon from '@mui/icons-material/Build';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||||
import ImportExportIcon from '@mui/icons-material/ImportExport';
|
import ImportExportIcon from '@mui/icons-material/ImportExport';
|
||||||
@@ -21,7 +20,7 @@ import {
|
|||||||
List
|
List
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { API, callAction } from 'api/app';
|
import { API } from 'api/app';
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
@@ -40,11 +39,6 @@ const Settings = () => {
|
|||||||
immediate: false
|
immediate: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// call checkUpgrade with no param to fetch EMS-ESP version
|
|
||||||
const { data } = useRequest(() => callAction({ action: 'checkUpgrade' }), {
|
|
||||||
initialData: { emsesp_version: '...' }
|
|
||||||
});
|
|
||||||
|
|
||||||
const doFormat = async () => {
|
const doFormat = async () => {
|
||||||
await sendAPI({ device: 'system', cmd: 'format', id: 0 }).then(() => {
|
await sendAPI({ device: 'system', cmd: 'format', id: 0 }).then(() => {
|
||||||
setConfirmFactoryReset(false);
|
setConfirmFactoryReset(false);
|
||||||
@@ -83,14 +77,6 @@ const Settings = () => {
|
|||||||
const content = () => (
|
const content = () => (
|
||||||
<>
|
<>
|
||||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
||||||
<ListMenuItem
|
|
||||||
icon={BuildIcon}
|
|
||||||
bgcolor="#72caf9"
|
|
||||||
label={LL.EMS_ESP_VER()}
|
|
||||||
text={data.emsesp_version}
|
|
||||||
to="version"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ListMenuItem
|
<ListMenuItem
|
||||||
icon={TuneIcon}
|
icon={TuneIcon}
|
||||||
bgcolor="#134ba2"
|
bgcolor="#134ba2"
|
||||||
@@ -151,7 +137,7 @@ const Settings = () => {
|
|||||||
bgcolor="#5d89f7"
|
bgcolor="#5d89f7"
|
||||||
label={LL.DOWNLOAD_UPLOAD()}
|
label={LL.DOWNLOAD_UPLOAD()}
|
||||||
text={LL.DOWNLOAD_UPLOAD_1()}
|
text={LL.DOWNLOAD_UPLOAD_1()}
|
||||||
to="upload"
|
to="downloadUpload"
|
||||||
/>
|
/>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
|
|||||||
@@ -1,277 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
|
||||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Dialog,
|
|
||||||
DialogActions,
|
|
||||||
DialogContent,
|
|
||||||
DialogTitle,
|
|
||||||
Divider,
|
|
||||||
Link,
|
|
||||||
Typography
|
|
||||||
} from '@mui/material';
|
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import * as SystemApi from 'api/system';
|
|
||||||
import { callAction } from 'api/app';
|
|
||||||
import { getDevVersion, getStableVersion } from 'api/system';
|
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
|
||||||
import { useRequest } from 'alova/client';
|
|
||||||
import RestartMonitor from 'app/status/RestartMonitor';
|
|
||||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
|
||||||
|
|
||||||
const Version = () => {
|
|
||||||
const { LL } = useI18nContext();
|
|
||||||
|
|
||||||
const [restarting, setRestarting] = useState<boolean>(false);
|
|
||||||
const [openDialog, setOpenDialog] = useState<boolean>(false);
|
|
||||||
const [useDev, setUseDev] = useState<boolean>(false);
|
|
||||||
const [upgradeAvailable, setUpgradeAvailable] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const { send: sendCheckUpgrade } = useRequest(
|
|
||||||
(version: string) => callAction({ action: 'checkUpgrade', param: version }),
|
|
||||||
{
|
|
||||||
immediate: false
|
|
||||||
}
|
|
||||||
).onSuccess((event) => {
|
|
||||||
const data = event.data as { emsesp_version: string; upgradeable: boolean };
|
|
||||||
setUpgradeAvailable(data.upgradeable);
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data, send: loadData, error } = useRequest(SystemApi.readSystemStatus);
|
|
||||||
|
|
||||||
const { send: sendUploadURL } = useRequest(
|
|
||||||
(url: string) => callAction({ action: 'uploadURL', param: url }),
|
|
||||||
{
|
|
||||||
immediate: false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// called immediately to get the latest version, on page load
|
|
||||||
const { data: latestVersion } = useRequest(getStableVersion, {
|
|
||||||
// uncomment next 2 lines for testing, uses https://github.com/emsesp/EMS-ESP32/releases/download/v3.6.5/EMS-ESP-3_6_5-ESP32-16MB+.bin
|
|
||||||
// immediate: false,
|
|
||||||
// initialData: '3.6.5'
|
|
||||||
});
|
|
||||||
|
|
||||||
// called immediately to get the latest version, on page load, then check for upgrade (works for both dev and stable)
|
|
||||||
const { data: latestDevVersion } = useRequest(getDevVersion, {
|
|
||||||
// uncomment next 2 lines for testing, uses https://github.com/emsesp/EMS-ESP32/releases/download/latest/EMS-ESP-3_7_0-dev_31-ESP32-16MB+.bin
|
|
||||||
// immediate: false,
|
|
||||||
// initialData: '3.7.0-dev.32'
|
|
||||||
}).onSuccess((event) => {
|
|
||||||
void sendCheckUpgrade(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
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';
|
|
||||||
|
|
||||||
const getBinURL = (useDevVersion: boolean) => {
|
|
||||||
if (!latestVersion || !latestDevVersion) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const filename =
|
|
||||||
'EMS-ESP-' +
|
|
||||||
(useDevVersion ? latestDevVersion : latestVersion).replaceAll('.', '_') +
|
|
||||||
'-' +
|
|
||||||
getPlatform() +
|
|
||||||
'.bin';
|
|
||||||
return useDevVersion
|
|
||||||
? DEV_URL + filename
|
|
||||||
: STABLE_URL + 'v' + latestVersion + '/' + 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(LL.EMS_ESP_VER());
|
|
||||||
|
|
||||||
const internet_live =
|
|
||||||
latestDevVersion !== undefined && latestVersion !== undefined;
|
|
||||||
|
|
||||||
const renderUploadDialog = () => {
|
|
||||||
if (!internet_live) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog
|
|
||||||
sx={dialogStyle}
|
|
||||||
open={openDialog}
|
|
||||||
onClose={() => setOpenDialog(false)}
|
|
||||||
>
|
|
||||||
<DialogTitle>
|
|
||||||
{LL.INSTALL('') +
|
|
||||||
' ' +
|
|
||||||
(useDev ? LL.DEVELOPMENT() : LL.STABLE()) +
|
|
||||||
' Firmware'}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent dividers>
|
|
||||||
<Typography mb={2}>
|
|
||||||
{LL.INSTALL_VERSION(useDev ? latestDevVersion : latestVersion)}
|
|
||||||
</Typography>
|
|
||||||
<Link
|
|
||||||
target="_blank"
|
|
||||||
href={useDev ? DEV_RELNOTES_URL : STABLE_RELNOTES_URL}
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
changelog
|
|
||||||
</Link>
|
|
||||||
|
|
|
||||||
<Link target="_blank" href={getBinURL(useDev)} color="primary">
|
|
||||||
{LL.DOWNLOAD(1)}
|
|
||||||
</Link>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button
|
|
||||||
startIcon={<CancelIcon />}
|
|
||||||
variant="outlined"
|
|
||||||
onClick={() => setOpenDialog(false)}
|
|
||||||
color="secondary"
|
|
||||||
>
|
|
||||||
{LL.CANCEL()}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
startIcon={<WarningIcon color="warning" />}
|
|
||||||
variant="outlined"
|
|
||||||
onClick={() => installFirmwareURL(getBinURL(useDev))}
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
{LL.INSTALL('')}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// useDevVersion = true to force using the dev version
|
|
||||||
const showFirmwareDialog = (useDevVersion: boolean) => {
|
|
||||||
if (useDevVersion || data.emsesp_version.includes('dev')) {
|
|
||||||
setUseDev(true);
|
|
||||||
}
|
|
||||||
setOpenDialog(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const content = () => {
|
|
||||||
if (!data) {
|
|
||||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDev = data.emsesp_version.includes('dev');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Box p={2} border="1px solid grey" borderRadius={2}>
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
<Grid mb={1}>
|
|
||||||
<Typography mb={1} fontWeight={'fontWeightBold'}>
|
|
||||||
{LL.VERSION()}
|
|
||||||
</Typography>
|
|
||||||
<Typography mb={1} fontWeight={'fontWeightBold'}>
|
|
||||||
Platform
|
|
||||||
</Typography>
|
|
||||||
<Typography mb={1} fontWeight={'fontWeightBold'}>
|
|
||||||
Release
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid>
|
|
||||||
<Typography mb={1}>
|
|
||||||
{data.emsesp_version}
|
|
||||||
{data.build_flags && (
|
|
||||||
<Typography variant="caption">
|
|
||||||
({data.build_flags})
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
<Typography mb={1}>{getPlatform()}</Typography>
|
|
||||||
<Typography>
|
|
||||||
{isDev ? LL.DEVELOPMENT() : LL.STABLE()}
|
|
||||||
<Link
|
|
||||||
target="_blank"
|
|
||||||
href={useDev ? DEV_RELNOTES_URL : STABLE_RELNOTES_URL}
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
(changelog)
|
|
||||||
</Link>
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
{!isDev && (
|
|
||||||
<Button
|
|
||||||
sx={{ mt: 2 }}
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
size="small"
|
|
||||||
onClick={() => showFirmwareDialog(true)}
|
|
||||||
>
|
|
||||||
{LL.SWITCH_DEV()}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Typography mt={2} color="warning">
|
|
||||||
<InfoOutlinedIcon color="warning" sx={{ verticalAlign: 'middle' }} />
|
|
||||||
|
|
||||||
{upgradeAvailable ? LL.UPGRADE_AVAILABLE() : LL.LATEST_VERSION()}
|
|
||||||
{upgradeAvailable &&
|
|
||||||
internet_live &&
|
|
||||||
(data.psram ? (
|
|
||||||
<Button
|
|
||||||
sx={{ ml: 2, textTransform: 'none' }}
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
size="small"
|
|
||||||
onClick={() => showFirmwareDialog(false)}
|
|
||||||
>
|
|
||||||
{isDev
|
|
||||||
? LL.INSTALL('v' + latestDevVersion)
|
|
||||||
: LL.INSTALL('v' + latestVersion)}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
|
|
||||||
<Link target="_blank" href={getBinURL(isDev)} color="primary">
|
|
||||||
{LL.DOWNLOAD(1)} v
|
|
||||||
{isDev ? latestDevVersion : latestVersion}
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
{renderUploadDialog()}
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Version;
|
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { Navigate, Route, Routes, useNavigate } from 'react-router-dom';
|
import {
|
||||||
|
Navigate,
|
||||||
|
Route,
|
||||||
|
Routes,
|
||||||
|
matchRoutes,
|
||||||
|
useLocation,
|
||||||
|
useNavigate
|
||||||
|
} from 'react-router';
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
|
|
||||||
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
import { RouterTabs, useLayoutTitle } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { WiFiNetwork } from 'types';
|
import type { WiFiNetwork } from 'types';
|
||||||
|
|
||||||
@@ -13,9 +20,22 @@ import WiFiNetworkScanner from './WiFiNetworkScanner';
|
|||||||
|
|
||||||
const Network = () => {
|
const Network = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.SETTINGS_OF(LL.NETWORK(0)));
|
useLayoutTitle(LL.NETWORK(0));
|
||||||
|
|
||||||
const { routerTab } = useRouterTab();
|
// this also works!
|
||||||
|
// const routerTab = useMatch(`settings/network/:path/*`)?.pathname || false;
|
||||||
|
const matchedRoutes = matchRoutes(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: '/settings/network/settings',
|
||||||
|
element: <NetworkSettings />,
|
||||||
|
dog: 'woof'
|
||||||
|
},
|
||||||
|
{ path: '/settings/network/scan', element: <WiFiNetworkScanner /> }
|
||||||
|
],
|
||||||
|
useLocation()
|
||||||
|
);
|
||||||
|
const routerTab = matchedRoutes?.[0]?.route.path || false;
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -24,7 +44,7 @@ const Network = () => {
|
|||||||
const selectNetwork = useCallback(
|
const selectNetwork = useCallback(
|
||||||
(network: WiFiNetwork) => {
|
(network: WiFiNetwork) => {
|
||||||
setSelectedNetwork(network);
|
setSelectedNetwork(network);
|
||||||
navigate('settings');
|
void navigate('/settings/network/settings');
|
||||||
},
|
},
|
||||||
[navigate]
|
[navigate]
|
||||||
);
|
);
|
||||||
@@ -36,19 +56,25 @@ const Network = () => {
|
|||||||
return (
|
return (
|
||||||
<WiFiConnectionContext.Provider
|
<WiFiConnectionContext.Provider
|
||||||
value={{
|
value={{
|
||||||
selectedNetwork,
|
...(selectedNetwork && { selectedNetwork }),
|
||||||
selectNetwork,
|
selectNetwork,
|
||||||
deselectNetwork
|
deselectNetwork
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RouterTabs value={routerTab}>
|
<RouterTabs value={routerTab}>
|
||||||
<Tab value="settings" label={LL.SETTINGS_OF(LL.NETWORK(1))} />
|
<Tab
|
||||||
<Tab value="scan" label={LL.NETWORK_SCAN()} />
|
value="/settings/network/settings"
|
||||||
|
label={LL.SETTINGS_OF(LL.NETWORK(1))}
|
||||||
|
/>
|
||||||
|
<Tab value="/settings/network/scan" label={LL.NETWORK_SCAN()} />
|
||||||
</RouterTabs>
|
</RouterTabs>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="scan" element={<WiFiNetworkScanner />} />
|
<Route path="scan" element={<WiFiNetworkScanner />} />
|
||||||
<Route path="settings" element={<NetworkSettings />} />
|
<Route path="settings" element={<NetworkSettings />} />
|
||||||
<Route path="*" element={<Navigate replace to="settings" />} />
|
<Route
|
||||||
|
path="*"
|
||||||
|
element={<Navigate replace to="/settings/network/settings" />}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</WiFiConnectionContext.Provider>
|
</WiFiConnectionContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import { updateValueDirty, useRest } from 'utils';
|
|||||||
import { validate } from 'validators';
|
import { validate } from 'validators';
|
||||||
import { createNetworkSettingsValidator } from 'validators/network';
|
import { createNetworkSettingsValidator } from 'validators/network';
|
||||||
|
|
||||||
import RestartMonitor from '../../status/RestartMonitor';
|
import SystemMonitor from '../../status/SystemMonitor';
|
||||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||||
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
|
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -400,7 +400,7 @@ const NetworkSettings = () => {
|
|||||||
return (
|
return (
|
||||||
<SectionContent>
|
<SectionContent>
|
||||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||||
{restarting ? <RestartMonitor /> : content()}
|
{restarting ? <SystemMonitor /> : content()}
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { useBlocker } from 'react-router-dom';
|
import { useBlocker } from 'react-router';
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
import CheckIcon from '@mui/icons-material/Done';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
||||||
import VpnKeyIcon from '@mui/icons-material/VpnKey';
|
import VpnKeyIcon from '@mui/icons-material/VpnKey';
|
||||||
@@ -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,7 +260,11 @@ const ManageUsers = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<GenerateToken username={generatingToken} onClose={closeGenerateToken} />
|
<GenerateToken
|
||||||
|
username={generatingToken || ''}
|
||||||
|
onClose={closeGenerateToken}
|
||||||
|
/>
|
||||||
|
{user && (
|
||||||
<User
|
<User
|
||||||
user={user}
|
user={user}
|
||||||
setUser={setUser}
|
setUser={setUser}
|
||||||
@@ -269,6 +273,7 @@ const ManageUsers = () => {
|
|||||||
onCancelEditing={cancelEditingUser}
|
onCancelEditing={cancelEditingUser}
|
||||||
validator={createUserValidator(data.users, creating)}
|
validator={createUserValidator(data.users, creating)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes, matchRoutes, useLocation } from 'react-router';
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
|
|
||||||
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
import { RouterTabs, useLayoutTitle } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import ManageUsers from './ManageUsers';
|
import ManageUsers from './ManageUsers';
|
||||||
@@ -10,20 +10,33 @@ import SecuritySettings from './SecuritySettings';
|
|||||||
|
|
||||||
const Security = () => {
|
const Security = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.SETTINGS_OF(LL.SECURITY(0)));
|
useLayoutTitle(LL.SECURITY(0));
|
||||||
|
|
||||||
const { routerTab } = useRouterTab();
|
const matchedRoutes = matchRoutes(
|
||||||
|
[
|
||||||
|
{ path: '/settings/security/settings', element: <ManageUsers />, dog: 'woof' },
|
||||||
|
{ path: '/settings/security/users', element: <SecuritySettings /> }
|
||||||
|
],
|
||||||
|
useLocation()
|
||||||
|
);
|
||||||
|
const routerTab = matchedRoutes?.[0]?.route.path || false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RouterTabs value={routerTab}>
|
<RouterTabs value={routerTab}>
|
||||||
<Tab value="settings" label={LL.SETTINGS_OF(LL.SECURITY(1))} />
|
<Tab
|
||||||
<Tab value="users" label={LL.MANAGE_USERS()} />
|
value="/settings/security/settings"
|
||||||
|
label={LL.SETTINGS_OF(LL.SECURITY(1))}
|
||||||
|
/>
|
||||||
|
<Tab value="/settings/security/users" label={LL.MANAGE_USERS()} />
|
||||||
</RouterTabs>
|
</RouterTabs>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="users" element={<ManageUsers />} />
|
<Route path="users" element={<ManageUsers />} />
|
||||||
<Route path="settings" element={<SecuritySettings />} />
|
<Route path="settings" element={<SecuritySettings />} />
|
||||||
<Route path="*" element={<Navigate replace to="settings" />} />
|
<Route
|
||||||
|
path="*"
|
||||||
|
element={<Navigate replace to="/settings/security/settings" />}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ import type { Theme } from '@mui/material';
|
|||||||
|
|
||||||
import * as APApi from 'api/ap';
|
import * as APApi from 'api/ap';
|
||||||
|
|
||||||
import { useAutoRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { 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 { APStatusType } from 'types';
|
import type { APStatusType } from 'types';
|
||||||
import { APNetworkStatus } from 'types';
|
import { APNetworkStatus } from 'types';
|
||||||
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {
|
export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -34,14 +35,14 @@ export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const APStatus = () => {
|
const APStatus = () => {
|
||||||
const {
|
const { data, send: loadData, error } = useRequest(APApi.readAPStatus);
|
||||||
data,
|
|
||||||
send: loadData,
|
useInterval(() => {
|
||||||
error
|
void loadData();
|
||||||
} = useAutoRequest(APApi.readAPStatus, { pollingTime: 3000 });
|
});
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.STATUS_OF(LL.ACCESS_POINT(0)));
|
useLayoutTitle(LL.ACCESS_POINT(0));
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -60,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 (
|
||||||
|
|||||||
@@ -8,20 +8,21 @@ import {
|
|||||||
Table
|
Table
|
||||||
} from '@table-library/react-table-library/table';
|
} from '@table-library/react-table-library/table';
|
||||||
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
|
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
|
||||||
import { useAutoRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { 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 { Translation } from 'i18n/i18n-types';
|
import type { Translation } from 'i18n/i18n-types';
|
||||||
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
import { readActivity } from '../../api/app';
|
import { readActivity } from '../../api/app';
|
||||||
import type { Stat } from '../main/types';
|
import type { Stat } from '../main/types';
|
||||||
|
|
||||||
const SystemActivity = () => {
|
const SystemActivity = () => {
|
||||||
const {
|
const { data, send: loadData, error } = useRequest(readActivity);
|
||||||
data,
|
|
||||||
send: loadData,
|
useInterval(() => {
|
||||||
error
|
void loadData();
|
||||||
} = useAutoRequest(readActivity, { pollingTime: 3000 });
|
});
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
@@ -66,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]();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -86,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 (
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ import {
|
|||||||
|
|
||||||
import * as SystemApi from 'api/system';
|
import * as SystemApi from 'api/system';
|
||||||
|
|
||||||
import { useAutoRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
import BBQKeesIcon from './bbqkees.svg';
|
import BBQKeesIcon from './bbqkees.svg';
|
||||||
|
|
||||||
@@ -30,17 +31,17 @@ function formatNumber(num: number) {
|
|||||||
const HardwareStatus = () => {
|
const HardwareStatus = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
useLayoutTitle(LL.STATUS_OF(LL.HARDWARE()));
|
useLayoutTitle(LL.HARDWARE());
|
||||||
|
|
||||||
const {
|
const { data, send: loadData, error } = useRequest(SystemApi.readSystemStatus);
|
||||||
data,
|
|
||||||
send: loadData,
|
useInterval(() => {
|
||||||
error
|
void loadData();
|
||||||
} = useAutoRequest(SystemApi.readSystemStatus, { pollingTime: 3000 });
|
});
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -98,7 +99,13 @@ const HardwareStatus = () => {
|
|||||||
' @ ' +
|
' @ ' +
|
||||||
data.cpu_freq_mhz +
|
data.cpu_freq_mhz +
|
||||||
' Mhz' +
|
' Mhz' +
|
||||||
(data.temperature ? ', T: ' + data.temperature + ' °C' : '')
|
// bit of a hack : if the CPU temp is higher than 90 (=32 Fahrenheit if using Celsius), show F, otherwise C
|
||||||
|
(data.temperature
|
||||||
|
? ', T: ' +
|
||||||
|
data.temperature +
|
||||||
|
' °' +
|
||||||
|
(data.temperature > 90 ? 'F' : 'C')
|
||||||
|
: '')
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|||||||
@@ -15,11 +15,12 @@ import type { Theme } from '@mui/material';
|
|||||||
|
|
||||||
import * as MqttApi from 'api/mqtt';
|
import * as MqttApi from 'api/mqtt';
|
||||||
|
|
||||||
import { useAutoRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { 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 { MqttStatusType } from 'types';
|
import type { MqttStatusType } from 'types';
|
||||||
import { MqttDisconnectReason } from 'types';
|
import { MqttDisconnectReason } from 'types';
|
||||||
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
export const mqttStatusHighlight = (
|
export const mqttStatusHighlight = (
|
||||||
{ enabled, connected }: MqttStatusType,
|
{ enabled, connected }: MqttStatusType,
|
||||||
@@ -54,14 +55,14 @@ export const mqttQueueHighlight = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const MqttStatus = () => {
|
const MqttStatus = () => {
|
||||||
const {
|
const { data, send: loadData, error } = useRequest(MqttApi.readMqttStatus);
|
||||||
data,
|
|
||||||
send: loadData,
|
useInterval(() => {
|
||||||
error
|
void loadData();
|
||||||
} = useAutoRequest(MqttApi.readMqttStatus, { pollingTime: 3000 });
|
});
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.STATUS_OF('MQTT'));
|
useLayoutTitle('MQTT');
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -70,9 +71,9 @@ const MqttStatus = () => {
|
|||||||
return LL.NOT_ENABLED();
|
return LL.NOT_ENABLED();
|
||||||
}
|
}
|
||||||
if (connected) {
|
if (connected) {
|
||||||
return LL.CONNECTED(0) + (connect_count > 1 ? ' (' + connect_count + ')' : '');
|
return LL.CONNECTED(0) + ' (' + connect_count + ')';
|
||||||
}
|
}
|
||||||
return LL.DISCONNECTED() + (connect_count > 1 ? ' (' + connect_count + ')' : '');
|
return LL.DISCONNECTED() + ' (' + connect_count + ')';
|
||||||
};
|
};
|
||||||
|
|
||||||
const disconnectReason = ({ disconnect_reason }: MqttStatusType) => {
|
const disconnectReason = ({ disconnect_reason }: MqttStatusType) => {
|
||||||
@@ -98,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,65 +1,40 @@
|
|||||||
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 { useAutoRequest, useRequest } from 'alova/client';
|
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||||
import { ButtonRow, 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 { formatDateTime, formatLocalDateTime } from 'utils';
|
import { useInterval } from 'utils';
|
||||||
|
import { formatDateTime } from 'utils';
|
||||||
|
|
||||||
const NTPStatus = () => {
|
const NTPStatus = () => {
|
||||||
const {
|
const { data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
|
||||||
data,
|
|
||||||
send: loadData,
|
|
||||||
error
|
|
||||||
} = useAutoRequest(NTPApi.readNTPStatus, { pollingTime: 3000 });
|
|
||||||
|
|
||||||
const [localTime, setLocalTime] = useState<string>('');
|
useInterval(() => {
|
||||||
const [settingTime, setSettingTime] = useState<boolean>(false);
|
void loadData();
|
||||||
const [processing, setProcessing] = useState<boolean>(false);
|
});
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.STATUS_OF('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;
|
||||||
|
|
||||||
@@ -76,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) => {
|
||||||
@@ -99,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 (
|
||||||
@@ -218,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()}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ import type { Theme } from '@mui/material';
|
|||||||
|
|
||||||
import * as NetworkApi from 'api/network';
|
import * as NetworkApi from 'api/network';
|
||||||
|
|
||||||
import { useAutoRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { 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 { NetworkStatusType } from 'types';
|
import type { NetworkStatusType } from 'types';
|
||||||
import { NetworkConnectionStatus } from 'types';
|
import { NetworkConnectionStatus } from 'types';
|
||||||
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
const isConnected = ({ status }: NetworkStatusType) =>
|
const isConnected = ({ status }: NetworkStatusType) =>
|
||||||
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
|
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
|
||||||
@@ -81,19 +82,21 @@ const IPs = (status: NetworkStatusType) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const NetworkStatus = () => {
|
const NetworkStatus = () => {
|
||||||
const {
|
const { data, send: loadData, error } = useRequest(NetworkApi.readNetworkStatus);
|
||||||
data,
|
|
||||||
send: loadData,
|
useInterval(() => {
|
||||||
error
|
void loadData();
|
||||||
} = useAutoRequest(NetworkApi.readNetworkStatus, { pollingTime: 3000 });
|
});
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.STATUS_OF(LL.NETWORK(1)));
|
useLayoutTitle(LL.NETWORK(1));
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const networkStatus = ({ status }: NetworkStatusType) => {
|
const networkStatus = ({ status }: NetworkStatusType) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
|
||||||
|
return LL.CONNECTED(0) + ' (Ethernet)';
|
||||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
|
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
|
||||||
return LL.INACTIVE(1);
|
return LL.INACTIVE(1);
|
||||||
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
|
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
|
||||||
@@ -101,13 +104,13 @@ const NetworkStatus = () => {
|
|||||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
|
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
|
||||||
return 'No SSID Available';
|
return 'No SSID Available';
|
||||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
|
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
|
||||||
return LL.CONNECTED(0) + ' (WiFi)';
|
return LL.CONNECTED(0) + ' (WiFi) (' + data.reconnect_count + ')';
|
||||||
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
|
|
||||||
return LL.CONNECTED(0) + ' (Ethernet)';
|
|
||||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
|
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
|
||||||
return LL.CONNECTED(1) + ' ' + LL.FAILED(0);
|
return (
|
||||||
|
LL.CONNECTED(1) + ' ' + LL.FAILED(0) + ' (' + data.reconnect_count + ')'
|
||||||
|
);
|
||||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
|
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
|
||||||
return LL.CONNECTED(1) + ' ' + LL.LOST();
|
return LL.CONNECTED(1) + ' ' + LL.LOST() + ' (' + data.reconnect_count + ')';
|
||||||
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
|
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
|
||||||
return LL.DISCONNECTED();
|
return LL.DISCONNECTED();
|
||||||
default:
|
default:
|
||||||
@@ -117,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 (
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
CircularProgress,
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
Typography
|
|
||||||
} from '@mui/material';
|
|
||||||
|
|
||||||
import { readSystemStatus } from 'api/system';
|
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
|
||||||
import { useAutoRequest } from 'alova/client';
|
|
||||||
import MessageBox from 'components/MessageBox';
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
|
||||||
|
|
||||||
const RestartMonitor = () => {
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
const { data } = useAutoRequest(readSystemStatus, {
|
|
||||||
pollingTime: 1000,
|
|
||||||
force: true,
|
|
||||||
initialData: { status: 'Getting ready...' },
|
|
||||||
async middleware(_, next) {
|
|
||||||
if (count++ >= 1) {
|
|
||||||
// skip first request (1 second) to allow AsyncWS to send its response
|
|
||||||
await next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onSuccess((event) => {
|
|
||||||
if (event.data.status === 'ready' || event.data.status === undefined) {
|
|
||||||
document.location.href = '/';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onError((error) => {
|
|
||||||
setErrorMessage(error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog fullWidth={true} sx={dialogStyle} open={true}>
|
|
||||||
<DialogContent dividers>
|
|
||||||
<Box m={0} py={0} display="flex" alignItems="center" flexDirection="column">
|
|
||||||
<Typography
|
|
||||||
color="secondary"
|
|
||||||
variant="h6"
|
|
||||||
fontWeight={400}
|
|
||||||
textAlign="center"
|
|
||||||
>
|
|
||||||
{data?.status === 'uploading'
|
|
||||||
? LL.WAIT_FIRMWARE()
|
|
||||||
: data?.status === 'restarting'
|
|
||||||
? LL.APPLICATION_RESTARTING()
|
|
||||||
: data?.status === 'ready'
|
|
||||||
? LL.RESTARTING_PRE()
|
|
||||||
: LL.RESTARTING_POST()}
|
|
||||||
…
|
|
||||||
</Typography>
|
|
||||||
<Typography mt={2} variant="h6" fontWeight={400} textAlign="center">
|
|
||||||
{LL.PLEASE_WAIT()}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
{errorMessage ? (
|
|
||||||
<MessageBox my={2} level="error" message={errorMessage} />
|
|
||||||
) : (
|
|
||||||
<Box py={2}>
|
|
||||||
<CircularProgress size={32} />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RestartMonitor;
|
|
||||||
@@ -2,6 +2,7 @@ import { useContext, useState } from 'react';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||||
|
import BuildIcon from '@mui/icons-material/Build';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||||
import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';
|
import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';
|
||||||
@@ -30,15 +31,17 @@ import { API } from 'api/app';
|
|||||||
import { readSystemStatus } from 'api/system';
|
import { readSystemStatus } from 'api/system';
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import { useAutoRequest, useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { type APIcall, busConnectionStatus } from 'app/main/types';
|
import { type APIcall, busConnectionStatus } from 'app/main/types';
|
||||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||||
import ListMenuItem from 'components/layout/ListMenuItem';
|
import ListMenuItem from 'components/layout/ListMenuItem';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { NTPSyncStatus, NetworkConnectionStatus } from 'types';
|
import { NTPSyncStatus, NetworkConnectionStatus } from 'types';
|
||||||
|
import { useInterval } from 'utils';
|
||||||
|
import { formatDateTime } from 'utils/time';
|
||||||
|
|
||||||
import RestartMonitor from './RestartMonitor';
|
import SystemMonitor from './SystemMonitor';
|
||||||
|
|
||||||
const SystemStatus = () => {
|
const SystemStatus = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
@@ -58,9 +61,8 @@ const SystemStatus = () => {
|
|||||||
data,
|
data,
|
||||||
send: loadData,
|
send: loadData,
|
||||||
error
|
error
|
||||||
} = useAutoRequest(readSystemStatus, {
|
} = useRequest(readSystemStatus, {
|
||||||
initialData: [],
|
initialData: [],
|
||||||
pollingTime: 3000,
|
|
||||||
async middleware(_, next) {
|
async middleware(_, next) {
|
||||||
if (!restarting) {
|
if (!restarting) {
|
||||||
await next();
|
await next();
|
||||||
@@ -68,6 +70,10 @@ const SystemStatus = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
void loadData();
|
||||||
|
});
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const formatDurationSec = (duration_sec: number) => {
|
const formatDurationSec = (duration_sec: number) => {
|
||||||
@@ -134,7 +140,12 @@ const SystemStatus = () => {
|
|||||||
case NTPSyncStatus.NTP_INACTIVE:
|
case NTPSyncStatus.NTP_INACTIVE:
|
||||||
return LL.INACTIVE(0);
|
return LL.INACTIVE(0);
|
||||||
case NTPSyncStatus.NTP_ACTIVE:
|
case NTPSyncStatus.NTP_ACTIVE:
|
||||||
return LL.ACTIVE();
|
return (
|
||||||
|
LL.ACTIVE() +
|
||||||
|
(data.ntp_time !== undefined
|
||||||
|
? ' (' + formatDateTime(data.ntp_time) + ')'
|
||||||
|
: '')
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return LL.UNKNOWN();
|
return LL.UNKNOWN();
|
||||||
}
|
}
|
||||||
@@ -237,12 +248,20 @@ 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 sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
||||||
|
<ListMenuItem
|
||||||
|
icon={BuildIcon}
|
||||||
|
bgcolor="#72caf9"
|
||||||
|
label="EMS-ESP Firmware"
|
||||||
|
text={'v' + data.emsesp_version}
|
||||||
|
to="version"
|
||||||
|
/>
|
||||||
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar sx={{ bgcolor: '#c5572c', color: 'white' }}>
|
<Avatar sx={{ bgcolor: '#c5572c', color: 'white' }}>
|
||||||
@@ -301,7 +320,7 @@ const SystemStatus = () => {
|
|||||||
icon={DeviceHubIcon}
|
icon={DeviceHubIcon}
|
||||||
bgcolor={activeHighlight(data.mqtt_status)}
|
bgcolor={activeHighlight(data.mqtt_status)}
|
||||||
label="MQTT"
|
label="MQTT"
|
||||||
text={data.mqtt_status ? LL.ACTIVE() : LL.INACTIVE(0)}
|
text={data.mqtt_status ? LL.CONNECTED(0) : LL.INACTIVE(0)}
|
||||||
to="/status/mqtt"
|
to="/status/mqtt"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -339,7 +358,7 @@ const SystemStatus = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
|
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
styled
|
styled
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
|
||||||
|
|
||||||
import { API } from 'api/app';
|
import { API } from 'api/app';
|
||||||
import { fetchLogES, readLogSettings, updateLogSettings } from 'api/system';
|
import { fetchLogES, readLogSettings, updateLogSettings } from 'api/system';
|
||||||
@@ -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')(
|
||||||
@@ -101,6 +102,7 @@ const SystemLog = () => {
|
|||||||
const [readOpen, setReadOpen] = useState(false);
|
const [readOpen, setReadOpen] = useState(false);
|
||||||
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
|
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
|
||||||
const [autoscroll, setAutoscroll] = useState(true);
|
const [autoscroll, setAutoscroll] = useState(true);
|
||||||
|
const [lastId, setLastId] = useState<number>(-1);
|
||||||
|
|
||||||
const ALPHA_NUMERIC_DASH_REGEX = /^[a-fA-F0-9 ]+$/;
|
const ALPHA_NUMERIC_DASH_REGEX = /^[a-fA-F0-9 ]+$/;
|
||||||
|
|
||||||
@@ -108,17 +110,20 @@ const SystemLog = () => {
|
|||||||
origData,
|
origData,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
setDirtyFlags,
|
setDirtyFlags,
|
||||||
updateDataValue
|
updateDataValue as (value: unknown) => void
|
||||||
);
|
);
|
||||||
|
|
||||||
useSSE(fetchLogES, {
|
useSSE(fetchLogES, {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
interceptByGlobalResponded: false
|
interceptByGlobalResponded: false
|
||||||
})
|
})
|
||||||
.onMessage((message: { id: number; data: string }) => {
|
.onMessage((message: { data: string }) => {
|
||||||
const rawData = message.data;
|
const rawData = message.data;
|
||||||
const logentry = JSON.parse(rawData) as LogEntry;
|
const logentry = JSON.parse(rawData) as LogEntry;
|
||||||
|
if (lastId < logentry.i) {
|
||||||
setLogEntries((log) => [...log, logentry]);
|
setLogEntries((log) => [...log, logentry]);
|
||||||
|
setLastId(logentry.i);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.onError(() => {
|
.onError(() => {
|
||||||
toast.error('No connection to Log service');
|
toast.error('No connection to Log service');
|
||||||
@@ -186,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 (
|
||||||
@@ -197,7 +202,7 @@ const SystemLog = () => {
|
|||||||
name="level"
|
name="level"
|
||||||
label={LL.LOG_LEVEL()}
|
label={LL.LOG_LEVEL()}
|
||||||
value={data.level}
|
value={data.level}
|
||||||
sx={{ width: '10ch' }}
|
sx={{ width: '14ch' }}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
@@ -261,16 +266,6 @@ const SystemLog = () => {
|
|||||||
>
|
>
|
||||||
{LL.EXPORT()}
|
{LL.EXPORT()}
|
||||||
</Button>
|
</Button>
|
||||||
{dirtyFlags && dirtyFlags.length !== 0 && (
|
|
||||||
<Button
|
|
||||||
startIcon={<WarningIcon color="warning" />}
|
|
||||||
variant="contained"
|
|
||||||
color="info"
|
|
||||||
onClick={saveSettings}
|
|
||||||
>
|
|
||||||
{LL.APPLY_CHANGES(dirtyFlags.length)}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{readOpen ? (
|
{readOpen ? (
|
||||||
@@ -315,6 +310,19 @@ const SystemLog = () => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{dirtyFlags && dirtyFlags.length !== 0 && (
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
startIcon={<WarningIcon color="warning" />}
|
||||||
|
variant="contained"
|
||||||
|
color="info"
|
||||||
|
onClick={saveSettings}
|
||||||
|
>
|
||||||
|
{LL.APPLY_CHANGES(dirtyFlags.length)}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
125
interface/src/app/status/SystemMonitor.tsx
Normal file
125
interface/src/app/status/SystemMonitor.tsx
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import { Box, Button, Dialog, DialogContent, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
import { callAction } from 'api/app';
|
||||||
|
import { readSystemStatus } from 'api/system';
|
||||||
|
|
||||||
|
import { dialogStyle } from 'CustomTheme';
|
||||||
|
import { useRequest } from 'alova/client';
|
||||||
|
import MessageBox from 'components/MessageBox';
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { SystemStatusCodes } from 'types';
|
||||||
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
|
import { LinearProgressWithLabel } from '../../components/upload/LinearProgressWithLabel';
|
||||||
|
|
||||||
|
const SystemMonitor = () => {
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string>();
|
||||||
|
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
const { send: setSystemStatus } = useRequest(
|
||||||
|
(status: string) => callAction({ action: 'systemStatus', param: status }),
|
||||||
|
{
|
||||||
|
immediate: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, send } = useRequest(readSystemStatus, {
|
||||||
|
force: true,
|
||||||
|
async middleware(_, next) {
|
||||||
|
if (count++ >= 1) {
|
||||||
|
// skip first request (1 second) to allow AsyncWS to send its response
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onSuccess((event) => {
|
||||||
|
if (
|
||||||
|
event.data.status === SystemStatusCodes.SYSTEM_STATUS_NORMAL ||
|
||||||
|
event.data.status === undefined
|
||||||
|
) {
|
||||||
|
document.location.href = '/';
|
||||||
|
} else if (
|
||||||
|
event.data.status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD
|
||||||
|
) {
|
||||||
|
setErrorMessage('Please check system logs for possible causes');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onError((error) => {
|
||||||
|
setErrorMessage(String(error.error?.message || 'An error occurred'));
|
||||||
|
});
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
void send();
|
||||||
|
}, 1000); // check every 1 second
|
||||||
|
|
||||||
|
const onCancel = async () => {
|
||||||
|
setErrorMessage(undefined);
|
||||||
|
await setSystemStatus(
|
||||||
|
SystemStatusCodes.SYSTEM_STATUS_NORMAL as unknown as string
|
||||||
|
);
|
||||||
|
document.location.href = '/';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog fullWidth={true} sx={dialogStyle} open={true}>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Box m={0} py={0} display="flex" alignItems="center" flexDirection="column">
|
||||||
|
<Typography
|
||||||
|
color="secondary"
|
||||||
|
variant="h6"
|
||||||
|
fontWeight={400}
|
||||||
|
textAlign="center"
|
||||||
|
>
|
||||||
|
{data?.status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING
|
||||||
|
? LL.WAIT_FIRMWARE()
|
||||||
|
: data?.status === SystemStatusCodes.SYSTEM_STATUS_PENDING_RESTART
|
||||||
|
? LL.APPLICATION_RESTARTING()
|
||||||
|
: data?.status === SystemStatusCodes.SYSTEM_STATUS_NORMAL
|
||||||
|
? LL.RESTARTING_PRE()
|
||||||
|
: data?.status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD
|
||||||
|
? 'Upload Failed'
|
||||||
|
: LL.RESTARTING_POST()}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{errorMessage ? (
|
||||||
|
<MessageBox my={2} level="error" message={errorMessage}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
sx={{ ml: 2 }}
|
||||||
|
startIcon={<CancelIcon />}
|
||||||
|
variant="contained"
|
||||||
|
color="error"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
{LL.RESTART()}
|
||||||
|
</Button>
|
||||||
|
</MessageBox>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography mt={2} variant="h6" fontWeight={400} textAlign="center">
|
||||||
|
{LL.PLEASE_WAIT()}…
|
||||||
|
</Typography>
|
||||||
|
{data && data.status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING && (
|
||||||
|
<Box width="100%" pl={2} pr={2} py={2}>
|
||||||
|
<LinearProgressWithLabel
|
||||||
|
value={Math.round(
|
||||||
|
data?.status - SystemStatusCodes.SYSTEM_STATUS_UPLOADING
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SystemMonitor;
|
||||||
496
interface/src/app/status/Version.tsx
Normal file
496
interface/src/app/status/Version.tsx
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
import { useContext, useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import CheckIcon from '@mui/icons-material/Done';
|
||||||
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
FormControlLabel,
|
||||||
|
Grid,
|
||||||
|
Link,
|
||||||
|
Typography
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import * as SystemApi from 'api/system';
|
||||||
|
import { API, callAction } from 'api/app';
|
||||||
|
import { getDevVersion, getStableVersion } from 'api/system';
|
||||||
|
|
||||||
|
import { dialogStyle } from 'CustomTheme';
|
||||||
|
import { useRequest } from 'alova/client';
|
||||||
|
import type { APIcall } from 'app/main/types';
|
||||||
|
import SystemMonitor from 'app/status/SystemMonitor';
|
||||||
|
import {
|
||||||
|
FormLoader,
|
||||||
|
SectionContent,
|
||||||
|
SingleUpload,
|
||||||
|
useLayoutTitle
|
||||||
|
} from 'components';
|
||||||
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
|
const Version = () => {
|
||||||
|
const { LL, locale } = useI18nContext();
|
||||||
|
const { me } = useContext(AuthenticatedContext);
|
||||||
|
|
||||||
|
const [restarting, setRestarting] = useState<boolean>(false);
|
||||||
|
const [openInstallDialog, setOpenInstallDialog] = useState<boolean>(false);
|
||||||
|
const [usingDevVersion, setUsingDevVersion] = useState<boolean>(false);
|
||||||
|
const [fetchDevVersion, setFetchDevVersion] = useState<boolean>(false);
|
||||||
|
const [devUpgradeAvailable, setDevUpgradeAvailable] = useState<boolean>(false);
|
||||||
|
const [stableUpgradeAvailable, setStableUpgradeAvailable] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
const [internetLive, setInternetLive] = useState<boolean>(false);
|
||||||
|
const [downloadOnly, setDownloadOnly] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const 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';
|
||||||
|
|
||||||
|
const { send: sendCheckUpgrade } = useRequest(
|
||||||
|
(versions: string) => callAction({ action: 'checkUpgrade', param: versions }),
|
||||||
|
{
|
||||||
|
immediate: false
|
||||||
|
}
|
||||||
|
).onSuccess((event) => {
|
||||||
|
const data = event.data as {
|
||||||
|
emsesp_version: string;
|
||||||
|
dev_upgradeable: boolean;
|
||||||
|
stable_upgradeable: boolean;
|
||||||
|
};
|
||||||
|
setDevUpgradeAvailable(data.dev_upgradeable);
|
||||||
|
setStableUpgradeAvailable(data.stable_upgradeable);
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: data,
|
||||||
|
send: loadData,
|
||||||
|
error
|
||||||
|
} = useRequest(SystemApi.readSystemStatus).onSuccess((event) => {
|
||||||
|
// older version of EMS-ESP using ESP32 (not S3) and no PSRAM, can't use OTA because of SSL support in HttpClient
|
||||||
|
if (event.data.arduino_version.startsWith('Tasmota')) {
|
||||||
|
setDownloadOnly(true);
|
||||||
|
}
|
||||||
|
setUsingDevVersion(event.data.emsesp_version.includes('dev'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const { send: sendUploadURL } = useRequest(
|
||||||
|
(url: string) => callAction({ action: 'uploadURL', param: url }),
|
||||||
|
{
|
||||||
|
immediate: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// called immediately to get the latest versions on page load
|
||||||
|
const { data: latestVersion } = useRequest(getStableVersion);
|
||||||
|
const { data: latestDevVersion } = useRequest(getDevVersion);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (latestVersion && latestDevVersion) {
|
||||||
|
sendCheckUpgrade(latestDevVersion.name + ',' + latestVersion.name)
|
||||||
|
.catch((error: Error) => {
|
||||||
|
toast.error('Failed to check for upgrades: ' + error.message);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setInternetLive(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [latestVersion, latestDevVersion]);
|
||||||
|
|
||||||
|
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
|
||||||
|
const DIVISIONS: Array<{ amount: number; name: string }> = [
|
||||||
|
{ 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: Date) {
|
||||||
|
let duration = (date.getTime() - new Date().getTime()) / 1000;
|
||||||
|
for (let i = 0; i < DIVISIONS.length; i++) {
|
||||||
|
const division = DIVISIONS[i];
|
||||||
|
if (division && Math.abs(duration) < division.amount) {
|
||||||
|
return rtf.format(
|
||||||
|
Math.round(duration),
|
||||||
|
division.name as Intl.RelativeTimeFormatUnit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (division) {
|
||||||
|
duration /= division.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rtf.format(0, 'seconds');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const doRestart = async () => {
|
||||||
|
setRestarting(true);
|
||||||
|
await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch(
|
||||||
|
(error: Error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBinURL = (showingDev: boolean) => {
|
||||||
|
if (!internetLive) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename =
|
||||||
|
'EMS-ESP-' +
|
||||||
|
(showingDev ? latestDevVersion.name : latestVersion.name).replaceAll(
|
||||||
|
'.',
|
||||||
|
'_'
|
||||||
|
) +
|
||||||
|
'-' +
|
||||||
|
getPlatform() +
|
||||||
|
'.bin';
|
||||||
|
return showingDev
|
||||||
|
? 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');
|
||||||
|
|
||||||
|
const renderInstallDialog = () => {
|
||||||
|
const binURL = getBinURL(fetchDevVersion);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
sx={dialogStyle}
|
||||||
|
open={openInstallDialog}
|
||||||
|
onClose={() => closeInstallDialog()}
|
||||||
|
>
|
||||||
|
<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={() => closeInstallDialog()}
|
||||||
|
color="secondary"
|
||||||
|
>
|
||||||
|
{LL.CANCEL()}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
startIcon={<DownloadIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => closeInstallDialog()}
|
||||||
|
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={() => installFirmwareURL(binURL)}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{LL.INSTALL()}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showFirmwareDialog = (useDevVersion: boolean) => {
|
||||||
|
setFetchDevVersion(useDevVersion);
|
||||||
|
setOpenInstallDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeInstallDialog = () => {
|
||||||
|
setOpenInstallDialog(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showButtons = (showingDev: boolean) => {
|
||||||
|
const choice = showingDev
|
||||||
|
? !usingDevVersion
|
||||||
|
? LL.SWITCH_RELEASE_TYPE(LL.DEVELOPMENT())
|
||||||
|
: devUpgradeAvailable
|
||||||
|
? LL.UPDATE_AVAILABLE()
|
||||||
|
: undefined
|
||||||
|
: usingDevVersion
|
||||||
|
? LL.SWITCH_RELEASE_TYPE(LL.STABLE())
|
||||||
|
: stableUpgradeAvailable
|
||||||
|
? LL.UPDATE_AVAILABLE()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (!choice) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CheckIcon
|
||||||
|
color="success"
|
||||||
|
sx={{ verticalAlign: 'middle', ml: 0.5, mr: 0.5 }}
|
||||||
|
/>
|
||||||
|
<span style={{ color: '#66bb6a', fontSize: '0.8em' }}>
|
||||||
|
{LL.LATEST_VERSION(usingDevVersion ? LL.DEVELOPMENT() : LL.STABLE())}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
sx={{ ml: 2 }}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
onClick={() => showFirmwareDialog(showingDev)}
|
||||||
|
>
|
||||||
|
{LL.REINSTALL()}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!me.admin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
sx={{ ml: 2 }}
|
||||||
|
variant="outlined"
|
||||||
|
color={choice === LL.UPDATE_AVAILABLE() ? 'success' : 'warning'}
|
||||||
|
size="small"
|
||||||
|
onClick={() => showFirmwareDialog(showingDev)}
|
||||||
|
>
|
||||||
|
{choice}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = () => {
|
||||||
|
if (!data) {
|
||||||
|
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDev = data.emsesp_version.includes('dev');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box p={2} border="1px solid grey" borderRadius={2}>
|
||||||
|
<Typography mb={2} variant="h6" color="primary">
|
||||||
|
{LL.THIS_VERSION()}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
rowSpacing={1}
|
||||||
|
sx={{
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
alignItems: 'baseline'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid size={{ xs: 4, md: 2 }}>
|
||||||
|
<Typography color="secondary">{LL.VERSION()}</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 8, md: 10 }}>
|
||||||
|
<Typography>
|
||||||
|
{data.emsesp_version}
|
||||||
|
{data.build_flags && (
|
||||||
|
<Typography variant="caption">
|
||||||
|
({data.build_flags})
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid size={{ xs: 4, md: 2 }}>
|
||||||
|
<Typography color="secondary">{LL.PLATFORM()}</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 8, md: 10 }}>
|
||||||
|
<Typography>
|
||||||
|
{getPlatform()}
|
||||||
|
<Typography variant="caption">
|
||||||
|
(
|
||||||
|
{data.psram ? (
|
||||||
|
<CheckIcon
|
||||||
|
color="success"
|
||||||
|
sx={{
|
||||||
|
fontSize: '1.5em',
|
||||||
|
verticalAlign: 'middle'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CloseIcon
|
||||||
|
color="error"
|
||||||
|
sx={{
|
||||||
|
fontSize: '1.5em',
|
||||||
|
verticalAlign: 'middle'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
PSRAM)
|
||||||
|
</Typography>
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid size={{ xs: 4, md: 2 }}>
|
||||||
|
<Typography color="secondary">{LL.RELEASE_TYPE()}</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 8, md: 10 }}>
|
||||||
|
<FormControlLabel
|
||||||
|
disabled={!isDev}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
sx={{
|
||||||
|
'&.Mui-checked': {
|
||||||
|
color: 'lightblue'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
slotProps={{
|
||||||
|
typography: {
|
||||||
|
color: 'grey'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
checked={!isDev}
|
||||||
|
label={LL.STABLE()}
|
||||||
|
sx={{ '& .MuiSvgIcon-root': { fontSize: 16 } }}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
disabled={isDev}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
sx={{
|
||||||
|
'&.Mui-checked': {
|
||||||
|
color: 'lightblue'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
slotProps={{
|
||||||
|
typography: {
|
||||||
|
color: 'grey'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
checked={isDev}
|
||||||
|
label={LL.DEVELOPMENT()}
|
||||||
|
sx={{ '& .MuiSvgIcon-root': { fontSize: 16 } }}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{internetLive ? (
|
||||||
|
<>
|
||||||
|
<Typography mt={2} mb={2} variant="h6" color="primary">
|
||||||
|
{LL.AVAILABLE_VERSION()}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
rowSpacing={1}
|
||||||
|
sx={{
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
alignItems: 'baseline'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid size={{ xs: 4, md: 2 }}>
|
||||||
|
<Typography color="secondary">{LL.STABLE()}</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 8, md: 10 }}>
|
||||||
|
<Typography>
|
||||||
|
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
|
||||||
|
{latestVersion.name}
|
||||||
|
</Link>
|
||||||
|
{latestVersion.published_at && (
|
||||||
|
<Typography component="span" variant="caption">
|
||||||
|
(
|
||||||
|
{formatTimeAgo(new Date(latestVersion.published_at))})
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{showButtons(false)}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid size={{ xs: 4, md: 2 }}>
|
||||||
|
<Typography color="secondary">{LL.DEVELOPMENT()}</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 8, md: 10 }}>
|
||||||
|
<Typography>
|
||||||
|
<Link target="_blank" href={DEV_RELNOTES_URL} color="primary">
|
||||||
|
{latestDevVersion.name}
|
||||||
|
</Link>
|
||||||
|
{latestDevVersion.published_at && (
|
||||||
|
<Typography component="span" variant="caption">
|
||||||
|
(
|
||||||
|
{formatTimeAgo(new Date(latestDevVersion.published_at))})
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{showButtons(true)}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Typography mt={2} color="warning">
|
||||||
|
<WarningIcon color="warning" sx={{ verticalAlign: 'middle', mr: 2 }} />
|
||||||
|
{LL.INTERNET_CONNECTION_REQUIRED()}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{me.admin && (
|
||||||
|
<>
|
||||||
|
{renderInstallDialog()}
|
||||||
|
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||||
|
{LL.UPLOAD()}
|
||||||
|
</Typography>
|
||||||
|
<SingleUpload text={LL.UPLOAD_DROP_TEXT()} doRestart={doRestart} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default 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;
|
||||||
|
|||||||
22
interface/src/components/ButtonTooltip.tsx
Normal file
22
interface/src/components/ButtonTooltip.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Tooltip, type TooltipProps, styled, tooltipClasses } from '@mui/material';
|
||||||
|
|
||||||
|
export const ButtonTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||||
|
<Tooltip
|
||||||
|
{...props}
|
||||||
|
placement="top"
|
||||||
|
arrow
|
||||||
|
classes={{ ...(className && { popper: className }) }}
|
||||||
|
/>
|
||||||
|
))(({ theme }) => ({
|
||||||
|
[`& .${tooltipClasses.arrow}`]: {
|
||||||
|
color: theme.palette.success.main
|
||||||
|
},
|
||||||
|
[`& .${tooltipClasses.tooltip}`]: {
|
||||||
|
backgroundColor: theme.palette.success.main,
|
||||||
|
color: 'rgba(0, 0, 0, 0.87)',
|
||||||
|
boxShadow: theme.shadows[1],
|
||||||
|
fontSize: 10
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default ButtonTooltip;
|
||||||
@@ -11,7 +11,7 @@ type MessageBoxLevel = 'warning' | 'success' | 'info' | 'error';
|
|||||||
|
|
||||||
export interface MessageBoxProps extends BoxProps {
|
export interface MessageBoxProps extends BoxProps {
|
||||||
level: MessageBoxLevel;
|
level: MessageBoxLevel;
|
||||||
message: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LEVEL_ICONS: {
|
const LEVEL_ICONS: {
|
||||||
@@ -53,8 +53,8 @@ const MessageBox: FC<MessageBoxProps> = ({
|
|||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<Icon />
|
<Icon />
|
||||||
<Typography sx={{ ml: 2, flexGrow: 1 }} variant="body1">
|
<Typography sx={{ ml: 2 }} variant="body1">
|
||||||
{message}
|
{message ?? ''}
|
||||||
</Typography>
|
</Typography>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,9 +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';
|
||||||
|
|||||||
@@ -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>
|
||||||
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user