mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 00:09:51 +03:00
Compare commits
1743 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df9f75a5c9 | ||
|
|
7bd8710eb6 | ||
|
|
32f2c6d341 | ||
|
|
20d8b6400f | ||
|
|
d3b086a675 | ||
|
|
e1ee83b907 | ||
|
|
763a2eaab1 | ||
|
|
5d78f1c814 | ||
|
|
3f99806ddd | ||
|
|
bc0a90ee41 | ||
|
|
3900b8fc94 | ||
|
|
f9a53cf320 | ||
|
|
99467558b4 | ||
|
|
2e25e46f6f | ||
|
|
3a1b7cae67 | ||
|
|
5bc3bd23e2 | ||
|
|
a04f4d784b | ||
|
|
9c423dc886 | ||
|
|
1b0f840d18 | ||
|
|
21eaf70181 | ||
|
|
14f2cd4bee | ||
|
|
917e268ac9 | ||
|
|
41228e894a | ||
|
|
365c95991b | ||
|
|
ba4ebe221c | ||
|
|
3ab5946e38 | ||
|
|
f427288c06 | ||
|
|
398cbadb64 | ||
|
|
38a1c9e9d7 | ||
|
|
5abfdf1177 | ||
|
|
385d2377db | ||
|
|
6655035561 | ||
|
|
755408e6aa | ||
|
|
df70ed0062 | ||
|
|
b893290b62 | ||
|
|
96ff5ed123 | ||
|
|
dcdd8d9e44 | ||
|
|
5c484d58dc | ||
|
|
96bf1a32ee | ||
|
|
ad81f84c02 | ||
|
|
529b46d776 | ||
|
|
bbc5bc8585 | ||
|
|
9c33b5cb8b | ||
|
|
ba7ceca798 | ||
|
|
4824152ae5 | ||
|
|
af935d8124 | ||
|
|
951e706fee | ||
|
|
9f1002df9a | ||
|
|
6ab2135cea | ||
|
|
e6bcdede73 | ||
|
|
8b136ddefe | ||
|
|
ca9ac86fde | ||
|
|
42a4c792ad | ||
|
|
a6b0c74f5f | ||
|
|
09e2945c15 | ||
|
|
a3f05cb08b | ||
|
|
0ab573225d | ||
|
|
4c8fd45908 | ||
|
|
8bb703fafe | ||
|
|
9cdeb7c07a | ||
|
|
8d4b43e9f6 | ||
|
|
d8c298f6fe | ||
|
|
8a16566f53 | ||
|
|
9d3456cb3a | ||
|
|
7310db6426 | ||
|
|
ac08bb6037 | ||
|
|
776f72650f | ||
|
|
0eb96e764c | ||
|
|
ec939dfc2e | ||
|
|
809b6be79c | ||
|
|
34f87a0a21 | ||
|
|
bc4418755e | ||
|
|
d24742facd | ||
|
|
9c16e2008e | ||
|
|
6a7a9636ac | ||
|
|
5e76bfa210 | ||
|
|
4da45a7240 | ||
|
|
3c84a1ced1 | ||
|
|
86919c1684 | ||
|
|
677f6c5a6e | ||
|
|
3ce67b8e6e | ||
|
|
aad9c12a7e | ||
|
|
877d60f46f | ||
|
|
bf02b0ad1c | ||
|
|
2f5b7cd0aa | ||
|
|
4bec32ea56 | ||
|
|
33ea1c6863 | ||
|
|
09ff892b91 | ||
|
|
f462afb547 | ||
|
|
83211e0ef8 | ||
|
|
b7b3cb177f | ||
|
|
10c8c2b4a7 | ||
|
|
c19345c345 | ||
|
|
04fd04b2ab | ||
|
|
6e3c2b2ec9 | ||
|
|
98e29516b0 | ||
|
|
6db5058e3d | ||
|
|
b3ada4a01f | ||
|
|
f38e0abf73 | ||
|
|
6376ed2361 | ||
|
|
04e43d5d41 | ||
|
|
0ba5d8e2bd | ||
|
|
95b8e79624 | ||
|
|
e9c3f8c6da | ||
|
|
27aa72eda1 | ||
|
|
01f6024776 | ||
|
|
aa5730c683 | ||
|
|
fcc2a48192 | ||
|
|
1c4da53e75 | ||
|
|
09a15727c7 | ||
|
|
f1034f4230 | ||
|
|
06e9b8d303 | ||
|
|
b912779ef9 | ||
|
|
ced63a6eb0 | ||
|
|
a5f5d36871 | ||
|
|
bd92345793 | ||
|
|
4c1b66279d | ||
|
|
fcf16eec4f | ||
|
|
508f18c29b | ||
|
|
b165e1c20c | ||
|
|
9ebcfe38bc | ||
|
|
57a585741d | ||
|
|
6173e94cc0 | ||
|
|
243aec37fd | ||
|
|
fb44e020fc | ||
|
|
81842e544b | ||
|
|
6babdab8de | ||
|
|
93d50bbee1 | ||
|
|
e6f0ecce62 | ||
|
|
aba597aa6a | ||
|
|
7444fdceff | ||
|
|
f60197e9bf | ||
|
|
4778206e3a | ||
|
|
005463c41f | ||
|
|
ae1bf1cbfb | ||
|
|
bde06621bd | ||
|
|
647acf6a68 | ||
|
|
120c0b5ca2 | ||
|
|
7e45c89fcd | ||
|
|
b7611c67bb | ||
|
|
d6ae55218f | ||
|
|
d17582d697 | ||
|
|
c3227a6352 | ||
|
|
d0c368f6a0 | ||
|
|
9aa9cc46e9 | ||
|
|
4ee045b84e | ||
|
|
2fe18c14c7 | ||
|
|
f46b002c5a | ||
|
|
65ea11b48b | ||
|
|
b2113add02 | ||
|
|
152e6ce1b9 | ||
|
|
78a5166e48 | ||
|
|
f6a4da0584 | ||
|
|
1818057c4c | ||
|
|
9c946b9808 | ||
|
|
c703106058 | ||
|
|
1bc70f09c5 | ||
|
|
d049572239 | ||
|
|
0feaed8869 | ||
|
|
7496f482ab | ||
|
|
f6faad7255 | ||
|
|
757757fa3f | ||
|
|
1fd3c11e12 | ||
|
|
86e29515e7 | ||
|
|
b1f2273bb5 | ||
|
|
e9cf3f5ab5 | ||
|
|
46eb4185d7 | ||
|
|
8da6761a48 | ||
|
|
f6c34ee2c9 | ||
|
|
b500076f32 | ||
|
|
e87ea67435 | ||
|
|
86c965b94d | ||
|
|
faec42ac71 | ||
|
|
86737fb38a | ||
|
|
c8e64668e9 | ||
|
|
89b7eaac1c | ||
|
|
ac889d921f | ||
|
|
a652a3a0c3 | ||
|
|
0f88b7cfbc | ||
|
|
8bb2157cd9 | ||
|
|
b96cede131 | ||
|
|
b2590fc9e0 | ||
|
|
8dfff29389 | ||
|
|
0ab081c135 | ||
|
|
7a3aca1220 | ||
|
|
1360a5d35b | ||
|
|
9226e5b303 | ||
|
|
4e4cc93092 | ||
|
|
faa3479f0a | ||
|
|
0924f441cb | ||
|
|
f39deefbb4 | ||
|
|
461f1bb102 | ||
|
|
1d1aaef291 | ||
|
|
3c51e80408 | ||
|
|
ff68d23642 | ||
|
|
643b42469e | ||
|
|
a996d00f70 | ||
|
|
6ccab21791 | ||
|
|
b5b965e339 | ||
|
|
2b54db255e | ||
|
|
45fc13f7a0 | ||
|
|
94407e5192 | ||
|
|
7e791e56ce | ||
|
|
2698116c63 | ||
|
|
e17f709fe1 | ||
|
|
c4ec535a82 | ||
|
|
7efc7616f8 | ||
|
|
9468248995 | ||
|
|
a72cbe9c0c | ||
|
|
171df2c5b7 | ||
|
|
ca2bb4ba6f | ||
|
|
9b92bfa81c | ||
|
|
0cc14215e6 | ||
|
|
2e1e294baf | ||
|
|
6cee81aeb0 | ||
|
|
6e653eea7e | ||
|
|
f788be691f | ||
|
|
c167aa90bc | ||
|
|
657056d774 | ||
|
|
1d979cc894 | ||
|
|
dda2d6316d | ||
|
|
cac7ddd6bb | ||
|
|
f677616b22 | ||
|
|
26daf548ff | ||
|
|
d92974dd88 | ||
|
|
52de833b19 | ||
|
|
bb39e8bd4c | ||
|
|
4295b2d114 | ||
|
|
01060574ab | ||
|
|
2e829b560d | ||
|
|
a2f0cafc5c | ||
|
|
6a675736eb | ||
|
|
c8466df61d | ||
|
|
ded26feeb6 | ||
|
|
b2482b092c | ||
|
|
67397dc345 | ||
|
|
4a11147467 | ||
|
|
962131592f | ||
|
|
168a31d331 | ||
|
|
00f08951db | ||
|
|
96d7069e8d | ||
|
|
a4ddacb29c | ||
|
|
93bbf2570e | ||
|
|
ba409b808d | ||
|
|
207d92be64 | ||
|
|
e3c775d0dd | ||
|
|
2bd7e04d18 | ||
|
|
8b58282bde | ||
|
|
ad6be2d5ef | ||
|
|
c47414dad5 | ||
|
|
d84a555171 | ||
|
|
7a5f13bcad | ||
|
|
1b6e3b89b6 | ||
|
|
ed694f4ec5 | ||
|
|
b659218d13 | ||
|
|
cbecebd80a | ||
|
|
7bb6427ec9 | ||
|
|
37724814be | ||
|
|
98dd25c5bb | ||
|
|
ccd69c2c3a | ||
|
|
6d0bd099e1 | ||
|
|
660a715893 | ||
|
|
981cdaecf1 | ||
|
|
18e4c3aa88 | ||
|
|
116c51443e | ||
|
|
66799d0c6c | ||
|
|
9ad9bc5536 | ||
|
|
d1ebf378dc | ||
|
|
4730389668 | ||
|
|
7881abaca2 | ||
|
|
f3d3a386ee | ||
|
|
1b623014c1 | ||
|
|
af3ed871af | ||
|
|
2a9be94f6e | ||
|
|
08a5437210 | ||
|
|
9ff8d01e93 | ||
|
|
0afda87092 | ||
|
|
18d32de483 | ||
|
|
d9ad835fbc | ||
|
|
afad1d7b95 | ||
|
|
e2f5124db9 | ||
|
|
875507d7a9 | ||
|
|
17b4964b01 | ||
|
|
5af1f932bd | ||
|
|
25ff2bd150 | ||
|
|
3654003577 | ||
|
|
ac96ea5308 | ||
|
|
dde270a84c | ||
|
|
52c3153123 | ||
|
|
8557fec581 | ||
|
|
b8e36eb13b | ||
|
|
39624dcfcc | ||
|
|
156d5a02d3 | ||
|
|
eb7ed0c524 | ||
|
|
0415ade906 | ||
|
|
bf6c522918 | ||
|
|
5f8f13e1a5 | ||
|
|
d92cd4ea2c | ||
|
|
2c19b9369a | ||
|
|
78403adeb4 | ||
|
|
5374127d56 | ||
|
|
87f28e7804 | ||
|
|
1e8adfb34f | ||
|
|
d3175e3d64 | ||
|
|
1b832b541a | ||
|
|
29557d764a | ||
|
|
ae46f40fce | ||
|
|
362d837baa | ||
|
|
2db7174068 | ||
|
|
1360ffaec0 | ||
|
|
91e0a29494 | ||
|
|
99b2da752f | ||
|
|
32884da60d | ||
|
|
d537247ead | ||
|
|
d8364c5df2 | ||
|
|
f62b5548a3 | ||
|
|
443fe640b3 | ||
|
|
365d246a89 | ||
|
|
bea0922ee8 | ||
|
|
eb6e8d0c93 | ||
|
|
1b27ccf70e | ||
|
|
3aa806ca65 | ||
|
|
f99735921c | ||
|
|
c934e6617c | ||
|
|
67ce23337f | ||
|
|
b57bc11070 | ||
|
|
f3d1ed5596 | ||
|
|
5112bf9e08 | ||
|
|
60594ff4b7 | ||
|
|
dc3467e7fc | ||
|
|
bb87d064a5 | ||
|
|
30388da021 | ||
|
|
1f7ddcec55 | ||
|
|
942eba868a | ||
|
|
d6e7b136a8 | ||
|
|
55c33ef51f | ||
|
|
d33a554ab1 | ||
|
|
cbbe6ac589 | ||
|
|
84b8fa8ff5 | ||
|
|
3a7eb634de | ||
|
|
5977b0585d | ||
|
|
7b0d73e275 | ||
|
|
143ce8c80f | ||
|
|
4e736a4ff1 | ||
|
|
d2523fc5d2 | ||
|
|
ab4f8d46ad | ||
|
|
75defd0858 | ||
|
|
e52f0954cd | ||
|
|
98e9b62c85 | ||
|
|
40737ea552 | ||
|
|
523f501bd0 | ||
|
|
905c496fa3 | ||
|
|
2ad375884f | ||
|
|
50b8db0c96 | ||
|
|
36f88c772f | ||
|
|
7086358ebc | ||
|
|
51769011be | ||
|
|
d15de88065 | ||
|
|
f909b32db8 | ||
|
|
e8e3fc5fb6 | ||
|
|
90a1bdd5d9 | ||
|
|
0b6eba294e | ||
|
|
98b2a3a6e1 | ||
|
|
3bd21d4486 | ||
|
|
2094d42c3b | ||
|
|
0156dd4956 | ||
|
|
da3e118444 | ||
|
|
2663bc15f4 | ||
|
|
50939c1077 | ||
|
|
b0fc26b6eb | ||
|
|
476f84be77 | ||
|
|
449bb4055c | ||
|
|
86daa13426 | ||
|
|
09aea280b5 | ||
|
|
34113c3698 | ||
|
|
9b21607da5 | ||
|
|
985da48947 | ||
|
|
32f3c646f8 | ||
|
|
d27ef2530d | ||
|
|
fb41606e43 | ||
|
|
cbb7d46ede | ||
|
|
3efe16c840 | ||
|
|
8b800ded21 | ||
|
|
31220b3fde | ||
|
|
f4cae5027e | ||
|
|
d17705db6c | ||
|
|
d6c5c87412 | ||
|
|
89f14f1dba | ||
|
|
5fddb08330 | ||
|
|
f61c447ea5 | ||
|
|
8b8b023665 | ||
|
|
158617f56b | ||
|
|
27b9aa6ddd | ||
|
|
e615274f83 | ||
|
|
2b24f2585f | ||
|
|
0e52deae7b | ||
|
|
bc6b48bd07 | ||
|
|
ce1b9f22cb | ||
|
|
b1d666d7b9 | ||
|
|
ed55a96b80 | ||
|
|
2ae45ecd6e | ||
|
|
f58dbf6ec1 | ||
|
|
adc4760b5f | ||
|
|
c44903e1b0 | ||
|
|
8e081ce04f | ||
|
|
5c6d704a48 | ||
|
|
e11d5bed9d | ||
|
|
5784bf744e | ||
|
|
85716bc6bc | ||
|
|
81e5ee8364 | ||
|
|
52d4505410 | ||
|
|
b28865a283 | ||
|
|
d2ff44e1cf | ||
|
|
9a34b2dd81 | ||
|
|
43dc177a6d | ||
|
|
34046795b7 | ||
|
|
2c132a86b0 | ||
|
|
759fe69909 | ||
|
|
181b672c1a | ||
|
|
2e63d60273 | ||
|
|
46578dbe69 | ||
|
|
cbc9607b26 | ||
|
|
7865ddc51f | ||
|
|
d9c2fe0fb9 | ||
|
|
02c386d76f | ||
|
|
ee0e6ed29f | ||
|
|
e858596658 | ||
|
|
b4519b40eb | ||
|
|
a0bc5fed35 | ||
|
|
2a7bd7e963 | ||
|
|
26b873da9e | ||
|
|
82a8f0481e | ||
|
|
53c8d9372e | ||
|
|
d314e1d050 | ||
|
|
e4a0799163 | ||
|
|
685f987a8e | ||
|
|
b57fff13eb | ||
|
|
4a3201f194 | ||
|
|
8b948bc521 | ||
|
|
91f0da84d2 | ||
|
|
551b2d900e | ||
|
|
06e3c0a41f | ||
|
|
ac3d16b2b4 | ||
|
|
c8c4a3d450 | ||
|
|
6aaccf4f90 | ||
|
|
2d70f637c9 | ||
|
|
cf042271c3 | ||
|
|
7330ca20f8 | ||
|
|
b5ccf79f70 | ||
|
|
9a2303ab73 | ||
|
|
07102e0279 | ||
|
|
0c1042abc9 | ||
|
|
2571f5e5e6 | ||
|
|
c9a968c1a5 | ||
|
|
b74e56974e | ||
|
|
fabaa230de | ||
|
|
c224abfc61 | ||
|
|
26f5c9debf | ||
|
|
ae9a86e796 | ||
|
|
c57a5c04d6 | ||
|
|
d1df21cb8e | ||
|
|
ff59276387 | ||
|
|
0e0ec7a7c7 | ||
|
|
3ecaeffec0 | ||
|
|
4f8d3d27ba | ||
|
|
6575e1d790 | ||
|
|
b255b65dc4 | ||
|
|
b1b15eef4c | ||
|
|
2f5e5535c8 | ||
|
|
5abe9530d7 | ||
|
|
a5113eb90d | ||
|
|
e6f839b554 | ||
|
|
b2ad600de2 | ||
|
|
ea71b43f78 | ||
|
|
d939795b8c | ||
|
|
db9b9992d6 | ||
|
|
c8fd8c4928 | ||
|
|
a5fae64db6 | ||
|
|
5767d83c85 | ||
|
|
7910893529 | ||
|
|
3c003657b9 | ||
|
|
348ffcd077 | ||
|
|
09ea35e340 | ||
|
|
fcac70a2ca | ||
|
|
b5b890beaa | ||
|
|
ca964c13a7 | ||
|
|
979503256a | ||
|
|
c9ab6b23ea | ||
|
|
e84a3bc99a | ||
|
|
310bb53985 | ||
|
|
5b87e74be8 | ||
|
|
8aa7cd166b | ||
|
|
72f530b969 | ||
|
|
b027ce0d91 | ||
|
|
ff5247eaf5 | ||
|
|
148e35ea53 | ||
|
|
8a01a1e471 | ||
|
|
c34208c3df | ||
|
|
bf5c11156a | ||
|
|
6e3b36c070 | ||
|
|
dc4bd64aff | ||
|
|
0d9cd64619 | ||
|
|
cc10c494c6 | ||
|
|
3ef3f561b9 | ||
|
|
2d9627373c | ||
|
|
d618d09bdf | ||
|
|
8a02f2a27a | ||
|
|
ceb63fa09f | ||
|
|
a256d2573c | ||
|
|
5fe9049537 | ||
|
|
6f681aa451 | ||
|
|
63d105437f | ||
|
|
2254bf9c16 | ||
|
|
90a719561b | ||
|
|
d06dc3e2cf | ||
|
|
ff058b06a1 | ||
|
|
26b0c67d13 | ||
|
|
ab24695371 | ||
|
|
1861365124 | ||
|
|
321558c583 | ||
|
|
dd25900af4 | ||
|
|
28252b987b | ||
|
|
b823366a82 | ||
|
|
90ce8bc8ce | ||
|
|
1362976508 | ||
|
|
cfe8c410ae | ||
|
|
5f87718deb | ||
|
|
f15fb9a5d1 | ||
|
|
57cc39c087 | ||
|
|
b9402d3a01 | ||
|
|
b59c76df3b | ||
|
|
7d526196a0 | ||
|
|
f1b3cb9646 | ||
|
|
67de1c6a47 | ||
|
|
41b2a67f09 | ||
|
|
f2636d42a4 | ||
|
|
0c0ef80025 | ||
|
|
bcd0c5ac52 | ||
|
|
005485188f | ||
|
|
9553161b07 | ||
|
|
71e7887da3 | ||
|
|
300fe721a1 | ||
|
|
047eae2d40 | ||
|
|
c3e5e8d6d1 | ||
|
|
466e9ec6fe | ||
|
|
74c88caa19 | ||
|
|
71d086a8db | ||
|
|
728e15772f | ||
|
|
e1ffd8860d | ||
|
|
4b9bddd565 | ||
|
|
f53921d068 | ||
|
|
445618d232 | ||
|
|
b035e27bc8 | ||
|
|
cecbb25857 | ||
|
|
c2823a5ed6 | ||
|
|
3dba97239d | ||
|
|
209afc87bb | ||
|
|
19e8e4a7a1 | ||
|
|
d8ff088231 | ||
|
|
6f14fcb6e8 | ||
|
|
56e95d1d85 | ||
|
|
e12083e3ba | ||
|
|
fa263918ba | ||
|
|
e81b1b9277 | ||
|
|
84635ac889 | ||
|
|
f80764d72b | ||
|
|
04dd9eef09 | ||
|
|
6bd744f12e | ||
|
|
6f155fab0b | ||
|
|
a54f3f7085 | ||
|
|
d86153a08e | ||
|
|
8eb7793cd0 | ||
|
|
79bff902e1 | ||
|
|
825e33c3ea | ||
|
|
0c8a7d51a8 | ||
|
|
05e1bbf86b | ||
|
|
004ec92d68 | ||
|
|
f00502863f | ||
|
|
fe8aa8f727 | ||
|
|
24eccf1e04 | ||
|
|
5ef494b702 | ||
|
|
1673acd537 | ||
|
|
d993b356f9 | ||
|
|
e2fec03a21 | ||
|
|
5e2d97c0b9 | ||
|
|
594e79be5d | ||
|
|
fa50846a05 | ||
|
|
36d5df65b8 | ||
|
|
486b365f9b | ||
|
|
5c59a20714 | ||
|
|
1c10d8015c | ||
|
|
d2d29afcbe | ||
|
|
982a43b8d4 | ||
|
|
17b108597e | ||
|
|
28e42b7920 | ||
|
|
904403f8fa | ||
|
|
84e8eaf33c | ||
|
|
959e09f6c1 | ||
|
|
c8c4b36ebd | ||
|
|
ddad1bbe09 | ||
|
|
a2fc137ebe | ||
|
|
936d887394 | ||
|
|
a396d0335f | ||
|
|
d575f3b3c2 | ||
|
|
0674426d29 | ||
|
|
80ec86361a | ||
|
|
1ebd38bf03 | ||
|
|
274855a19e | ||
|
|
d26d2ae556 | ||
|
|
0a7e1f629e | ||
|
|
a84e9e0923 | ||
|
|
aea2277e26 | ||
|
|
a5d2beb3e7 | ||
|
|
729832ae76 | ||
|
|
21f746d1fa | ||
|
|
d83de8bd15 | ||
|
|
12da8f9b67 | ||
|
|
70550a4866 | ||
|
|
789a4e1340 | ||
|
|
35c999e2fd | ||
|
|
bfdffc14c5 | ||
|
|
e74d3d4cd5 | ||
|
|
514a96c8e4 | ||
|
|
210181feef | ||
|
|
942de63b83 | ||
|
|
93d68996b4 | ||
|
|
369c7554f2 | ||
|
|
c92d9123a0 | ||
|
|
f0be8cf581 | ||
|
|
bb85255ef1 | ||
|
|
e46c5263d9 | ||
|
|
e7372922d7 | ||
|
|
0c9c15f854 | ||
|
|
216e7e5307 | ||
|
|
a92474bf37 | ||
|
|
432220dc86 | ||
|
|
a921b4c7b4 | ||
|
|
d1348f2be4 | ||
|
|
f191f1c81d | ||
|
|
3dfd34b7bf | ||
|
|
ada142ff29 | ||
|
|
ec8b55c1e8 | ||
|
|
70eb9e6b08 | ||
|
|
7eef95f57c | ||
|
|
bac266eadc | ||
|
|
4ba9f6f330 | ||
|
|
0b0ae2fbc2 | ||
|
|
50bf7932a0 | ||
|
|
d3615d2961 | ||
|
|
2c572f8099 | ||
|
|
944aa861f8 | ||
|
|
62c3c91665 | ||
|
|
ecafda86f1 | ||
|
|
354741a81d | ||
|
|
63e6752bd1 | ||
|
|
1e1baa9781 | ||
|
|
94678a6726 | ||
|
|
64b202225a | ||
|
|
0d81dcde10 | ||
|
|
d0ccbc2963 | ||
|
|
8b29c1973d | ||
|
|
c960db5180 | ||
|
|
9233f0dfcc | ||
|
|
dc3c6cad05 | ||
|
|
77798085ce | ||
|
|
3c03f4b18f | ||
|
|
955c8b4346 | ||
|
|
a3759b1959 | ||
|
|
00e87dc3b0 | ||
|
|
13211249c9 | ||
|
|
c677fed916 | ||
|
|
cc5a28ca14 | ||
|
|
8cbae88dc3 | ||
|
|
ae890dab37 | ||
|
|
45ec315252 | ||
|
|
d27cd392f6 | ||
|
|
751008dcf2 | ||
|
|
c16b9968e1 | ||
|
|
ab798bee12 | ||
|
|
30561411e1 | ||
|
|
e1e8c1064a | ||
|
|
94ff01bf2c | ||
|
|
4b1b0e74df | ||
|
|
78205d77f3 | ||
|
|
53a0a41a02 | ||
|
|
8d437c31ce | ||
|
|
6f396087d2 | ||
|
|
a6cd5d2ddb | ||
|
|
261cfe3911 | ||
|
|
bafe23431e | ||
|
|
5759012914 | ||
|
|
c9dbf282f6 | ||
|
|
db1a335509 | ||
|
|
cdb64a4c15 | ||
|
|
fd7cbdbdc4 | ||
|
|
bcbfc09bf4 | ||
|
|
5f3212afc4 | ||
|
|
af7d072486 | ||
|
|
a93fd314ec | ||
|
|
501f4a856a | ||
|
|
55af79dfeb | ||
|
|
607a725395 | ||
|
|
ef602ca70b | ||
|
|
50d489d3c1 | ||
|
|
3c10d750f0 | ||
|
|
cab93b16b6 | ||
|
|
d3d2270f80 | ||
|
|
9b3b758be7 | ||
|
|
dfdaf596bc | ||
|
|
8ee8998381 | ||
|
|
de59434d95 | ||
|
|
e6f9d9b3c8 | ||
|
|
a41dc71965 | ||
|
|
dd200f6d34 | ||
|
|
b1c42b8d8f | ||
|
|
aba870f0ba | ||
|
|
0380fe1fff | ||
|
|
78c4a646d2 | ||
|
|
44056318fd | ||
|
|
f1bb2c75a6 | ||
|
|
dd0489596e | ||
|
|
8389e06aae | ||
|
|
afbb670c78 | ||
|
|
666da8d4e3 | ||
|
|
8bb42addc0 | ||
|
|
0dfd32412e | ||
|
|
54eee7ca9f | ||
|
|
ad936c7f6c | ||
|
|
ee5ec1b7d1 | ||
|
|
d2c5ae8aa9 | ||
|
|
9e3c4134e1 | ||
|
|
c8ea1d0c8a | ||
|
|
bba5c5472c | ||
|
|
29c3923a52 | ||
|
|
1c20929886 | ||
|
|
0078a2ef8e | ||
|
|
ee1c70f608 | ||
|
|
e29bc4e56a | ||
|
|
e1595daeb9 | ||
|
|
77e85f02de | ||
|
|
557892bbc7 | ||
|
|
f3bfc6f126 | ||
|
|
5937a0e3b2 | ||
|
|
94749e4615 | ||
|
|
ea093844b4 | ||
|
|
4d496de488 | ||
|
|
31bfc5388c | ||
|
|
e9cc86e072 | ||
|
|
9ed32307d3 | ||
|
|
23cfdb95f2 | ||
|
|
71ff695290 | ||
|
|
f48aeb0917 | ||
|
|
84c90dd587 | ||
|
|
4c186606bd | ||
|
|
2ffa5fbe36 | ||
|
|
471d18f7fa | ||
|
|
0985cea3d3 | ||
|
|
f0509ae333 | ||
|
|
127a20b29c | ||
|
|
f179030d6c | ||
|
|
76db7b3d21 | ||
|
|
00a3d67707 | ||
|
|
a165dd51c4 | ||
|
|
05eaa3ce54 | ||
|
|
d652bca32d | ||
|
|
0464030544 | ||
|
|
5927781a6b | ||
|
|
763efaf4af | ||
|
|
d9f4ce7347 | ||
|
|
218cab3935 | ||
|
|
81c6351c93 | ||
|
|
dce29857d3 | ||
|
|
3b9da0ecfd | ||
|
|
a728a4d779 | ||
|
|
9bae82592f | ||
|
|
50400459ee | ||
|
|
2ede730cfb | ||
|
|
45609e30ea | ||
|
|
20f817410f | ||
|
|
e276025d60 | ||
|
|
a89a7b5430 | ||
|
|
044807accb | ||
|
|
3e9f63a913 | ||
|
|
c80470d8f5 | ||
|
|
55d19bbc2d | ||
|
|
a3f4376d8f | ||
|
|
2466df0c3d | ||
|
|
425cf33f74 | ||
|
|
077a9937fe | ||
|
|
e6d41f6697 | ||
|
|
59791cdaa5 | ||
|
|
c7a76f90ea | ||
|
|
605e3c26d7 | ||
|
|
a59fc3f3e7 | ||
|
|
b5667c17d3 | ||
|
|
bc80aaea63 | ||
|
|
6746c9a2d3 | ||
|
|
11ad4d8a0f | ||
|
|
752f0177cf | ||
|
|
2078305ff4 | ||
|
|
924502113e | ||
|
|
c95e804733 | ||
|
|
6a356d4a45 | ||
|
|
2bfcbd8fb1 | ||
|
|
19bfcea412 | ||
|
|
0d7a013bba | ||
|
|
931b153290 | ||
|
|
9f91fffcab | ||
|
|
6aea27e30f | ||
|
|
134f2671fb | ||
|
|
6438d1ee57 | ||
|
|
a6a1d44204 | ||
|
|
cf97c7eb59 | ||
|
|
e8713ff260 | ||
|
|
993724d945 | ||
|
|
8c89fdec95 | ||
|
|
a49bfec7d9 | ||
|
|
050ecd9290 | ||
|
|
395bd604ce | ||
|
|
4f91c78b4f | ||
|
|
20405ae9ab | ||
|
|
c18640eab0 | ||
|
|
854584138b | ||
|
|
2d129f6233 | ||
|
|
4aa3e27880 | ||
|
|
094387335d | ||
|
|
ff1cf12e0c | ||
|
|
2f44beccc2 | ||
|
|
558e48f671 | ||
|
|
e60048f5fb | ||
|
|
6ad1dd1a77 | ||
|
|
4bfbdf31e4 | ||
|
|
71c2b4b7e8 | ||
|
|
292f743b14 | ||
|
|
dd6dfffd57 | ||
|
|
ec705a5307 | ||
|
|
f45f071710 | ||
|
|
4630e4f4a0 | ||
|
|
d872ce0ab6 | ||
|
|
c04b032abf | ||
|
|
b218bf1d2e | ||
|
|
18f787703a | ||
|
|
a3719b12e5 | ||
|
|
8c91311c1e | ||
|
|
096336042e | ||
|
|
4ca25fcfc2 | ||
|
|
12b4bd3795 | ||
|
|
ba96d587ab | ||
|
|
411f3be3aa | ||
|
|
ffacefb792 | ||
|
|
2d1689b79a | ||
|
|
78e0d30687 | ||
|
|
deb0952266 | ||
|
|
9f77cfd0bf | ||
|
|
767c6da325 | ||
|
|
f034d908b6 | ||
|
|
bc391d014e | ||
|
|
d42cd8ebf9 | ||
|
|
ac5635b671 | ||
|
|
c76b250c3b | ||
|
|
4630287924 | ||
|
|
d3cae8ec52 | ||
|
|
149fa72566 | ||
|
|
a0d13df033 | ||
|
|
c9a3fad9eb | ||
|
|
eeb96199f9 | ||
|
|
765b1a1b39 | ||
|
|
78eef7cb9d | ||
|
|
1ad9f8d9cf | ||
|
|
36734a4768 | ||
|
|
d0f91c5f43 | ||
|
|
1cd980f46a | ||
|
|
74162ab4e7 | ||
|
|
0ef5579540 | ||
|
|
9501f02aa2 | ||
|
|
ea8850f8bd | ||
|
|
949842118b | ||
|
|
a4d31711a0 | ||
|
|
94f0d040ea | ||
|
|
bb6e2b9336 | ||
|
|
d0166f7f12 | ||
|
|
3cef6d7a65 | ||
|
|
6a1bc00f04 | ||
|
|
bee4b3970d | ||
|
|
f3858546de | ||
|
|
de40cb8920 | ||
|
|
8ddf1315eb | ||
|
|
00d5b16de7 | ||
|
|
0b02bb417a | ||
|
|
d68260411d | ||
|
|
7227937660 | ||
|
|
2ac3640760 | ||
|
|
71de48fd32 | ||
|
|
834eceab16 | ||
|
|
20de1734d1 | ||
|
|
63e734b0aa | ||
|
|
cf53c56ac7 | ||
|
|
e82433c530 | ||
|
|
3a4e6aa852 | ||
|
|
6c46770330 | ||
|
|
e66e2c40a6 | ||
|
|
39bc0d5fb2 | ||
|
|
50e73e2a04 | ||
|
|
efa9875744 | ||
|
|
8182b0e95a | ||
|
|
6091621858 | ||
|
|
ba568581ff | ||
|
|
c78190c3a0 | ||
|
|
84c968e053 | ||
|
|
6b7da4068c | ||
|
|
6dc63907f1 | ||
|
|
e3e14e7a66 | ||
|
|
997ced3938 | ||
|
|
e2f149caf9 | ||
|
|
da94fc052d | ||
|
|
b18da9064b | ||
|
|
77a6304fc3 | ||
|
|
7c44f22c45 | ||
|
|
d39d62c2f8 | ||
|
|
8f68163f90 | ||
|
|
b3ee5f4d9a | ||
|
|
5398abb074 | ||
|
|
65397d3e1e | ||
|
|
887d53528b | ||
|
|
1603eafbb2 | ||
|
|
33bcb54aaf | ||
|
|
d36f87707a | ||
|
|
226c1fd6c5 | ||
|
|
74b961ab29 | ||
|
|
e4358ba489 | ||
|
|
26522cb061 | ||
|
|
7a2038e124 | ||
|
|
e98d84efdb | ||
|
|
7faffafd8a | ||
|
|
bedfd3ee30 | ||
|
|
94473bdeaa | ||
|
|
be128da9e0 | ||
|
|
381afcbb6d | ||
|
|
1dc008855e | ||
|
|
bbf4431b5f | ||
|
|
c38ad8e382 | ||
|
|
59af1fd62a | ||
|
|
55ae0bec93 | ||
|
|
64cb509d3b | ||
|
|
9d67a56f29 | ||
|
|
b5ee7100f4 | ||
|
|
ee1515d787 | ||
|
|
6f0e98b1a1 | ||
|
|
b8e4430a14 | ||
|
|
65e3187418 | ||
|
|
ea48cf3722 | ||
|
|
2c0ad1a4cc | ||
|
|
7de8fd8b53 | ||
|
|
7eddf4882c | ||
|
|
58e95610e8 | ||
|
|
1416bad9cc | ||
|
|
0e0e9eccf1 | ||
|
|
0dcc953100 | ||
|
|
38fffd4932 | ||
|
|
17c7ae7d7c | ||
|
|
15760117ee | ||
|
|
1cd5e0781d | ||
|
|
f64fbd1040 | ||
|
|
ceb9246cf2 | ||
|
|
032a631b61 | ||
|
|
5d0f6b665b | ||
|
|
4bbf096350 | ||
|
|
e1a950ec21 | ||
|
|
ff14fab492 | ||
|
|
df66d0441c | ||
|
|
ede8e7dfce | ||
|
|
4edfa4d244 | ||
|
|
9d155d6a0e | ||
|
|
1fa92eec57 | ||
|
|
7fc28fd1bb | ||
|
|
bb24da92e4 | ||
|
|
dd338f5f4b | ||
|
|
486bc5ac28 | ||
|
|
68215f37f5 | ||
|
|
4449d3fce8 | ||
|
|
cbb20439ed | ||
|
|
ec357b71f1 | ||
|
|
5acdb4dc31 | ||
|
|
dd959a7316 | ||
|
|
0b6dbe8bfa | ||
|
|
78e6263a2e | ||
|
|
7f5e3d3b4c | ||
|
|
66d5d3db8c | ||
|
|
3b2a4d1eb4 | ||
|
|
485195e035 | ||
|
|
d735f397d8 | ||
|
|
5e41481e27 | ||
|
|
d52c54d6fb | ||
|
|
36c85eed5e | ||
|
|
b09deb1494 | ||
|
|
71d22f6768 | ||
|
|
b68e9dfd4b | ||
|
|
b4362a4539 | ||
|
|
4e783d414c | ||
|
|
9fe6b9d0e5 | ||
|
|
cb4cce119b | ||
|
|
665c33d1a9 | ||
|
|
0ed3bfff4a | ||
|
|
9ba0c6df37 | ||
|
|
312b0d4665 | ||
|
|
7acbda4cda | ||
|
|
247fc6be8e | ||
|
|
1407a61f7a | ||
|
|
a3c302176b | ||
|
|
03130d3de2 | ||
|
|
80d42d92db | ||
|
|
115a596c1e | ||
|
|
ab5815a944 | ||
|
|
7e174a1b7d | ||
|
|
1f08940e47 | ||
|
|
8db5724b77 | ||
|
|
a21352ae4f | ||
|
|
a38f4978fa | ||
|
|
4f05ddab93 | ||
|
|
bbbeb155f0 | ||
|
|
0946254764 | ||
|
|
316dfd02e4 | ||
|
|
c35833fab1 | ||
|
|
81cd6ed764 | ||
|
|
63ff517b30 | ||
|
|
30a2a9aa2a | ||
|
|
cfc8570dca | ||
|
|
7841b44b43 | ||
|
|
e3f51b34b5 | ||
|
|
dcedced8fe | ||
|
|
e437597ef4 | ||
|
|
6d3f768e92 | ||
|
|
c114581400 | ||
|
|
fbfd792416 | ||
|
|
755f1d17d6 | ||
|
|
3e4f7fdc17 | ||
|
|
f6238cd6ab | ||
|
|
754f4e8244 | ||
|
|
a2925011ed | ||
|
|
d658b67e93 | ||
|
|
9b7b5ebfa8 | ||
|
|
b54777bccb | ||
|
|
17b9b8ec3a | ||
|
|
3dc2ab54ac | ||
|
|
97451d2b7a | ||
|
|
e2c675d9a5 | ||
|
|
3e3a600a60 | ||
|
|
1f0d2b147b | ||
|
|
c99aaf1e7d | ||
|
|
7e87dc9de6 | ||
|
|
f556fb61ca | ||
|
|
84dca5e794 | ||
|
|
60d4997985 | ||
|
|
d2900c347a | ||
|
|
285b0dc01d | ||
|
|
172153b840 | ||
|
|
ce71b8b5f6 | ||
|
|
d72d2b33bd | ||
|
|
d15fbe7801 | ||
|
|
3627f884c3 | ||
|
|
e89260b8b0 | ||
|
|
ab6267e122 | ||
|
|
731ec684f0 | ||
|
|
bbf43a0744 | ||
|
|
ae51189953 | ||
|
|
9b54c1aa04 | ||
|
|
caab1cae39 | ||
|
|
da04a0d161 | ||
|
|
9351b6c44a | ||
|
|
5d6231f51c | ||
|
|
945697c538 | ||
|
|
dced355a29 | ||
|
|
f133fdfb1c | ||
|
|
687c76703b | ||
|
|
e5558f7fa9 | ||
|
|
60b424705d | ||
|
|
6ed378086b | ||
|
|
66d7dde79e | ||
|
|
80ea1e95ee | ||
|
|
4098cef279 | ||
|
|
c7628ac07f | ||
|
|
8cd08fa765 | ||
|
|
1f493b208c | ||
|
|
3e8d175242 | ||
|
|
360240ee58 | ||
|
|
95c5fb7391 | ||
|
|
fcd221a9b0 | ||
|
|
397522292c | ||
|
|
d0aac18b88 | ||
|
|
fbf799e4c4 | ||
|
|
7bb35812ff | ||
|
|
ad49267b29 | ||
|
|
88404bcb56 | ||
|
|
33af6ec57e | ||
|
|
4b3a0bf4e2 | ||
|
|
508dd399be | ||
|
|
ad2dbd6fc5 | ||
|
|
a51bc276b5 | ||
|
|
132acaf758 | ||
|
|
cd807cd035 | ||
|
|
df547de5f6 | ||
|
|
e1ed362d7b | ||
|
|
4cbf7e2a2d | ||
|
|
1ec00bceed | ||
|
|
68ebe55ca4 | ||
|
|
be854ec14c | ||
|
|
ca8647578b | ||
|
|
c90a1ca9ea | ||
|
|
45734d52af | ||
|
|
da0850124c | ||
|
|
3eb86fd6c3 | ||
|
|
df2a0f6ec4 | ||
|
|
3b196fc90d | ||
|
|
6370296c53 | ||
|
|
6ffb0d9676 | ||
|
|
0e85b54cba | ||
|
|
d1b948aa56 | ||
|
|
5e85c4f8ec | ||
|
|
8b2466dcde | ||
|
|
c754d19015 | ||
|
|
ad680de897 | ||
|
|
78ae4d7a7d | ||
|
|
74acf2fe0f | ||
|
|
bab9fd8ec4 | ||
|
|
cfce36c212 | ||
|
|
5c6d6da4f9 | ||
|
|
5005d06507 | ||
|
|
9a09062a84 | ||
|
|
6dc993a276 | ||
|
|
c45ceec33b | ||
|
|
c28de99907 | ||
|
|
b587c08768 | ||
|
|
71043963e7 | ||
|
|
04b17a15bb | ||
|
|
4aa9d11574 | ||
|
|
56b5739cfc | ||
|
|
bb1704ed7a | ||
|
|
9163fc74d4 | ||
|
|
49113edeff | ||
|
|
718ede8439 | ||
|
|
5ee49d8dad | ||
|
|
c19885d730 | ||
|
|
d2e64468d5 | ||
|
|
e3b6a3308f | ||
|
|
c3f88ae0c8 | ||
|
|
31a5868864 | ||
|
|
4cb50a1dff | ||
|
|
2062703565 | ||
|
|
5e00f07fd8 | ||
|
|
3bf2110df1 | ||
|
|
c3ff60dced | ||
|
|
7cf3b8f1a9 | ||
|
|
4536d60e3e | ||
|
|
2f1ea4da67 | ||
|
|
34f6b412f6 | ||
|
|
b16a16d100 | ||
|
|
55750844ea | ||
|
|
6c09134552 | ||
|
|
8fa74c63c9 | ||
|
|
275ac351fe | ||
|
|
b728827324 | ||
|
|
2db99a3e32 | ||
|
|
263c011a7b | ||
|
|
8eeebb0cef | ||
|
|
f055e53987 | ||
|
|
6dce5f5931 | ||
|
|
14cfbf78bd | ||
|
|
6de577839b | ||
|
|
bfcdf3ef98 | ||
|
|
ffa7ddebb8 | ||
|
|
29838a433a | ||
|
|
1f1422bedd | ||
|
|
d41e634611 | ||
|
|
22cc890cab | ||
|
|
15e940bd2d | ||
|
|
c788f53bb9 | ||
|
|
10f1c5781e | ||
|
|
75b0869a77 | ||
|
|
c4c341922b | ||
|
|
43b4adc618 | ||
|
|
bad982dbdd | ||
|
|
5d45064c2d | ||
|
|
6390f4aa48 | ||
|
|
bb94d56bd0 | ||
|
|
14f3d9ab12 | ||
|
|
d9ecf0efb8 | ||
|
|
9b66b02e46 | ||
|
|
feca878fdd | ||
|
|
f53fd74873 | ||
|
|
bbaf892523 | ||
|
|
451b3abddf | ||
|
|
ba03add3d3 | ||
|
|
a41de7ed1c | ||
|
|
28de5bb097 | ||
|
|
d08a1224e2 | ||
|
|
327cf7ec75 | ||
|
|
83438129a2 | ||
|
|
9b3b7fc8ff | ||
|
|
8000497302 | ||
|
|
84589a4b40 | ||
|
|
067129f5a9 | ||
|
|
ffcf98b06b | ||
|
|
b2f001e416 | ||
|
|
44c31c9c61 | ||
|
|
8aa659eee2 | ||
|
|
51bf163149 | ||
|
|
d5c02f6b94 | ||
|
|
55fa968afb | ||
|
|
a9aac33a46 | ||
|
|
1f45506b37 | ||
|
|
9d9d88b171 | ||
|
|
291da3f7fe | ||
|
|
70cfbc3715 | ||
|
|
9d80c2cea7 | ||
|
|
a7f7959f91 | ||
|
|
082268d9fc | ||
|
|
945ef2f1b0 | ||
|
|
1f1feed3ae | ||
|
|
290890f6e7 | ||
|
|
b7bfd803eb | ||
|
|
2151905d46 | ||
|
|
fdde118af1 | ||
|
|
5f9ba2e04b | ||
|
|
ca871f654b | ||
|
|
3c91ac27dc | ||
|
|
94b4cb0baf | ||
|
|
1b956c6ad7 | ||
|
|
b9b79bbd9a | ||
|
|
9802581301 | ||
|
|
c1f39fbf57 | ||
|
|
f66e7712c3 | ||
|
|
2bb6d985cc | ||
|
|
d300ed38ea | ||
|
|
9cbb810fe4 | ||
|
|
cfa486d8cc | ||
|
|
ab1924d266 | ||
|
|
d6de0f6fa8 | ||
|
|
d1afea104e | ||
|
|
fae8cf83cd | ||
|
|
5a69ac074f | ||
|
|
15f0560005 | ||
|
|
6f7fa6abd9 | ||
|
|
cbd55b0366 | ||
|
|
f1dbd3018d | ||
|
|
502613b433 | ||
|
|
2bdc0d59cf | ||
|
|
ce0ee49ebf | ||
|
|
bc3bdb1221 | ||
|
|
3bea7576b5 | ||
|
|
d70b5d7dc0 | ||
|
|
eb30c1e5c7 | ||
|
|
17cbd2623f | ||
|
|
dc27a44c0c | ||
|
|
f72b02ab0b | ||
|
|
c0946f1e0c | ||
|
|
575fdcb8cd | ||
|
|
783ea7901c | ||
|
|
44e6bb79a2 | ||
|
|
ab3b9f13b5 | ||
|
|
dc6e7f7b1b | ||
|
|
94b75dda24 | ||
|
|
a529879889 | ||
|
|
4b7bbb3d50 | ||
|
|
88c98efd94 | ||
|
|
147be12583 | ||
|
|
137e047205 | ||
|
|
4bd6db31c0 | ||
|
|
1350638fb3 | ||
|
|
b6de431a56 | ||
|
|
1aef27da33 | ||
|
|
1e78979ed0 | ||
|
|
ba90ebda4c | ||
|
|
7c1bade54d | ||
|
|
a46b394714 | ||
|
|
6dff1136c5 | ||
|
|
ccbb56d403 | ||
|
|
4f3a7e5451 | ||
|
|
e4cda4087e | ||
|
|
6eeb8de02c | ||
|
|
29c4fec90a | ||
|
|
5c9ba8de43 | ||
|
|
2358f6a9c9 | ||
|
|
7097279dca | ||
|
|
c3eb553425 | ||
|
|
24f64fac6b | ||
|
|
4cdd5e9f20 | ||
|
|
de9e261807 | ||
|
|
c26793c68d | ||
|
|
11fd833cdb | ||
|
|
2b21a0c31f | ||
|
|
7e888f6408 | ||
|
|
bba70ce852 | ||
|
|
e96b5af0c8 | ||
|
|
5f5e786c0e | ||
|
|
8d66e43117 | ||
|
|
ccbc809ecf | ||
|
|
e67fde24f1 | ||
|
|
923494fdce | ||
|
|
73bff2cabe | ||
|
|
c20fc5a345 | ||
|
|
f71c62f167 | ||
|
|
bdaf9e6dc6 | ||
|
|
a2730fb17c | ||
|
|
5061ddf38e | ||
|
|
1735c036cc | ||
|
|
fa703db41e | ||
|
|
9665f4136a | ||
|
|
abeef2be4a | ||
|
|
995420354e | ||
|
|
1af1a1863a | ||
|
|
fd04f8be5a | ||
|
|
ccc9e6dcfb | ||
|
|
9e23710c6d | ||
|
|
3878a3ee0b | ||
|
|
0c9d0a4d15 | ||
|
|
739c007c95 | ||
|
|
6f27253441 | ||
|
|
b900932574 | ||
|
|
0903b31fcf | ||
|
|
d080f5db0f | ||
|
|
5029a1625e | ||
|
|
2950f167aa | ||
|
|
afdf9a3dfb | ||
|
|
132ca9d106 | ||
|
|
990d75d42a | ||
|
|
1c48aa8444 | ||
|
|
67e07813cb | ||
|
|
7fbeed88a9 | ||
|
|
9053e7ac88 | ||
|
|
e3c94cc1f7 | ||
|
|
e8d6c4d451 | ||
|
|
ba1813c767 | ||
|
|
bcd8992abc | ||
|
|
557b532f74 | ||
|
|
d450464a1a | ||
|
|
5d10b28433 | ||
|
|
eafe358deb | ||
|
|
048bd877da | ||
|
|
7f18dd942f | ||
|
|
424234ec48 | ||
|
|
4168a08276 | ||
|
|
996063d76d | ||
|
|
085f5ff22f | ||
|
|
5b5dc6a8cc | ||
|
|
4103bad8de | ||
|
|
d6a8563cc7 | ||
|
|
c9ef0bcd7b | ||
|
|
6b978759ca | ||
|
|
bc31d54028 | ||
|
|
7bad0e04b1 | ||
|
|
7144c746c6 | ||
|
|
d5592e5662 | ||
|
|
12b027110e | ||
|
|
644896faff | ||
|
|
f41f7c0769 | ||
|
|
68f1a891ca | ||
|
|
b186311968 | ||
|
|
c21b594cf0 | ||
|
|
21ec46843a | ||
|
|
4da827501a | ||
|
|
5d0c7bfd32 | ||
|
|
d583409af4 | ||
|
|
559e607601 | ||
|
|
276b27d8a6 | ||
|
|
2134f42cfd | ||
|
|
88135d2750 | ||
|
|
1b1a4bcad4 | ||
|
|
af8e82a860 | ||
|
|
c8d5e37b44 | ||
|
|
781fe03b5d | ||
|
|
702103aa66 | ||
|
|
d2503431c6 | ||
|
|
dccdf18226 | ||
|
|
a92f287256 | ||
|
|
a193d67f11 | ||
|
|
6873e113bb | ||
|
|
77860d9d05 | ||
|
|
de97010cfb | ||
|
|
89245c7af7 | ||
|
|
d51745774f | ||
|
|
deda1daa04 | ||
|
|
8594c4a7eb | ||
|
|
d0d73aa5e2 | ||
|
|
1f43bb201c | ||
|
|
6e92d31f5d | ||
|
|
ae4070b7f2 | ||
|
|
ee2ac18b69 | ||
|
|
85ce697dad | ||
|
|
82eb79ce40 | ||
|
|
c76b4b9ede | ||
|
|
9c2e814e16 | ||
|
|
2e9499ea90 | ||
|
|
52296bfed1 | ||
|
|
a53d54a1d6 | ||
|
|
81cf08b723 | ||
|
|
f192d7dffc | ||
|
|
5425896988 | ||
|
|
9e2be00b5c | ||
|
|
06004ce478 | ||
|
|
5bda018d25 | ||
|
|
b12729a874 | ||
|
|
d3a84e02e4 | ||
|
|
9865c84df5 | ||
|
|
33d2cb9a49 | ||
|
|
37c121e8de | ||
|
|
9dd0bf01e2 | ||
|
|
92ac601072 | ||
|
|
dfd7647838 | ||
|
|
058246e2ce | ||
|
|
8ed789892b | ||
|
|
57775af24b | ||
|
|
845681b6dc | ||
|
|
ea2c9cbc9a | ||
|
|
96bb3013a3 | ||
|
|
aeee37fdae | ||
|
|
551497bfeb | ||
|
|
57057cac0e | ||
|
|
dccd9f09e7 | ||
|
|
b91e474343 | ||
|
|
f25262c191 | ||
|
|
723662a6bc | ||
|
|
757fcd3ea4 | ||
|
|
ec3e28436d | ||
|
|
6a291ef1e2 | ||
|
|
1e529a9e19 | ||
|
|
05d393e7fe | ||
|
|
c757ace2b4 | ||
|
|
5ab066de5f | ||
|
|
b0ae22d493 | ||
|
|
03a6abcfc6 | ||
|
|
e58491ed97 | ||
|
|
1e92ae05c0 | ||
|
|
513b6181a4 | ||
|
|
05b54bc6f5 | ||
|
|
1d4634a76c | ||
|
|
342cf12ae7 | ||
|
|
6883dbbce1 | ||
|
|
a3d3706b89 | ||
|
|
da911e374a | ||
|
|
26904f4e0e | ||
|
|
ec5601f3ca | ||
|
|
a2ee2a5e6b | ||
|
|
78d5f8b76d | ||
|
|
dd0cc00004 | ||
|
|
825836c447 | ||
|
|
4996eb9e3c | ||
|
|
0bc3a3c34e | ||
|
|
ca5bc313ee | ||
|
|
069df92dbf | ||
|
|
bdce4ee9f9 | ||
|
|
756a136124 | ||
|
|
4809ef3537 | ||
|
|
2c056d6807 | ||
|
|
90af466a2f | ||
|
|
20cbbcd9f4 | ||
|
|
036953044e | ||
|
|
499456fa77 | ||
|
|
22d9705412 | ||
|
|
663c853aff | ||
|
|
103ffa4761 | ||
|
|
3baedf01d1 | ||
|
|
bfad6d34b5 | ||
|
|
2aa2564078 | ||
|
|
6561bb5a6c | ||
|
|
ac75176292 | ||
|
|
1005079f71 | ||
|
|
e0e07a9deb | ||
|
|
c65005e5a6 | ||
|
|
64f2f82e0c | ||
|
|
98495c8114 | ||
|
|
d0ac0b7804 | ||
|
|
e45c31345e | ||
|
|
23cd677133 | ||
|
|
805c1298fb | ||
|
|
5199edff1e | ||
|
|
f3adc13c6d | ||
|
|
40f5f7026d | ||
|
|
b8b96763cf | ||
|
|
80b94fcd00 | ||
|
|
8d09d3c654 | ||
|
|
03564b3a82 | ||
|
|
a7569256d0 | ||
|
|
b0d4a094c1 | ||
|
|
1b232adc72 | ||
|
|
ed81e095ee | ||
|
|
76734b77f1 | ||
|
|
d8284ec09f | ||
|
|
6e982acde8 | ||
|
|
db6f8eaba1 | ||
|
|
eb7ad7163f | ||
|
|
6a478eec5e | ||
|
|
1dc08d6399 | ||
|
|
c3cfed5ac3 | ||
|
|
aba1dc93d9 | ||
|
|
d9bbf35afc | ||
|
|
dec3abfab9 | ||
|
|
bb98f13b19 | ||
|
|
8b10970e03 | ||
|
|
b859ab9d78 | ||
|
|
26208103fc | ||
|
|
d150b017e3 | ||
|
|
49414adfd2 | ||
|
|
5c675c7ce7 | ||
|
|
f16aaf7874 | ||
|
|
d80831e708 | ||
|
|
3e3e7156ec | ||
|
|
dbb2a365cb | ||
|
|
52a8c7288d | ||
|
|
42f6bf6182 | ||
|
|
9d4d3738ff | ||
|
|
1647a6d0a7 | ||
|
|
e5e058672d | ||
|
|
f5cd8e2523 | ||
|
|
fd2cc8aff1 | ||
|
|
f244fb837b | ||
|
|
b4848ab7f9 | ||
|
|
0fa60f0502 | ||
|
|
8d290317d7 | ||
|
|
cb5c8f3a85 | ||
|
|
5a0f4c1462 | ||
|
|
e4445413fd | ||
|
|
9be13eb0b9 | ||
|
|
2c7eeeca7b | ||
|
|
b59a552288 | ||
|
|
def67899ed | ||
|
|
ea0870c180 | ||
|
|
379d57ca8e | ||
|
|
761df2b4cc | ||
|
|
56e1f02f69 | ||
|
|
f921ef4708 | ||
|
|
f11dc9bc25 | ||
|
|
0ebfcd7238 | ||
|
|
5fb9cd142f | ||
|
|
7d5e112efb | ||
|
|
e82dd816de | ||
|
|
887a245d82 | ||
|
|
2f706b33fa | ||
|
|
dcbdb04009 | ||
|
|
008903cbbd | ||
|
|
b67113fc1f | ||
|
|
3afbe832cc | ||
|
|
9adfa0ecfc | ||
|
|
f081d7fd3c | ||
|
|
394a3253aa | ||
|
|
13890d2835 | ||
|
|
6fd3e567cd | ||
|
|
b6d8e55b00 | ||
|
|
e6d3d347ab | ||
|
|
c159ce7eb9 | ||
|
|
65c9bf22dc | ||
|
|
87dfffeddb | ||
|
|
8d6c676fed | ||
|
|
324d27896b | ||
|
|
fd5fcf356f | ||
|
|
0dde5a9d2b | ||
|
|
73f7603c1d | ||
|
|
5faffc3886 | ||
|
|
d09d5a7dbe | ||
|
|
b906fecdff | ||
|
|
a6e4122e44 | ||
|
|
e663ecb458 | ||
|
|
95876e28bf | ||
|
|
348932d929 | ||
|
|
bd28516324 | ||
|
|
d1fc050bed | ||
|
|
bace01e4f7 | ||
|
|
0a56ee7dbb | ||
|
|
c90be99216 | ||
|
|
a0a431e0e2 | ||
|
|
4489e7149c | ||
|
|
ec81420894 | ||
|
|
619dd0c99d | ||
|
|
9f5a5108fb | ||
|
|
974510b2c7 | ||
|
|
f2f10f0c79 | ||
|
|
39cbcd4d6f | ||
|
|
1accdfcafb | ||
|
|
e37bbe420c | ||
|
|
58c4455076 | ||
|
|
a413ffb9d2 | ||
|
|
1f96622e74 | ||
|
|
9527cf6abf | ||
|
|
13f0bc3296 | ||
|
|
58a0ec9cca | ||
|
|
fe385de342 | ||
|
|
ae5fb26387 | ||
|
|
cd67ab03ff | ||
|
|
94f134f3fe | ||
|
|
ca95e44a81 | ||
|
|
ecc045b2a8 | ||
|
|
9c205a07c5 | ||
|
|
c414354708 | ||
|
|
ddacd2d9d7 | ||
|
|
987fcb4a5a | ||
|
|
4c70da28e6 | ||
|
|
246684f28c | ||
|
|
e450a0e096 | ||
|
|
90d2144588 | ||
|
|
4302d314cf | ||
|
|
22322a55ed | ||
|
|
d09c0436e0 | ||
|
|
9b619216cb | ||
|
|
fe0a855618 | ||
|
|
3bca7c9a13 | ||
|
|
87887494a5 | ||
|
|
614a8cb14b | ||
|
|
da6e64e89f | ||
|
|
87d0db0b5c | ||
|
|
c756df90fc | ||
|
|
d3053d8ce2 | ||
|
|
4c728cf777 | ||
|
|
17b90af972 | ||
|
|
a0339f5ae2 | ||
|
|
732aaffbb6 | ||
|
|
d50606ce13 | ||
|
|
777c9db0f6 | ||
|
|
815397dba6 | ||
|
|
01f361e7cd | ||
|
|
1278776297 | ||
|
|
a3a8e515bf | ||
|
|
96af9afc83 | ||
|
|
c841c8c284 | ||
|
|
d6ee8ccb2d | ||
|
|
a7930d8403 | ||
|
|
eeeb889ba7 | ||
|
|
f1f4147628 | ||
|
|
42118e0169 | ||
|
|
b67d69a5c8 | ||
|
|
d8144c901d | ||
|
|
e9d9cf7e48 | ||
|
|
0867d7fe0e | ||
|
|
b646331adc | ||
|
|
30109a54ce | ||
|
|
e85ca7dcd3 | ||
|
|
faf05ceb72 | ||
|
|
049e37ba82 | ||
|
|
d187ee23ea | ||
|
|
20b0c9653d | ||
|
|
022b667858 | ||
|
|
1b4af09185 | ||
|
|
58ca4efd42 | ||
|
|
f1bb183017 | ||
|
|
081c11c503 | ||
|
|
d54843635a | ||
|
|
12638275fa | ||
|
|
25c50435ca | ||
|
|
e1c61cfadb | ||
|
|
06fd860964 | ||
|
|
ab4e1f63c5 | ||
|
|
fb998a7e6a | ||
|
|
dbbbfc1170 | ||
|
|
eb432d9acb | ||
|
|
23f65b9eb2 | ||
|
|
c11ea4fe0d | ||
|
|
6b1cfffb6f | ||
|
|
918c0315eb | ||
|
|
106517fc8b | ||
|
|
96070d6b42 | ||
|
|
b22473d0d0 | ||
|
|
ef842b6c52 | ||
|
|
e9415c1708 | ||
|
|
0114efc66f | ||
|
|
84e7847c16 | ||
|
|
3f935942ea | ||
|
|
8f45322b7b | ||
|
|
6e095c1085 | ||
|
|
acaceefc89 | ||
|
|
21c3a4bee8 | ||
|
|
a959af80b5 | ||
|
|
e0d7e7698e | ||
|
|
dbf9912c5a | ||
|
|
f9038b9180 | ||
|
|
506aacbd83 | ||
|
|
63e31d672b | ||
|
|
0d5d353e99 | ||
|
|
766ec6a6e4 | ||
|
|
872effb297 | ||
|
|
fb13b79a76 | ||
|
|
706daeb678 | ||
|
|
9d6af82f9c | ||
|
|
2bc37027dd | ||
|
|
e70b6b210e | ||
|
|
df8a36c695 | ||
|
|
702af4b1c8 | ||
|
|
cd15e11ce3 | ||
|
|
ca8b236ccb | ||
|
|
06b057f4a2 | ||
|
|
0158c9b434 | ||
|
|
368728d12b | ||
|
|
c1cab103aa | ||
|
|
d9da85638c | ||
|
|
a25989d213 | ||
|
|
6bfe4fe258 | ||
|
|
e7cbb8cc77 | ||
|
|
06354efa76 | ||
|
|
345f05b23f | ||
|
|
f0489cb918 | ||
|
|
d0b265985b | ||
|
|
82f0902fa4 | ||
|
|
1dcb44f81a | ||
|
|
9bbc421407 | ||
|
|
54f335946e | ||
|
|
d95d8278de | ||
|
|
c813339945 | ||
|
|
cc44bc9d7f | ||
|
|
4cd655fb36 | ||
|
|
5845c37672 | ||
|
|
9ae81779ff | ||
|
|
df4aa64883 | ||
|
|
70f94322ee | ||
|
|
c2ccb4edda | ||
|
|
f42fafb25f | ||
|
|
0bccf9d358 | ||
|
|
d3f105d9b9 | ||
|
|
5c240316ee | ||
|
|
2fde0c6d8c | ||
|
|
76d78f34ee | ||
|
|
3efb3a99e9 | ||
|
|
65dfc3b878 | ||
|
|
b820aad3fc | ||
|
|
09d4cae6f4 | ||
|
|
85308e52a7 | ||
|
|
4f42ae7efc | ||
|
|
7cc2eac79d | ||
|
|
e3487b4845 | ||
|
|
354b2facf7 | ||
|
|
0d381fd5e9 | ||
|
|
65b7d64e92 | ||
|
|
e79115d719 | ||
|
|
be4f49e96d | ||
|
|
6b24a71ad7 | ||
|
|
1ccacdc600 | ||
|
|
3ea71e1dfb | ||
|
|
d8e324a005 | ||
|
|
7122e878a5 | ||
|
|
c8848a9e76 | ||
|
|
84499dab35 | ||
|
|
e9c695f76a | ||
|
|
029ade8dd6 | ||
|
|
0ebb509205 | ||
|
|
9fa59310d2 | ||
|
|
1f8e6cc82b | ||
|
|
1a4ce643fc |
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,35 +1,50 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Problem Report
|
||||||
about: Create a report to help us improve
|
about: Create a Report to help us improve
|
||||||
title: ''
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Before creating a new issue please check that you have:*
|
<!-- Thanks for reporting a problem for this project. READ THIS FIRST:
|
||||||
|
|
||||||
* *searched the existing [issues](https://github.com/emsesp/EMS-ESP32/issues) (both open and closed)*
|
Please DO NOT OPEN AN ISSUE if your EMS-ESP version is not the latest from the dev branch, please update your device before submitting your issue. Your problem might already be solved. The latest precompiled binaries of EMS-ESP can be downloaded from https://github.com/emsesp/EMS-ESP32/releases/tag/latest
|
||||||
* *searched the [documentation help section](https://emsesp.github.io/docs)*
|
|
||||||
|
|
||||||
*Completing this template will help developers and contributors to address the issue. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.*
|
Please take a few minutes to complete the requested information below.
|
||||||
|
|
||||||
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.*
|
-->
|
||||||
|
|
||||||
**Bug description**
|
### PROBLEM DESCRIPTION
|
||||||
*A clear and concise description of what the bug is. Mention which EMS-ESP version you're using.*
|
|
||||||
|
|
||||||
**Steps to reproduce**
|
_A clear and concise description of what the problem is._
|
||||||
*Steps to reproduce the behavior.*
|
|
||||||
|
|
||||||
**Expected behavior**
|
### REQUESTED INFORMATION
|
||||||
*A clear and concise description of what you expected to happen.*
|
|
||||||
|
|
||||||
**Screenshots**
|
_Make sure your have performed every step and checked the applicable boxes before submitting your issue. Thank you!_
|
||||||
*If applicable, add screenshots to help explain your problem.*
|
|
||||||
|
|
||||||
**Device information**
|
- [ ] Searched the problem in [issues](https://github.com/emsesp/EMS-ESP32/issues)
|
||||||
*Copy-paste here the information as it is outputted by the device. You can get this information by from http://ems-esp.local/api/system*
|
- [ ] Searched the problem in [discussions](https://github.com/emsesp/EMS-ESP32/discussions)
|
||||||
|
- [ ] Searched the problem in the [docs](https://emsesp.github.io/docs/Troubleshooting/)
|
||||||
|
- [ ] Searched the problem in the [chat](https://discord.gg/3J3GgnzpyT)
|
||||||
|
- [ ] Provide the output of http://ems-esp.local/api/system :
|
||||||
|
|
||||||
**Additional context**
|
```lua
|
||||||
*Add any other context about the problem here.*
|
System information output here:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### TO REPRODUCE
|
||||||
|
|
||||||
|
_Steps to reproduce the behavior:_
|
||||||
|
|
||||||
|
### EXPECTED BEHAVIOUR
|
||||||
|
|
||||||
|
_A clear and concise description of what you expected to happen._
|
||||||
|
|
||||||
|
### SCREENSHOTS
|
||||||
|
|
||||||
|
_If applicable, add screenshots to help explain your problem._
|
||||||
|
|
||||||
|
### ADDITIONAL CONTEXT
|
||||||
|
|
||||||
|
_Add any other context about the problem here._
|
||||||
|
|
||||||
|
**(Please, remember to close the issue when the problem has been addressed)**
|
||||||
|
|||||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: EMS-ESP Docs
|
||||||
|
url: https://emsesp.github.io/docs/
|
||||||
|
about: All the information related to EMS-ESP.
|
||||||
|
- name: EMS-ESP Discussions and Support
|
||||||
|
url: https://github.com/emsesp/EMS-ESP32/discussions
|
||||||
|
about: EMS-ESP usage Questions, Feature Requests and Projects.
|
||||||
|
- name: EMS-ESP Users Chat
|
||||||
|
url: https://discord.gg/3J3GgnzpyT
|
||||||
|
about: Chat for feedback, questions and troubleshooting.
|
||||||
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Before creating a new feature request please check that you have searched the existing [issues](https://github.com/emsesp/EMS-ESP32/issues) (both open and closed)*
|
|
||||||
|
|
||||||
*Completing this template will help developers and contributors evaluating the feature. If the information provided is not enough the issue will likely be closed.*
|
|
||||||
|
|
||||||
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the request then you can delete them.*
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
*A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]*
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
*A clear and concise description of what you want to happen.*
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
*A clear and concise description of any alternative solutions or features you've considered.*
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
*Add any other context or screenshots about the feature request here.*
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
name: Questions & Troubleshooting
|
|
||||||
about: Anything not a bug or feature request
|
|
||||||
title: ''
|
|
||||||
labels: question
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Before creating a new issue please check that you have:*
|
|
||||||
|
|
||||||
* *searched the existing [issues](https://github.com/emsesp/EMS-ESP32/issues) (both open and closed)*
|
|
||||||
* *searched the [documentation help section](https://emsesp.github.io/docs)*
|
|
||||||
|
|
||||||
*Completing this template will help developers and contributors help you. Try to be as specific and extensive as possible. If the information provided is not enough the issue will likely be closed.*
|
|
||||||
|
|
||||||
*You can now remove this line and the above ones. Text in italic is meant to be replaced by your own words. If any of the sections below are not relevant to the issue (for instance, the screenshots) then you can delete them.*
|
|
||||||
|
|
||||||
**Question**
|
|
||||||
*A clear and concise description of what the problem/doubt is.*
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
*If applicable, add screenshots to help explain your problem.*
|
|
||||||
|
|
||||||
**Device information**
|
|
||||||
*Copy-paste here the information as it is outputted by the device. You can get this information from http://ems-esp.local/api/system*
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
*Add any other context about the problem here.*
|
|
||||||
41
.github/workflows/pre_release.yml
vendored
41
.github/workflows/pre_release.yml
vendored
@@ -1,57 +1,58 @@
|
|||||||
name: "pre-release"
|
name: 'pre-release'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "dev"
|
- 'dev'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-release:
|
pre-release:
|
||||||
|
name: 'Automatic pre-release build'
|
||||||
name: "Automatic pre-release build"
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/setup-python@v4
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-node@v3
|
||||||
- uses: actions/setup-node@v2
|
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '18'
|
||||||
|
|
||||||
- name: Get EMS-ESP source code and version
|
- name: Get EMS-ESP source code and version
|
||||||
id: build_info
|
id: build_info
|
||||||
run: |
|
run: |
|
||||||
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/version.h | awk -F'"' '{print $2}'`
|
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/version.h | awk -F'"' '{print $2}'`
|
||||||
echo "::set-output name=version::$version"
|
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
|
||||||
platformio upgrade
|
|
||||||
platformio update
|
|
||||||
|
|
||||||
- name: Build WebUI
|
- name: Build WebUI
|
||||||
run: |
|
run: |
|
||||||
cd interface
|
cd interface
|
||||||
npm ci
|
yarn install
|
||||||
npm run build
|
yarn run typesafe-i18n --no-watch
|
||||||
|
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||||
|
yarn run build
|
||||||
|
|
||||||
- name: Build firmware
|
- name: Build firmware
|
||||||
run: |
|
run: |
|
||||||
platformio run -e ci
|
platformio run -e ci
|
||||||
|
|
||||||
|
- name: Build S3 firmware
|
||||||
|
run: |
|
||||||
|
platformio run -e ci_s3
|
||||||
|
|
||||||
- name: Create a GH Release
|
- name: Create a GH Release
|
||||||
id: "automatic_releases"
|
id: 'automatic_releases'
|
||||||
uses: "marvinpinto/action-automatic-releases@latest"
|
uses: 'marvinpinto/action-automatic-releases@latest'
|
||||||
with:
|
with:
|
||||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
title: Development Build v${{steps.build_info.outputs.version}}
|
title: Development Build v${{steps.build_info.outputs.VERSION}}
|
||||||
automatic_release_tag: "latest"
|
automatic_release_tag: 'latest'
|
||||||
prerelease: true
|
prerelease: true
|
||||||
files: |
|
files: |
|
||||||
CHANGELOG_LATEST.md
|
CHANGELOG_LATEST.md
|
||||||
./build/firmware/*.*
|
./build/firmware/*.*
|
||||||
|
|
||||||
|
|||||||
43
.github/workflows/sonar_check.yml
vendored
43
.github/workflows/sonar_check.yml
vendored
@@ -7,51 +7,24 @@ on:
|
|||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build and analyze
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository_owner == 'emsesp'
|
# if: github.repository_owner == 'emsesp'
|
||||||
# if: github.repository == 'emsesp/EMS-ESP32'
|
# if: github.repository == 'emsesp/EMS-ESP32'
|
||||||
env:
|
env:
|
||||||
# https://binaries.sonarsource.com/?prefix=Distribution/sonar-scanner-cli/
|
BUILD_WRAPPER_OUT_DIR: bw-output
|
||||||
SONAR_SCANNER_VERSION: 4.7.0.2747
|
|
||||||
SONAR_SERVER_URL: "https://sonarcloud.io"
|
|
||||||
BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||||
- name: Set up JDK 11
|
- name: Install sonar-scanner and build-wrapper
|
||||||
uses: actions/setup-java@v1
|
uses: SonarSource/sonarcloud-github-c-cpp@v2
|
||||||
with:
|
|
||||||
java-version: 11
|
|
||||||
- name: Cache SonarCloud packages
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: ~/.sonar/cache
|
|
||||||
key: ${{ runner.os }}-sonar
|
|
||||||
restore-keys: ${{ runner.os }}-sonar
|
|
||||||
- name: Download and set up sonar-scanner
|
|
||||||
env:
|
|
||||||
SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip
|
|
||||||
run: |
|
|
||||||
mkdir -p $HOME/.sonar
|
|
||||||
curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }}
|
|
||||||
unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
|
|
||||||
echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH
|
|
||||||
- name: Download and set up build-wrapper
|
|
||||||
env:
|
|
||||||
BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip
|
|
||||||
run: |
|
|
||||||
curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }}
|
|
||||||
unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/
|
|
||||||
echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH
|
|
||||||
- name: Run build-wrapper
|
- name: Run build-wrapper
|
||||||
run: |
|
run: |
|
||||||
make clean
|
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make all
|
||||||
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make clean all
|
|
||||||
- name: Run sonar-scanner
|
- name: Run sonar-scanner
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
sonar-scanner
|
sonar-scanner --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}"
|
||||||
|
|||||||
32
.github/workflows/tagged_release.yml
vendored
32
.github/workflows/tagged_release.yml
vendored
@@ -1,45 +1,49 @@
|
|||||||
name: "tagged-release"
|
name: 'tagged-release'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tagged-release:
|
tagged-release:
|
||||||
|
name: 'Tagged Release'
|
||||||
name: "Tagged Release"
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/setup-python@v4
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-node@v3
|
||||||
- uses: actions/setup-node@v2
|
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '18'
|
||||||
|
|
||||||
- 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
|
||||||
platformio upgrade
|
platformio upgrade
|
||||||
platformio update
|
pio pkg update
|
||||||
|
|
||||||
- name: Build WebUI
|
- name: Build WebUI
|
||||||
run: |
|
run: |
|
||||||
cd interface
|
cd interface
|
||||||
npm ci
|
yarn install
|
||||||
npm run build
|
yarn run typesafe-i18n --no-watch
|
||||||
|
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||||
|
yarn run build
|
||||||
|
|
||||||
- name: Build firmware
|
- name: Build firmware
|
||||||
run: |
|
run: |
|
||||||
platformio run -e ci
|
platformio run -e ci
|
||||||
|
|
||||||
|
- name: Build S3 firmware
|
||||||
|
run: |
|
||||||
|
platformio run -e ci_s3
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: "marvinpinto/action-automatic-releases@latest"
|
uses: 'marvinpinto/action-automatic-releases@latest'
|
||||||
with:
|
with:
|
||||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
prerelease: false
|
prerelease: false
|
||||||
files: |
|
files: |
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
|
|||||||
58
.github/workflows/test_release.yml
vendored
Normal file
58
.github/workflows/test_release.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
name: 'test-release'
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'dev2'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pre-release:
|
||||||
|
name: 'Automatic test-release build'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
|
||||||
|
- name: Get EMS-ESP source code and version
|
||||||
|
id: build_info
|
||||||
|
run: |
|
||||||
|
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/version.h | awk -F'"' '{print $2}'`
|
||||||
|
echo "VERSION=$version" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Install PlatformIO
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -U platformio
|
||||||
|
|
||||||
|
- name: Build WebUI
|
||||||
|
run: |
|
||||||
|
cd interface
|
||||||
|
yarn install
|
||||||
|
yarn run typesafe-i18n --no-watch
|
||||||
|
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||||
|
yarn run build
|
||||||
|
|
||||||
|
- name: Build firmware
|
||||||
|
run: |
|
||||||
|
platformio run -e ci
|
||||||
|
|
||||||
|
- name: Build S3 firmware
|
||||||
|
run: |
|
||||||
|
platformio run -e ci_s3
|
||||||
|
|
||||||
|
- name: Create a GH Release
|
||||||
|
id: 'automatic_releases'
|
||||||
|
uses: 'marvinpinto/action-automatic-releases@latest'
|
||||||
|
with:
|
||||||
|
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
title: Test Build v${{steps.build_info.outputs.VERSION}}
|
||||||
|
automatic_release_tag: 'test'
|
||||||
|
prerelease: true
|
||||||
|
files: |
|
||||||
|
CHANGELOG_LATEST.md
|
||||||
|
./build/firmware/*.*
|
||||||
48
.gitignore
vendored
48
.gitignore
vendored
@@ -1,37 +1,61 @@
|
|||||||
# vscode
|
# vscode
|
||||||
.vscode
|
.vscode/c_cpp_properties.json
|
||||||
.directory
|
.vscode/extensions.json
|
||||||
workspace.code-workspace
|
.vscode/launch.json
|
||||||
|
# .vscode/settings.json
|
||||||
|
|
||||||
# build
|
# c++ compiling
|
||||||
build/
|
|
||||||
.clang_complete
|
.clang_complete
|
||||||
.gcc-flags.json
|
.gcc-flags.json
|
||||||
cppcheck.out.xml
|
cppcheck.out.xml
|
||||||
debug.log
|
|
||||||
|
|
||||||
# platformio
|
# platformio
|
||||||
.pio
|
.pio
|
||||||
pio_local.ini
|
pio_local.ini
|
||||||
/.VSCodeCounter
|
|
||||||
|
|
||||||
# OS specific
|
# OS specific
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*Thumbs.db
|
*Thumbs.db
|
||||||
|
|
||||||
# project specfic
|
|
||||||
/scripts/stackdmp.txt
|
|
||||||
emsesp
|
emsesp
|
||||||
|
|
||||||
|
# web specfic
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
/data/www
|
/data/www
|
||||||
/lib/framework/WWWData.h
|
/lib/framework/WWWData.h
|
||||||
/interface/build
|
/interface/build
|
||||||
node_modules
|
node_modules
|
||||||
/interface/.eslintcache
|
/interface/.eslintcache
|
||||||
|
stats.html
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# scripts
|
||||||
test.sh
|
test.sh
|
||||||
|
scripts/run.sh
|
||||||
scripts/__pycache__
|
scripts/__pycache__
|
||||||
.temp
|
/scripts/stackdmp.txt
|
||||||
|
|
||||||
|
# i18n generated files
|
||||||
|
interface/src/i18n/i18n-react.tsx
|
||||||
|
interface/src/i18n/i18n-types.ts
|
||||||
|
interface/src/i18n/i18n-util.ts
|
||||||
|
interface/src/i18n/i18n-util.sync.ts
|
||||||
|
interface/src/i18n/i18n-util.async.ts
|
||||||
|
|
||||||
# sonar
|
# sonar
|
||||||
.scannerwork/
|
.scannerwork/
|
||||||
sonar/
|
sonar/
|
||||||
build_wrapper_output_directory/
|
bw-output/
|
||||||
|
|
||||||
|
# entity dump results
|
||||||
|
# dump_entities.csv
|
||||||
|
# dump_entities.xls*
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"singleQuote": true,
|
|
||||||
"semi": true,
|
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"printWidth": 120
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 120,
|
||||||
|
"bracketSpacing": true
|
||||||
}
|
}
|
||||||
12
.vscode/extensions.json
vendored
Normal file
12
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"platformio.platformio-ide"
|
||||||
|
],
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
"ms-vscode.cpptools-extension-pack"
|
||||||
|
]
|
||||||
|
}
|
||||||
46
.vscode/settings.json
vendored
Normal file
46
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"search.exclude": {
|
||||||
|
"**/.yarn": true,
|
||||||
|
"**/.pnp.*": true
|
||||||
|
},
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll": true
|
||||||
|
// "source.organizeImports": true
|
||||||
|
},
|
||||||
|
"eslint.nodePath": "interface/.yarn/sdks",
|
||||||
|
"eslint.workingDirectories": ["interface"],
|
||||||
|
"prettier.prettierPath": "",
|
||||||
|
"typescript.tsdk": "interface/.yarn/sdks/typescript/lib",
|
||||||
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||||
|
"files.associations": {
|
||||||
|
"*.tsx": "typescriptreact",
|
||||||
|
"*.tcc": "cpp",
|
||||||
|
"optional": "cpp",
|
||||||
|
"istream": "cpp",
|
||||||
|
"ostream": "cpp",
|
||||||
|
"ratio": "cpp",
|
||||||
|
"system_error": "cpp",
|
||||||
|
"array": "cpp",
|
||||||
|
"functional": "cpp",
|
||||||
|
"regex": "cpp",
|
||||||
|
"tuple": "cpp",
|
||||||
|
"type_traits": "cpp",
|
||||||
|
"utility": "cpp",
|
||||||
|
"string": "cpp",
|
||||||
|
"string_view": "cpp"
|
||||||
|
},
|
||||||
|
"todo-tree.filtering.excludeGlobs": [
|
||||||
|
"**/vendor/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/bower_components/**",
|
||||||
|
"**/build/**",
|
||||||
|
"**/.vscode/**",
|
||||||
|
"**/.github/**",
|
||||||
|
"**/_output/**",
|
||||||
|
"**/*.min.*",
|
||||||
|
"**/*.map",
|
||||||
|
"**/ArduinoJson/**"
|
||||||
|
],
|
||||||
|
"cSpell.enableFiletypes": ["!cpp"]
|
||||||
|
}
|
||||||
18
.vscode/tasks.json
vendored
Normal file
18
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "shell",
|
||||||
|
"label": "build standalone emsesp",
|
||||||
|
"command": "make",
|
||||||
|
"args": [],
|
||||||
|
"problemMatcher": ["$gcc"],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
182
CHANGELOG.md
182
CHANGELOG.md
@@ -5,13 +5,178 @@ 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.4.3]
|
## [3.6.2] October 1 2023
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Power entities
|
||||||
|
- Optional input of BSSID for AP connection
|
||||||
|
- Return empty json if no entries in scheduler/custom/analogsnesor/temperaturesensor
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
- Fix for new installations with filesystem not initializing
|
- Wifi full scan to get strongest AP. This prevents some freezes when EMS-ESP would jump to a weaker AP in a Mesh setup.
|
||||||
|
- Add missing dhw tags
|
||||||
|
- Sending a dash/- to the Reset command doesn't return an error [#1308](https://github.com/emsesp/EMS-ESP32/discussions/1308)
|
||||||
|
|
||||||
# [3.4.2]
|
## Changed
|
||||||
|
|
||||||
|
- MQTT queue max 300 messages, check heap and maxAlloc
|
||||||
|
- API call commands are logged as WARN in the log
|
||||||
|
- Reset Command renamed to 'reset' in lowercase in EN
|
||||||
|
|
||||||
|
## [3.6.1] September 9 2023
|
||||||
|
|
||||||
|
## **IMPORTANT! BREAKING CHANGES**
|
||||||
|
|
||||||
|
- `shower_data` MQTT topic shows duration is seconds (was previously a full english sentence)
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Show WiFi rssi in Network Status Page, show quality as color
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- Issue in espMqttClient causing a memory leak when MQTT broker is disconnected due to network unavailability [#1264](https://github.com/emsesp/EMS-ESP32/issues/1264)
|
||||||
|
- Using MQTT enum values correctly formatted in MQTT Discovery [#1280](https://github.com/emsesp/EMS-ESP32/issues/1280)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- MQTT free mem check set to 60 kb
|
||||||
|
- Small cosmetic changes to Searching in Customization web page
|
||||||
|
- Updated to espressif32@6.4.0
|
||||||
|
|
||||||
|
# [3.6.0] August 13 2023
|
||||||
|
|
||||||
|
## **IMPORTANT! BREAKING CHANGES**
|
||||||
|
|
||||||
|
There are breaking changes between 3.5.x and earlier versions of 3.6.0. Please read carefully before applying the update.
|
||||||
|
|
||||||
|
- The sensors have been renamed. `dallassensor` is now `temperaturesensor` in the MQTT topic and named `ts` in the Customizations file. Likewise `analogs` is now `analogsensor` in MQTT and called `as` in the Customizations file. If you have previous customizations you will need to manually update by downloading, changing the JSON file and uploading. It's also recommended cleaning up any old MQTT topics from your broker using an application like MQTTExplorer.
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Workaround for better Domoticz MQTT integration? [#904](https://github.com/emsesp/EMS-ESP32/issues/904)
|
||||||
|
- Show MAC address without connecting to network enhancement [#933](https://github.com/emsesp/EMS-ESP32/issues/933)
|
||||||
|
- Warn user in WebUI of unsaved changes [#911](https://github.com/emsesp/EMS-ESP32/issues/911)
|
||||||
|
- Detect old Tado thermostat, device-id 0x19, no entities
|
||||||
|
- Some more HM200 entities [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
|
||||||
|
- Added Scheduler [#701](https://github.com/emsesp/EMS-ESP32/issues/701)
|
||||||
|
- Added Custom Entities read/write from EMS bus
|
||||||
|
- Build S3 binary with github actions
|
||||||
|
- Greenstar HIU [#1158](https://github.com/emsesp/EMS-ESP32/issues/1158)
|
||||||
|
- AM200 code 10 [#1161](https://github.com/emsesp/EMS-ESP32/issues/1161)
|
||||||
|
- Ventilation device (Logavent HRV176) [#1172](https://github.com/emsesp/EMS-ESP32/issues/1172)
|
||||||
|
- Turn ETH off on wifi connect [#1167](https://github.com/emsesp/EMS-ESP32/issues/1167)
|
||||||
|
- Support for multiple EMS-ESPs with HA [#1196](https://github.com/emsesp/EMS-ESP32/issues/1196)
|
||||||
|
- Italian translation [#1199](https://github.com/emsesp/EMS-ESP32/issues/1199)
|
||||||
|
- Turkish language support [#1076](https://github.com/emsesp/EMS-ESP32/issues/1076)
|
||||||
|
- Buderus GB182 - HC1 mode change not work bug [#1193](https://github.com/emsesp/EMS-ESP32/issues/1193)
|
||||||
|
- Minimal flow temperature enhancement [#1192](https://github.com/emsesp/EMS-ESP32/issues/1192)
|
||||||
|
- Roomtemperature Switching Difference enhancement [#1191](https://github.com/emsesp/EMS-ESP32/issues/1191)
|
||||||
|
- Dew Point Temperature Difference enhancement [#1190](https://github.com/emsesp/EMS-ESP32/issues/1190)
|
||||||
|
- Control of heating circuit mode enhancement [#1187](https://github.com/emsesp/EMS-ESP32/issues/1187)
|
||||||
|
- Warn user in WebUI of unsaved changes enhancement [#911](https://github.com/emsesp/EMS-ESP32/issues/911)
|
||||||
|
- Create safebuild app to fit into factory partition to give ESP32 more flash memory enhancement [#608](https://github.com/emsesp/EMS-ESP32/issues/608)
|
||||||
|
- Support ESP32 S2, C3 mini and S3 [#605](https://github.com/emsesp/EMS-ESP32/issues/605)
|
||||||
|
- Support Buderus AM200 [#1161](https://github.com/emsesp/EMS-ESP32/issues/1161)
|
||||||
|
- Custom telegram handler [#1155](https://github.com/emsesp/EMS-ESP32/issues/1155)
|
||||||
|
- Added support for TLS in MQTT (ESP32-S3 only) [#1178](https://github.com/emsesp/EMS-ESP32/issues/1178)
|
||||||
|
- Boardprofile BBQKees Gateway S3
|
||||||
|
- Custom entity type RAW [#1212](https://github.com/emsesp/EMS-ESP32/discussions/1212)
|
||||||
|
- API command response [#1212](https://github.com/emsesp/EMS-ESP32/discussions/1212)
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- HA-discovery for analog sensor commands [#1035](https://github.com/emsesp/EMS-ESP32/issues/1035)
|
||||||
|
- Enum order of RC3x nofrost mode
|
||||||
|
- Heartbeat interval
|
||||||
|
- Exhaust temperature always zero on GB125/MC110/RC310 bug [#1147](https://github.com/emsesp/EMS-ESP32/issues/1147)
|
||||||
|
- thermostat modetype is not changing when mode changes (e.g. to night) bugSomething isn't working [#1098](https://github.com/emsesp/EMS-ESP32/issues/1098)
|
||||||
|
- NTP: cant apply changed timezone [#1182](https://github.com/emsesp/EMS-ESP32/issues/1182)
|
||||||
|
- Missing Status of VS1 for Buderus SM200 enhancement [#1034](https://github.com/emsesp/EMS-ESP32/issues/1034)
|
||||||
|
- Allowed gpios for S3
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Optional upgrade to platform-espressif32 6.3.0 (after 5.3.0) [#862](https://github.com/emsesp/EMS-ESP32/issues/862)
|
||||||
|
- Use byte 3 for detection RC30 active heatingcircuit [#786](https://github.com/emsesp/EMS-ESP32/issues/786)
|
||||||
|
- Write repeated selflowtemp if tx-queue is empty without verify [#954](https://github.com/emsesp/EMS-ESP32/issues/954)
|
||||||
|
- HA discovery recreate after disconnect by device [#1067](https://github.com/emsesp/EMS-ESP32/issues/1067)
|
||||||
|
- File upload: check flash size (overflow) instead of filesize
|
||||||
|
- Improved HA Discovery so previous configs no longer need to be removed when starting [#1077](https://github.com/emsesp/EMS-ESP32/pull/1077) (thanks @pswid)
|
||||||
|
- Enlarge UART-Stack to 2,5k
|
||||||
|
- Retry timeout for Mqtt-QOS1/2 10seconds
|
||||||
|
- Optimize WebUI rendering when using Dialog Boxes [#1116](https://github.com/emsesp/EMS-ESP32/issues/1116)
|
||||||
|
- Optimize Web libraries to reduce bundle size (3.6.x) [#1112](https://github.com/emsesp/EMS-ESP32/issues/1112)
|
||||||
|
- Use [espMqttClient](https://github.com/bertmelis/espMqttClient) with integrated queue [#1178](https://github.com/emsesp/EMS-ESP32/issues/1178)
|
||||||
|
- Move Sensors from Web dashboard to it's own tab enhancement [#1170](https://github.com/emsesp/EMS-ESP32/issues/1170)
|
||||||
|
- Optimize WebUI dashboard data [#1169](https://github.com/emsesp/EMS-ESP32/issues/1169)
|
||||||
|
- Replace React core library with Preact to save on memory footprint
|
||||||
|
- Response to `system/send` raw reads gives combined data for telegrams with more parts
|
||||||
|
|
||||||
|
# [3.5.0] February 6 2023
|
||||||
|
|
||||||
|
## **IMPORTANT! BREAKING CHANGES**
|
||||||
|
|
||||||
|
- When upgrading to v3.5 for the first time from v3.4 on a BBQKees Gateway board you will need to use the [EMS-EPS Flasher](https://github.com/emsesp/EMS-ESP-Flasher/releases) to correctly re-partition the flash. Make sure you backup the settings and customizations from the WebUI (System->Upload/Download) and restore after the upgrade.
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Translations in Web UI and all device entity names (DE, NL, SV, PL, NO, FR) [#22](https://github.com/emsesp/EMS-ESP32/issues/22)
|
||||||
|
- Add support for Lolin C3 mini [#620](https://github.com/emsesp/EMS-ESP32/pull/620)
|
||||||
|
- Add support for ESP32-S2 [#667](https://github.com/emsesp/EMS-ESP32/pull/667)
|
||||||
|
- Add devices: Greenstar 30Ri boiler, Junkers FW500 thermostat, Buderus BC30 controller
|
||||||
|
- Add program memory info
|
||||||
|
- Add mqtt queue and connection infos
|
||||||
|
- Adapt min/max if ems-value is not in this range
|
||||||
|
- Add heat pump settings for inputs and limits [#600](https://github.com/emsesp/EMS-ESP32/issues/600)
|
||||||
|
- Add hybrid heatpump [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
|
||||||
|
- Add translated tags
|
||||||
|
- Add min/max to customization table [#686](https://github.com/emsesp/EMS-ESP32/issues/686)
|
||||||
|
- Add MD5 check [#637](https://github.com/emsesp/EMS-ESP32/issues/637)
|
||||||
|
- Add more bus-ids [#673](https://github.com/emsesp/EMS-ESP32/issues/673)
|
||||||
|
- Use HA connectivity device class for Status, added boot time [#751](https://github.com/emsesp/EMS-ESP32/issues/751)
|
||||||
|
- Add commands for analog sensors outputs
|
||||||
|
- Support for multiple EMS-ESPs with MQTT and HA [[#759](https://github.com/emsesp/EMS-ESP32/issues/759)]
|
||||||
|
- Settings for heatpump silent mode and additional heater [[#802](https://github.com/emsesp/EMS-ESP32/issues/802)] [[#803](https://github.com/emsesp/EMS-ESP32/issues/803)]
|
||||||
|
- Zone module MZ100 [#826](https://github.com/emsesp/EMS-ESP32/issues/826)
|
||||||
|
- Default MQTT hostname is blank [#829](https://github.com/emsesp/EMS-ESP32/issues/829)
|
||||||
|
- wwCurFlow for ems+ devices [#829](https://github.com/emsesp/EMS-ESP32/issues/829)
|
||||||
|
- Add Rego 3000, TR120RF thermostats [#917](https://github.com/emsesp/EMS-ESP32/issues/917)
|
||||||
|
- Add config for ESP32-S3
|
||||||
|
- Add heatpump silent mode and other entities [#896](https://github.com/emsesp/EMS-ESP32/issues/896)
|
||||||
|
- Allow reboot to other partition (factory or asymetric OTA)
|
||||||
|
- Blacklist entities to remove from memory [#891](https://github.com/emsesp/EMS-ESP32/issues/891)
|
||||||
|
- Add boiler pump operating mode [#944](https://github.com/emsesp/EMS-ESP32/issues/944)
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- Factory Reset not working [#628](https://github.com/emsesp/EMS-ESP32/issues/628)
|
||||||
|
- Valid 4 byte values [#820](https://github.com/emsesp/EMS-ESP32/issues/820)
|
||||||
|
- Commands for multiple thermostats [#826](https://github.com/emsesp/EMS-ESP32/issues/826)
|
||||||
|
- API queries for multiple devices [#865](https://github.com/emsesp/EMS-ESP32/issues/865)
|
||||||
|
- Console crash when using call with command `hcx` only. [#841](https://github.com/emsesp/EMS-ESP32/issues/841)
|
||||||
|
- `heatingPump2Mod` was wrong, changed to absBurnPow [[#908](https://github.com/emsesp/EMS-ESP32/issues/908)
|
||||||
|
- Rounding of web input values
|
||||||
|
- Analog sensor with single gpio number [#915](https://github.com/emsesp/EMS-ESP32/issues/915)
|
||||||
|
- HA dallas and analog configs: remove/rebuild on change [#888](https://github.com/emsesp/EMS-ESP32/issues/888)
|
||||||
|
- Modes and set seltemp for RC30 and RC20 [#932](https://github.com/emsesp/EMS-ESP32/issues/932)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Discovery in HomeAssistant don't work with custom base topic. [#596](https://github.com/emsesp/EMS-ESP32/issues/596) Base topic containing `/` are changed to `_`
|
||||||
|
- RF room temperature sensor are shown as thermostat
|
||||||
|
- Render mqtt float json values with trailing zero
|
||||||
|
- Removed flash strings, to increase available heap memory
|
||||||
|
- Reload page after restart button is pressed
|
||||||
|
- Analog/dallas values command as list like ems-devices
|
||||||
|
- Analog/dallas HA-entities based on id
|
||||||
|
- MQTT Base is a mandatory field. Removed MQTT topic length from settings
|
||||||
|
- HA duration class for time entities [[#822](https://github.com/emsesp/EMS-ESP32/issues/822)
|
||||||
|
- AM200 alternative heatsource as class heatsource [[#857](https://github.com/emsesp/EMS-ESP32/issues/857)
|
||||||
|
|
||||||
|
# [3.4.2] September 18 2022
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
@@ -35,7 +200,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
- Shorten "friendly names" in Home Assistant [#555](https://github.com/emsesp/EMS-ESP32/issues/555)
|
- Shorten "friendly names" in Home Assistant [#555](https://github.com/emsesp/EMS-ESP32/issues/555)
|
||||||
|
|
||||||
- platformio 2.3.0 (IDF 4, Arduino 2)
|
- platformio 2.3.0 (IDF 4, Arduino 2)
|
||||||
- remove master-thermostat, support multiple thermostats
|
- remove master-thermostat, support multiple thermostats
|
||||||
- merge up- and download in webui [#577](https://github.com/emsesp/EMS-ESP32/issues/577)
|
- merge up- and download in webui [#577](https://github.com/emsesp/EMS-ESP32/issues/577)
|
||||||
@@ -48,13 +212,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
|
- Controller data in web-ui only for IVT [#522](https://github.com/emsesp/EMS-ESP32/issues/522)
|
||||||
|
- Rename hidden `climate` to a more explaining name [#523](https://github.com/emsesp/EMS-ESP32/issues/523)
|
||||||
|
- Minor changes to the Customizations web page [#527](https://github.com/emsesp/EMS-ESP32/pull/527)
|
||||||
|
|
||||||
# [3.4.0] May 23 2022
|
# [3.4.0] May 23 2022
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
- WebUI optimizations, updated look&feel and better performance [#124](https://github.com/emsesp/EMS-ESP32/issues/124)
|
- WebUI optimizations, updated look&feel and better performance [#124](https://github.com/emsesp/EMS-ESP32/issues/124)
|
||||||
- Auto refresh of WebUI after successful firmware upload [#178](https://github.com/emsesp/EMS-ESP32/issues/178)
|
- Auto refresh of WebUI after successful firmware upload [#178](https://github.com/emsesp/EMS-ESP32/issues/178)
|
||||||
- New Customization Service in WebUI. First feature is the ability to enable/disabled Enitites (device values) from EMS devices [#206](https://github.com/emsesp/EMS-ESP32/issues/206)
|
- New Customization Service in WebUI. First feature is the ability to enable/disabled Entities (device values) from EMS devices [#206](https://github.com/emsesp/EMS-ESP32/issues/206)
|
||||||
- Option to disable Telnet Console [#209](https://github.com/emsesp/EMS-ESP32/issues/209)
|
- Option to disable Telnet Console [#209](https://github.com/emsesp/EMS-ESP32/issues/209)
|
||||||
- Added Hide SSID, Max Clients and Preferred Channel to Access Point
|
- Added Hide SSID, Max Clients and Preferred Channel to Access Point
|
||||||
- Merged in MichaelDvP's changes like Fahrenheit conversion, publish single (for IOBroker) and a few other critical optimizations
|
- Merged in MichaelDvP's changes like Fahrenheit conversion, publish single (for IOBroker) and a few other critical optimizations
|
||||||
@@ -154,7 +322,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added pool data to telegrams 0x494 & 0x495 [#102](https://github.com/emsesp/EMS-ESP32/issues/102)
|
- Added pool data to telegrams 0x494 & 0x495 [#102](https://github.com/emsesp/EMS-ESP32/issues/102)
|
||||||
- Add RC300 second summermode telegram [#108](https://github.com/emsesp/EMS-ESP32/issues/108)
|
- Add RC300 second summermode telegram [#108](https://github.com/emsesp/EMS-ESP32/issues/108)
|
||||||
- Add support for the RC25 thermostat [#106](https://github.com/emsesp/EMS-ESP32/issues/106)
|
- Add support for the RC25 thermostat [#106](https://github.com/emsesp/EMS-ESP32/issues/106)
|
||||||
- Add new command 'entities' for a device, e.g. http://ems-esp/api/boiler/entities to show the shortname, description and HA Entity name (if HA enabled) [#116](https://github.com/emsesp/EMS-ESP32/issues/116)
|
- Add new command 'entities' for a device, e.g. <http://ems-esp/api/boiler/entities> to show the shortname, description and HA Entity name (if HA enabled) [#116](https://github.com/emsesp/EMS-ESP32/issues/116)
|
||||||
- Support for Junkers program and remote (fb10/fb110) temperature
|
- Support for Junkers program and remote (fb10/fb110) temperature
|
||||||
- Home Assistant `state_class` attribute for Wh, kWh, W and KW [#129](https://github.com/emsesp/EMS-ESP32/issues/129)
|
- Home Assistant `state_class` attribute for Wh, kWh, W and KW [#129](https://github.com/emsesp/EMS-ESP32/issues/129)
|
||||||
- Add current room influence for RC300 [#136](https://github.com/emsesp/EMS-ESP32/issues/136)
|
- Add current room influence for RC300 [#136](https://github.com/emsesp/EMS-ESP32/issues/136)
|
||||||
@@ -376,4 +544,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- some names of mqtt-tags like in v2.2.1
|
- some names of mqtt-tags like in v2.2.1
|
||||||
- new ESP32 partition side to allow for smoother OTA and fallback
|
- new ESP32 partition side to allow for smoother OTA and fallback
|
||||||
- Network Gateway IP is optional (#682)emsesp/EMS-ESP
|
- Network Gateway IP is optional (#682)emsesp/EMS-ESP
|
||||||
- moved to a new GitHub repo https://github.com/emsesp/EMS-ESP32
|
- moved to a new GitHub repo <https://github.com/emsesp/EMS-ESP32>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [3.6.2]
|
||||||
|
|
||||||
|
## **IMPORTANT! BREAKING CHANGES**
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|||||||
29
Makefile
29
Makefile
@@ -1,7 +1,7 @@
|
|||||||
#
|
#
|
||||||
# GNUMakefile for EMS-ESP
|
# GNUMakefile for EMS-ESP
|
||||||
# (c) 2020 Paul Derbyshire
|
|
||||||
#
|
#
|
||||||
|
|
||||||
NUMJOBS=${NUMJOBS:-" -j4 "}
|
NUMJOBS=${NUMJOBS:-" -j4 "}
|
||||||
MAKEFLAGS+="j "
|
MAKEFLAGS+="j "
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
@@ -17,23 +17,32 @@ MAKEFLAGS+="j "
|
|||||||
#TARGET := $(notdir $(CURDIR))
|
#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
|
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/*
|
||||||
INCLUDES := src lib_standalone lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/* src/devices
|
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
|
||||||
LIBRARIES :=
|
LIBRARIES :=
|
||||||
|
|
||||||
CPPCHECK = cppcheck
|
CPPCHECK = cppcheck
|
||||||
|
# CHECKFLAGS = -q --force --std=c++17
|
||||||
CHECKFLAGS = -q --force --std=c++11
|
CHECKFLAGS = -q --force --std=c++11
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# Languages Standard
|
# Languages Standard
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
C_STANDARD := -std=c11
|
C_STANDARD := -std=c17
|
||||||
CXX_STANDARD := -std=c++11
|
# CXX_STANDARD := -std=c++17
|
||||||
|
CXX_STANDARD := -std=gnu++11
|
||||||
|
|
||||||
|
# C_STANDARD := -std=c11
|
||||||
|
# CXX_STANDARD := -std=c++11
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# Defined Symbols
|
# Defined Symbols
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
DEFINES += -DFACTORY_WIFI_HOSTNAME=\"ems-esp\" -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0 -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_USE_SERIAL -DEMSESP_DEFAULT_BOARD_PROFILE=\"LOLIN\"
|
DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0
|
||||||
|
DEFINES += -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -D__linux__ -DEMC_RX_BUFFER_SIZE=1500
|
||||||
|
DEFINES += $(ARGS)
|
||||||
|
|
||||||
|
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.0-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# Sources & Files
|
# Sources & Files
|
||||||
@@ -66,15 +75,13 @@ CXX := /usr/bin/g++
|
|||||||
# CXXFLAGS C++ Compiler Flags
|
# CXXFLAGS C++ Compiler Flags
|
||||||
# LDFLAGS Linker Flags
|
# LDFLAGS Linker Flags
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
CPPFLAGS += $(DEFINES) $(INCLUDE)
|
CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
|
||||||
CPPFLAGS += -ggdb
|
CPPFLAGS += -ggdb
|
||||||
CPPFLAGS += -g3
|
CPPFLAGS += -g3
|
||||||
CPPFLAGS += -Os
|
CPPFLAGS += -Os
|
||||||
|
|
||||||
CFLAGS += $(CPPFLAGS)
|
CFLAGS += $(CPPFLAGS)
|
||||||
CFLAGS += -Wall
|
CFLAGS += -Wall -Wextra -Werror -Wswitch-enum -Wno-unused-parameter -Wno-inconsistent-missing-override -Wno-missing-braces -Wno-unused-lambda-capture -Wno-sign-compare
|
||||||
CFLAGS += -Wextra
|
|
||||||
CFLAGS += -Wno-unused-parameter
|
|
||||||
|
|
||||||
CXXFLAGS += $(CFLAGS) -MMD
|
CXXFLAGS += $(CFLAGS) -MMD
|
||||||
|
|
||||||
@@ -114,6 +121,8 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
|
|||||||
# Targets
|
# Targets
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
|
.SILENT: $(OUTPUT)
|
||||||
|
|
||||||
all: $(OUTPUT)
|
all: $(OUTPUT)
|
||||||
|
|
||||||
$(OUTPUT): $(OBJS)
|
$(OUTPUT): $(OBJS)
|
||||||
|
|||||||
131
README.md
131
README.md
@@ -1,17 +1,5 @@
|
|||||||
# 
|
# 
|
||||||
|
|
||||||
**EMS-ESP** is an open-source firmware for the Espressif ESP8266 and ESP32 microcontroller that communicates with **EMS** (Energy Management System) based equipment from manufacturers like Bosch, Buderus, Nefit, Junkers, Worcester and Sieger.
|
|
||||||
|
|
||||||
This project is the specifically for the ESP32. Compared with the previous ESP8266 (version 2) release it has the following enhancements:
|
|
||||||
|
|
||||||
- Ethernet Support
|
|
||||||
- Pre-configured circuit board layouts
|
|
||||||
- Supports writing EMS values directly from within Web UI
|
|
||||||
- Mock API server for faster offline development and testing
|
|
||||||
- Improved API and MQTT commands
|
|
||||||
- Improvements to Dallas temperature sensors
|
|
||||||
- Embedded log tracing in the Web UI
|
|
||||||
|
|
||||||
[](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md)
|
[](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md)
|
||||||
[](https://github.com/emsesp/EMS-ESP32/commits/main)
|
[](https://github.com/emsesp/EMS-ESP32/commits/main)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
@@ -20,34 +8,54 @@ This project is the specifically for the ESP32. Compared with the previous ESP82
|
|||||||
[](https://github.com/emsesp/EMS-ESP32/releases)
|
[](https://github.com/emsesp/EMS-ESP32/releases)
|
||||||
[](https://discord.gg/3J3GgnzpyT)
|
[](https://discord.gg/3J3GgnzpyT)
|
||||||
|
|
||||||
If you like **EMS-ESP**, please give it a star, or fork it and contribute!
|
|
||||||
|
|
||||||
[](https://github.com/emsesp/EMS-ESP32/stargazers)
|
[](https://github.com/emsesp/EMS-ESP32/stargazers)
|
||||||
[](https://github.com/emsesp/EMS-ES32P/network)
|
[](https://github.com/emsesp/EMS-ES32P/network)
|
||||||
[](https://www.paypal.com/paypalme/prderbyshire/2)
|
[](https://www.paypal.com/paypalme/prderbyshire/2)
|
||||||
|
|
||||||
Note, EMS-ESP requires a small hardware circuit that can convert the EMS bus data to be read by the microcontroller. These can be ordered at <https://bbqkees-electronics.nl> or contact the contributors that can provide the schematic and designs.
|
**EMS-ESP** is an open-source firmware for the Espressif ESP32 microcontroller that communicates with **EMS** (Energy Management System) based equipment from manufacturers like Bosch, Buderus, Nefit, Junkers, Worcester and Sieger. It requires a small gateway circuit to interface with the EMS bus which can be purchased from <https://bbqkees-electronics.nl> or custom built.
|
||||||
|
|
||||||
<img src="media/gateway-integration.jpg" width=40%>
|
## **Features**
|
||||||
|
|
||||||
---
|
- A multi-user, multi-language secure web interface to change settings and monitor incoming data
|
||||||
|
|
||||||
# **Features**
|
|
||||||
|
|
||||||
- A multi-user secure web interface to change settings and monitor incoming data
|
|
||||||
- A console, accessible via Serial and Telnet for more advanced monitoring
|
- A console, accessible via Serial and Telnet for more advanced monitoring
|
||||||
- Native support for Home Assistant and Domoticz via [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/)
|
- Native support for Home Assistant, Domoticz and openHAB via [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/)
|
||||||
- Can run standalone as an independent WiFi Access Point or join an existing WiFi network
|
- Can run standalone as an independent WiFi Access Point or join an existing WiFi network
|
||||||
- Easy first-time configuration via a web Captive Portal
|
- Easy first-time configuration via a web Captive Portal
|
||||||
- Support for more than [100 EMS devices](https://emsesp.github.io/docs/#/Supported-EMS-Devices) (boilers, thermostats, solar modules, mixer modules, heat pumps, gateways)
|
- Support for more than [110+ EMS devices](https://emsesp.github.io/docs/All-Devices/) (boilers, thermostats, solar modules, mixer modules, heat pumps, gateways, switches, heat sources)
|
||||||
|
|
||||||
|
## **Documentation**
|
||||||
|
|
||||||
|
For the complete documentation on how to install, configure and get support visit the [EMS-ESP Wiki](https://emsesp.github.io/docs).
|
||||||
|
|
||||||
|
## **Support**
|
||||||
|
|
||||||
|
To chat with the community reach out on our [Discord Server](https://discord.gg/3J3GgnzpyT).
|
||||||
|
|
||||||
|
If you like **EMS-ESP**, please give it a star, or fork it and contribute or offer a small donation!
|
||||||
|
|
||||||
## **Demo**
|
## **Demo**
|
||||||
|
|
||||||
See a demo [here](https://ems-esp.derbyshire.nl). Log in with any username/password.
|
For a live demo of the Web UI click [here](https://ems-esp.derbyshire.nl) and log in with any username/password.
|
||||||
|
|
||||||
# **Screenshots**
|
## **Contributors ✨**
|
||||||
|
|
||||||
## Web Interface
|
EMS-ESP is a project owned and maintained by [proddy](https://github.com/proddy) and [MichaelDvP](https://github.com/MichaelDvP).
|
||||||
|
|
||||||
|
## **Libraries used**
|
||||||
|
|
||||||
|
- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the framework that provides the core of the Web UI
|
||||||
|
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries
|
||||||
|
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON
|
||||||
|
- [espMqttClient](https://github.com/bertmelis/espMqttClient) for the MQTT client, with custom modifications from @MichaelDvP and @proddy
|
||||||
|
- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance
|
||||||
|
|
||||||
|
## **License**
|
||||||
|
|
||||||
|
This program is licensed under GPL-3.0
|
||||||
|
|
||||||
|
## **Screenshots**
|
||||||
|
|
||||||
|
### Web Interface
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
| ---------------------------------- | -------------------------------- |
|
| ---------------------------------- | -------------------------------- |
|
||||||
@@ -55,75 +63,10 @@ See a demo [here](https://ems-esp.derbyshire.nl). Log in with any username/passw
|
|||||||
| <img src="media/web_devices.png"> | <img src="media/web_mqtt.png"> |
|
| <img src="media/web_devices.png"> | <img src="media/web_mqtt.png"> |
|
||||||
| <img src="media/web_edit.png"> | <img src="media/web_log.png"> |
|
| <img src="media/web_edit.png"> | <img src="media/web_log.png"> |
|
||||||
|
|
||||||
## Telnet Console
|
### Telnet Console
|
||||||
|
|
||||||
<img src="media/console.png" width=80% height=80%>
|
<img src="media/console0.png" width=80% height=80%>
|
||||||
|
|
||||||
## In Home Assistant
|
### In Home Assistant
|
||||||
|
|
||||||
<img src="media/ha_lovelace.png" width=80% height=80%>
|
<img src="media/ha_lovelace.png" width=80% height=80%>
|
||||||
|
|
||||||
# **Installing**
|
|
||||||
|
|
||||||
Refer to the [official documentation](https://emsesp.github.io/docs) to how to install the firmware and configure it. The documentation is being constantly updated as new features and settings are added.
|
|
||||||
|
|
||||||
You can choose to use an pre-built firmware image or compile the code yourself:
|
|
||||||
|
|
||||||
- [Uploading a pre-built firmware build](https://emsesp.github.io/docs/#/Uploading-firmware)
|
|
||||||
- [Building the firmware from source code and flashing manually](https://emsesp.github.io/docs/#/Building-firmware)
|
|
||||||
|
|
||||||
# **Support Information**
|
|
||||||
|
|
||||||
If you're looking for support on **EMS-ESP** there are some options available:
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
- [Official EMS-ESP Documentation](https://emsesp.github.io/docs): For information on how to build and upload the firmware
|
|
||||||
- [FAQ and Troubleshooting](https://emsesp.github.io/docs/#/Troubleshooting): For information on common problems and solutions. See also [BBQKees's wiki](https://bbqkees-electronics.nl/wiki/gateway/troubleshooting.html)
|
|
||||||
|
|
||||||
## Support Community
|
|
||||||
|
|
||||||
- [Discord Server](https://discord.gg/3J3GgnzpyT): For support, troubleshooting and general questions. You have better chances to get fast answers from members of the community
|
|
||||||
- [Search in Issues](https://github.com/emsesp/EMS-ESP32/issues): You might find an answer to your question by searching current or closed issues
|
|
||||||
|
|
||||||
## Developer's Community
|
|
||||||
|
|
||||||
- [Bug Report](https://github.com/emsesp/EMS-ESP32/issues/new?template=bug_report.md): For reporting Bugs
|
|
||||||
- [Feature Request](https://github.com/emsesp/EMS-ESP32/issues/new?template=feature_request.md): For requesting features/functions
|
|
||||||
- [Troubleshooting](https://github.com/emsesp/EMS-ESP32/issues/new?template=questions---troubleshooting.md): As a last resort, you can open new _Troubleshooting & Question_ issue on GitHub if the solution could not be found using the other channels. Just remember: the more info you provide the more chances you'll have to get an accurate answer
|
|
||||||
|
|
||||||
# **Contributors ✨**
|
|
||||||
|
|
||||||
EMS-ESP is a project originally created and owned by [proddy](https://github.com/proddy). Key contributors are:
|
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- markdownlint-disable -->
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td align="center">
|
|
||||||
<a href="https://github.com/MichaelDvP"><img src="https://avatars.githubusercontent.com/u/59284019?v=3?s=100" width="100px;" alt=""/><br /><sub><b>MichaelDvP</b></sub></a><br /></a> <a href="https://github.com/emsesp/EMS-ESP/commits?author=MichaelDvP" title="v2 Commits">v2</a>
|
|
||||||
<a href="https://github.com/emsesp/EMS-ESP32/commits?author=MichaelDvP" title="v3 Commits">v3</a>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<!-- markdownlint-restore -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
|
|
||||||
You can also contribute to EMS-ESP by
|
|
||||||
|
|
||||||
- providing Pull Requests (Features, Fixes, suggestions)
|
|
||||||
- testing new released features and report issues on your EMS equipment
|
|
||||||
- contributing to missing [Documentation](https://emsesp.github.io/docs)
|
|
||||||
|
|
||||||
# **Libraries used**
|
|
||||||
|
|
||||||
- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the framework that provides the core of the Web UI
|
|
||||||
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries
|
|
||||||
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for JSON
|
|
||||||
- [AsyncMqttClient](https://github.com/marvinroger/async-mqtt-client) for the MQTT client, with custom modifications from @bertmelis and @proddy
|
|
||||||
- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance
|
|
||||||
|
|
||||||
# **License**
|
|
||||||
|
|
||||||
This program is licensed under GPL-3.0
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# 
|
|
||||||
|
|
||||||
# Firmware Installation
|
|
||||||
|
|
||||||
Follow the instructions in the [documentation](https://emsesp.github.io/docs) on how to install the firmware binaries in the Assets below.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# 
|
|
||||||
|
|
||||||
This is a snapshot of the current "beta" development code and firmware binaries for the ESP32. It has all the latest features and fixes but please be aware that this is still experimental firmware used for testing and thus may contain the odd bug. Use at your own risk and remember to report an issue if you find something unusual.
|
|
||||||
|
|
||||||
# Firmware Installation
|
|
||||||
|
|
||||||
Follow the instructions in the [documentation](https://emsesp.github.io/docs) on how to install the firmware binaries in the Assets below.
|
|
||||||
4011
dump_entities.csv
Normal file
4011
dump_entities.csv
Normal file
File diff suppressed because it is too large
Load Diff
6
esp32_asym_partition_4M.csv
Normal file
6
esp32_asym_partition_4M.csv
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 0x5000,
|
||||||
|
otadata, data, ota, , 0x2000,
|
||||||
|
app0, app, ota_0, , 0x2A0000,
|
||||||
|
app1, app, ota_1, , 0x140000,
|
||||||
|
spiffs, data, spiffs, , 64K,
|
||||||
|
6
esp32_partition_16M.csv
Normal file
6
esp32_partition_16M.csv
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 0x5000,
|
||||||
|
otadata, data, ota, , 0x2000,
|
||||||
|
app0, app, ota_0, , 0x7F0000,
|
||||||
|
app1, app, ota_1, , 0x7F0000,
|
||||||
|
spiffs, data, spiffs, , 64K,
|
||||||
|
6
esp32_partition_4M.csv
Normal file
6
esp32_partition_4M.csv
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 0x5000,
|
||||||
|
otadata, data, ota, , 0x2000,
|
||||||
|
app0, app, ota_0, , 0x1F0000,
|
||||||
|
app1, app, ota_1, , 0x1F0000,
|
||||||
|
spiffs, data, spiffs, , 64K,
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
# Name, Type, SubType, Offset, Size, Flags
|
|
||||||
nvs, data, nvs, 0x9000, 0x5000,
|
|
||||||
otadata, data, ota, 0xE000, 0x2000,
|
|
||||||
app0, app, ota_0, 0x10000, 0x1F0000,
|
|
||||||
app1, app, ota_1, 0x200000, 0x1F0000,
|
|
||||||
spiffs, data, spiffs, 0x3F0000, 0x10000,
|
|
||||||
|
@@ -7,8 +7,8 @@ build_flags =
|
|||||||
|
|
||||||
; Access point settings
|
; Access point settings
|
||||||
-D FACTORY_AP_PROVISION_MODE=AP_MODE_DISCONNECTED
|
-D FACTORY_AP_PROVISION_MODE=AP_MODE_DISCONNECTED
|
||||||
-D FACTORY_AP_SSID=\"ems-esp\" ; 1-64 characters
|
-D FACTORY_AP_SSID=\"ems-esp\"
|
||||||
-D FACTORY_AP_PASSWORD=\"ems-esp-neo\" ; 8-64 characters
|
-D FACTORY_AP_PASSWORD=\"ems-esp-neo\"
|
||||||
-D FACTORY_AP_LOCAL_IP=\"192.168.4.1\"
|
-D FACTORY_AP_LOCAL_IP=\"192.168.4.1\"
|
||||||
-D FACTORY_AP_GATEWAY_IP=\"192.168.4.1\"
|
-D FACTORY_AP_GATEWAY_IP=\"192.168.4.1\"
|
||||||
-D FACTORY_AP_SUBNET_MASK=\"255.255.255.0\"
|
-D FACTORY_AP_SUBNET_MASK=\"255.255.255.0\"
|
||||||
@@ -28,11 +28,11 @@ build_flags =
|
|||||||
; OTA settings
|
; OTA settings
|
||||||
-D FACTORY_OTA_PORT=8266
|
-D FACTORY_OTA_PORT=8266
|
||||||
-D FACTORY_OTA_PASSWORD=\"ems-esp-neo\"
|
-D FACTORY_OTA_PASSWORD=\"ems-esp-neo\"
|
||||||
-D FACTORY_OTA_ENABLED=true
|
-D FACTORY_OTA_ENABLED=false
|
||||||
|
|
||||||
; MQTT settings
|
; MQTT settings
|
||||||
-D FACTORY_MQTT_ENABLED=false
|
-D FACTORY_MQTT_ENABLED=false
|
||||||
-D FACTORY_MQTT_HOST=\"test.mosquitto.org\"
|
-D FACTORY_MQTT_HOST=\"\"
|
||||||
-D FACTORY_MQTT_PORT=1883
|
-D FACTORY_MQTT_PORT=1883
|
||||||
-D FACTORY_MQTT_USERNAME=\"\"
|
-D FACTORY_MQTT_USERNAME=\"\"
|
||||||
-D FACTORY_MQTT_PASSWORD=\"\"
|
-D FACTORY_MQTT_PASSWORD=\"\"
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
# This enables lint extensions
|
|
||||||
EXTEND_ESLINT=true
|
|
||||||
|
|
||||||
# This is the name of your project. It appears on the sign-in page and in the menu bar.
|
|
||||||
REACT_APP_PROJECT_NAME=EMS-ESP
|
|
||||||
|
|
||||||
# This is the url path your project will be exposed under.
|
|
||||||
REACT_APP_PROJECT_PATH=ems-esp
|
|
||||||
2
interface/.env.development
Normal file
2
interface/.env.development
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_ALOVA_TIPS=0
|
||||||
|
REACT_APP_ALOVA_TIPS=0
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
GENERATE_SOURCEMAP=false
|
|
||||||
|
|
||||||
REACT_APP_HOSTED=true
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
GENERATE_SOURCEMAP=false
|
|
||||||
12
interface/.eslintignore
Normal file
12
interface/.eslintignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
.yarn/
|
||||||
|
|
||||||
|
.prettierrc
|
||||||
|
.eslintrc*
|
||||||
|
env.d.ts
|
||||||
|
progmem-generator.js
|
||||||
|
unpack.ts
|
||||||
|
vite.config.ts
|
||||||
|
package.json
|
||||||
108
interface/.eslintrc.json
Normal file
108
interface/.eslintrc.json
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"airbnb/hooks",
|
||||||
|
"airbnb-typescript",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"plugin:react/jsx-runtime",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||||
|
"plugin:prettier/recommended",
|
||||||
|
"plugin:import/recommended"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
|
},
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module",
|
||||||
|
"tsconfigRootDir": ".",
|
||||||
|
"project": ["tsconfig.json"]
|
||||||
|
},
|
||||||
|
"plugins": ["react", "@typescript-eslint", "autofix", "react-hooks"],
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"typescript": {
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"version": "18.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"object-shorthand": "error",
|
||||||
|
"no-console": "warn",
|
||||||
|
"@typescript-eslint/consistent-type-definitions": ["off", "type"],
|
||||||
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-enum-comparison": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
|
"@typescript-eslint/naming-convention": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-argument": "off",
|
||||||
|
"@typescript-eslint/restrict-plus-operands": "off",
|
||||||
|
"@typescript-eslint/no-unused-expressions": "off",
|
||||||
|
"@typescript-eslint/no-implied-eval": "off",
|
||||||
|
"@typescript-eslint/no-misused-promises": "off",
|
||||||
|
"arrow-body-style": ["error", "as-needed"],
|
||||||
|
"react-hooks/exhaustive-deps": "warn",
|
||||||
|
"@typescript-eslint/consistent-type-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"prefer": "type-imports"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"import/order": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"groups": ["builtin", "external", "parent", "sibling", "index", "object", "type"],
|
||||||
|
"pathGroups": [
|
||||||
|
{
|
||||||
|
"pattern": "@/**/**",
|
||||||
|
"group": "parent",
|
||||||
|
"position": "before"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"alphabetize": { "order": "asc" }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// "autofix/no-unused-vars": [
|
||||||
|
// "error",
|
||||||
|
// {
|
||||||
|
// "argsIgnorePattern": "^_",
|
||||||
|
// "ignoreRestSiblings": true,
|
||||||
|
// "destructuredArrayIgnorePattern": "^_"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
"react/self-closing-comp": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"html": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/ban-types": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"extendDefaults": true,
|
||||||
|
"types": {
|
||||||
|
"{}": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
7
interface/.gitignore
vendored
Normal file
7
interface/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
6
interface/.prettierignore
Normal file
6
interface/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
.prettierrc
|
||||||
|
.yarn/
|
||||||
|
.typesafe-i18n.json
|
||||||
5
interface/.typesafe-i18n.json
Normal file
5
interface/.typesafe-i18n.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"adapter": "react",
|
||||||
|
"baseLocale": "pl",
|
||||||
|
"$schema": "https://unpkg.com/typesafe-i18n@5.26.2/schema/typesafe-i18n.json"
|
||||||
|
}
|
||||||
9
interface/.yarn/plugins/@yarnpkg/plugin-typescript.cjs
vendored
Normal file
9
interface/.yarn/plugins/@yarnpkg/plugin-typescript.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
873
interface/.yarn/releases/yarn-3.4.1.cjs
vendored
Executable file
873
interface/.yarn/releases/yarn-3.4.1.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
20
interface/.yarn/sdks/eslint/bin/eslint.js
vendored
Executable file
20
interface/.yarn/sdks/eslint/bin/eslint.js
vendored
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require eslint/bin/eslint.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real eslint/bin/eslint.js your application uses
|
||||||
|
module.exports = absRequire(`eslint/bin/eslint.js`);
|
||||||
20
interface/.yarn/sdks/eslint/lib/api.js
vendored
Normal file
20
interface/.yarn/sdks/eslint/lib/api.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require eslint
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real eslint your application uses
|
||||||
|
module.exports = absRequire(`eslint`);
|
||||||
6
interface/.yarn/sdks/eslint/package.json
vendored
Normal file
6
interface/.yarn/sdks/eslint/package.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "eslint",
|
||||||
|
"version": "8.36.0-sdk",
|
||||||
|
"main": "./lib/api.js",
|
||||||
|
"type": "commonjs"
|
||||||
|
}
|
||||||
5
interface/.yarn/sdks/integrations.yml
vendored
Normal file
5
interface/.yarn/sdks/integrations.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# This file is automatically generated by @yarnpkg/sdks.
|
||||||
|
# Manual changes might be lost!
|
||||||
|
|
||||||
|
integrations:
|
||||||
|
- vscode
|
||||||
20
interface/.yarn/sdks/prettier/index.js
vendored
Executable file
20
interface/.yarn/sdks/prettier/index.js
vendored
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require prettier/index.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real prettier/index.js your application uses
|
||||||
|
module.exports = absRequire(`prettier/index.js`);
|
||||||
6
interface/.yarn/sdks/prettier/package.json
vendored
Normal file
6
interface/.yarn/sdks/prettier/package.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "prettier",
|
||||||
|
"version": "2.8.7-sdk",
|
||||||
|
"main": "./index.js",
|
||||||
|
"type": "commonjs"
|
||||||
|
}
|
||||||
20
interface/.yarn/sdks/typescript/bin/tsc
vendored
Executable file
20
interface/.yarn/sdks/typescript/bin/tsc
vendored
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/bin/tsc
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/bin/tsc your application uses
|
||||||
|
module.exports = absRequire(`typescript/bin/tsc`);
|
||||||
20
interface/.yarn/sdks/typescript/bin/tsserver
vendored
Executable file
20
interface/.yarn/sdks/typescript/bin/tsserver
vendored
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/bin/tsserver
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/bin/tsserver your application uses
|
||||||
|
module.exports = absRequire(`typescript/bin/tsserver`);
|
||||||
20
interface/.yarn/sdks/typescript/lib/tsc.js
vendored
Normal file
20
interface/.yarn/sdks/typescript/lib/tsc.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/lib/tsc.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/lib/tsc.js your application uses
|
||||||
|
module.exports = absRequire(`typescript/lib/tsc.js`);
|
||||||
223
interface/.yarn/sdks/typescript/lib/tsserver.js
vendored
Normal file
223
interface/.yarn/sdks/typescript/lib/tsserver.js
vendored
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
const moduleWrapper = tsserver => {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
return tsserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {isAbsolute} = require(`path`);
|
||||||
|
const pnpApi = require(`pnpapi`);
|
||||||
|
|
||||||
|
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||||
|
const isPortal = str => str.startsWith("portal:/");
|
||||||
|
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||||
|
|
||||||
|
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||||
|
return `${locator.name}@${locator.reference}`;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||||
|
// doesn't understand. This layer makes sure to remove the protocol
|
||||||
|
// before forwarding it to TS, and to add it back on all returned paths.
|
||||||
|
|
||||||
|
function toEditorPath(str) {
|
||||||
|
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||||
|
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
||||||
|
// We also take the opportunity to turn virtual paths into physical ones;
|
||||||
|
// this makes it much easier to work with workspaces that list peer
|
||||||
|
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||||
|
// file instances instead of the real ones.
|
||||||
|
//
|
||||||
|
// We only do this to modules owned by the the dependency tree roots.
|
||||||
|
// This avoids breaking the resolution when jumping inside a vendor
|
||||||
|
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||||
|
// errors on react).
|
||||||
|
//
|
||||||
|
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||||
|
if (resolved) {
|
||||||
|
const locator = pnpApi.findPackageLocator(resolved);
|
||||||
|
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||||
|
str = resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str = normalize(str);
|
||||||
|
|
||||||
|
if (str.match(/\.zip\//)) {
|
||||||
|
switch (hostInfo) {
|
||||||
|
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||||
|
// VSCode only adds it automatically for supported schemes,
|
||||||
|
// so we have to do it manually for the `zip` scheme.
|
||||||
|
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||||
|
//
|
||||||
|
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||||
|
//
|
||||||
|
// 2021-10-08: VSCode changed the format in 1.61.
|
||||||
|
// Before | ^zip:/c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
// 2022-04-06: VSCode changed the format in 1.66.
|
||||||
|
// Before | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip/c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
// 2022-05-06: VSCode changed the format in 1.68
|
||||||
|
// Before | ^/zip/c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
case `vscode <1.61`: {
|
||||||
|
str = `^zip:${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode <1.66`: {
|
||||||
|
str = `^/zip/${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode <1.68`: {
|
||||||
|
str = `^/zip${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode`: {
|
||||||
|
str = `^/zip/${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// To make "go to definition" work,
|
||||||
|
// We have to resolve the actual file system path from virtual path
|
||||||
|
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||||
|
case `coc-nvim`: {
|
||||||
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
|
str = resolve(`zipfile:${str}`);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||||
|
// We have to resolve the actual file system path from virtual path,
|
||||||
|
// everything else is up to neovim
|
||||||
|
case `neovim`: {
|
||||||
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
|
str = `zipfile://${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
str = `zip:${str}`;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromEditorPath(str) {
|
||||||
|
switch (hostInfo) {
|
||||||
|
case `coc-nvim`: {
|
||||||
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
|
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||||
|
// So in order to convert it back, we use .* to match all the thing
|
||||||
|
// before `zipfile:`
|
||||||
|
return process.platform === `win32`
|
||||||
|
? str.replace(/^.*zipfile:\//, ``)
|
||||||
|
: str.replace(/^.*zipfile:/, ``);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `neovim`: {
|
||||||
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
|
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||||
|
return str.replace(/^zipfile:\/\//, ``);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode`:
|
||||||
|
default: {
|
||||||
|
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force enable 'allowLocalPluginLoads'
|
||||||
|
// TypeScript tries to resolve plugins using a path relative to itself
|
||||||
|
// which doesn't work when using the global cache
|
||||||
|
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
||||||
|
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
||||||
|
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||||
|
// https://github.com/microsoft/vscode/issues/45856
|
||||||
|
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||||
|
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||||
|
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||||
|
this.projectService.allowLocalPluginLoads = true;
|
||||||
|
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
// And here is the point where we hijack the VSCode <-> TS communications
|
||||||
|
// by adding ourselves in the middle. We locate everything that looks
|
||||||
|
// like an absolute path of ours and normalize it.
|
||||||
|
|
||||||
|
const Session = tsserver.server.Session;
|
||||||
|
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||||
|
let hostInfo = `unknown`;
|
||||||
|
|
||||||
|
Object.assign(Session.prototype, {
|
||||||
|
onMessage(/** @type {string | object} */ message) {
|
||||||
|
const isStringMessage = typeof message === 'string';
|
||||||
|
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||||
|
|
||||||
|
if (
|
||||||
|
parsedMessage != null &&
|
||||||
|
typeof parsedMessage === `object` &&
|
||||||
|
parsedMessage.arguments &&
|
||||||
|
typeof parsedMessage.arguments.hostInfo === `string`
|
||||||
|
) {
|
||||||
|
hostInfo = parsedMessage.arguments.hostInfo;
|
||||||
|
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
||||||
|
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
||||||
|
// The RegExp from https://semver.org/ but without the caret at the start
|
||||||
|
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
||||||
|
) ?? []).map(Number)
|
||||||
|
|
||||||
|
if (major === 1) {
|
||||||
|
if (minor < 61) {
|
||||||
|
hostInfo += ` <1.61`;
|
||||||
|
} else if (minor < 66) {
|
||||||
|
hostInfo += ` <1.66`;
|
||||||
|
} else if (minor < 68) {
|
||||||
|
hostInfo += ` <1.68`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||||
|
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return originalOnMessage.call(
|
||||||
|
this,
|
||||||
|
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
send(/** @type {any} */ msg) {
|
||||||
|
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||||
|
return typeof value === `string` ? toEditorPath(value) : value;
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tsserver;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/lib/tsserver.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/lib/tsserver.js your application uses
|
||||||
|
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));
|
||||||
223
interface/.yarn/sdks/typescript/lib/tsserverlibrary.js
vendored
Normal file
223
interface/.yarn/sdks/typescript/lib/tsserverlibrary.js
vendored
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
const moduleWrapper = tsserver => {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
return tsserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {isAbsolute} = require(`path`);
|
||||||
|
const pnpApi = require(`pnpapi`);
|
||||||
|
|
||||||
|
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||||
|
const isPortal = str => str.startsWith("portal:/");
|
||||||
|
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||||
|
|
||||||
|
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||||
|
return `${locator.name}@${locator.reference}`;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||||
|
// doesn't understand. This layer makes sure to remove the protocol
|
||||||
|
// before forwarding it to TS, and to add it back on all returned paths.
|
||||||
|
|
||||||
|
function toEditorPath(str) {
|
||||||
|
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||||
|
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
||||||
|
// We also take the opportunity to turn virtual paths into physical ones;
|
||||||
|
// this makes it much easier to work with workspaces that list peer
|
||||||
|
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||||
|
// file instances instead of the real ones.
|
||||||
|
//
|
||||||
|
// We only do this to modules owned by the the dependency tree roots.
|
||||||
|
// This avoids breaking the resolution when jumping inside a vendor
|
||||||
|
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||||
|
// errors on react).
|
||||||
|
//
|
||||||
|
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||||
|
if (resolved) {
|
||||||
|
const locator = pnpApi.findPackageLocator(resolved);
|
||||||
|
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||||
|
str = resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str = normalize(str);
|
||||||
|
|
||||||
|
if (str.match(/\.zip\//)) {
|
||||||
|
switch (hostInfo) {
|
||||||
|
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||||
|
// VSCode only adds it automatically for supported schemes,
|
||||||
|
// so we have to do it manually for the `zip` scheme.
|
||||||
|
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||||
|
//
|
||||||
|
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||||
|
//
|
||||||
|
// 2021-10-08: VSCode changed the format in 1.61.
|
||||||
|
// Before | ^zip:/c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
// 2022-04-06: VSCode changed the format in 1.66.
|
||||||
|
// Before | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip/c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
// 2022-05-06: VSCode changed the format in 1.68
|
||||||
|
// Before | ^/zip/c:/foo/bar.zip/package.json
|
||||||
|
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||||
|
//
|
||||||
|
case `vscode <1.61`: {
|
||||||
|
str = `^zip:${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode <1.66`: {
|
||||||
|
str = `^/zip/${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode <1.68`: {
|
||||||
|
str = `^/zip${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode`: {
|
||||||
|
str = `^/zip/${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// To make "go to definition" work,
|
||||||
|
// We have to resolve the actual file system path from virtual path
|
||||||
|
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||||
|
case `coc-nvim`: {
|
||||||
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
|
str = resolve(`zipfile:${str}`);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||||
|
// We have to resolve the actual file system path from virtual path,
|
||||||
|
// everything else is up to neovim
|
||||||
|
case `neovim`: {
|
||||||
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
|
str = `zipfile://${str}`;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
str = `zip:${str}`;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromEditorPath(str) {
|
||||||
|
switch (hostInfo) {
|
||||||
|
case `coc-nvim`: {
|
||||||
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
|
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||||
|
// So in order to convert it back, we use .* to match all the thing
|
||||||
|
// before `zipfile:`
|
||||||
|
return process.platform === `win32`
|
||||||
|
? str.replace(/^.*zipfile:\//, ``)
|
||||||
|
: str.replace(/^.*zipfile:/, ``);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `neovim`: {
|
||||||
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
|
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||||
|
return str.replace(/^zipfile:\/\//, ``);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case `vscode`:
|
||||||
|
default: {
|
||||||
|
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force enable 'allowLocalPluginLoads'
|
||||||
|
// TypeScript tries to resolve plugins using a path relative to itself
|
||||||
|
// which doesn't work when using the global cache
|
||||||
|
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
||||||
|
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
||||||
|
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||||
|
// https://github.com/microsoft/vscode/issues/45856
|
||||||
|
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||||
|
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||||
|
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||||
|
this.projectService.allowLocalPluginLoads = true;
|
||||||
|
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
// And here is the point where we hijack the VSCode <-> TS communications
|
||||||
|
// by adding ourselves in the middle. We locate everything that looks
|
||||||
|
// like an absolute path of ours and normalize it.
|
||||||
|
|
||||||
|
const Session = tsserver.server.Session;
|
||||||
|
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||||
|
let hostInfo = `unknown`;
|
||||||
|
|
||||||
|
Object.assign(Session.prototype, {
|
||||||
|
onMessage(/** @type {string | object} */ message) {
|
||||||
|
const isStringMessage = typeof message === 'string';
|
||||||
|
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||||
|
|
||||||
|
if (
|
||||||
|
parsedMessage != null &&
|
||||||
|
typeof parsedMessage === `object` &&
|
||||||
|
parsedMessage.arguments &&
|
||||||
|
typeof parsedMessage.arguments.hostInfo === `string`
|
||||||
|
) {
|
||||||
|
hostInfo = parsedMessage.arguments.hostInfo;
|
||||||
|
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
||||||
|
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
||||||
|
// The RegExp from https://semver.org/ but without the caret at the start
|
||||||
|
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
||||||
|
) ?? []).map(Number)
|
||||||
|
|
||||||
|
if (major === 1) {
|
||||||
|
if (minor < 61) {
|
||||||
|
hostInfo += ` <1.61`;
|
||||||
|
} else if (minor < 66) {
|
||||||
|
hostInfo += ` <1.66`;
|
||||||
|
} else if (minor < 68) {
|
||||||
|
hostInfo += ` <1.68`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||||
|
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return originalOnMessage.call(
|
||||||
|
this,
|
||||||
|
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
send(/** @type {any} */ msg) {
|
||||||
|
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||||
|
return typeof value === `string` ? toEditorPath(value) : value;
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tsserver;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/lib/tsserverlibrary.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/lib/tsserverlibrary.js your application uses
|
||||||
|
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`));
|
||||||
20
interface/.yarn/sdks/typescript/lib/typescript.js
vendored
Normal file
20
interface/.yarn/sdks/typescript/lib/typescript.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require typescript/lib/typescript.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real typescript/lib/typescript.js your application uses
|
||||||
|
module.exports = absRequire(`typescript/lib/typescript.js`);
|
||||||
6
interface/.yarn/sdks/typescript/package.json
vendored
Normal file
6
interface/.yarn/sdks/typescript/package.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "typescript",
|
||||||
|
"version": "5.0.2-sdk",
|
||||||
|
"main": "./lib/typescript.js",
|
||||||
|
"type": "commonjs"
|
||||||
|
}
|
||||||
14
interface/.yarnrc.yml
Normal file
14
interface/.yarnrc.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
plugins:
|
||||||
|
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
|
||||||
|
spec: '@yarnpkg/plugin-typescript'
|
||||||
|
|
||||||
|
yarnPath: .yarn/releases/yarn-3.4.1.cjs
|
||||||
|
|
||||||
|
# uing pnp
|
||||||
|
# nodeLinker: pnp
|
||||||
|
|
||||||
|
# use these if not using PnP and have node_modules
|
||||||
|
nodeLinker: node-modules
|
||||||
|
compressionLevel: 0
|
||||||
|
nmMode: hardlinks-local
|
||||||
|
enableGlobalCache: true
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
||||||
const ProgmemGenerator = require('./progmem-generator.js');
|
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
|
||||||
|
|
||||||
module.exports = function override(config, env) {
|
|
||||||
const hosted = process.env.REACT_APP_HOSTED;
|
|
||||||
|
|
||||||
if (env === 'production' && !hosted) {
|
|
||||||
// rename the ouput file, we need it's path to be short, for embedded FS
|
|
||||||
config.output.filename = 'js/[id].[chunkhash:4].js';
|
|
||||||
config.output.chunkFilename = 'js/[id].[chunkhash:4].js';
|
|
||||||
|
|
||||||
// take out the manifest plugin
|
|
||||||
config.plugins = config.plugins.filter((plugin) => !(plugin instanceof WebpackManifestPlugin));
|
|
||||||
|
|
||||||
// shorten css filenames
|
|
||||||
const miniCssExtractPlugin = config.plugins.find((plugin) => plugin instanceof MiniCssExtractPlugin);
|
|
||||||
miniCssExtractPlugin.options.filename = 'css/[id].[contenthash:4].css';
|
|
||||||
miniCssExtractPlugin.options.chunkFilename = 'css/[id].[contenthash:4].c.css';
|
|
||||||
|
|
||||||
// don't emit license file
|
|
||||||
const terserPlugin = config.optimization.minimizer.find((plugin) => plugin instanceof TerserPlugin);
|
|
||||||
terserPlugin.options.extractComments = false;
|
|
||||||
|
|
||||||
// build progmem data files
|
|
||||||
config.plugins.push(new ProgmemGenerator({ outputPath: '../lib/framework/WWWData.h', bytesPerLine: 20 }));
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
};
|
|
||||||
14
interface/index.html
Normal file
14
interface/index.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<link rel="stylesheet" href="/css/roboto.css" />
|
||||||
|
<link rel="manifest" href="/app/manifest.json" />
|
||||||
|
<title>EMS-ESP</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
29916
interface/package-lock.json
generated
29916
interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,103 +1,77 @@
|
|||||||
{
|
{
|
||||||
"name": "EMS-ESP",
|
"name": "EMS-ESP",
|
||||||
"version": "3.4.0",
|
"version": "3.6.0",
|
||||||
|
"description": "build EMS-ESP WebUI",
|
||||||
|
"homepage": "https://emsesp.github.io/docs",
|
||||||
|
"author": "proddy",
|
||||||
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:3080",
|
|
||||||
"dependencies": {
|
|
||||||
"@emotion/react": "^11.10.4",
|
|
||||||
"@emotion/styled": "^11.10.4",
|
|
||||||
"@msgpack/msgpack": "^2.8.0",
|
|
||||||
"@mui/icons-material": "^5.10.3",
|
|
||||||
"@mui/material": "^5.10.5",
|
|
||||||
"@table-library/react-table-library": "4.0.18",
|
|
||||||
"@types/lodash": "^4.14.185",
|
|
||||||
"@types/node": "^18.7.18",
|
|
||||||
"@types/react": "^18.0.20",
|
|
||||||
"@types/react-dom": "^18.0.6",
|
|
||||||
"@types/react-router-dom": "^5.3.3",
|
|
||||||
"async-validator": "^4.2.5",
|
|
||||||
"axios": "^0.27.2",
|
|
||||||
"http-proxy-middleware": "^2.0.6",
|
|
||||||
"jwt-decode": "^3.1.2",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"notistack": "^2.0.5",
|
|
||||||
"parse-ms": "^3.0.0",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-app-rewired": "^2.2.1",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-dropzone": "^14.2.2",
|
|
||||||
"react-icons": "^4.4.0",
|
|
||||||
"react-router-dom": "^6.4.0",
|
|
||||||
"react-scripts": "5.0.1",
|
|
||||||
"sockette": "^2.0.6",
|
|
||||||
"typescript": "^4.8.3"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-app-rewired start",
|
"dev": "vite",
|
||||||
"build": "react-app-rewired build",
|
"build": "vite build",
|
||||||
"test": "react-app-rewired test",
|
"build-hosted": "vite build --mode hosted",
|
||||||
"eject": "react-scripts eject",
|
"preview": "vite preview",
|
||||||
|
"preview-standalone": "npm-run-all -p preview typesafe-i18n mock-api",
|
||||||
|
"mock-api": "node --watch ../mock-api ../mock-api/server.js",
|
||||||
|
"standalone": "npm-run-all -p dev typesafe-i18n mock-api",
|
||||||
|
"typesafe-i18n": "typesafe-i18n",
|
||||||
"format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'",
|
"format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'",
|
||||||
"build-hosted": "env-cmd -f .env.hosted npm run build",
|
"lint": "eslint . --cache --fix"
|
||||||
"build-localhost": "PUBLIC_URL=/ react-app-rewired build",
|
|
||||||
"mock-api": "nodemon --watch ../mock-api ../mock-api/server.js",
|
|
||||||
"standalone": "npm-run-all -p start mock-api",
|
|
||||||
"lint": "eslint . --ext .ts,.tsx"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"dependencies": {
|
||||||
"extends": [
|
"@alova/adapter-xhr": "^1.0.1",
|
||||||
"react-app",
|
"@emotion/react": "^11.11.1",
|
||||||
"react-app/jest"
|
"@emotion/styled": "^11.11.0",
|
||||||
],
|
"@mui/icons-material": "^5.14.11",
|
||||||
"rules": {
|
"@mui/material": "^5.14.11",
|
||||||
"eol-last": 1,
|
"@table-library/react-table-library": "4.1.7",
|
||||||
"react/jsx-closing-bracket-location": 1,
|
"@types/lodash-es": "^4.17.9",
|
||||||
"react/jsx-closing-tag-location": 1,
|
"@types/node": "^20.8.0",
|
||||||
"react/jsx-wrap-multilines": 1,
|
"@types/react": "^18.2.24",
|
||||||
"react/jsx-curly-newline": 1,
|
"@types/react-dom": "^18.2.8",
|
||||||
"no-multiple-empty-lines": [
|
"@types/react-router-dom": "^5.3.3",
|
||||||
1,
|
"alova": "^2.13.1",
|
||||||
{
|
"async-validator": "^4.2.5",
|
||||||
"max": 1
|
"history": "^5.3.0",
|
||||||
}
|
"jwt-decode": "^3.1.2",
|
||||||
],
|
"lodash-es": "^4.17.21",
|
||||||
"no-trailing-spaces": 1,
|
"mime-types": "^2.1.35",
|
||||||
"semi": 1,
|
"react": "latest",
|
||||||
"no-extra-semi": 1,
|
"react-dom": "latest",
|
||||||
"react/jsx-max-props-per-line": [
|
"react-dropzone": "^14.2.3",
|
||||||
1,
|
"react-icons": "^4.11.0",
|
||||||
{
|
"react-router-dom": "^6.16.0",
|
||||||
"when": "multiline"
|
"react-toastify": "^9.1.3",
|
||||||
}
|
"sockette": "^2.0.6",
|
||||||
],
|
"typesafe-i18n": "^5.26.2",
|
||||||
"react/jsx-first-prop-new-line": [
|
"typescript": "^5.2.2"
|
||||||
1,
|
|
||||||
"multiline"
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-shadow": 1,
|
|
||||||
"max-len": [
|
|
||||||
1,
|
|
||||||
{
|
|
||||||
"code": 200
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"arrow-parens": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.2%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^2.0.20",
|
"@babel/core": "^7.23.0",
|
||||||
"npm-run-all": "^4.1.5"
|
"@preact/compat": "^17.1.2",
|
||||||
}
|
"@preact/preset-vite": "^2.5.0",
|
||||||
|
"@types/babel__core": "^7",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||||
|
"@typescript-eslint/parser": "^6.7.3",
|
||||||
|
"eslint": "^8.50.0",
|
||||||
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
|
"eslint-plugin-autofix": "^1.1.0",
|
||||||
|
"eslint-plugin-import": "^2.28.1",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
|
"eslint-plugin-prettier": "alpha",
|
||||||
|
"eslint-plugin-react": "^7.33.2",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"preact": "^10.18.1",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"rollup-plugin-visualizer": "^5.9.2",
|
||||||
|
"terser": "^5.20.0",
|
||||||
|
"vite": "^4.4.9",
|
||||||
|
"vite-plugin-svgr": "^4.1.0",
|
||||||
|
"vite-tsconfig-paths": "^4.2.1"
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@3.4.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
const { resolve, relative, sep } = require('path');
|
|
||||||
const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } = require('fs');
|
const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } = require('fs');
|
||||||
|
const { resolve, relative, sep } = require('path');
|
||||||
var zlib = require('zlib');
|
var zlib = require('zlib');
|
||||||
var mime = require('mime-types');
|
var mime = require('mime-types');
|
||||||
|
|
||||||
const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n';
|
const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n';
|
||||||
|
const INDENT = ' ';
|
||||||
|
|
||||||
function getFilesSync(dir, files = []) {
|
function getFilesSync(dir, files = []) {
|
||||||
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
|
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
|
||||||
@@ -17,9 +18,9 @@ function getFilesSync(dir, files = []) {
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
function coherseToBuffer(input) {
|
// function coherseToBuffer(input) {
|
||||||
return Buffer.isBuffer(input) ? input : Buffer.from(input);
|
// return Buffer.isBuffer(input) ? input : Buffer.from(input);
|
||||||
}
|
// }
|
||||||
|
|
||||||
function cleanAndOpen(path) {
|
function cleanAndOpen(path) {
|
||||||
if (existsSync(path)) {
|
if (existsSync(path)) {
|
||||||
@@ -28,17 +29,16 @@ function cleanAndOpen(path) {
|
|||||||
return createWriteStream(path, { flags: 'w+' });
|
return createWriteStream(path, { flags: 'w+' });
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProgmemGenerator {
|
export default function ProgmemGenerator({ outputPath = './WWWData.h', bytesPerLine = 20 }) {
|
||||||
constructor(options = {}) {
|
return {
|
||||||
const { outputPath, bytesPerLine = 20, indent = ' ', includes = ARDUINO_INCLUDES } = options;
|
name: 'ProgmemGenerator',
|
||||||
this.options = { outputPath, bytesPerLine, indent, includes };
|
writeBundle: () => {
|
||||||
}
|
console.log('Generating ' + outputPath);
|
||||||
|
const includes = ARDUINO_INCLUDES;
|
||||||
apply(compiler) {
|
const indent = INDENT;
|
||||||
compiler.hooks.emit.tapAsync({ name: 'ProgmemGenerator' }, (compilation, callback) => {
|
|
||||||
const { outputPath, bytesPerLine, indent, includes } = this.options;
|
|
||||||
const fileInfo = [];
|
const fileInfo = [];
|
||||||
const writeStream = cleanAndOpen(resolve(compilation.options.context, outputPath));
|
const writeStream = cleanAndOpen(resolve(outputPath));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const writeIncludes = () => {
|
const writeIncludes = () => {
|
||||||
writeStream.write(includes);
|
writeStream.write(includes);
|
||||||
@@ -48,7 +48,8 @@ class ProgmemGenerator {
|
|||||||
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;
|
var size = 0;
|
||||||
writeStream.write('const uint8_t ' + variable + '[] PROGMEM = {');
|
writeStream.write('const uint8_t ' + variable + '[] = {');
|
||||||
|
// const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 });
|
||||||
const zipBuffer = zlib.gzipSync(buffer);
|
const zipBuffer = zlib.gzipSync(buffer);
|
||||||
zipBuffer.forEach((b) => {
|
zipBuffer.forEach((b) => {
|
||||||
if (!(size % bytesPerLine)) {
|
if (!(size % bytesPerLine)) {
|
||||||
@@ -72,22 +73,22 @@ class ProgmemGenerator {
|
|||||||
|
|
||||||
const writeFiles = () => {
|
const writeFiles = () => {
|
||||||
// process static files
|
// process static files
|
||||||
const buildPath = compilation.options.output.path;
|
const buildPath = resolve('build');
|
||||||
for (const filePath of getFilesSync(buildPath)) {
|
for (const filePath of getFilesSync(buildPath)) {
|
||||||
const readStream = readFileSync(filePath);
|
const readStream = readFileSync(filePath);
|
||||||
const relativeFilePath = relative(buildPath, filePath);
|
const relativeFilePath = relative(buildPath, filePath);
|
||||||
writeFile(relativeFilePath, readStream);
|
writeFile(relativeFilePath, readStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
// process assets
|
// process assets
|
||||||
const { assets } = compilation;
|
// const { assets } = compilation;
|
||||||
Object.keys(assets).forEach((relativeFilePath) => {
|
// Object.keys(assets).forEach((relativeFilePath) => {
|
||||||
writeFile(relativeFilePath, coherseToBuffer(assets[relativeFilePath].source()));
|
// writeFile(relativeFilePath, coherseToBuffer(assets[relativeFilePath].source()));
|
||||||
});
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateWWWClass = () => {
|
const generateWWWClass = () =>
|
||||||
// eslint-disable-next-line max-len
|
`typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;
|
||||||
return `typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;
|
|
||||||
|
|
||||||
class WWWData {
|
class WWWData {
|
||||||
${indent}public:
|
${indent}public:
|
||||||
@@ -98,8 +99,6 @@ ${fileInfo
|
|||||||
${indent.repeat(2)}}
|
${indent.repeat(2)}}
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
};
|
|
||||||
|
|
||||||
const writeWWWClass = () => {
|
const writeWWWClass = () => {
|
||||||
writeStream.write(generateWWWClass());
|
writeStream.write(generateWWWClass());
|
||||||
};
|
};
|
||||||
@@ -109,13 +108,11 @@ ${indent.repeat(2)}}
|
|||||||
writeWWWClass();
|
writeWWWClass();
|
||||||
|
|
||||||
writeStream.on('finish', () => {
|
writeStream.on('finish', () => {
|
||||||
callback();
|
// callback();
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
writeStream.end();
|
writeStream.end();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ProgmemGenerator;
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 5.6 KiB |
@@ -1,24 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* Just supporting latin due to size constrains on the esp chip
|
* Uses font-size 400 (normal) only and Latin (plus extra unicode chars) to keep flash memory to a minimum
|
||||||
*
|
* View fonts on https://fonts.google.com/
|
||||||
* The framework only makes use of 400 (regular) + 500 (medium) weight fonts.
|
* Download woff2 using e.g. https://fonts.googleapis.com/css2?family=Lato or https://fonts.googleapis.com/css2?family=Roboto
|
||||||
*
|
|
||||||
* If using light or strong typography variants you will need to add additional fonts.
|
|
||||||
*/
|
*/
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2');
|
/* src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2) format('woff2'); */
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
|
src:
|
||||||
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
local('Roboto'),
|
||||||
}
|
local('Roboto-Regular'),
|
||||||
|
url(../fonts/re.woff2) format('woff2');
|
||||||
@font-face {
|
unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131, U+0141-0144, U+0152-0153, U+015A-015B,
|
||||||
font-family: 'Roboto';
|
U+015E-015F, U+0179-017C, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
|
||||||
font-style: normal;
|
U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
font-weight: 500;
|
|
||||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/md.woff2) format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
|
|
||||||
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1, user-scalable=0, maximum-scale=1, minimum-scale=1"
|
|
||||||
/>
|
|
||||||
<link rel="stylesheet" href="%PUBLIC_URL%/css/roboto.css" />
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/app/manifest.json" />
|
|
||||||
<title>EMS-ESP</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,44 +1,51 @@
|
|||||||
import { FC, createRef, createContext, useContext, RefObject } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { SnackbarProvider } from 'notistack';
|
import { ToastContainer, Slide } from 'react-toastify';
|
||||||
|
|
||||||
import { IconButton } from '@mui/material';
|
import 'react-toastify/dist/ReactToastify.min.css';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
|
||||||
|
|
||||||
|
import { localStorageDetector } from 'typesafe-i18n/detectors';
|
||||||
import { FeaturesLoader } from './contexts/features';
|
import { FeaturesLoader } from './contexts/features';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import AppRouting from 'AppRouting';
|
||||||
|
import CustomTheme from 'CustomTheme';
|
||||||
|
|
||||||
import CustomTheme from './CustomTheme';
|
import TypesafeI18n from 'i18n/i18n-react';
|
||||||
import AppRouting from './AppRouting';
|
import { detectLocale } from 'i18n/i18n-util';
|
||||||
|
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||||
|
|
||||||
|
const detectedLocale = detectLocale(localStorageDetector);
|
||||||
|
|
||||||
const App: FC = () => {
|
const App: FC = () => {
|
||||||
const notistackRef: RefObject<any> = createRef();
|
const [wasLoaded, setWasLoaded] = useState(false);
|
||||||
|
|
||||||
const onClickDismiss = (key: string | number | undefined) => () => {
|
useEffect(() => {
|
||||||
notistackRef.current.closeSnackbar(key);
|
void loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true));
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const ColorModeContext = createContext({ toggleColorMode: () => {} });
|
if (!wasLoaded) return null;
|
||||||
|
|
||||||
const colorMode = useContext(ColorModeContext);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColorModeContext.Provider value={colorMode}>
|
<TypesafeI18n locale={detectedLocale}>
|
||||||
<CustomTheme>
|
<CustomTheme>
|
||||||
<SnackbarProvider
|
<FeaturesLoader>
|
||||||
maxSnack={3}
|
<AppRouting />
|
||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
</FeaturesLoader>
|
||||||
ref={notistackRef}
|
<ToastContainer
|
||||||
action={(key) => (
|
position="bottom-left"
|
||||||
<IconButton onClick={onClickDismiss(key)} size="small">
|
autoClose={3000}
|
||||||
<CloseIcon />
|
hideProgressBar={false}
|
||||||
</IconButton>
|
newestOnTop={false}
|
||||||
)}
|
closeOnClick={true}
|
||||||
>
|
rtl={false}
|
||||||
<FeaturesLoader>
|
pauseOnFocusLoss={false}
|
||||||
<AppRouting />
|
draggable={false}
|
||||||
</FeaturesLoader>
|
pauseOnHover={false}
|
||||||
</SnackbarProvider>
|
transition={Slide}
|
||||||
|
closeButton={false}
|
||||||
|
theme="light"
|
||||||
|
/>
|
||||||
</CustomTheme>
|
</CustomTheme>
|
||||||
</ColorModeContext.Provider>
|
</TypesafeI18n>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
import { FC, useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { Navigate, Routes, Route, useLocation } from 'react-router-dom';
|
|
||||||
import { useSnackbar, VariantType } from 'notistack';
|
|
||||||
|
|
||||||
import { Authentication, AuthenticationContext } from './contexts/authentication';
|
import { Route, Routes, Navigate, useLocation } from 'react-router-dom';
|
||||||
import { FeaturesContext } from './contexts/features';
|
|
||||||
import { RequireAuthenticated, RequireUnauthenticated } from './components';
|
|
||||||
|
|
||||||
import SignIn from './SignIn';
|
import { toast } from 'react-toastify';
|
||||||
import AuthenticatedRouting from './AuthenticatedRouting';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import AuthenticatedRouting from 'AuthenticatedRouting';
|
||||||
|
import SignIn from 'SignIn';
|
||||||
|
import { RequireAuthenticated, RequireUnauthenticated } from 'components';
|
||||||
|
|
||||||
|
import { Authentication, AuthenticationContext } from 'contexts/authentication';
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
interface SecurityRedirectProps {
|
interface SecurityRedirectProps {
|
||||||
message: string;
|
message: string;
|
||||||
variant?: VariantType;
|
|
||||||
signOut?: boolean;
|
signOut?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RootRedirect: FC<SecurityRedirectProps> = ({ message, variant, signOut }) => {
|
const RootRedirect: FC<SecurityRedirectProps> = ({ message, signOut }) => {
|
||||||
const authenticationContext = useContext(AuthenticationContext);
|
const authenticationContext = useContext(AuthenticationContext);
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
signOut && authenticationContext.signOut(false);
|
signOut && authenticationContext.signOut(false);
|
||||||
enqueueSnackbar(message, { variant });
|
toast.success(message);
|
||||||
}, [message, variant, signOut, authenticationContext, enqueueSnackbar]);
|
}, [message, signOut, authenticationContext]);
|
||||||
return <Navigate to="/" />;
|
return <Navigate to="/" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,24 +41,22 @@ export const RemoveTrailingSlashes = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AppRouting: FC = () => {
|
const AppRouting: FC = () => {
|
||||||
const { features } = useContext(FeaturesContext);
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Authentication>
|
<Authentication>
|
||||||
<RemoveTrailingSlashes />
|
<RemoveTrailingSlashes />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/unauthorized" element={<RootRedirect message="Please sign in to continue" signOut />} />
|
<Route path="/unauthorized" element={<RootRedirect message={LL.PLEASE_SIGNIN()} signOut />} />
|
||||||
<Route path="/fileUpdated" element={<RootRedirect message="Upload successful" variant="success" />} />
|
<Route path="/fileUpdated" element={<RootRedirect message={LL.UPLOAD_SUCCESSFUL()} />} />
|
||||||
{features.security && (
|
<Route
|
||||||
<Route
|
path="/"
|
||||||
path="/"
|
element={
|
||||||
element={
|
<RequireUnauthenticated>
|
||||||
<RequireUnauthenticated>
|
<SignIn />
|
||||||
<SignIn />
|
</RequireUnauthenticated>
|
||||||
</RequireUnauthenticated>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Route
|
<Route
|
||||||
path="/*"
|
path="/*"
|
||||||
element={
|
element={
|
||||||
|
|||||||
@@ -1,66 +1,64 @@
|
|||||||
import { FC, useCallback, useContext, useEffect } from 'react';
|
import { Navigate, Routes, Route } from 'react-router-dom';
|
||||||
import { Navigate, Routes, Route, useNavigate, useLocation } from 'react-router-dom';
|
import Dashboard from './project/Dashboard';
|
||||||
import { AxiosError } from 'axios';
|
import Help from './project/Help';
|
||||||
|
import Settings from './project/Settings';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { FeaturesContext } from './contexts/features';
|
import { Layout, RequireAdmin } from 'components';
|
||||||
import * as AuthenticationApi from './api/authentication';
|
import AccessPoint from 'framework/ap/AccessPoint';
|
||||||
import { PROJECT_PATH } from './api/env';
|
import Mqtt from 'framework/mqtt/Mqtt';
|
||||||
import { AXIOS } from './api/endpoints';
|
import NetworkConnection from 'framework/network/NetworkConnection';
|
||||||
import { Layout, RequireAdmin } from './components';
|
import NetworkTime from 'framework/ntp/NetworkTime';
|
||||||
|
import Security from 'framework/security/Security';
|
||||||
|
import System from 'framework/system/System';
|
||||||
|
|
||||||
import ProjectRouting from './project/ProjectRouting';
|
const AuthenticatedRouting: FC = () => (
|
||||||
|
// const location = useLocation();
|
||||||
|
// const navigate = useNavigate();
|
||||||
|
// const handleApiResponseError = useCallback(
|
||||||
|
// (error: AxiosError) => {
|
||||||
|
// if (error.response && error.response.status === 401) {
|
||||||
|
// AuthenticationApi.storeLoginRedirect(location);
|
||||||
|
// navigate('/unauthorized');
|
||||||
|
// }
|
||||||
|
// return Promise.reject(error);
|
||||||
|
// },
|
||||||
|
// [location, navigate]
|
||||||
|
// );
|
||||||
|
// useEffect(() => {
|
||||||
|
// const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError);
|
||||||
|
// return () => AXIOS.interceptors.response.eject(axiosHandlerId);
|
||||||
|
// }, [handleApiResponseError]);
|
||||||
|
|
||||||
import NetworkConnection from './framework/network/NetworkConnection';
|
<Layout>
|
||||||
import AccessPoint from './framework/ap/AccessPoint';
|
<Routes>
|
||||||
import NetworkTime from './framework/ntp/NetworkTime';
|
<Route path="/dashboard/*" element={<Dashboard />} />
|
||||||
import Mqtt from './framework/mqtt/Mqtt';
|
<Route
|
||||||
import System from './framework/system/System';
|
path="/settings/*"
|
||||||
import Security from './framework/security/Security';
|
element={
|
||||||
|
<RequireAdmin>
|
||||||
|
<Settings />
|
||||||
|
</RequireAdmin>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route path="/help/*" element={<Help />} />
|
||||||
|
|
||||||
const AuthenticatedRouting: FC = () => {
|
<Route path="/network/*" element={<NetworkConnection />} />
|
||||||
const { features } = useContext(FeaturesContext);
|
<Route path="/ap/*" element={<AccessPoint />} />
|
||||||
const location = useLocation();
|
<Route path="/ntp/*" element={<NetworkTime />} />
|
||||||
const navigate = useNavigate();
|
<Route path="/mqtt/*" element={<Mqtt />} />
|
||||||
|
<Route
|
||||||
const handleApiResponseError = useCallback(
|
path="/security/*"
|
||||||
(error: AxiosError) => {
|
element={
|
||||||
if (error.response && error.response.status === 401) {
|
<RequireAdmin>
|
||||||
AuthenticationApi.storeLoginRedirect(location);
|
<Security />
|
||||||
navigate('/unauthorized');
|
</RequireAdmin>
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
/>
|
||||||
},
|
<Route path="/system/*" element={<System />} />
|
||||||
[location, navigate]
|
<Route path="/*" element={<Navigate to="/" />} />
|
||||||
);
|
</Routes>
|
||||||
|
</Layout>
|
||||||
useEffect(() => {
|
);
|
||||||
const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError);
|
|
||||||
return () => AXIOS.interceptors.response.eject(axiosHandlerId);
|
|
||||||
}, [handleApiResponseError]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Layout>
|
|
||||||
<Routes>
|
|
||||||
{features.project && <Route path={`/${PROJECT_PATH}/*`} element={<ProjectRouting />} />}
|
|
||||||
<Route path="/network/*" element={<NetworkConnection />} />
|
|
||||||
<Route path="/ap/*" element={<AccessPoint />} />
|
|
||||||
{features.ntp && <Route path="/ntp/*" element={<NetworkTime />} />}
|
|
||||||
{features.mqtt && <Route path="/mqtt/*" element={<Mqtt />} />}
|
|
||||||
{features.security && (
|
|
||||||
<Route
|
|
||||||
path="/security/*"
|
|
||||||
element={
|
|
||||||
<RequireAdmin>
|
|
||||||
<Security />
|
|
||||||
</RequireAdmin>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Route path="/system/*" element={<System />} />
|
|
||||||
<Route path="/*" element={<Navigate to={AuthenticationApi.getDefaultRoute(features)} />} />
|
|
||||||
</Routes>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthenticatedRouting;
|
export default AuthenticatedRouting;
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { CssBaseline } from '@mui/material';
|
import { CssBaseline } from '@mui/material';
|
||||||
import { createTheme, responsiveFontSizes, ThemeProvider } from '@mui/material/styles';
|
import { createTheme, responsiveFontSizes, ThemeProvider } from '@mui/material/styles';
|
||||||
import { blueGrey, blue } from '@mui/material/colors';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { RequiredChildrenProps } from './utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
|
||||||
|
export const dialogStyle = {
|
||||||
|
'& .MuiDialog-paper': {
|
||||||
|
borderRadius: '8px',
|
||||||
|
borderColor: '#565656',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: '1px'
|
||||||
|
},
|
||||||
|
backdropFilter: 'blur(1px)'
|
||||||
|
};
|
||||||
|
|
||||||
const theme = responsiveFontSizes(
|
const theme = responsiveFontSizes(
|
||||||
createTheme({
|
createTheme({
|
||||||
@@ -14,10 +22,13 @@ const theme = responsiveFontSizes(
|
|||||||
palette: {
|
palette: {
|
||||||
mode: 'dark',
|
mode: 'dark',
|
||||||
secondary: {
|
secondary: {
|
||||||
main: blue[500]
|
main: '#2196f3' // blue[500]
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
main: blueGrey[500]
|
main: '#607d8b' // blueGrey[500]
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
disabled: '#eee' // white
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,24 +1,40 @@
|
|||||||
import { FC, useContext, useState } from 'react';
|
|
||||||
import { ValidateFieldsError } from 'async-validator';
|
|
||||||
import { useSnackbar } from 'notistack';
|
|
||||||
|
|
||||||
import { Box, Fab, Paper, Typography } from '@mui/material';
|
|
||||||
import ForwardIcon from '@mui/icons-material/Forward';
|
import ForwardIcon from '@mui/icons-material/Forward';
|
||||||
|
import { Box, Paper, Typography, MenuItem, TextField, Button } from '@mui/material';
|
||||||
|
import { useRequest } from 'alova';
|
||||||
|
import { useContext, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { FeaturesContext } from './contexts/features';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
|
||||||
import * as AuthenticationApi from './api/authentication';
|
import type { Locales } from 'i18n/i18n-types';
|
||||||
import { PROJECT_NAME } from './api/env';
|
import type { ChangeEventHandler, FC } from 'react';
|
||||||
import { AuthenticationContext } from './contexts/authentication';
|
import type { SignInRequest } from 'types';
|
||||||
|
import * as AuthenticationApi from 'api/authentication';
|
||||||
|
import { PROJECT_NAME } from 'api/env';
|
||||||
|
|
||||||
import { AxiosError } from 'axios';
|
import { ValidatedPasswordField, ValidatedTextField } from 'components';
|
||||||
|
import { AuthenticationContext } from 'contexts/authentication';
|
||||||
|
|
||||||
import { extractErrorMessage, onEnterCallback, updateValue } from './utils';
|
import DEflag from 'i18n/DE.svg';
|
||||||
import { SignInRequest } from './types';
|
import FRflag from 'i18n/FR.svg';
|
||||||
import { ValidatedTextField } from './components';
|
import GBflag from 'i18n/GB.svg';
|
||||||
import { SIGN_IN_REQUEST_VALIDATOR, validate } from './validators';
|
import ITflag from 'i18n/IT.svg';
|
||||||
|
import NLflag from 'i18n/NL.svg';
|
||||||
|
import NOflag from 'i18n/NO.svg';
|
||||||
|
import PLflag from 'i18n/PL.svg';
|
||||||
|
import SVflag from 'i18n/SV.svg';
|
||||||
|
import TRflag from 'i18n/TR.svg';
|
||||||
|
import { I18nContext } from 'i18n/i18n-react';
|
||||||
|
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||||
|
import { onEnterCallback, updateValue } from 'utils';
|
||||||
|
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
|
||||||
|
|
||||||
const SignIn: FC = () => {
|
const SignIn: FC = () => {
|
||||||
const authenticationContext = useContext(AuthenticationContext);
|
const authenticationContext = useContext(AuthenticationContext);
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
|
||||||
|
const { LL, setLocale, locale } = useContext(I18nContext);
|
||||||
|
|
||||||
|
const { features } = useContext(FeaturesContext);
|
||||||
|
|
||||||
const [signInRequest, setSignInRequest] = useState<SignInRequest>({
|
const [signInRequest, setSignInRequest] = useState<SignInRequest>({
|
||||||
username: '',
|
username: '',
|
||||||
@@ -27,37 +43,52 @@ const SignIn: FC = () => {
|
|||||||
const [processing, setProcessing] = useState<boolean>(false);
|
const [processing, setProcessing] = useState<boolean>(false);
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
|
const { send: callSignIn, onSuccess } = useRequest((request: SignInRequest) => AuthenticationApi.signIn(request), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
onSuccess((response) => {
|
||||||
|
if (response.data) {
|
||||||
|
authenticationContext.signIn(response.data.access_token);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const updateLoginRequestValue = updateValue(setSignInRequest);
|
const updateLoginRequestValue = updateValue(setSignInRequest);
|
||||||
|
|
||||||
|
const signIn = async () => {
|
||||||
|
await callSignIn(signInRequest).catch((event) => {
|
||||||
|
if (event.message === 'Unauthorized') {
|
||||||
|
toast.warning(LL.INVALID_LOGIN());
|
||||||
|
} else {
|
||||||
|
toast.error(LL.ERROR() + ' ' + event.message);
|
||||||
|
}
|
||||||
|
setProcessing(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const validateAndSignIn = async () => {
|
const validateAndSignIn = async () => {
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
|
SIGN_IN_REQUEST_VALIDATOR.messages({
|
||||||
|
required: LL.IS_REQUIRED('%s')
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
|
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
|
||||||
signIn();
|
await signIn();
|
||||||
} catch (errors: any) {
|
} catch (errors: any) {
|
||||||
setFieldErrors(errors);
|
setFieldErrors(errors);
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const signIn = async () => {
|
|
||||||
try {
|
|
||||||
const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest);
|
|
||||||
authenticationContext.signIn(loginResponse.access_token);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
if (error instanceof AxiosError) {
|
|
||||||
if (error.response?.status === 401) {
|
|
||||||
enqueueSnackbar('Invalid login details', { variant: 'warning' });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
enqueueSnackbar(extractErrorMessage(error, 'Unexpected error, please try again'), { variant: 'error' });
|
|
||||||
}
|
|
||||||
setProcessing(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitOnEnter = onEnterCallback(signIn);
|
const submitOnEnter = onEnterCallback(signIn);
|
||||||
|
|
||||||
|
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({ target }) => {
|
||||||
|
const loc = target.value as Locales;
|
||||||
|
localStorage.setItem('lang', loc);
|
||||||
|
await loadLocaleAsync(loc);
|
||||||
|
setLocale(loc);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
@@ -72,43 +103,88 @@ const SignIn: FC = () => {
|
|||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
padding: theme.spacing(2),
|
padding: theme.spacing(2),
|
||||||
paddingTop: '200px',
|
paddingTop: '172px',
|
||||||
backgroundImage: 'url("/app/icon.png")',
|
backgroundImage: 'url("/app/icon.png")',
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
backgroundPosition: '50% ' + theme.spacing(2),
|
backgroundPosition: '50% ' + theme.spacing(2),
|
||||||
backgroundSize: 'auto 150px',
|
|
||||||
width: '100%'
|
width: '100%'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Typography variant="h4">{PROJECT_NAME}</Typography>
|
<Typography variant="h4">{PROJECT_NAME}</Typography>
|
||||||
<ValidatedTextField
|
<Typography variant="subtitle2">{features.version}</Typography>
|
||||||
fieldErrors={fieldErrors}
|
|
||||||
disabled={processing}
|
<TextField name="locale" variant="outlined" value={locale} onChange={onLocaleSelected} size="small" select>
|
||||||
name="username"
|
<MenuItem key="de" value="de">
|
||||||
label="Username"
|
<img src={DEflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
value={signInRequest.username}
|
DE
|
||||||
onChange={updateLoginRequestValue}
|
</MenuItem>
|
||||||
margin="normal"
|
<MenuItem key="en" value="en">
|
||||||
variant="outlined"
|
<img src={GBflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
fullWidth
|
EN
|
||||||
/>
|
</MenuItem>
|
||||||
<ValidatedTextField
|
<MenuItem key="fr" value="fr">
|
||||||
fieldErrors={fieldErrors}
|
<img src={FRflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
disabled={processing}
|
FR
|
||||||
type="password"
|
</MenuItem>
|
||||||
name="password"
|
<MenuItem key="it" value="it">
|
||||||
label="Password"
|
<img src={ITflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
value={signInRequest.password}
|
IT
|
||||||
onChange={updateLoginRequestValue}
|
</MenuItem>
|
||||||
onKeyDown={submitOnEnter}
|
<MenuItem key="nl" value="nl">
|
||||||
margin="normal"
|
<img src={NLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
variant="outlined"
|
NL
|
||||||
fullWidth
|
</MenuItem>
|
||||||
/>
|
<MenuItem key="no" value="no">
|
||||||
<Fab variant="extended" color="primary" sx={{ mt: 2 }} onClick={validateAndSignIn} disabled={processing}>
|
<img src={NOflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
NO
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="pl" value="pl">
|
||||||
|
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
PL
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="sv" value="sv">
|
||||||
|
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
SV
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="tr" value="tr">
|
||||||
|
<img src={TRflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
TR
|
||||||
|
</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<Box display="flex" flexDirection="column" alignItems="center">
|
||||||
|
<ValidatedTextField
|
||||||
|
fieldErrors={fieldErrors}
|
||||||
|
disabled={processing}
|
||||||
|
sx={{
|
||||||
|
width: 240
|
||||||
|
}}
|
||||||
|
name="username"
|
||||||
|
label={LL.USERNAME(0)}
|
||||||
|
value={signInRequest.username}
|
||||||
|
onChange={updateLoginRequestValue}
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
<ValidatedPasswordField
|
||||||
|
fieldErrors={fieldErrors}
|
||||||
|
disabled={processing}
|
||||||
|
sx={{
|
||||||
|
width: 240
|
||||||
|
}}
|
||||||
|
name="password"
|
||||||
|
label={LL.PASSWORD()}
|
||||||
|
value={signInRequest.password}
|
||||||
|
onChange={updateLoginRequestValue}
|
||||||
|
onKeyDown={submitOnEnter}
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button variant="contained" color="primary" sx={{ mt: 2 }} onClick={validateAndSignIn} disabled={processing}>
|
||||||
<ForwardIcon sx={{ mr: 1 }} />
|
<ForwardIcon sx={{ mr: 1 }} />
|
||||||
Sign In
|
{LL.SIGN_IN()}
|
||||||
</Fab>
|
</Button>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,7 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
import { alovaInstance } from './endpoints';
|
||||||
|
|
||||||
import { APSettings, APStatus } from '../types';
|
import type { APSettings, APStatus } from 'types';
|
||||||
import { AXIOS } from './endpoints';
|
|
||||||
|
|
||||||
export function readAPStatus(): AxiosPromise<APStatus> {
|
export const readAPStatus = () => alovaInstance.Get<APStatus>('/rest/apStatus');
|
||||||
return AXIOS.get('/apStatus');
|
export const readAPSettings = () => alovaInstance.Get<APSettings>('/rest/apSettings');
|
||||||
}
|
export const updateAPSettings = (data: APSettings) => alovaInstance.Post<APSettings>('/rest/apSettings', data);
|
||||||
|
|
||||||
export function readAPSettings(): AxiosPromise<APSettings> {
|
|
||||||
return AXIOS.get('/apSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateAPSettings(apSettings: APSettings): AxiosPromise<APSettings> {
|
|
||||||
return AXIOS.post('/apSettings', apSettings);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,29 +1,16 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
|
||||||
import * as H from 'history';
|
|
||||||
import jwtDecode from 'jwt-decode';
|
import jwtDecode from 'jwt-decode';
|
||||||
import { Path } from 'react-router-dom';
|
import { ACCESS_TOKEN, alovaInstance } from './endpoints';
|
||||||
|
import type * as H from 'history';
|
||||||
|
import type { Path } from 'react-router-dom';
|
||||||
|
|
||||||
import { Features, Me, SignInRequest, SignInResponse } from '../types';
|
import type { Me, SignInRequest, SignInResponse } from 'types';
|
||||||
|
|
||||||
import { ACCESS_TOKEN, AXIOS } from './endpoints';
|
|
||||||
import { PROJECT_PATH } from './env';
|
|
||||||
|
|
||||||
export const SIGN_IN_PATHNAME = 'loginPathname';
|
export const SIGN_IN_PATHNAME = 'loginPathname';
|
||||||
export const SIGN_IN_SEARCH = 'loginSearch';
|
export const SIGN_IN_SEARCH = 'loginSearch';
|
||||||
|
|
||||||
export const getDefaultRoute = (features: Features) => (features.project ? `/${PROJECT_PATH}` : '/wifi');
|
export const verifyAuthorization = () => alovaInstance.Get('/rest/verifyAuthorization');
|
||||||
|
export const signIn = (request: SignInRequest) => alovaInstance.Post<SignInResponse>('/rest/signIn', request);
|
||||||
|
|
||||||
export function verifyAuthorization(): AxiosPromise<void> {
|
|
||||||
return AXIOS.get('/verifyAuthorization');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function signIn(request: SignInRequest): AxiosPromise<SignInResponse> {
|
|
||||||
return AXIOS.post('/signIn', request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fallback to sessionStorage if localStorage is absent. WebView may not have local storage enabled.
|
|
||||||
*/
|
|
||||||
export function getStorage() {
|
export function getStorage() {
|
||||||
return localStorage || sessionStorage;
|
return localStorage || sessionStorage;
|
||||||
}
|
}
|
||||||
@@ -40,18 +27,18 @@ export function clearLoginRedirect() {
|
|||||||
getStorage().removeItem(SIGN_IN_SEARCH);
|
getStorage().removeItem(SIGN_IN_SEARCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchLoginRedirect(features: Features): Partial<Path> {
|
export function fetchLoginRedirect(): Partial<Path> {
|
||||||
const signInPathname = getStorage().getItem(SIGN_IN_PATHNAME);
|
const signInPathname = getStorage().getItem(SIGN_IN_PATHNAME);
|
||||||
const signInSearch = getStorage().getItem(SIGN_IN_SEARCH);
|
const signInSearch = getStorage().getItem(SIGN_IN_SEARCH);
|
||||||
clearLoginRedirect();
|
clearLoginRedirect();
|
||||||
return {
|
return {
|
||||||
pathname: signInPathname || getDefaultRoute(features),
|
pathname: signInPathname || `/dashboard`,
|
||||||
search: (signInPathname && signInSearch) || undefined
|
search: (signInPathname && signInSearch) || undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clearAccessToken = () => localStorage.removeItem(ACCESS_TOKEN);
|
export const clearAccessToken = () => localStorage.removeItem(ACCESS_TOKEN);
|
||||||
export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken) as Me;
|
export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken);
|
||||||
|
|
||||||
export function addAccessTokenParameter(url: string) {
|
export function addAccessTokenParameter(url: string) {
|
||||||
const accessToken = getStorage().getItem(ACCESS_TOKEN);
|
const accessToken = getStorage().getItem(ACCESS_TOKEN);
|
||||||
|
|||||||
@@ -1,105 +1,60 @@
|
|||||||
import axios, { AxiosPromise, CancelToken } from 'axios';
|
import { xhrRequestAdapter } from '@alova/adapter-xhr';
|
||||||
|
import { createAlova } from 'alova';
|
||||||
|
import ReactHook from 'alova/react';
|
||||||
|
import { unpack } from '../api/unpack';
|
||||||
|
|
||||||
import { decode } from '@msgpack/msgpack';
|
|
||||||
|
|
||||||
export const WS_BASE_URL = '/ws/';
|
|
||||||
export const API_BASE_URL = '/rest/';
|
|
||||||
export const ES_BASE_URL = '/es/';
|
|
||||||
export const EMSESP_API_BASE_URL = '/api/';
|
|
||||||
export const ACCESS_TOKEN = 'access_token';
|
export const ACCESS_TOKEN = 'access_token';
|
||||||
export const WEB_SOCKET_ROOT = calculateWebSocketRoot(WS_BASE_URL);
|
|
||||||
export const EVENT_SOURCE_ROOT = calculateEventSourceRoot(ES_BASE_URL);
|
|
||||||
|
|
||||||
export const AXIOS = axios.create({
|
const host = window.location.host;
|
||||||
baseURL: API_BASE_URL,
|
export const WEB_SOCKET_ROOT = 'ws://' + host + '/ws/';
|
||||||
headers: {
|
export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/';
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
export const alovaInstance = createAlova({
|
||||||
transformRequest: [
|
statesHook: ReactHook,
|
||||||
(data, headers) => {
|
timeout: 3000, // 3 seconds but throwing a timeout error
|
||||||
if (headers) {
|
localCache: null,
|
||||||
if (localStorage.getItem(ACCESS_TOKEN)) {
|
// localCache: {
|
||||||
headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
// GET: {
|
||||||
}
|
// mode: 'placeholder', // see https://alova.js.org/learning/response-cache/#cache-replaceholder-mode
|
||||||
if (headers['Content-Type'] !== 'application/json') {
|
// expire: 2000
|
||||||
return data;
|
// }
|
||||||
}
|
// },
|
||||||
}
|
requestAdapter: xhrRequestAdapter(),
|
||||||
return JSON.stringify(data);
|
beforeRequest(method) {
|
||||||
|
if (localStorage.getItem(ACCESS_TOKEN)) {
|
||||||
|
method.config.headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
|
|
||||||
|
responded: {
|
||||||
|
onSuccess: async (response) => {
|
||||||
|
// if (response.status === 202) {
|
||||||
|
// throw new Error('Wait'); // wifi scan in progress
|
||||||
|
// } else
|
||||||
|
if (response.status === 205) {
|
||||||
|
throw new Error('Reboot required');
|
||||||
|
} else if (response.status === 400) {
|
||||||
|
throw new Error('Request Failed');
|
||||||
|
} else if (response.status >= 400) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
const data = await response.data;
|
||||||
|
if (response.data instanceof ArrayBuffer) {
|
||||||
|
return unpack(data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interceptor for request failure. This interceptor will be entered when the request is wrong.
|
||||||
|
// http errors like 401 (unauthorized) are handled either in the methods or AuthenticatedRouting()
|
||||||
|
// onError: (error, method) => {
|
||||||
|
// alert(error.message);
|
||||||
|
// }
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AXIOS_API = axios.create({
|
export const alovaInstanceGH = createAlova({
|
||||||
baseURL: EMSESP_API_BASE_URL,
|
baseURL: 'https://api.github.com/repos/emsesp/EMS-ESP32/releases',
|
||||||
headers: {
|
statesHook: ReactHook,
|
||||||
'Content-Type': 'application/json'
|
requestAdapter: xhrRequestAdapter()
|
||||||
},
|
|
||||||
transformRequest: [
|
|
||||||
(data, headers) => {
|
|
||||||
if (headers) {
|
|
||||||
if (localStorage.getItem(ACCESS_TOKEN)) {
|
|
||||||
headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
|
||||||
}
|
|
||||||
if (headers['Content-Type'] !== 'application/json') {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JSON.stringify(data);
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AXIOS_BIN = axios.create({
|
|
||||||
baseURL: API_BASE_URL,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
responseType: 'arraybuffer',
|
|
||||||
transformRequest: [
|
|
||||||
(data, headers) => {
|
|
||||||
if (headers) {
|
|
||||||
if (localStorage.getItem(ACCESS_TOKEN)) {
|
|
||||||
headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
|
||||||
}
|
|
||||||
if (headers['Content-Type'] !== 'application/json') {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JSON.stringify(data);
|
|
||||||
}
|
|
||||||
],
|
|
||||||
transformResponse: [
|
|
||||||
(data) => {
|
|
||||||
return decode(data);
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
function calculateWebSocketRoot(webSocketPath: string) {
|
|
||||||
const location = window.location;
|
|
||||||
const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
||||||
return webProtocol + '//' + location.host + webSocketPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateEventSourceRoot(endpointPath: string) {
|
|
||||||
const location = window.location;
|
|
||||||
return location.protocol + '//' + location.host + endpointPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FileUploadConfig {
|
|
||||||
cancelToken?: CancelToken;
|
|
||||||
onUploadProgress?: (progressEvent: ProgressEvent) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const startUploadFile = (url: string, file: File, config?: FileUploadConfig): AxiosPromise<void> => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', file);
|
|
||||||
|
|
||||||
return AXIOS.post(url, formData, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
},
|
|
||||||
...(config || {})
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
export const PROJECT_NAME = process.env.REACT_APP_PROJECT_NAME || 'EMS-ESP';
|
export const PROJECT_NAME = 'EMS-ESP';
|
||||||
export const PROJECT_PATH = process.env.REACT_APP_PROJECT_PATH || 'project';
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
import { alovaInstance } from './endpoints';
|
||||||
|
|
||||||
import { Features } from '../types';
|
import type { Features } from 'types';
|
||||||
|
|
||||||
import { AXIOS } from './endpoints';
|
export const readFeatures = () => alovaInstance.Get<Features>('/rest/features');
|
||||||
|
|
||||||
export function readFeatures(): AxiosPromise<Features> {
|
|
||||||
return AXIOS.get('/features');
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
import { alovaInstance } from './endpoints';
|
||||||
import { MqttSettings, MqttStatus } from '../types';
|
import type { MqttSettings, MqttStatus } from 'types';
|
||||||
|
|
||||||
import { AXIOS } from './endpoints';
|
export const readMqttStatus = () => alovaInstance.Get<MqttStatus>('/rest/mqttStatus');
|
||||||
|
export const readMqttSettings = () => alovaInstance.Get<MqttSettings>('/rest/mqttSettings');
|
||||||
export function readMqttStatus(): AxiosPromise<MqttStatus> {
|
export const updateMqttSettings = (data: MqttSettings) => alovaInstance.Post<MqttSettings>('/rest/mqttSettings', data);
|
||||||
return AXIOS.get('/mqttStatus');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readMqttSettings(): AxiosPromise<MqttSettings> {
|
|
||||||
return AXIOS.get('/mqttSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateMqttSettings(ntpSettings: MqttSettings): AxiosPromise<MqttSettings> {
|
|
||||||
return AXIOS.post('/mqttSettings', ntpSettings);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,25 +1,15 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
import { alovaInstance } from './endpoints';
|
||||||
|
|
||||||
import { WiFiNetworkList, NetworkSettings, NetworkStatus } from '../types';
|
import type { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types';
|
||||||
|
|
||||||
import { AXIOS } from './endpoints';
|
export const readNetworkStatus = () => alovaInstance.Get<NetworkStatus>('/rest/networkStatus');
|
||||||
|
export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks');
|
||||||
export function readNetworkStatus(): AxiosPromise<NetworkStatus> {
|
export const listNetworks = () =>
|
||||||
return AXIOS.get('/networkStatus');
|
alovaInstance.Get<WiFiNetworkList>('/rest/listNetworks', {
|
||||||
}
|
name: 'listNetworks',
|
||||||
|
timeout: 20000 // timeout 20 seconds
|
||||||
export function scanNetworks(): AxiosPromise<void> {
|
});
|
||||||
return AXIOS.get('/scanNetworks');
|
export const readNetworkSettings = () =>
|
||||||
}
|
alovaInstance.Get<NetworkSettings>('/rest/networkSettings', { name: 'networkSettings' });
|
||||||
|
export const updateNetworkSettings = (wifiSettings: NetworkSettings) =>
|
||||||
export function listNetworks(): AxiosPromise<WiFiNetworkList> {
|
alovaInstance.Post<NetworkSettings>('/rest/networkSettings', wifiSettings);
|
||||||
return AXIOS.get('/listNetworks');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readNetworkSettings(): AxiosPromise<NetworkSettings> {
|
|
||||||
return AXIOS.get('/networkSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateNetworkSettings(wifiSettings: NetworkSettings): AxiosPromise<NetworkSettings> {
|
|
||||||
return AXIOS.post('/networkSettings', wifiSettings);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
import { alovaInstance } from './endpoints';
|
||||||
import { NTPSettings, NTPStatus, Time } from '../types';
|
import type { NTPSettings, NTPStatus, Time } from 'types';
|
||||||
|
|
||||||
import { AXIOS } from './endpoints';
|
export const readNTPStatus = () => alovaInstance.Get<NTPStatus>('/rest/ntpStatus');
|
||||||
|
export const readNTPSettings = () =>
|
||||||
|
alovaInstance.Get<NTPSettings>('/rest/ntpSettings', {
|
||||||
|
name: 'ntpSettings'
|
||||||
|
});
|
||||||
|
export const updateNTPSettings = (data: NTPSettings) => alovaInstance.Post<NTPSettings>('/rest/ntpSettings', data);
|
||||||
|
|
||||||
export function readNTPStatus(): AxiosPromise<NTPStatus> {
|
export const updateTime = (data: Time) => alovaInstance.Post<Time>('/rest/time', data);
|
||||||
return AXIOS.get('/ntpStatus');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readNTPSettings(): AxiosPromise<NTPSettings> {
|
|
||||||
return AXIOS.get('/ntpSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateNTPSettings(ntpSettings: NTPSettings): AxiosPromise<NTPSettings> {
|
|
||||||
return AXIOS.post('/ntpSettings', ntpSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTime(time: Time): AxiosPromise<Time> {
|
|
||||||
return AXIOS.post('/time', time);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
import { alovaInstance } from './endpoints';
|
||||||
|
|
||||||
import { SecuritySettings, Token } from '../types';
|
import type { SecuritySettings, Token } from 'types';
|
||||||
|
|
||||||
import { AXIOS } from './endpoints';
|
export const readSecuritySettings = () => alovaInstance.Get<SecuritySettings>('/rest/securitySettings');
|
||||||
|
|
||||||
export function readSecuritySettings(): AxiosPromise<SecuritySettings> {
|
export const updateSecuritySettings = (securitySettings: SecuritySettings) =>
|
||||||
return AXIOS.get('/securitySettings');
|
alovaInstance.Post('/rest/securitySettings', securitySettings);
|
||||||
}
|
|
||||||
|
|
||||||
export function updateSecuritySettings(securitySettings: SecuritySettings): AxiosPromise<SecuritySettings> {
|
export const generateToken = (username?: string) =>
|
||||||
return AXIOS.post('/securitySettings', securitySettings);
|
alovaInstance.Get<Token>('/rest/generateToken', {
|
||||||
}
|
params: { username }
|
||||||
|
});
|
||||||
export function generateToken(username?: string): AxiosPromise<Token> {
|
|
||||||
return AXIOS.get('/generateToken', { params: { username } });
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,41 +1,42 @@
|
|||||||
import { AxiosPromise } from 'axios';
|
import { alovaInstance, alovaInstanceGH } from './endpoints';
|
||||||
|
import type { OTASettings, SystemStatus, LogSettings } from 'types';
|
||||||
|
|
||||||
import { OTASettings, SystemStatus, LogSettings, LogEntries } from '../types';
|
// SystemStatus - also used to ping in Restart monitor for pinging
|
||||||
|
export const readSystemStatus = () => alovaInstance.Get<SystemStatus>('/rest/systemStatus');
|
||||||
|
|
||||||
import { AXIOS, AXIOS_BIN, FileUploadConfig, startUploadFile } from './endpoints';
|
// commands
|
||||||
|
export const restart = () => alovaInstance.Post('/rest/restart');
|
||||||
|
export const partition = () => alovaInstance.Post('/rest/partition');
|
||||||
|
export const factoryReset = () => alovaInstance.Post('/rest/factoryReset');
|
||||||
|
|
||||||
export function readSystemStatus(timeout?: number): AxiosPromise<SystemStatus> {
|
// OTA
|
||||||
return AXIOS.get('/systemStatus', { timeout });
|
export const readOTASettings = () => alovaInstance.Get<OTASettings>(`/rest/otaSettings`);
|
||||||
}
|
export const updateOTASettings = (data: any) => alovaInstance.Post('/rest/otaSettings', data);
|
||||||
|
|
||||||
export function restart(): AxiosPromise<void> {
|
// SystemLog
|
||||||
return AXIOS.post('/restart');
|
export const readLogSettings = () => alovaInstance.Get<LogSettings>(`/rest/logSettings`);
|
||||||
}
|
export const updateLogSettings = (data: any) => alovaInstance.Post('/rest/logSettings', data);
|
||||||
|
export const fetchLog = () => alovaInstance.Post('/rest/fetchLog');
|
||||||
|
|
||||||
export function factoryReset(): AxiosPromise<void> {
|
// Get versions from github
|
||||||
return AXIOS.post('/factoryReset');
|
export const getStableVersion = () =>
|
||||||
}
|
alovaInstanceGH.Get('latest', {
|
||||||
|
transformData(response: any) {
|
||||||
export function readOTASettings(): AxiosPromise<OTASettings> {
|
return response.data.name.substring(1);
|
||||||
return AXIOS.get('/otaSettings');
|
}
|
||||||
}
|
});
|
||||||
|
export const getDevVersion = () =>
|
||||||
export function updateOTASettings(otaSettings: OTASettings): AxiosPromise<OTASettings> {
|
alovaInstanceGH.Get('tags/latest', {
|
||||||
return AXIOS.post('/otaSettings', otaSettings);
|
transformData(response: any) {
|
||||||
}
|
return response.data.name.split(/\s+/).splice(-1)[0].substring(1);
|
||||||
|
}
|
||||||
export const uploadFile = (file: File, config?: FileUploadConfig): AxiosPromise<void> =>
|
});
|
||||||
startUploadFile('/uploadFile', file, config);
|
|
||||||
|
|
||||||
export function readLogSettings(): AxiosPromise<LogSettings> {
|
|
||||||
return AXIOS.get('/logSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateLogSettings(logSettings: LogSettings): AxiosPromise<LogSettings> {
|
|
||||||
return AXIOS.post('/logSettings', logSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readLogEntries(): AxiosPromise<LogEntries> {
|
|
||||||
return AXIOS_BIN.get('/fetchLog');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export const uploadFile = (file: File) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
return alovaInstance.Post('/rest/uploadFile', formData, {
|
||||||
|
timeout: 60000, // override timeout for uploading firmware - 1 minute
|
||||||
|
enableUpload: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
1131
interface/src/api/unpack.ts
Normal file
1131
interface/src/api/unpack.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,25 @@
|
|||||||
import { FC } from 'react';
|
import { Box } from '@mui/material';
|
||||||
import { Box, BoxProps } from '@mui/material';
|
import type { BoxProps } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
const ButtonRow: FC<BoxProps> = ({ children, ...rest }) => {
|
const ButtonRow: FC<BoxProps> = ({ children, ...rest }) => (
|
||||||
return (
|
<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': {
|
||||||
'&:first-of-type': {
|
ml: 0
|
||||||
ml: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
{...rest}
|
}}
|
||||||
>
|
{...rest}
|
||||||
{children}
|
>
|
||||||
</Box>
|
{children}
|
||||||
);
|
</Box>
|
||||||
};
|
);
|
||||||
|
|
||||||
export default ButtonRow;
|
export default ButtonRow;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Box, BoxProps, SvgIconProps, Theme, Typography, useTheme } from '@mui/material';
|
|
||||||
|
|
||||||
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
||||||
|
import ErrorIcon from '@mui/icons-material/Error';
|
||||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||||
import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined';
|
import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined';
|
||||||
import ErrorIcon from '@mui/icons-material/Error';
|
import { Box, Typography, useTheme } from '@mui/material';
|
||||||
|
import type { BoxProps, SvgIconProps, Theme } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
type MessageBoxLevel = 'warning' | 'success' | 'info' | 'error';
|
type MessageBoxLevel = 'warning' | 'success' | 'info' | 'error';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Paper, Divider } from '@mui/material';
|
import { Paper, Divider } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { RequiredChildrenProps } from '../utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
|
||||||
interface SectionContentProps extends RequiredChildrenProps {
|
interface SectionContentProps extends RequiredChildrenProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ export * from './upload';
|
|||||||
export { default as SectionContent } from './SectionContent';
|
export { default as SectionContent } from './SectionContent';
|
||||||
export { default as ButtonRow } from './ButtonRow';
|
export { default as ButtonRow } from './ButtonRow';
|
||||||
export { default as MessageBox } from './MessageBox';
|
export { default as MessageBox } from './MessageBox';
|
||||||
|
export { default as BlockNavigation } from './routing/BlockNavigation';
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { FC } from 'react';
|
import { FormControlLabel } from '@mui/material';
|
||||||
import { FormControlLabel, FormControlLabelProps } from '@mui/material';
|
import type { FormControlLabelProps } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
const BlockFormControlLabel: FC<FormControlLabelProps> = (props) => (
|
const BlockFormControlLabel: FC<FormControlLabelProps> = (props) => (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { FC, useState } from 'react';
|
|
||||||
|
|
||||||
import { IconButton, InputAdornment } from '@mui/material';
|
|
||||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||||
|
import { IconButton, InputAdornment } from '@mui/material';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import ValidatedTextField, { ValidatedTextFieldProps } from './ValidatedTextField';
|
import ValidatedTextField from './ValidatedTextField';
|
||||||
|
import type { ValidatedTextFieldProps } from './ValidatedTextField';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
type ValidatedPasswordFieldProps = Omit<ValidatedTextFieldProps, 'type'>;
|
type ValidatedPasswordFieldProps = Omit<ValidatedTextFieldProps, 'type'>;
|
||||||
|
|
||||||
@@ -19,11 +20,7 @@ const ValidatedPasswordField: FC<ValidatedPasswordFieldProps> = ({ InputProps, .
|
|||||||
...InputProps,
|
...InputProps,
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
<IconButton
|
<IconButton onClick={() => setShowPassword(!showPassword)} edge="end">
|
||||||
aria-label="toggle password visibility"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
edge="end"
|
|
||||||
>
|
|
||||||
{showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
|
{showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FC } from 'react';
|
import { FormHelperText, TextField } from '@mui/material';
|
||||||
import { ValidateFieldsError } from 'async-validator';
|
import type { TextFieldProps } from '@mui/material';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
import { FormHelperText, TextField, TextFieldProps } from '@mui/material';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
interface ValidatedFieldProps {
|
interface ValidatedFieldProps {
|
||||||
fieldErrors?: ValidateFieldsError;
|
fieldErrors?: ValidateFieldsError;
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { FC, useState, useEffect } from 'react';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Box, Toolbar } from '@mui/material';
|
import { Box, Toolbar } from '@mui/material';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
import { PROJECT_NAME } from '../../api/env';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { RequiredChildrenProps } from '../../utils';
|
|
||||||
|
|
||||||
import LayoutDrawer from './LayoutDrawer';
|
|
||||||
import LayoutAppBar from './LayoutAppBar';
|
import LayoutAppBar from './LayoutAppBar';
|
||||||
|
import LayoutDrawer from './LayoutDrawer';
|
||||||
import { LayoutContext } from './context';
|
import { LayoutContext } from './context';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
export const DRAWER_WIDTH = 240;
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
import { PROJECT_NAME } from 'api/env';
|
||||||
|
|
||||||
|
export const DRAWER_WIDTH = 210;
|
||||||
|
|
||||||
const Layout: FC<RequiredChildrenProps> = ({ children }) => {
|
const Layout: FC<RequiredChildrenProps> = ({ children }) => {
|
||||||
const [mobileOpen, setMobileOpen] = useState(false);
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
|
|||||||
@@ -1,50 +1,36 @@
|
|||||||
import { FC, useContext } from 'react';
|
|
||||||
|
|
||||||
import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material';
|
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
|
import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material';
|
||||||
import LayoutAuthMenu from './LayoutAuthMenu';
|
import LayoutAuthMenu from './LayoutAuthMenu';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { FeaturesContext } from '../../contexts/features';
|
export const DRAWER_WIDTH = 210;
|
||||||
|
|
||||||
export const DRAWER_WIDTH = 240;
|
|
||||||
|
|
||||||
interface LayoutAppBarProps {
|
interface LayoutAppBarProps {
|
||||||
title: string;
|
title: string;
|
||||||
onToggleDrawer: () => void;
|
onToggleDrawer: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LayoutAppBar: FC<LayoutAppBarProps> = ({ title, onToggleDrawer }) => {
|
const LayoutAppBar: FC<LayoutAppBarProps> = ({ title, onToggleDrawer }) => (
|
||||||
const { features } = useContext(FeaturesContext);
|
<AppBar
|
||||||
|
position="fixed"
|
||||||
return (
|
sx={{
|
||||||
<AppBar
|
width: { md: `calc(100% - ${DRAWER_WIDTH}px)` },
|
||||||
position="fixed"
|
ml: { md: `${DRAWER_WIDTH}px` },
|
||||||
sx={{
|
boxShadow: 'none',
|
||||||
width: { md: `calc(100% - ${DRAWER_WIDTH}px)` },
|
backgroundColor: '#2e586a'
|
||||||
ml: { md: `${DRAWER_WIDTH}px` },
|
}}
|
||||||
boxShadow: 'none',
|
>
|
||||||
backgroundColor: '#2e586a'
|
<Toolbar>
|
||||||
}}
|
<IconButton color="inherit" edge="start" onClick={onToggleDrawer} sx={{ mr: 2, display: { md: 'none' } }}>
|
||||||
>
|
<MenuIcon />
|
||||||
<Toolbar>
|
</IconButton>
|
||||||
<IconButton
|
<Typography variant="h6" noWrap component="div">
|
||||||
color="inherit"
|
{title}
|
||||||
aria-label="open drawer"
|
</Typography>
|
||||||
edge="start"
|
<Box flexGrow={1} />
|
||||||
onClick={onToggleDrawer}
|
<LayoutAuthMenu />
|
||||||
sx={{ mr: 2, display: { md: 'none' } }}
|
</Toolbar>
|
||||||
>
|
</AppBar>
|
||||||
<MenuIcon />
|
);
|
||||||
</IconButton>
|
|
||||||
<Typography variant="h6" noWrap component="div">
|
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
<Box flexGrow={1} />
|
|
||||||
{features.security && <LayoutAuthMenu />}
|
|
||||||
</Toolbar>
|
|
||||||
</AppBar>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LayoutAppBar;
|
export default LayoutAppBar;
|
||||||
|
|||||||
@@ -1,11 +1,35 @@
|
|||||||
import { FC, useState, useContext } from 'react';
|
|
||||||
|
|
||||||
import { Box, Button, Divider, IconButton, Popover, Typography, Avatar, styled, TypographyProps } from '@mui/material';
|
|
||||||
|
|
||||||
import PersonIcon from '@mui/icons-material/Person';
|
|
||||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
||||||
|
import PersonIcon from '@mui/icons-material/Person';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
IconButton,
|
||||||
|
Popover,
|
||||||
|
Typography,
|
||||||
|
Avatar,
|
||||||
|
styled,
|
||||||
|
MenuItem,
|
||||||
|
TextField
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useState, useContext } from 'react';
|
||||||
|
import type { TypographyProps } from '@mui/material';
|
||||||
|
|
||||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
import type { Locales } from 'i18n/i18n-types';
|
||||||
|
import type { FC, ChangeEventHandler } from 'react';
|
||||||
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
|
import DEflag from 'i18n/DE.svg';
|
||||||
|
import FRflag from 'i18n/FR.svg';
|
||||||
|
import GBflag from 'i18n/GB.svg';
|
||||||
|
import ITflag from 'i18n/IT.svg';
|
||||||
|
import NLflag from 'i18n/NL.svg';
|
||||||
|
import NOflag from 'i18n/NO.svg';
|
||||||
|
import PLflag from 'i18n/PL.svg';
|
||||||
|
import SVflag from 'i18n/SV.svg';
|
||||||
|
import TRflag from 'i18n/TR.svg';
|
||||||
|
import { I18nContext } from 'i18n/i18n-react';
|
||||||
|
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||||
|
|
||||||
const ItemTypography = styled(Typography)<TypographyProps>({
|
const ItemTypography = styled(Typography)<TypographyProps>({
|
||||||
maxWidth: '250px',
|
maxWidth: '250px',
|
||||||
@@ -23,6 +47,15 @@ const LayoutAuthMenu: FC = () => {
|
|||||||
setAnchorEl(event.currentTarget);
|
setAnchorEl(event.currentTarget);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { locale, LL, setLocale } = useContext(I18nContext);
|
||||||
|
|
||||||
|
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({ target }) => {
|
||||||
|
const loc = target.value as Locales;
|
||||||
|
localStorage.setItem('lang', loc);
|
||||||
|
await loadLocaleAsync(loc);
|
||||||
|
setLocale(loc);
|
||||||
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
};
|
};
|
||||||
@@ -32,7 +65,60 @@ const LayoutAuthMenu: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconButton id="open-auth-menu" sx={{ padding: 0 }} aria-describedby={id} color="inherit" onClick={handleClick}>
|
<TextField
|
||||||
|
name="locale"
|
||||||
|
InputProps={{ style: { fontSize: 10 } }}
|
||||||
|
variant="outlined"
|
||||||
|
value={locale}
|
||||||
|
onChange={onLocaleSelected}
|
||||||
|
size="small"
|
||||||
|
select
|
||||||
|
>
|
||||||
|
<MenuItem key="de" value="de">
|
||||||
|
<img src={DEflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
DE
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="en" value="en">
|
||||||
|
<img src={GBflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
EN
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="fr" value="fr">
|
||||||
|
<img src={FRflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
FR
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="it" value="it">
|
||||||
|
<img src={ITflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
IT
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="nl" value="nl">
|
||||||
|
<img src={NLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
NL
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="no" value="no">
|
||||||
|
<img src={NOflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
NO
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="pl" value="pl">
|
||||||
|
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
PL
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="sv" value="sv">
|
||||||
|
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
SV
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="tr" value="tr">
|
||||||
|
<img src={TRflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
TR
|
||||||
|
</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
id="open-auth-menu"
|
||||||
|
sx={{ ml: 1, padding: 0 }}
|
||||||
|
aria-describedby={id}
|
||||||
|
color="inherit"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
<AccountCircleIcon />
|
<AccountCircleIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Popover
|
<Popover
|
||||||
@@ -56,13 +142,15 @@ const LayoutAuthMenu: FC = () => {
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
<Box pl={2}>
|
<Box pl={2}>
|
||||||
<ItemTypography variant="h6">{me.username}</ItemTypography>
|
<ItemTypography variant="h6">{me.username}</ItemTypography>
|
||||||
<ItemTypography variant="body1">{me.admin ? 'Admin User' : 'Guest User'}</ItemTypography>
|
<ItemTypography variant="body1">
|
||||||
|
{me.admin ? LL.ADMIN() : LL.GUEST()} {LL.USER(2)}
|
||||||
|
</ItemTypography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Box p={1.5}>
|
<Box p={1.5}>
|
||||||
<Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}>
|
<Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}>
|
||||||
Sign Out
|
{LL.SIGN_OUT()}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
|
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
|
||||||
|
|
||||||
import { PROJECT_NAME } from '../../api/env';
|
|
||||||
|
|
||||||
import LayoutMenu from './LayoutMenu';
|
|
||||||
import { DRAWER_WIDTH } from './Layout';
|
import { DRAWER_WIDTH } from './Layout';
|
||||||
|
import LayoutMenu from './LayoutMenu';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import { PROJECT_NAME } from 'api/env';
|
||||||
|
|
||||||
const LayoutDrawerLogo = styled('img')(({ theme }) => ({
|
const LayoutDrawerLogo = styled('img')(({ theme }) => ({
|
||||||
[theme.breakpoints.down('sm')]: {
|
[theme.breakpoints.down('sm')]: {
|
||||||
@@ -13,7 +11,7 @@ const LayoutDrawerLogo = styled('img')(({ theme }) => ({
|
|||||||
marginRight: theme.spacing(2)
|
marginRight: theme.spacing(2)
|
||||||
},
|
},
|
||||||
[theme.breakpoints.up('sm')]: {
|
[theme.breakpoints.up('sm')]: {
|
||||||
height: 36,
|
height: 38,
|
||||||
marginRight: theme.spacing(2)
|
marginRight: theme.spacing(2)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -29,9 +27,7 @@ const LayoutDrawer: FC<LayoutDrawerProps> = ({ mobileOpen, onClose }) => {
|
|||||||
<Toolbar disableGutters>
|
<Toolbar disableGutters>
|
||||||
<Box display="flex" alignItems="center" px={2}>
|
<Box display="flex" alignItems="center" px={2}>
|
||||||
<LayoutDrawerLogo src="/app/icon.png" alt={PROJECT_NAME} />
|
<LayoutDrawerLogo src="/app/icon.png" alt={PROJECT_NAME} />
|
||||||
<Typography variant="h6" color="textPrimary">
|
<Typography variant="h6">{PROJECT_NAME}</Typography>
|
||||||
{PROJECT_NAME}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Divider absolute />
|
<Divider absolute />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|||||||
@@ -1,39 +1,52 @@
|
|||||||
import { FC, useContext } from 'react';
|
|
||||||
|
|
||||||
import { Divider, List } from '@mui/material';
|
|
||||||
|
|
||||||
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
|
||||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||||
|
|
||||||
|
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
import InfoIcon from '@mui/icons-material/Info';
|
||||||
import LockIcon from '@mui/icons-material/Lock';
|
import LockIcon from '@mui/icons-material/Lock';
|
||||||
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
|
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
|
||||||
|
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
||||||
|
import TuneIcon from '@mui/icons-material/Tune';
|
||||||
|
import { Divider, List } from '@mui/material';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { FeaturesContext } from '../../contexts/features';
|
import LayoutMenuItem from 'components/layout/LayoutMenuItem';
|
||||||
import ProjectMenu from '../../project/ProjectMenu';
|
|
||||||
|
|
||||||
import LayoutMenuItem from './LayoutMenuItem';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const LayoutMenu: FC = () => {
|
const LayoutMenu: FC = () => {
|
||||||
const { features } = useContext(FeaturesContext);
|
|
||||||
const authenticatedContext = useContext(AuthenticatedContext);
|
const authenticatedContext = useContext(AuthenticatedContext);
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{features.project && (
|
|
||||||
<List disablePadding component="nav">
|
|
||||||
<ProjectMenu />
|
|
||||||
<Divider />
|
|
||||||
</List>
|
|
||||||
)}
|
|
||||||
<List disablePadding component="nav">
|
<List disablePadding component="nav">
|
||||||
<LayoutMenuItem icon={SettingsEthernetIcon} label="Network Connection" to="/network" />
|
<LayoutMenuItem icon={DashboardIcon} label={LL.DASHBOARD()} to={`/dashboard`} />
|
||||||
<LayoutMenuItem icon={SettingsInputAntennaIcon} label="Access Point" to="/ap" />
|
<LayoutMenuItem
|
||||||
{features.ntp && <LayoutMenuItem icon={AccessTimeIcon} label="Network Time" to="/ntp" />}
|
icon={TuneIcon}
|
||||||
{features.mqtt && <LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" />}
|
label={LL.SETTINGS_OF('')}
|
||||||
<LayoutMenuItem icon={LockIcon} label="Security" to="/security" disabled={!authenticatedContext.me.admin} />
|
to={`/settings`}
|
||||||
<LayoutMenuItem icon={SettingsIcon} label="System" to="/system" />
|
disabled={!authenticatedContext.me.admin}
|
||||||
|
/>
|
||||||
|
<LayoutMenuItem icon={InfoIcon} label={LL.HELP_OF('')} to={`/help`} />
|
||||||
|
<Divider />
|
||||||
|
</List>
|
||||||
|
<List disablePadding component="nav">
|
||||||
|
<LayoutMenuItem icon={SettingsEthernetIcon} label={LL.NETWORK(0)} to="/network" />
|
||||||
|
<LayoutMenuItem icon={SettingsInputAntennaIcon} label={LL.ACCESS_POINT(0)} to="/ap" />
|
||||||
|
<LayoutMenuItem icon={AccessTimeIcon} label="NTP" to="/ntp" />
|
||||||
|
<LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" />
|
||||||
|
<LayoutMenuItem
|
||||||
|
icon={LockIcon}
|
||||||
|
label={LL.SECURITY(0)}
|
||||||
|
to="/security"
|
||||||
|
disabled={!authenticatedContext.me.admin}
|
||||||
|
/>
|
||||||
|
<LayoutMenuItem icon={SettingsIcon} label={LL.SYSTEM(0)} to="/system" />
|
||||||
</List>
|
</List>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { FC } from 'react';
|
import { ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import type { SvgIconProps } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { ListItem, ListItemButton, ListItemIcon, ListItemText, SvgIconProps } from '@mui/material';
|
import { routeMatches } from 'utils';
|
||||||
|
|
||||||
import { grey } from '@mui/material/colors';
|
|
||||||
|
|
||||||
import { routeMatches } from '../../utils';
|
|
||||||
|
|
||||||
interface LayoutMenuItemProps {
|
interface LayoutMenuItemProps {
|
||||||
icon: React.ComponentType<SvgIconProps>;
|
icon: React.ComponentType<SvgIconProps>;
|
||||||
@@ -17,13 +15,15 @@ interface LayoutMenuItemProps {
|
|||||||
const LayoutMenuItem: FC<LayoutMenuItemProps> = ({ icon: Icon, label, to, disabled }) => {
|
const LayoutMenuItem: FC<LayoutMenuItemProps> = ({ icon: Icon, label, to, disabled }) => {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const selected = routeMatches(to, pathname);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem disablePadding selected={routeMatches(to, pathname)}>
|
<ListItem disablePadding>
|
||||||
<ListItemButton component={Link} to={to} disabled={disabled}>
|
<ListItemButton component={Link} to={to} disabled={disabled} selected={selected}>
|
||||||
<ListItemIcon sx={{ color: grey[500] }}>
|
<ListItemIcon sx={{ color: selected ? '#90caf9' : '#9e9e9e' }}>
|
||||||
<Icon />
|
<Icon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>{label}</ListItemText>
|
<ListItemText sx={{ color: selected ? '#90caf9' : '#f5f5f5' }}>{label}</ListItemText>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Box, Paper, Typography } from '@mui/material';
|
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import { Box, Paper, Typography } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
interface ApplicationErrorProps {
|
interface ApplicationErrorProps {
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Box, Button, CircularProgress, Typography } from '@mui/material';
|
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import { Box, Button, CircularProgress, Typography } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { MessageBox } from '..';
|
import { MessageBox } from 'components';
|
||||||
|
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
interface FormLoaderProps {
|
interface FormLoaderProps {
|
||||||
message?: string;
|
message?: string;
|
||||||
@@ -12,12 +13,14 @@ interface FormLoaderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FormLoader: FC<FormLoaderProps> = ({ errorMessage, onRetry, message = 'Loading…' }) => {
|
const FormLoader: FC<FormLoaderProps> = ({ errorMessage, onRetry, message = 'Loading…' }) => {
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
return (
|
return (
|
||||||
<MessageBox my={2} level="error" message={errorMessage}>
|
<MessageBox my={2} level="error" message={errorMessage}>
|
||||||
{onRetry && (
|
{onRetry && (
|
||||||
<Button startIcon={<RefreshIcon />} variant="contained" color="error" onClick={onRetry}>
|
<Button startIcon={<RefreshIcon />} variant="contained" color="error" onClick={onRetry}>
|
||||||
Retry
|
{LL.RETRY()}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</MessageBox>
|
</MessageBox>
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
import { FC } from 'react';
|
import { CircularProgress, Box, Typography } from '@mui/material';
|
||||||
|
import type { Theme } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { CircularProgress, Box, Typography, Theme } from '@mui/material';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
interface LoadingSpinnerProps {
|
interface LoadingSpinnerProps {
|
||||||
height?: number | string;
|
height?: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LoadingSpinner: FC<LoadingSpinnerProps> = ({ height = '100%' }) => (
|
const LoadingSpinner: FC<LoadingSpinnerProps> = ({ height = '100%' }) => {
|
||||||
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column" padding={2} height={height}>
|
const { LL } = useI18nContext();
|
||||||
<CircularProgress
|
|
||||||
sx={(theme: Theme) => ({
|
return (
|
||||||
margin: theme.spacing(4),
|
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column" padding={2} height={height}>
|
||||||
color: theme.palette.text.secondary
|
<CircularProgress
|
||||||
})}
|
sx={(theme: Theme) => ({
|
||||||
size={100}
|
margin: theme.spacing(4),
|
||||||
/>
|
color: theme.palette.text.secondary
|
||||||
<Typography variant="h4" color="textSecondary">
|
})}
|
||||||
Loading…
|
size={100}
|
||||||
</Typography>
|
/>
|
||||||
</Box>
|
<Typography variant="h4" color="textSecondary">
|
||||||
);
|
{LL.LOADING()}…
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default LoadingSpinner;
|
export default LoadingSpinner;
|
||||||
|
|||||||
32
interface/src/components/routing/BlockNavigation.tsx
Normal file
32
interface/src/components/routing/BlockNavigation.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { unstable_Blocker as Blocker } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { dialogStyle } from 'CustomTheme';
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
|
interface BlockNavigationProps {
|
||||||
|
blocker: Blocker;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BlockNavigation: FC<BlockNavigationProps> = ({ blocker }) => {
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog sx={dialogStyle} open={blocker.state === 'blocked'}>
|
||||||
|
<DialogTitle>{LL.BLOCK_NAVIGATE_1()}</DialogTitle>
|
||||||
|
<DialogContent dividers>{LL.BLOCK_NAVIGATE_2()}</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="outlined" onClick={() => blocker.reset?.()} color="secondary">
|
||||||
|
{LL.STAY()}
|
||||||
|
</Button>
|
||||||
|
<Button variant="contained" onClick={() => blocker.proceed?.()} color="primary">
|
||||||
|
{LL.LEAVE()}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BlockNavigation;
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { FC, useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
import { RequiredChildrenProps } from '../../utils';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
const RequireAdmin: FC<RequiredChildrenProps> = ({ children }) => {
|
const RequireAdmin: FC<RequiredChildrenProps> = ({ children }) => {
|
||||||
const authenticatedContext = useContext(AuthenticatedContext);
|
const authenticatedContext = useContext(AuthenticatedContext);
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { FC, useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { Navigate, useLocation } from 'react-router-dom';
|
import { Navigate, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import type { AuthenticatedContextValue } from 'contexts/authentication/context';
|
||||||
AuthenticatedContext,
|
import type { FC } from 'react';
|
||||||
AuthenticatedContextValue,
|
|
||||||
AuthenticationContext
|
|
||||||
} from '../../contexts/authentication/context';
|
|
||||||
import { storeLoginRedirect } from '../../api/authentication';
|
|
||||||
|
|
||||||
import { RequiredChildrenProps } from '../../utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
import { storeLoginRedirect } from 'api/authentication';
|
||||||
|
import { AuthenticatedContext, AuthenticationContext } from 'contexts/authentication/context';
|
||||||
|
|
||||||
const RequireAuthenticated: FC<RequiredChildrenProps> = ({ children }) => {
|
const RequireAuthenticated: FC<RequiredChildrenProps> = ({ children }) => {
|
||||||
const authenticationContext = useContext(AuthenticationContext);
|
const authenticationContext = useContext(AuthenticationContext);
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { FC, useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import * as AuthenticationApi from '../../api/authentication';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
import { AuthenticationContext } from '../../contexts/authentication';
|
import * as AuthenticationApi from 'api/authentication';
|
||||||
import { RequiredChildrenProps } from '../../utils';
|
import { AuthenticationContext } from 'contexts/authentication';
|
||||||
import { FeaturesContext } from '../../contexts/features';
|
|
||||||
|
|
||||||
const RequireUnauthenticated: FC<RequiredChildrenProps> = ({ children }) => {
|
const RequireUnauthenticated: FC<RequiredChildrenProps> = ({ children }) => {
|
||||||
const { features } = useContext(FeaturesContext);
|
|
||||||
const authenticationContext = useContext(AuthenticationContext);
|
const authenticationContext = useContext(AuthenticationContext);
|
||||||
|
|
||||||
return authenticationContext.me ? <Navigate to={AuthenticationApi.fetchLoginRedirect(features)} /> : <>{children}</>;
|
return authenticationContext.me ? <Navigate to={AuthenticationApi.fetchLoginRedirect()} /> : <>{children}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RequireUnauthenticated;
|
export default RequireUnauthenticated;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { FC } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Tabs, useMediaQuery, useTheme } from '@mui/material';
|
import { Tabs, useMediaQuery, useTheme } from '@mui/material';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { RequiredChildrenProps } from '../../utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
|
|
||||||
interface RouterTabsProps extends RequiredChildrenProps {
|
interface RouterTabsProps extends RequiredChildrenProps {
|
||||||
value: string | false;
|
value: string | false;
|
||||||
@@ -15,7 +14,7 @@ const RouterTabs: FC<RouterTabsProps> = ({ value, children }) => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const smallDown = useMediaQuery(theme.breakpoints.down('sm'));
|
const smallDown = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
const handleTabChange = (event: React.ChangeEvent<{}>, path: string) => {
|
const handleTabChange = (event: React.ChangeEvent<HTMLInputElement>, path: string) => {
|
||||||
navigate(path);
|
navigate(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { FC, Fragment } from 'react';
|
|
||||||
import { useDropzone, DropzoneState } from 'react-dropzone';
|
|
||||||
|
|
||||||
import { Box, Button, LinearProgress, Theme, Typography, useTheme } from '@mui/material';
|
|
||||||
|
|
||||||
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||||
|
import { Box, Button, LinearProgress, Typography, useTheme } from '@mui/material';
|
||||||
|
import { Fragment } from 'react';
|
||||||
|
import { useDropzone } from 'react-dropzone';
|
||||||
|
import type { Theme } from '@mui/material';
|
||||||
|
import type { Progress } from 'alova';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import type { DropzoneState } from 'react-dropzone';
|
||||||
|
|
||||||
const progressPercentage = (progress: ProgressEvent) => Math.round((progress.loaded * 100) / progress.total);
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const getBorderColor = (theme: Theme, props: DropzoneState) => {
|
const getBorderColor = (theme: Theme, props: DropzoneState) => {
|
||||||
if (props.isDragAccept) {
|
if (props.isDragAccept) {
|
||||||
@@ -24,31 +26,35 @@ const getBorderColor = (theme: Theme, props: DropzoneState) => {
|
|||||||
export interface SingleUploadProps {
|
export interface SingleUploadProps {
|
||||||
onDrop: (acceptedFiles: File[]) => void;
|
onDrop: (acceptedFiles: File[]) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
uploading: boolean;
|
isUploading: boolean;
|
||||||
progress?: ProgressEvent;
|
progress: Progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, progress }) => {
|
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, isUploading, progress }) => {
|
||||||
|
const uploading = isUploading && progress.total > 0;
|
||||||
|
|
||||||
const dropzoneState = useDropzone({
|
const dropzoneState = useDropzone({
|
||||||
onDrop,
|
onDrop,
|
||||||
accept: {
|
accept: {
|
||||||
'application/octet-stream': ['.bin'],
|
'application/octet-stream': ['.bin'],
|
||||||
'application/json': ['.json']
|
'application/json': ['.json'],
|
||||||
|
'text/plain': ['.md5']
|
||||||
},
|
},
|
||||||
disabled: uploading,
|
disabled: isUploading,
|
||||||
multiple: false
|
multiple: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = dropzoneState;
|
const { getRootProps, getInputProps } = dropzoneState;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const progressText = () => {
|
const progressText = () => {
|
||||||
if (uploading) {
|
if (uploading) {
|
||||||
if (progress?.lengthComputable) {
|
if (progress.total) {
|
||||||
return `Uploading: ${progressPercentage(progress)}%`;
|
return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%';
|
||||||
}
|
}
|
||||||
return 'Uploading\u2026';
|
|
||||||
}
|
}
|
||||||
return 'Drop file or click here';
|
return LL.UPLOAD_DROP_TEXT();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -60,7 +66,7 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
|||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
borderStyle: 'dashed',
|
borderStyle: 'dashed',
|
||||||
color: theme.palette.grey[700],
|
color: theme.palette.grey[400],
|
||||||
transition: 'border .24s ease-in-out',
|
transition: 'border .24s ease-in-out',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
cursor: uploading ? 'default' : 'pointer',
|
cursor: uploading ? 'default' : 'pointer',
|
||||||
@@ -76,12 +82,12 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<Box width="100%" p={2}>
|
<Box width="100%" p={2}>
|
||||||
<LinearProgress
|
<LinearProgress
|
||||||
variant={!progress || progress.lengthComputable ? 'determinate' : 'indeterminate'}
|
variant="determinate"
|
||||||
value={!progress ? 0 : progress.lengthComputable ? progressPercentage(progress) : 0}
|
value={progress.total === 0 ? 0 : Math.round((progress.loaded * 100) / progress.total)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>
|
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>
|
||||||
Cancel
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
export { default as SingleUpload } from './SingleUpload';
|
export { default as SingleUpload } from './SingleUpload';
|
||||||
export { default as useFileUpload } from './useFileUpload';
|
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
import axios, { AxiosPromise, CancelTokenSource } from 'axios';
|
|
||||||
import { useSnackbar } from 'notistack';
|
|
||||||
|
|
||||||
import { extractErrorMessage } from '../../utils';
|
|
||||||
import { FileUploadConfig } from '../../api/endpoints';
|
|
||||||
|
|
||||||
interface MediaUploadOptions {
|
|
||||||
upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useFileUpload = ({ upload }: MediaUploadOptions) => {
|
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
|
||||||
const [uploading, setUploading] = useState<boolean>(false);
|
|
||||||
const [uploadProgress, setUploadProgress] = useState<ProgressEvent>();
|
|
||||||
const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>();
|
|
||||||
|
|
||||||
const resetUploadingStates = () => {
|
|
||||||
setUploading(false);
|
|
||||||
setUploadProgress(undefined);
|
|
||||||
setUploadCancelToken(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelUpload = useCallback(() => {
|
|
||||||
uploadCancelToken?.cancel();
|
|
||||||
resetUploadingStates();
|
|
||||||
}, [uploadCancelToken]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
uploadCancelToken?.cancel();
|
|
||||||
};
|
|
||||||
}, [uploadCancelToken]);
|
|
||||||
|
|
||||||
const uploadFile = async (images: File[]) => {
|
|
||||||
try {
|
|
||||||
const cancelToken = axios.CancelToken.source();
|
|
||||||
setUploadCancelToken(cancelToken);
|
|
||||||
setUploading(true);
|
|
||||||
await upload(images[0], {
|
|
||||||
onUploadProgress: setUploadProgress,
|
|
||||||
cancelToken: cancelToken.token
|
|
||||||
});
|
|
||||||
resetUploadingStates();
|
|
||||||
enqueueSnackbar('File uploaded', { variant: 'success' });
|
|
||||||
} catch (error: unknown) {
|
|
||||||
if (axios.isCancel(error)) {
|
|
||||||
enqueueSnackbar('Upload aborted', { variant: 'warning' });
|
|
||||||
} else {
|
|
||||||
resetUploadingStates();
|
|
||||||
enqueueSnackbar(extractErrorMessage(error, 'Upload failed'), { variant: 'error' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return [uploadFile, cancelUpload, uploading, uploadProgress] as const;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useFileUpload;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user