Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b379816178 | ||
|
|
e4353255ca | ||
|
|
923f2c7635 | ||
|
|
a8309ef705 | ||
|
|
bad05485d0 | ||
|
|
38d7799ffa | ||
|
|
6e6696ccd4 | ||
|
|
feb546920e | ||
|
|
81b05b9de4 | ||
|
|
ab10b042be | ||
|
|
45e4b3c423 | ||
|
|
6a5a139e33 | ||
|
|
1908d3200e | ||
|
|
f4cf40d38f | ||
|
|
514de84b97 | ||
|
|
7e7d0bb354 | ||
|
|
e468b90442 | ||
|
|
926959ab47 | ||
|
|
4663090a8f | ||
|
|
b0868a4d0b | ||
|
|
9088b00006 | ||
|
|
9c0fe8999d | ||
|
|
901aa5f7c1 | ||
|
|
3ee28c035b | ||
|
|
1e31b90905 | ||
|
|
48333041f4 | ||
|
|
717c9dbe89 | ||
|
|
fd86eb7aa7 | ||
|
|
9b4b35eff5 | ||
|
|
ac528caf2b | ||
|
|
fbe626e791 | ||
|
|
9ca37d30d8 | ||
|
|
84986898c8 | ||
|
|
5cebce3f4b | ||
|
|
bb344b14a8 | ||
|
|
20e613b444 | ||
|
|
a0952e0012 | ||
|
|
49e66f809e | ||
|
|
5152ed8731 | ||
|
|
33811a826f | ||
|
|
d33aebfd31 | ||
|
|
7d53c52a6b | ||
|
|
f015a8686e | ||
|
|
ab791d2a2d | ||
|
|
a303858da7 | ||
|
|
4c58c62cfe | ||
|
|
4593332204 | ||
|
|
9f5d8c4280 | ||
|
|
b0d44bf0ba | ||
|
|
59b8434e36 | ||
|
|
e6f27cbaf3 | ||
|
|
6c13df1aff | ||
|
|
44bf059b2f | ||
|
|
9ade464c2e | ||
|
|
aed5d4e05b | ||
|
|
b57648df14 | ||
|
|
7bce887077 | ||
|
|
cacd9c7f95 | ||
|
|
cd64bfe84b | ||
|
|
52ed68ed24 | ||
|
|
20577021a6 | ||
|
|
838a2236a1 | ||
|
|
d03941b792 | ||
|
|
92299c3079 | ||
|
|
d9f05532cd | ||
|
|
2fd2dff126 | ||
|
|
8e14ad0536 | ||
|
|
2b325102f3 | ||
|
|
1f2be606fa | ||
|
|
6b29a2a724 | ||
|
|
f79edeb053 | ||
|
|
84b92096d4 | ||
|
|
70f4814cb1 | ||
|
|
49579eb2b4 | ||
|
|
cc6caeeca9 | ||
|
|
90af22eba1 | ||
|
|
f4b4b41264 | ||
|
|
9918b03c11 | ||
|
|
95f44246bf | ||
|
|
b098723f78 | ||
|
|
a819513a92 | ||
|
|
7531b28b4c | ||
|
|
2b8586d1b7 | ||
|
|
8167291ffc | ||
|
|
0dfcf9607a | ||
|
|
4827831107 | ||
|
|
0ebb6d5d30 | ||
|
|
f4ed5062f6 | ||
|
|
434b129746 | ||
|
|
2f5041ac7d | ||
|
|
24f9766b39 | ||
|
|
ba872217d7 | ||
|
|
b2ae517dac | ||
|
|
32b52dc0c8 | ||
|
|
33a3fe9bbd | ||
|
|
245dc76503 | ||
|
|
21dbfd79d8 | ||
|
|
b9f2250278 | ||
|
|
a0601f1891 | ||
|
|
70403ee433 | ||
|
|
f3cde58c8f | ||
|
|
71c267776a | ||
|
|
359ff94a8c | ||
|
|
efe1c1492f | ||
|
|
2aaaf7c358 | ||
|
|
cefaffa0f0 | ||
|
|
a22d56dc41 | ||
|
|
51eca94504 | ||
|
|
2b6d24d328 | ||
|
|
4932025423 | ||
|
|
c466abf4d0 | ||
|
|
2ece3a8b4c | ||
|
|
3f760e1875 | ||
|
|
1da134c38c | ||
|
|
0bf96e9864 | ||
|
|
1f5c19306e | ||
|
|
db10b3821c | ||
|
|
4245a6d04d | ||
|
|
6920c1159b | ||
|
|
3801e1eade | ||
|
|
3e419303a7 | ||
|
|
0de1e6a3af | ||
|
|
99c415f898 | ||
|
|
f68d83dbf0 | ||
|
|
6e97905bce | ||
|
|
572a10d5a0 | ||
|
|
eaa6199de0 | ||
|
|
70799101e4 | ||
|
|
9cc70d0323 | ||
|
|
20c5da6e55 | ||
|
|
4c356bc368 | ||
|
|
aeb04deae4 | ||
|
|
873e4d6547 | ||
|
|
9ba20304c2 | ||
|
|
252619b1a0 | ||
|
|
38b129de66 | ||
|
|
da028d1af9 | ||
|
|
bba361016d | ||
|
|
c362af1f81 | ||
|
|
520507afd9 | ||
|
|
4e7584167b | ||
|
|
112bd61a25 | ||
|
|
1df8bc0dfe | ||
|
|
f2076fe309 | ||
|
|
67dea15bf2 | ||
|
|
910f19fe55 | ||
|
|
290bf0b6d2 | ||
|
|
4c87a776e6 | ||
|
|
4436093816 | ||
|
|
b85aa7b084 | ||
|
|
99e28b639d | ||
|
|
9b94b4da26 | ||
|
|
5b46986628 | ||
|
|
d0e5690bfd | ||
|
|
ddbfa06bcc | ||
|
|
2c6fc99c68 | ||
|
|
d8ad0ac72c | ||
|
|
9de50d7981 | ||
|
|
2c5c46dc48 | ||
|
|
6423b4cbb9 | ||
|
|
2566aa725a | ||
|
|
eb463bbb2b | ||
|
|
1a8a559d84 | ||
|
|
57c1d787e3 | ||
|
|
999a32424f | ||
|
|
c2ed20ba23 | ||
|
|
01cbb37359 | ||
|
|
3e205e9536 | ||
|
|
b29d6b4478 | ||
|
|
b9424e4c21 | ||
|
|
87fe912d8d | ||
|
|
59343c2f82 | ||
|
|
700fc64383 | ||
|
|
ca256b9949 | ||
|
|
68750f3455 | ||
|
|
906fc417c8 | ||
|
|
ac203c56df | ||
|
|
734ad45f1c | ||
|
|
4a1f2e0dd3 | ||
|
|
24e3514640 | ||
|
|
dadb05e022 | ||
|
|
ca3dfb3c07 | ||
|
|
cf452942fa | ||
|
|
28ee40bebe | ||
|
|
6267752a2a | ||
|
|
2ece24e077 | ||
|
|
59013e62d3 | ||
|
|
824763aae9 | ||
|
|
a8c5b04feb | ||
|
|
64f069ec24 | ||
|
|
515e438d4f | ||
|
|
9ec064f9d9 | ||
|
|
60c6b04a01 | ||
|
|
9deaa81a72 | ||
|
|
ae703db6ee | ||
|
|
267f54bc0e | ||
|
|
8aa26fb2cd | ||
|
|
e7667f2537 | ||
|
|
bac15d2ecf | ||
|
|
ae27e07903 | ||
|
|
605dfa7366 | ||
|
|
85db19e33f | ||
|
|
052bc6b356 | ||
|
|
24985e2bca | ||
|
|
8e3eb8b3d2 | ||
|
|
2be44dbd94 | ||
|
|
7c6b26faa2 | ||
|
|
cfc42512ed | ||
|
|
50f5834296 | ||
|
|
07e91bb73a | ||
|
|
3c547eeed8 | ||
|
|
6f011fce3b | ||
|
|
5aebed95a4 | ||
|
|
8321f9f6e9 | ||
|
|
bf9d69ab56 | ||
|
|
faaff57d2b | ||
|
|
40667ef1c1 | ||
|
|
04b4e3298b | ||
|
|
43cbd3a61e | ||
|
|
bfb091f2f4 | ||
|
|
4974303e6b | ||
|
|
664bb0a663 | ||
|
|
491596d03d | ||
|
|
75538be235 | ||
|
|
1cb99ea2fc | ||
|
|
c0f4bde5fc | ||
|
|
a4430dd49d | ||
|
|
b513d40bd6 | ||
|
|
fbaaf89a21 | ||
|
|
7625a0154e | ||
|
|
396ee803b2 | ||
|
|
5a42319a07 | ||
|
|
61b9820f78 | ||
|
|
a383a45269 | ||
|
|
7bd4869ae6 | ||
|
|
49a6559453 | ||
|
|
2e16a10720 | ||
|
|
cd973b50ff | ||
|
|
b6fc67fff2 | ||
|
|
12f00d785d | ||
|
|
de1ff6ed49 | ||
|
|
d0a6cbe5dd | ||
|
|
35924ce550 | ||
|
|
ad1d8cbedd | ||
|
|
6294ce59f7 | ||
|
|
d445ff40c2 | ||
|
|
c6010c3ab4 | ||
|
|
9da80d7b7d | ||
|
|
2c62e58dbd | ||
|
|
efc54375dd | ||
|
|
0c3946033b | ||
|
|
9dc86aa77d | ||
|
|
0cbe320a5f | ||
|
|
1c749a5310 | ||
|
|
a647a69791 | ||
|
|
12db089c73 | ||
|
|
f5ff7cfcf1 | ||
|
|
2ddf71ec46 | ||
|
|
9017374292 | ||
|
|
633f50cf5b | ||
|
|
1993e9c65b | ||
|
|
8edc8610dc | ||
|
|
6700b373cd | ||
|
|
8d824799a0 | ||
|
|
dc86c62d8b | ||
|
|
4ae4ff72e3 | ||
|
|
958eebb16c | ||
|
|
43e48d2ba6 | ||
|
|
b6d8099c74 | ||
|
|
25bac7c5ce | ||
|
|
5d7a080e39 | ||
|
|
b2a76dba98 | ||
|
|
15ad69c8cb | ||
|
|
e3ca006265 | ||
|
|
961cc13948 | ||
|
|
50da233258 | ||
|
|
15ad58b5cc | ||
|
|
61a27ff430 | ||
|
|
befdb0a708 | ||
|
|
06b79600ac | ||
|
|
b9e633ad06 | ||
|
|
736a78faed | ||
|
|
d76e69f9a7 | ||
|
|
1612e1d142 | ||
|
|
56a33fd4c5 | ||
|
|
1ca3244868 | ||
|
|
8f358f09b8 | ||
|
|
d903de6526 | ||
|
|
c41ade2983 | ||
|
|
99dbbc7f39 | ||
|
|
4dd51cc889 | ||
|
|
54c651f7e2 | ||
|
|
d1c9d7ffdc | ||
|
|
052b40680d | ||
|
|
b0d2519192 | ||
|
|
997da11cc5 | ||
|
|
476232ea22 | ||
|
|
4c83f4e2de | ||
|
|
4bf39e18d9 | ||
|
|
edfa533a4d | ||
|
|
28262145ab | ||
|
|
51136ce982 | ||
|
|
84893ec582 | ||
|
|
4236405b90 | ||
|
|
292c66e25c | ||
|
|
5aa6c15c76 | ||
|
|
1f89dc47cb | ||
|
|
31393128c8 | ||
|
|
232a18425a | ||
|
|
88e2eb2a49 | ||
|
|
e635d5f748 | ||
|
|
08a23cdb2f | ||
|
|
1ead940623 | ||
|
|
382362668e | ||
|
|
987f8bc949 | ||
|
|
6f096333d3 | ||
|
|
5a25111b20 | ||
|
|
c7cb16cafa | ||
|
|
a746a698b6 | ||
|
|
9a7f7255ff | ||
|
|
d3df6b8f4f | ||
|
|
fcffd17b11 | ||
|
|
7fc6be9f39 | ||
|
|
7362744ff8 | ||
|
|
f4d058155a | ||
|
|
b04612e80d | ||
|
|
5f00e4d34d | ||
|
|
e967feb091 | ||
|
|
0445b18bb9 | ||
|
|
99f1f749cf | ||
|
|
f4d16f1c47 | ||
|
|
68071d6caf | ||
|
|
44975da7db | ||
|
|
e4566518b2 | ||
|
|
c038cb5b96 | ||
|
|
d7eb3db60b | ||
|
|
44e781d7da | ||
|
|
0951d58c8f | ||
|
|
37c0ed0b49 | ||
|
|
556bf7b888 | ||
|
|
280be2eaea | ||
|
|
13f9357f8e | ||
|
|
56e015db0e | ||
|
|
1068fbf079 | ||
|
|
45f7afb3e1 | ||
|
|
0d3fc2b656 | ||
|
|
2dc66b2e3b | ||
|
|
0b9129d8f5 | ||
|
|
82d702fedc | ||
|
|
197be75677 | ||
|
|
3f9aff1b10 | ||
|
|
f5ed50fa88 | ||
|
|
645b52602f | ||
|
|
27a8e60e9a | ||
|
|
201ffa108b | ||
|
|
1661bd6691 | ||
|
|
d431f4bbc5 | ||
|
|
2b8e92a70d | ||
|
|
d70ab3cfa8 | ||
|
|
9402c6ad28 | ||
|
|
da5e8e9778 | ||
|
|
feedfdf810 | ||
|
|
6db0a502bf | ||
|
|
f07957d45e | ||
|
|
3d75d71767 | ||
|
|
b259bdeddd | ||
|
|
042465387f | ||
|
|
d09e4d648c | ||
|
|
754910ad5f | ||
|
|
977e080215 | ||
|
|
a9ac947c96 | ||
|
|
28e6ef1d63 | ||
|
|
b0f273157d | ||
|
|
49add531c4 | ||
|
|
8a1d2a5dca | ||
|
|
31c5883f5f | ||
|
|
c625fbb43d | ||
|
|
0f474e8d2a | ||
|
|
73813a8383 | ||
|
|
d8fb110b56 | ||
|
|
77e62626a9 | ||
|
|
24ee83f13d | ||
|
|
6791a36d85 | ||
|
|
c6a5a64e64 | ||
|
|
311062dc41 | ||
|
|
391ce1f009 | ||
|
|
9f6d99352a | ||
|
|
02b1d6260a | ||
|
|
e8ade14259 | ||
|
|
f0804dfed4 | ||
|
|
f506344ada | ||
|
|
ddca518cca | ||
|
|
21d3678fa7 | ||
|
|
3dd8d7d8e5 | ||
|
|
92aa538a84 | ||
|
|
557a5583e8 | ||
|
|
b00ddff7dd | ||
|
|
cc3b311ab0 | ||
|
|
6cedca5d76 | ||
|
|
d82452e00f | ||
|
|
57a5c0ba38 | ||
|
|
28c8519e5b | ||
|
|
cfcb7191ea | ||
|
|
6921227ec8 | ||
|
|
1c9991ee83 | ||
|
|
fd475fcfff | ||
|
|
f47f995e90 | ||
|
|
d93e943326 | ||
|
|
7ca9f2d5cd | ||
|
|
ff1d45a008 | ||
|
|
53f0a72362 | ||
|
|
3a47242219 | ||
|
|
5556568feb | ||
|
|
6011a15cd4 | ||
|
|
2c014778d9 | ||
|
|
0b2712f52f | ||
|
|
69f1acd823 | ||
|
|
c0f15d50d6 | ||
|
|
1d3fe51e8b | ||
|
|
d364d50c05 | ||
|
|
615d324b11 | ||
|
|
eb36b6755f | ||
|
|
40c28d3b21 | ||
|
|
e726282023 | ||
|
|
327f734e4f | ||
|
|
b361f64d4f | ||
|
|
b38d215b3d | ||
|
|
99aa498bc4 | ||
|
|
f14798dec9 | ||
|
|
65873f3dee | ||
|
|
fc98c88358 | ||
|
|
7c0440c148 | ||
|
|
ad051269ed | ||
|
|
497d8f25a1 | ||
|
|
eb142f26f2 | ||
|
|
b01989f041 | ||
|
|
84d89e80cd | ||
|
|
d059d2cda4 | ||
|
|
751413f050 | ||
|
|
ce5e28554f | ||
|
|
860be3f96c | ||
|
|
89fb3a3d6e | ||
|
|
474a07564c | ||
|
|
59b69a3b3b | ||
|
|
2553392e42 | ||
|
|
fcd6901558 | ||
|
|
716ffbb945 | ||
|
|
cd4b9aeafe | ||
|
|
5d712b6f54 | ||
|
|
754c68fccc | ||
|
|
858f4da0d1 | ||
|
|
90910a7a4f | ||
|
|
cd218b59f9 | ||
|
|
9625c49c95 | ||
|
|
2dfed10d2e | ||
|
|
a74d3737dc | ||
|
|
6c82a34aa0 | ||
|
|
320d5656ac | ||
|
|
a4a5778001 | ||
|
|
a80a003865 | ||
|
|
4607e46c6f | ||
|
|
0aa3e2e49c | ||
|
|
595bb36956 | ||
|
|
afdf7e6df3 | ||
|
|
3e1c7d7ef2 | ||
|
|
7ce7c3deb7 | ||
|
|
c9b390ec60 | ||
|
|
cc2b826356 | ||
|
|
0b2c493731 | ||
|
|
0d7bbc51e7 | ||
|
|
db81c30d24 | ||
|
|
41ea8de0d6 | ||
|
|
c4b5b37568 | ||
|
|
76e6b23a4f | ||
|
|
283e295a98 | ||
|
|
606009afb0 | ||
|
|
da303b8b4f | ||
|
|
d4048661ae | ||
|
|
47853547ca | ||
|
|
6ae9188af4 | ||
|
|
d958ec2ac8 | ||
|
|
07aac8e6e9 | ||
|
|
9a341c9032 | ||
|
|
21e8b08ed9 | ||
|
|
c41d477adc | ||
|
|
f8445a4c85 | ||
|
|
71f5be011b | ||
|
|
a39b7ddbac | ||
|
|
cab5c822c5 | ||
|
|
4056bd3c2d | ||
|
|
a985ae9c0a | ||
|
|
804a3bf380 | ||
|
|
a8bef96463 | ||
|
|
5e43a3f695 | ||
|
|
8e46e05e1b | ||
|
|
ec0e27dbb4 | ||
|
|
3f55d93b41 | ||
|
|
5418a8c28a | ||
|
|
e180192379 | ||
|
|
f498d69b7d | ||
|
|
dfae1b3f70 | ||
|
|
a9ebb55f95 | ||
|
|
abfac8aa40 | ||
|
|
78956ccf83 | ||
|
|
eed25f23f6 | ||
|
|
134ce56f80 | ||
|
|
28a4778187 | ||
|
|
c654c2e5e8 | ||
|
|
e580d4e637 | ||
|
|
2192914cbb | ||
|
|
ca6dee1777 | ||
|
|
6f3bdc5dca | ||
|
|
095cdcd69e | ||
|
|
f022633f29 | ||
|
|
1f85fcc9b0 | ||
|
|
a3ede75842 | ||
|
|
0defce37f0 | ||
|
|
4355b39b1e | ||
|
|
efec738c89 | ||
|
|
10c09a361b | ||
|
|
e3d1a69223 | ||
|
|
4b82253ae9 | ||
|
|
47a12c30ca | ||
|
|
613655767b | ||
|
|
1182027f1c | ||
|
|
e9421d557a | ||
|
|
bbc0c0df94 | ||
|
|
610e32714c | ||
|
|
7ab81d3805 | ||
|
|
9a1e41e1fa | ||
|
|
ae71d12f06 | ||
|
|
c25234df83 | ||
|
|
03284ba046 | ||
|
|
5fa433ecca | ||
|
|
ac2a4c700a | ||
|
|
eeb7b00f60 | ||
|
|
c8c540b453 | ||
|
|
a0585f75f7 | ||
|
|
637af1d392 | ||
|
|
fe6c31acc1 | ||
|
|
04cfcf751e | ||
|
|
51c51d49ad | ||
|
|
4271aa5b1f | ||
|
|
17ada1c894 | ||
|
|
2e14d125ba | ||
|
|
ce8fa4c24b | ||
|
|
cfe2e83e6b | ||
|
|
b17c62618d | ||
|
|
6b162a1193 | ||
|
|
6653530b63 | ||
|
|
aacae6444e | ||
|
|
b91fe12258 | ||
|
|
ae7ca00e69 | ||
|
|
0103f1f080 | ||
|
|
e8d29d54d6 | ||
|
|
092f2ce0b3 | ||
|
|
d89233276b | ||
|
|
7d797187e8 | ||
|
|
6edcc72364 | ||
|
|
250dfc1377 | ||
|
|
45489c1c87 | ||
|
|
09b250f189 | ||
|
|
a84465ddba | ||
|
|
3b95671442 | ||
|
|
52e66077a7 | ||
|
|
84bf4d21a3 | ||
|
|
04cfc510d0 | ||
|
|
d846e67ec1 | ||
|
|
9c60a1bc10 | ||
|
|
f06ac52a43 | ||
|
|
9e5fd43a2d | ||
|
|
8d50e7dc82 | ||
|
|
1e6b7bd2a6 | ||
|
|
8346be7a81 | ||
|
|
9dc5683fc2 | ||
|
|
e7cb82175a | ||
|
|
1ef0aa14c0 | ||
|
|
0b3f6d6aab | ||
|
|
0beef1d770 | ||
|
|
b944db30fd | ||
|
|
6cf3a3feaf | ||
|
|
9680b8ccb3 | ||
|
|
a88a11c243 | ||
|
|
7ff76f69a1 | ||
|
|
c2e4e6d221 | ||
|
|
60edc456a9 | ||
|
|
625f804130 | ||
|
|
7100aaedec | ||
|
|
c867dbc7b2 | ||
|
|
88097f1829 | ||
|
|
7cf4834336 | ||
|
|
b5355307a3 | ||
|
|
76f693e5d7 | ||
|
|
fb3b0ca6e3 | ||
|
|
3c71977e2e | ||
|
|
5574a2663a | ||
|
|
41960b4212 | ||
|
|
6a500b8d40 | ||
|
|
dcede59379 | ||
|
|
695153c527 | ||
|
|
c409cca20e | ||
|
|
a5a9204f83 | ||
|
|
25d42ad345 | ||
|
|
f70a407611 | ||
|
|
89f2e66c80 | ||
|
|
4562be52d4 | ||
|
|
ebd931f7af | ||
|
|
6a47ff6422 | ||
|
|
1237185bb4 | ||
|
|
d7fe819699 | ||
|
|
770ffd1483 | ||
|
|
9373e77636 | ||
|
|
cb5034548d | ||
|
|
f9ad870ff0 | ||
|
|
b538bc3871 | ||
|
|
e5b0528aca | ||
|
|
a12bd2271e | ||
|
|
15c5858732 | ||
|
|
dd4227f444 | ||
|
|
fe8a7d8db9 | ||
|
|
ec79ca71b2 | ||
|
|
7a1544e76a | ||
|
|
27ffc23664 | ||
|
|
8ef8ee91e9 | ||
|
|
571625f0c3 | ||
|
|
fc2d6eb6bb | ||
|
|
48800504be | ||
|
|
f94a019733 | ||
|
|
535d8b9e02 | ||
|
|
161d44ee04 | ||
|
|
7ea155b943 | ||
|
|
30acef2452 | ||
|
|
82b50fd3a5 | ||
|
|
9e5503dd04 | ||
|
|
2ec2ed62c8 | ||
|
|
274c11c3ec | ||
|
|
76c4bb556f | ||
|
|
40f484cc54 | ||
|
|
85b701b8c2 | ||
|
|
7e875ba89d | ||
|
|
c718d7999f | ||
|
|
d1472992da | ||
|
|
995f90aa49 | ||
|
|
a4c27618b4 | ||
|
|
05019529f1 | ||
|
|
1b5ab3261c | ||
|
|
6033555ac0 | ||
|
|
fd4e1975a3 | ||
|
|
124ac12150 | ||
|
|
8c1ffa3f5d | ||
|
|
f76a7e36e8 | ||
|
|
6484c4bc8b | ||
|
|
bba2421ba9 | ||
|
|
3c69c7feaf | ||
|
|
45b2ffe1af | ||
|
|
a57c0f4700 | ||
|
|
c33116166f | ||
|
|
50a2063e45 | ||
|
|
cd30cf2d6c | ||
|
|
419126652e | ||
|
|
06ab83dca6 | ||
|
|
313fddf722 | ||
|
|
f4def36ccb | ||
|
|
4be8f2ba93 | ||
|
|
881b282604 | ||
|
|
9675bfe07d | ||
|
|
443351e95a | ||
|
|
d4cd1c61ee | ||
|
|
75892cb523 | ||
|
|
63e0041644 | ||
|
|
70e2d8dd4e | ||
|
|
b2624f6055 | ||
|
|
0bb8144677 | ||
|
|
ad50447ec6 | ||
|
|
84de7437c6 | ||
|
|
419dd50255 | ||
|
|
9dea02e578 | ||
|
|
1c181e091e | ||
|
|
32782bab0e | ||
|
|
330783cb5a | ||
|
|
504b004e01 | ||
|
|
edb861da70 | ||
|
|
64ad15895f | ||
|
|
44b4944334 | ||
|
|
f9272d0959 | ||
|
|
a113e344a1 | ||
|
|
2d80f03f01 | ||
|
|
542fe9f029 | ||
|
|
a1992f5910 | ||
|
|
45a057b0c6 | ||
|
|
4a1a26e8b7 | ||
|
|
db35e6a427 | ||
|
|
7358ed25a5 | ||
|
|
0d385c29a3 | ||
|
|
51076e68c8 | ||
|
|
3270f1252b | ||
|
|
d8ee105e1e | ||
|
|
567fccd7ee | ||
|
|
f7f7319698 | ||
|
|
62a6b61fc9 | ||
|
|
1a87d9fa77 | ||
|
|
8131e30002 | ||
|
|
83c76bdafc | ||
|
|
cf1566e116 | ||
|
|
5d2c977029 | ||
|
|
823c0ef5fb | ||
|
|
aefc588e6e | ||
|
|
1949439d69 | ||
|
|
c131b8daf1 | ||
|
|
8a61969c8f | ||
|
|
3cf85770c7 | ||
|
|
310b339f0d | ||
|
|
229b7810df | ||
|
|
a686beb9a7 | ||
|
|
42c1bee8ea | ||
|
|
c289556775 | ||
|
|
941d2f2a3e | ||
|
|
0a265e79a6 | ||
|
|
7f30c01c58 | ||
|
|
0661f5921a | ||
|
|
1c16e95b3a | ||
|
|
44efa45d75 | ||
|
|
ef89776517 | ||
|
|
2f3f1357c8 | ||
|
|
dcdeffff9a | ||
|
|
229b492ccc | ||
|
|
2d45ba1981 | ||
|
|
b73022096d | ||
|
|
d1124703b7 | ||
|
|
4a8dd81c93 | ||
|
|
f474a31484 | ||
|
|
e9ec2c5304 | ||
|
|
c7b9ca68c2 | ||
|
|
ffa033cca9 | ||
|
|
3d3f6e09b6 | ||
|
|
7252f5385b | ||
|
|
ad641880a6 | ||
|
|
1f6d62a635 | ||
|
|
3cd4fd2d61 | ||
|
|
d565faa75d | ||
|
|
786e118f06 | ||
|
|
eca612d514 | ||
|
|
060f43839d | ||
|
|
9c3c4a6f4b | ||
|
|
101cc1a672 | ||
|
|
e47f71344d | ||
|
|
fee6de6a7f | ||
|
|
2bf25a1406 | ||
|
|
dd59296be9 | ||
|
|
b382726ed0 | ||
|
|
01390fe540 | ||
|
|
8596e1bef7 | ||
|
|
13fffbc5b6 | ||
|
|
5f4857ddae | ||
|
|
5f36b41785 | ||
|
|
bd8f0c90e1 | ||
|
|
f49683353d | ||
|
|
4f012d6bc6 | ||
|
|
e48e353066 | ||
|
|
6ad7af54dc | ||
|
|
6b69bef2ef | ||
|
|
d2a27d3d91 | ||
|
|
189aa80743 | ||
|
|
f122bd5797 | ||
|
|
d76f86f7db | ||
|
|
ccf5364187 | ||
|
|
a3bfe3e61a | ||
|
|
de10e340b6 | ||
|
|
40ca26f23b | ||
|
|
da28213613 | ||
|
|
6615a821f1 | ||
|
|
d627d669bc | ||
|
|
ef6fbad0cb | ||
|
|
ef9f7d4c16 | ||
|
|
44a0fa0986 | ||
|
|
4d9338323a | ||
|
|
1a6bfb65e6 | ||
|
|
3b112cc8c8 | ||
|
|
014a008c3a | ||
|
|
374be7b065 | ||
|
|
2ba3630de9 | ||
|
|
24cb9467c0 | ||
|
|
79c49e64e8 | ||
|
|
c8c0b8fb91 | ||
|
|
e5585cb0e7 | ||
|
|
bd32f6e2ef | ||
|
|
08f8ea0cd9 | ||
|
|
2d48b28b0a | ||
|
|
9cfaee9b21 | ||
|
|
9393e271de | ||
|
|
948595fb3b | ||
|
|
6149b619e7 | ||
|
|
b7406357c5 | ||
|
|
2279a51d52 | ||
|
|
4d3d0995c9 | ||
|
|
0ca752662d | ||
|
|
4246f5d206 | ||
|
|
e8289ec17d | ||
|
|
cde441bca6 | ||
|
|
740b609e1e | ||
|
|
c459c17fab | ||
|
|
11bbda9bb3 | ||
|
|
6a5c523948 | ||
|
|
99c23606c5 | ||
|
|
608b1ee156 | ||
|
|
12ab5c9964 | ||
|
|
779c825e92 | ||
|
|
96df718304 | ||
|
|
4bee440a56 | ||
|
|
bb4e4c7cda | ||
|
|
582ab220b3 | ||
|
|
3e2ad83b2c | ||
|
|
ae29a1734c | ||
|
|
457ce5e563 | ||
|
|
154bdb08a9 | ||
|
|
22b2dc9ea8 | ||
|
|
3ede1a4a5a | ||
|
|
9e3138b892 | ||
|
|
51cb40acd2 | ||
|
|
4702073458 | ||
|
|
e01cffff46 | ||
|
|
d88e438679 | ||
|
|
70683fc8e7 | ||
|
|
bd7ee0d1e8 | ||
|
|
17e2fdd4cb | ||
|
|
a468f58128 | ||
|
|
581a0698af | ||
|
|
f02f88f336 | ||
|
|
43d53b0616 | ||
|
|
54d6bad5e5 | ||
|
|
c37d4f768b | ||
|
|
a4b003b7ac | ||
|
|
433120ab22 | ||
|
|
109da5f515 | ||
|
|
56e6cf406a | ||
|
|
a2289a0625 | ||
|
|
02943a1082 | ||
|
|
64a36496e3 | ||
|
|
f2009ebd62 | ||
|
|
638892ca70 | ||
|
|
90fdbd36ec | ||
|
|
0dabc45780 | ||
|
|
0ffec91984 | ||
|
|
9cacf443e1 | ||
|
|
3f21be4fb0 | ||
|
|
7587f8212e | ||
|
|
1eed80375d | ||
|
|
806eed6040 | ||
|
|
d1828ecdb4 | ||
|
|
5cddb8ecda | ||
|
|
1651d9d6d9 | ||
|
|
40fc049cdc | ||
|
|
89afe62121 | ||
|
|
e5b40ee29e | ||
|
|
4a3d760c06 | ||
|
|
aa326f975d | ||
|
|
1fbf534f9a | ||
|
|
86aa38bfd5 | ||
|
|
ca929031ee | ||
|
|
f80477a093 | ||
|
|
abd7698d6d | ||
|
|
d5fe3261f4 | ||
|
|
3f46ae7beb | ||
|
|
46af09f776 | ||
|
|
f40b272eeb | ||
|
|
9876e3afd7 | ||
|
|
41df6a6c0b | ||
|
|
dbd25daff8 | ||
|
|
71eefb6272 | ||
|
|
a4a340dbd4 | ||
|
|
2c01893bb1 | ||
|
|
c4e496b001 | ||
|
|
16e90c3082 | ||
|
|
543b4a132b | ||
|
|
e4a5296fbf | ||
|
|
6c796b8552 | ||
|
|
e30ad43d36 | ||
|
|
f841b85d7d | ||
|
|
735b7bece6 | ||
|
|
4c9fdaf8b0 | ||
|
|
2ccc4acf34 | ||
|
|
c24343f107 | ||
|
|
81ffcd8ee9 | ||
|
|
432452ea9a | ||
|
|
6c14c7d623 | ||
|
|
30126072dc | ||
|
|
f7812c9b0a | ||
|
|
dca43f430b | ||
|
|
0947ce9234 | ||
|
|
91fa2ad23b | ||
|
|
1b57c56fdc | ||
|
|
d13509e302 | ||
|
|
85d300ce71 | ||
|
|
d6e1a3ab4c | ||
|
|
f1da8542ce | ||
|
|
a183621268 | ||
|
|
035de980e3 | ||
|
|
99f0688377 | ||
|
|
56412193e7 | ||
|
|
c5751b0bd3 | ||
|
|
86e57d243d | ||
|
|
7fc57a3be7 | ||
|
|
75069a44c7 | ||
|
|
2536865c4d | ||
|
|
d266f822ca | ||
|
|
5b8979a0f1 | ||
|
|
3bc79b9dfd | ||
|
|
0ae75e6db6 | ||
|
|
89e2a15eb7 | ||
|
|
b1896d9944 | ||
|
|
d0dedcfd2e | ||
|
|
73297c8e65 | ||
|
|
017c7290d6 | ||
|
|
6a8b5b06d0 | ||
|
|
93a6bc501e | ||
|
|
e8f050c194 | ||
|
|
712517b6fa | ||
|
|
434378eda5 | ||
|
|
cf38fdb720 | ||
|
|
95d30cf482 | ||
|
|
6542c02b7f | ||
|
|
a13144932a | ||
|
|
6597830631 | ||
|
|
ba08140197 | ||
|
|
d65ce3b596 | ||
|
|
4864a6e618 | ||
|
|
2bbb2bb1db | ||
|
|
47ce004f61 | ||
|
|
507e392509 | ||
|
|
dcfb014d1f | ||
|
|
4e880fb6e0 | ||
|
|
898e2b1e30 | ||
|
|
a68f3dc4eb | ||
|
|
c446fe0c6d | ||
|
|
4246e6c768 | ||
|
|
4f9cfdf65d | ||
|
|
b1cd233dda | ||
|
|
4f63b6093f | ||
|
|
380a50e39b | ||
|
|
3bd3a4ff7b | ||
|
|
46bf79455c | ||
|
|
2d5c26d44f | ||
|
|
9760b7c9d6 | ||
|
|
0b5a00c99a | ||
|
|
ba8482c707 | ||
|
|
1b34360b05 | ||
|
|
c84dab21e4 | ||
|
|
1640287897 | ||
|
|
adcf5bab0e | ||
|
|
e024700851 | ||
|
|
f4fc33d111 | ||
|
|
fb446022e2 | ||
|
|
e021d7cd9d | ||
|
|
3331ed2f49 | ||
|
|
939a8ddc26 | ||
|
|
3774f676cf | ||
|
|
7923509d1e | ||
|
|
30d6a44b0d | ||
|
|
6d46ecaa6c | ||
|
|
5a448ab4f5 | ||
|
|
a71262437d | ||
|
|
52ab963845 | ||
|
|
67280724ba | ||
|
|
d8a4b5aa34 | ||
|
|
fe0b2d0646 | ||
|
|
d3368e4aad | ||
|
|
903b510411 | ||
|
|
0dbc99d9ee | ||
|
|
505ca96547 | ||
|
|
89623d00f9 | ||
|
|
e47bcb3fb6 | ||
|
|
50d0d68f45 | ||
|
|
2f75418c70 | ||
|
|
013bc58371 | ||
|
|
55948cd431 | ||
|
|
dfca896a64 | ||
|
|
8fc6f3f744 | ||
|
|
f65058fa03 | ||
|
|
33567bab41 | ||
|
|
106662b1d7 | ||
|
|
b73115c002 | ||
|
|
cff208c5a9 | ||
|
|
a83dd9569d | ||
|
|
807cd4df9e | ||
|
|
ac3151f871 | ||
|
|
d4106b5cbd | ||
|
|
bcb6c2675f | ||
|
|
6b51666248 | ||
|
|
628a007112 | ||
|
|
ee65342545 | ||
|
|
94ea973fb5 | ||
|
|
9f8424e93f | ||
|
|
2e750236be | ||
|
|
c701315ab5 | ||
|
|
a53dc5c0a0 | ||
|
|
895ca80b2b | ||
|
|
1dcb7dd13d | ||
|
|
e75afcba42 | ||
|
|
0f0cf7a5db | ||
|
|
31c7d66245 | ||
|
|
cac7f653c9 | ||
|
|
219428c249 | ||
|
|
f4fa1e097d | ||
|
|
35b517b434 | ||
|
|
b3f956ba63 | ||
|
|
3ae8b3f8a7 | ||
|
|
c48349ba56 | ||
|
|
0763146254 | ||
|
|
7da5d42009 | ||
|
|
5ac847076a | ||
|
|
a88ea776a9 | ||
|
|
73a5ff1fb6 | ||
|
|
7e8901c99f | ||
|
|
45816ef5db | ||
|
|
fe682f8399 | ||
|
|
9ffb6fb26a | ||
|
|
063244e4ca | ||
|
|
d7b7a0a577 | ||
|
|
f735585239 | ||
|
|
30349ec217 | ||
|
|
55517d48ed | ||
|
|
0f93ab84e9 | ||
|
|
4dd1891594 | ||
|
|
27e1472d88 | ||
|
|
a3d63c095a | ||
|
|
dc486a2aa3 | ||
|
|
9963bdee09 | ||
|
|
499401d457 | ||
|
|
e2fcf5f92c | ||
|
|
b8bba7513a | ||
|
|
65957527cb | ||
|
|
e2ce68e3ef | ||
|
|
44cf02e5ba | ||
|
|
a4a70270ee | ||
|
|
82e1cac1c7 | ||
|
|
21ffe8e3f3 | ||
|
|
45f20d5089 | ||
|
|
cea12a35b7 | ||
|
|
686320a805 | ||
|
|
14ad51d2d2 | ||
|
|
28c15b3ff4 | ||
|
|
f5af28c886 | ||
|
|
6b50039863 | ||
|
|
34c5f63c00 | ||
|
|
53bca4efab | ||
|
|
9029593691 | ||
|
|
4aad859e20 | ||
|
|
b9d1d4a94f | ||
|
|
be8b01e289 | ||
|
|
360dedd9b2 | ||
|
|
e5d3d02307 | ||
|
|
d3e14ca7e5 | ||
|
|
5998208449 | ||
|
|
4fc84621ae | ||
|
|
8253a84689 | ||
|
|
6b7f14c69d | ||
|
|
cfaca604f5 | ||
|
|
0226b9db31 | ||
|
|
7f8d12b9f7 | ||
|
|
2ae341cf5e | ||
|
|
7850b550d8 | ||
|
|
10596a8bdc | ||
|
|
252e4715d4 | ||
|
|
420b989adb | ||
|
|
bdb388c79d | ||
|
|
87148f95f2 | ||
|
|
fe124354cd | ||
|
|
9a28fa71f0 | ||
|
|
4139cdaf35 | ||
|
|
ebe04620e1 | ||
|
|
7e053cd632 | ||
|
|
1fd65c77fe | ||
|
|
2bfc838614 | ||
|
|
69082db381 | ||
|
|
5ce2130f05 | ||
|
|
5b448ec7a6 | ||
|
|
f72b27d1c1 | ||
|
|
9973c5f2b7 | ||
|
|
88ee415dd1 | ||
|
|
4f168cf346 | ||
|
|
ad7b5d2cd8 | ||
|
|
1e522de4a7 | ||
|
|
c2d2d085cd | ||
|
|
b3a4c12d67 | ||
|
|
d5993ba27e | ||
|
|
ef563603a9 | ||
|
|
7e2ca72310 | ||
|
|
7c17c806ad | ||
|
|
8e0f529a00 | ||
|
|
47e15a3d47 | ||
|
|
cac7007069 | ||
|
|
ac55c900e7 | ||
|
|
25806e48dc | ||
|
|
3871f4b6ab | ||
|
|
27c892806b | ||
|
|
2b9f0480a1 | ||
|
|
72cc433265 | ||
|
|
7721b074e6 | ||
|
|
f5fd176b35 | ||
|
|
6db1d7925e | ||
|
|
0ed7545947 | ||
|
|
95e22e8d4f | ||
|
|
cf7d1d8041 | ||
|
|
443d0f0c6c | ||
|
|
c20e48179f | ||
|
|
3fbb7de712 | ||
|
|
0294603dfc | ||
|
|
5458252e4a | ||
|
|
31ce219544 | ||
|
|
86cd567968 | ||
|
|
5c66d32bc0 | ||
|
|
5ac5c93c90 | ||
|
|
84480f4072 | ||
|
|
b82953405d | ||
|
|
5271626947 | ||
|
|
bc16854ccb | ||
|
|
6db0485c7b | ||
|
|
b5fc0d30a8 | ||
|
|
38557aac7f | ||
|
|
d621fbfab1 | ||
|
|
f19eec7fae | ||
|
|
8174842b05 | ||
|
|
ff07d261b2 | ||
|
|
c55db8a895 | ||
|
|
70a9554b72 | ||
|
|
bdd7d92108 | ||
|
|
a7cbc19755 | ||
|
|
93ff3e4d1f | ||
|
|
1dfc748e87 | ||
|
|
e1f17a7ff1 | ||
|
|
56c40ff0fd | ||
|
|
9dd3b75165 | ||
|
|
20fc8f0279 | ||
|
|
944704a12d | ||
|
|
fba9f14b2c | ||
|
|
a7c24aeeee | ||
|
|
186eb998ba | ||
|
|
d630ba743d | ||
|
|
e9fabdcc96 | ||
|
|
31698678fb | ||
|
|
ebd2e89faa | ||
|
|
17e0fb1c15 | ||
|
|
6e3e77a1a0 | ||
|
|
ef44f90491 | ||
|
|
66376885e9 | ||
|
|
db941e2627 | ||
|
|
5f78d34f4d | ||
|
|
85281ae128 | ||
|
|
e5d1b213c9 | ||
|
|
e4ab9a4130 | ||
|
|
a1ec179229 | ||
|
|
2f77855ec1 | ||
|
|
d11173fe90 | ||
|
|
fde8b82b78 | ||
|
|
b20eded30e | ||
|
|
3ec17c41be | ||
|
|
0ed64707f3 | ||
|
|
417533e90c | ||
|
|
e268affb5a | ||
|
|
4474ada291 | ||
|
|
a65c3288d8 | ||
|
|
affcef7e37 | ||
|
|
173c2ba338 | ||
|
|
b5fcf95b88 | ||
|
|
d954851d43 | ||
|
|
df602c27d5 | ||
|
|
3d9fa10485 | ||
|
|
6990040626 | ||
|
|
b42ea6cd3c | ||
|
|
8c7fd8b330 | ||
|
|
4e0039625e | ||
|
|
6c909f8935 | ||
|
|
4f5d385e52 | ||
|
|
84fe1d87b3 | ||
|
|
bb0bc41f99 | ||
|
|
1c2d67ded7 | ||
|
|
7372a05ce5 | ||
|
|
642befa8d5 | ||
|
|
3b5a9e2d4b | ||
|
|
6b3d9d9baf | ||
|
|
436852d077 | ||
|
|
b78ced4fa9 | ||
|
|
1bc81df00e | ||
|
|
bf025537b6 | ||
|
|
ad4812218d | ||
|
|
0fbc4b8ff4 | ||
|
|
7ef82f5caf | ||
|
|
1141ea7933 | ||
|
|
1e9344161b | ||
|
|
6c021765bb | ||
|
|
bc151af51d | ||
|
|
8bd09e0e80 | ||
|
|
5978cb9703 | ||
|
|
8c447a95dd | ||
|
|
870431dc1a | ||
|
|
1125b1f545 | ||
|
|
d255fc737a | ||
|
|
37cb51ee94 | ||
|
|
02cabcc0ec | ||
|
|
3ffb6c046e | ||
|
|
7bf8b81995 | ||
|
|
0ddda4a267 | ||
|
|
c46ad970cb | ||
|
|
733abcffbc | ||
|
|
139f928731 | ||
|
|
3da9986232 | ||
|
|
a284df01d1 | ||
|
|
412f5cf3e0 | ||
|
|
42ba1e4aed | ||
|
|
1893b06c13 | ||
|
|
b0cbb70e53 | ||
|
|
78e81fef27 | ||
|
|
dd08231a34 | ||
|
|
8c78b7ef53 | ||
|
|
c650fdcdfc | ||
|
|
e99973d4b7 | ||
|
|
4012a86bd8 | ||
|
|
048fee5c88 | ||
|
|
a287fd97e4 | ||
|
|
3a249183f8 | ||
|
|
393dad97e0 | ||
|
|
113abc71d4 | ||
|
|
63261ec0d5 | ||
|
|
507935d067 | ||
|
|
c579b73f75 | ||
|
|
e69ec148cc | ||
|
|
a76ade9bb2 | ||
|
|
4fec149345 | ||
|
|
5bc164de76 | ||
|
|
969b84043c | ||
|
|
da72dafbfb | ||
|
|
1adf7f6b2b | ||
|
|
3e085ccbaa | ||
|
|
3e8e2ab513 | ||
|
|
4f802ece52 | ||
|
|
313ac34e59 | ||
|
|
54f75889fa | ||
|
|
f64c86d23f | ||
|
|
9e634310b4 | ||
|
|
33666e7d6b | ||
|
|
40d4c7e410 | ||
|
|
8cd4699dbc | ||
|
|
49d5aff8f9 | ||
|
|
b2ee9cb363 | ||
|
|
e9c20ad992 | ||
|
|
63b880e624 | ||
|
|
768f67ae0a | ||
|
|
fbf5fa53c6 | ||
|
|
4a5824e7a7 | ||
|
|
32f3c48b65 | ||
|
|
f7fbcd2451 | ||
|
|
c6253854ba | ||
|
|
544644e94b | ||
|
|
f6cebf6956 | ||
|
|
1b82aa02aa | ||
|
|
e565a7e73d | ||
|
|
59c4dfa33b | ||
|
|
1a8e3c2e31 | ||
|
|
226917e1cd | ||
|
|
fe813ee247 | ||
|
|
4cd830a0bf | ||
|
|
1259ca9ac9 | ||
|
|
d0857fe36c | ||
|
|
56a7438114 | ||
|
|
09df542db8 | ||
|
|
1df102ab93 | ||
|
|
02b3b780bb | ||
|
|
f84f3da012 | ||
|
|
c65c8fdaaa | ||
|
|
b484123f1b | ||
|
|
b60119790e | ||
|
|
216427cea0 | ||
|
|
c53e2fcc14 | ||
|
|
9fd80fd136 | ||
|
|
5fc284722f | ||
|
|
ad6e395da0 | ||
|
|
6c79a406cd | ||
|
|
456e8bd249 | ||
|
|
de95f28598 | ||
|
|
43405afdb2 | ||
|
|
97f95e17db | ||
|
|
6725d61b3f | ||
|
|
ebda2cb100 | ||
|
|
bb72b2494d | ||
|
|
3b0a509cae | ||
|
|
2f981f9383 | ||
|
|
fc3c08e411 | ||
|
|
ff811f287a | ||
|
|
8ebb3bc37b | ||
|
|
f7ff571c5f | ||
|
|
6a82cbee71 | ||
|
|
b0585bd0a9 | ||
|
|
6ad4d242c2 | ||
|
|
030b1d9826 | ||
|
|
f6ffa7b3b9 | ||
|
|
137efe2233 | ||
|
|
62411d039e | ||
|
|
54b1716e77 | ||
|
|
9f7aba8039 | ||
|
|
a176b8d566 | ||
|
|
26a97ec36c | ||
|
|
f29807aad8 | ||
|
|
92d3c7a4cd | ||
|
|
af3a0f6af0 | ||
|
|
ce6164ca20 | ||
|
|
e9fc3fcecf | ||
|
|
25a1a24890 | ||
|
|
bf7fc1d5a9 | ||
|
|
2126b49ab3 | ||
|
|
ae6767b1d2 | ||
|
|
133629dbee | ||
|
|
fb0a9bc7e3 | ||
|
|
2a3492766f | ||
|
|
2ec7e5653e | ||
|
|
30bfb245f5 | ||
|
|
84d89bc4f5 | ||
|
|
7f55fabfec | ||
|
|
9e0f04dc8b | ||
|
|
574c2954b3 | ||
|
|
9d38e26b81 | ||
|
|
4e75a0c81e | ||
|
|
d311e967fa | ||
|
|
d0a6c111cb | ||
|
|
aa6d8b4662 | ||
|
|
3f5fd89fd7 | ||
|
|
f43a5a005c | ||
|
|
755bf3c412 | ||
|
|
84f3fb0ba5 | ||
|
|
f2df6c114d | ||
|
|
f93659e303 | ||
|
|
226404d39c | ||
|
|
c1bcc143b9 | ||
|
|
5ca9062d45 | ||
|
|
0b209eaabb | ||
|
|
47ef191ea2 | ||
|
|
25e76d761e | ||
|
|
9731a51dae | ||
|
|
81220fa590 | ||
|
|
24e8c4d81a | ||
|
|
013520b0c0 | ||
|
|
f012bcb027 | ||
|
|
922e1ce529 | ||
|
|
7a707cc64b | ||
|
|
1967acfef2 | ||
|
|
e24cb04003 | ||
|
|
67974746ff | ||
|
|
8d916d41d7 | ||
|
|
08444e3942 | ||
|
|
9c41a0cd26 | ||
|
|
2c50069d98 | ||
|
|
31059f6728 | ||
|
|
2000752b2c | ||
|
|
dd34830443 | ||
|
|
c58f4ee51b | ||
|
|
00f7e8cb2a | ||
|
|
439904e27f | ||
|
|
4d6590ece2 | ||
|
|
9b0c4b970d | ||
|
|
a80ce1b12a | ||
|
|
57dd74c180 | ||
|
|
90c67e4226 | ||
|
|
093cd6a6a3 | ||
|
|
975db86306 | ||
|
|
eba688e11b | ||
|
|
05b44204d5 | ||
|
|
0bb017c1d5 | ||
|
|
a6efd0b335 | ||
|
|
c3d237e9c6 | ||
|
|
09d28f5570 | ||
|
|
ba02937ec6 | ||
|
|
3d78b14deb | ||
|
|
f558551d1d | ||
|
|
12fe66370f | ||
|
|
423aaa492c | ||
|
|
2016586475 | ||
|
|
bf88167152 | ||
|
|
52be84f04e | ||
|
|
03d187b147 | ||
|
|
3329047579 | ||
|
|
86c70e5abb | ||
|
|
b17f4108ea | ||
|
|
4e523cc455 | ||
|
|
49c9be9159 | ||
|
|
f1bfe0760f | ||
|
|
35f02fdaad | ||
|
|
6ac09966ba | ||
|
|
af9a7617f5 | ||
|
|
42c89732c8 | ||
|
|
054475ddbf | ||
|
|
126d556d6a | ||
|
|
529a302e77 | ||
|
|
acbf978452 | ||
|
|
90b22716ea | ||
|
|
fb90c504a6 | ||
|
|
65fd834654 | ||
|
|
a84acf5b8b | ||
|
|
e9505db146 | ||
|
|
4fb49b011e | ||
|
|
6742991469 | ||
|
|
2430cc5aac | ||
|
|
54590aaf26 | ||
|
|
7c5e67b272 | ||
|
|
c62c8ea4dd | ||
|
|
383653d608 | ||
|
|
001dc3f33d | ||
|
|
37d45b3fbb | ||
|
|
f72b8c6034 | ||
|
|
c11e5a9383 | ||
|
|
b5147a149e | ||
|
|
655529f30b | ||
|
|
fd5319e0ce | ||
|
|
f8c84ca672 | ||
|
|
1b81ac8916 | ||
|
|
c64e2ebb16 | ||
|
|
3771f2c088 | ||
|
|
6d398bd5c7 | ||
|
|
5c355a5ff3 | ||
|
|
d2f8b346e6 | ||
|
|
5cdaa931f9 | ||
|
|
693863a51e | ||
|
|
bc7178e5f7 |
No files matched your search
@@ -6,9 +6,8 @@
|
||||
|
||||
# Environment
|
||||
|
||||
- mongoose version (e.g. 7.3): _X.Y_
|
||||
- Compiler/IDE (e.g. arm-none-eabi-gcc 9.3): _?_
|
||||
- Target OS (e.g. FreeRTOS 10.3.1, Linux 5.8): _?_
|
||||
- Target hardware/board (e.g. STM32F411): _?_
|
||||
- Connectivity chip/module (e.g. WizNet W5500): _?_
|
||||
- Target SDK (e.g. STM32CubeF1 1.8.4): _?_
|
||||
- mongoose version: _X.Y_
|
||||
- Compiler/IDE and SDK: ZZZ version _X.Y_
|
||||
- Target hardware/board: _?_
|
||||
- Connectivity chip/module: _?_
|
||||
- Target RTOS/OS (if applicable): _?_
|
||||
@@ -0,0 +1,35 @@
|
||||
name: CIFuzz
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "mongoose.c"
|
||||
- "mongoose.h"
|
||||
jobs:
|
||||
Fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh '^src/' ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- name: Build Fuzzers
|
||||
id: build
|
||||
if: ${{ env.GO == 1 }}
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'mongoose'
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Run Fuzzers
|
||||
if: ${{ env.GO == 1 }}
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'mongoose'
|
||||
fuzz-seconds: 600
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v1
|
||||
if: env.GO == 1 && failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
||||
@@ -1,95 +1,281 @@
|
||||
name: build
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "**Makefile"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
env:
|
||||
IPV6: 0
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
cc: [gcc, clang, g++, clang++]
|
||||
target: [test, mip_test]
|
||||
ssl: [MBEDTLS, OPENSSL]
|
||||
name: linux ${{ matrix.target }} CC=${{ matrix.cc }} SSL=${{ matrix.ssl }}
|
||||
env:
|
||||
CC: ${{ matrix.cc }}
|
||||
SSL: ${{ matrix.ssl }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install packages
|
||||
run: sudo apt-get install libmbedtls-dev
|
||||
- name: make
|
||||
run: make test test++ IPV6=0
|
||||
- name: openssl
|
||||
run: make test SSL=OPENSSL IPV6=0
|
||||
- name: unamalgamated
|
||||
run: make unamalgamated
|
||||
examples:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh '^test|^src/.*.[ch]' ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: ./test/setup_ga_network.sh && sudo apt -y update ; sudo apt -y install libmbedtls-dev && make ${{ matrix.target }}
|
||||
s390:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install packages
|
||||
run: sudo apt-get install libmbedtls-dev
|
||||
- name: examples
|
||||
run: make clean examples
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh '^test|^src/.*.[ch]' ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: sudo apt -y update ; sudo apt -y install qemu binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make s390
|
||||
armhf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh '^test|^src/.*.[ch]' ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: sudo apt -y update ; sudo apt -y install qemu binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make armhf
|
||||
linux2:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh '^test|^src/.*.[ch]' ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: sudo apt -y update ; sudo apt -y install libmbedtls-dev valgrind
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make unamalgamated && make valgrind && make mg_prefix
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
env: { HOMEBREW_NO_AUTO_UPDATE: 1 }
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install packages
|
||||
run: HOMEBREW_NO_AUTO_UPDATE=1 brew install jq mbedtls openssl
|
||||
- name: make
|
||||
run: make test++ upload-coverage IPV6=0
|
||||
- name: openssl
|
||||
run: make test SSL=OPENSSL IPV6=0 OPENSSL=`echo /usr/local/Cellar/openssl*/*`
|
||||
- name: exports
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh '^test|^src/.*.[ch]' ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: brew install jq mbedtls openssl
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make test upload-coverage TFLAGS=-DNO_SNTP_CHECK SSL=OPENSSL ASAN_OPTIONS= OPENSSL=`echo /usr/local/Cellar/openssl*/*`
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make test SSL=MBEDTLS TFLAGS=-DNO_SNTP_CHECK ASAN_OPTIONS= MBEDTLS=`echo /usr/local/Cellar/mbedtls*/*`
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make mg_prefix
|
||||
windows:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: vc98
|
||||
run: make vc98
|
||||
- name: vc2017
|
||||
run: make vc2017
|
||||
- name: mingw
|
||||
run: make mingw
|
||||
- name: mingw++
|
||||
run: make mingw++
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh '^test|^src/.*.[ch]' ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make vc98 vc17 vc22 mingw mingw++
|
||||
arduino-xiao:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh '^examples/arduino|^src/.*.[ch]' ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make arduino-xiao
|
||||
arm:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: arm
|
||||
run: make arm
|
||||
esp32:
|
||||
- uses: actions/checkout@v3
|
||||
- run: make arm
|
||||
riscv:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: esp32
|
||||
run: make -C examples/esp32 build
|
||||
esp8266:
|
||||
- uses: actions/checkout@v3
|
||||
- run: make riscv
|
||||
|
||||
examples:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: esp8266
|
||||
run: make -C examples/esp8266 build
|
||||
stm32-freertos-tcp:
|
||||
- uses: actions/checkout@v3
|
||||
- run: sudo apt -y install libmbedtls-dev libpcap-dev
|
||||
- run: make clean examples
|
||||
- run: make clean test MG_ENABLE_POLL=1
|
||||
examples_win:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# - uses: egor-tensin/setup-mingw@v2
|
||||
# with:
|
||||
# platform: x64
|
||||
- name: test they build
|
||||
run: make examples_win
|
||||
shell: cmd
|
||||
- name: test they clean
|
||||
run: make clean_examples_win
|
||||
shell: cmd
|
||||
examples_mac:
|
||||
runs-on: macos-latest
|
||||
env: { HOMEBREW_NO_AUTO_UPDATE: 1 }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: make examples_mac
|
||||
- run: make clean_examples_mac
|
||||
|
||||
test_f7:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: stm32-freertos-tcp
|
||||
run: make -C examples/stm32-freertos-tcp build
|
||||
stm32-nucleo-f746z:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh "^src|examples/stm32/nucleo-f7" ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: sudo apt -y update; sudo apt -y install gcc-arm-none-eabi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/stm32/nucleo-f746zg-baremetal test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/stm32/nucleo-f746zg-freertos test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/stm32/nucleo-f746zg-freertos-tcp test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
|
||||
test_f4:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: stm32-nucleo-f746z
|
||||
run: make -C examples/stm32-nucleo-f746z build
|
||||
stm32-nucleo-f429z:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh "^src|examples/stm32/nucleo-f4" ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: sudo apt -y update; sudo apt -y install gcc-arm-none-eabi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/stm32/nucleo-f429zi-baremetal test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/stm32/nucleo-f429zi-freertos test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
|
||||
test_h7:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: stm32-nucleo-f429z
|
||||
run: make -C examples/stm32-nucleo-f429z build
|
||||
stm32-nucleo-h743z:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 3 }
|
||||
- run: if ./test/match_changed_files.sh "^src|examples/stm32/nucleo-h7" ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: sudo apt -y update; sudo apt -y install gcc-arm-none-eabi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/stm32/nucleo-h743zi-baremetal test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
|
||||
test_tm4c:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: stm32-nucleo-h743z
|
||||
run: make -C examples/stm32-nucleo-h743z build
|
||||
nxp-mimxrt1020-azurertos:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh "^src|examples/ti/ek-tm4c" ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: sudo apt -y update; sudo apt -y install gcc-arm-none-eabi
|
||||
- run: echo GO=0 > $GITHUB_ENV ; if ./test/match_changed_files.sh "^src|examples/ti/ek-tm4c1294xl-baremetal" ; then echo GO=1 > $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/ti/ek-tm4c1294xl-baremetal test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- run: echo GO=0 > $GITHUB_ENV ; if ./test/match_changed_files.sh "^src|examples/ti/ek-tm4c1294xl-freertos" ; then echo GO=1 > $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/ti/ek-tm4c1294xl-freertos test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
|
||||
test_pico_w5500:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-mimxrt1020-azurertos
|
||||
run: make -C examples/nxp-mimxrt1020-azurertos build
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh "^src|examples/rp2040/pico-w5500" ; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: sudo apt -y update; sudo apt -y install gcc-arm-none-eabi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/rp2040/pico-w5500 test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
|
||||
matrix_examples:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
example:
|
||||
- path: esp32/device-dashboard
|
||||
- path: esp32/uart-bridge
|
||||
- path: esp8266/http-client-server
|
||||
- path: stm32/nucleo-h743zi-baremetal
|
||||
- path: stm32/stm32-nucleo-f746z
|
||||
- path: stm32/stm32-nucleo-f746zg
|
||||
- path: stm32/stm32-nucleo-f429z
|
||||
- path: stm32/stm32-nucleo-h743z
|
||||
- path: stm32/nucleo-f429zi-baremetal
|
||||
- path: stm32/nucleo-f429zi-freertos
|
||||
- path: stm32/nucleo-f429zi-rndis
|
||||
- path: stm32/nucleo-f746zg-baremetal
|
||||
- path: stm32/nucleo-f746zg-freertos
|
||||
- path: stm32/nucleo-f746zg-freertos-tcp
|
||||
- path: stm32/nucleo-f746zg-rndis
|
||||
- path: nxp/nxp-mimxrt1020-azurertos
|
||||
- path: nxp/nxp-frdmk66f-freertos
|
||||
- path: nxp/nxp-lpcxpresso54s018m-freertos
|
||||
- path: nxp/nxp-mimxrt1020-freertos
|
||||
- path: nxp/nxp-evkbimxrt1050-lwip-freertos
|
||||
- path: nxp/nxp-evkmimxrt1020-lwip-freertos
|
||||
- path: nxp/nxp-evkmimxrt1024-lwip-freertos
|
||||
- path: nxp/nxp-evkmimxrt1060-lwip-freertos
|
||||
- path: nxp/nxp-evkmimxrt1064-lwip-freertos
|
||||
- path: nxp/nxp-evkmimxrt1160-cm7-lwip-freertos
|
||||
- path: nxp/nxp-evkmimxrt1170-cm7-lwip-freertos
|
||||
- path: nxp/nxp-frdmk64f-lwip-freertos
|
||||
- path: nxp/nxp-frdmk66f-lwip-freertos
|
||||
- path: nxp/nxp-lpcxpresso54018-lwip-freertos
|
||||
- path: nxp/nxp-lpcxpresso54608-lwip-freertos
|
||||
- path: nxp/nxp-lpcxpresso54618-lwip-freertos
|
||||
- path: nxp/nxp-lpcxpresso54628-lwip-freertos
|
||||
- path: nxp/nxp-twrk65f180m-lwip-freertos
|
||||
- path: nxp/nxp-twrkv58f220m-lwip-freertos
|
||||
- path: infineon/infineon-xmc4700_4800-lwip-rtx-rtos
|
||||
- path: ti/ti-ek-tm4c1294xl-http-server
|
||||
- path: ti/ek-tm4c1294xl-baremetal
|
||||
- path: ti/ek-tm4c1294xl-freertos
|
||||
- path: rp2040/pico-rmii
|
||||
- path: rp2040/pico-rndis-dashboard
|
||||
- path: rp2040/pico-w
|
||||
- path: rp2040/pico-w5500
|
||||
name: ${{ matrix.example.path }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh '^src|^examples/${{ matrix.example.path }}'; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: sudo apt -y update ; sudo apt -y install gcc-arm-none-eabi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/${{ matrix.example.path }} build
|
||||
|
||||
cube_examples:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh '^src|examples/stm32/nucleo-*-cube-*'; then echo GO=1 >> $GITHUB_ENV ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: cd test/cube && make
|
||||
|
||||
zephyr_examples:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with: { fetch-depth: 2 }
|
||||
- run: if ./test/match_changed_files.sh '^src/.*.[ch]|^examples/zephyr'; then sudo apt -y update ; sudo apt -y install gcc-arm-none-eabi ; fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/zephyr init
|
||||
- name: minify manifest
|
||||
if: ${{ env.GO == 1 }}
|
||||
uses: mikefarah/yq@master
|
||||
with:
|
||||
cmd: yq -i eval '(.manifest.defaults, .manifest.remotes, .manifest.projects[] | select(.name == "cmsis" or .name == "hal_stm32" or .name == "mbedtls" or .name == "mcuboot" or .name == "picolibc" | del(.null) ), .manifest.self) as $i ireduce({};setpath($i | path; $i)) | del(.manifest.projects.[].null) | del(..|select(length==0))' examples/zephyr/zephyrproject/zephyr/west.yml
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/zephyr update
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/zephyr/device-dashboard build
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/zephyr/http-client build
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/zephyr/http-server build
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/zephyr/mqtt-aws-client build
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C examples/zephyr/websocket-server build
|
||||
@@ -1,5 +1,5 @@
|
||||
Copyright (c) 2004-2013 Sergey Lyubka
|
||||
Copyright (c) 2013-2021 Cesanta Software Limited
|
||||
Copyright (c) 2013-2023 Cesanta Software Limited
|
||||
All rights reserved
|
||||
|
||||
This software is dual-licensed: you can redistribute it and/or modify
|
||||
@@ -13,4 +13,4 @@ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
Alternatively, you can license this software under a commercial
|
||||
license, as set out in <https://www.cesanta.com/license>.
|
||||
license, as set out in <https://mongoose.ws/licensing/>.
|
||||
@@ -1,112 +1,181 @@
|
||||
SRCS = mongoose.c test/unit_test.c test/packed_fs.c
|
||||
HDRS = $(wildcard src/*.h)
|
||||
DEFS ?= -DMG_MAX_HTTP_HEADERS=7 -DMG_ENABLE_LINES -DMG_ENABLE_PACKED_FS=1
|
||||
WARN ?= -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion
|
||||
HDRS = $(wildcard src/*.h) $(wildcard src/tcpip/*.h)
|
||||
DEFS ?= -DMG_MAX_HTTP_HEADERS=7 -DMG_ENABLE_LINES -DMG_ENABLE_PACKED_FS=1 -DMG_ENABLE_SSI=1 -DMG_ENABLE_ASSERT=1
|
||||
WARN ?= -pedantic -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion -Wundef
|
||||
OPTS ?= -O3 -g3
|
||||
INCS ?= -Isrc -I.
|
||||
CFLAGS ?= $(OPTS) $(WARN) $(INCS) $(DEFS) $(TFLAGS) $(EXTRA)
|
||||
SSL ?= MBEDTLS
|
||||
SSL ?=
|
||||
CWD ?= $(realpath $(CURDIR))
|
||||
DOCKER ?= docker run --rm -e Tmp=. -e WINEDEBUG=-all -v $(CWD):$(CWD) -w $(CWD)
|
||||
VCFLAGS = /nologo /W3 /O2 /I. $(DEFS) $(TFLAGS)
|
||||
ENV ?= -e Tmp=. -e WINEDEBUG=-all
|
||||
DOCKER ?= docker run --platform linux/amd64 --rm $(ENV) -v $(CWD):$(CWD) -w $(CWD)
|
||||
VCFLAGS = /nologo /W3 /O2 /MD /I. $(DEFS) $(TFLAGS)
|
||||
IPV6 ?= 1
|
||||
ASAN_OPTIONS ?=
|
||||
EXAMPLES := $(wildcard examples/*)
|
||||
ASAN ?= -fsanitize=address,undefined,alignment -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common
|
||||
ASAN_OPTIONS ?= detect_leaks=1
|
||||
EXAMPLES := $(dir $(wildcard examples/*/Makefile))
|
||||
EXAMPLES_MAC := $(filter-out examples/mip-pcap/ examples/mip-tap/, $(EXAMPLES))
|
||||
EXAMPLES_WIN := $(dir $(wildcard examples/device-dashboard/Makefile) $(wildcard examples/file-*/Makefile) $(wildcard examples/http-*/Makefile) $(wildcard examples/mqtt-*/Makefile) $(wildcard examples/websocket-*/Makefile) $(wildcard examples/webui-*/Makefile))
|
||||
EXAMPLES_EMBEDDED := $(filter-out $(wildcard examples/zephyr/*/), $(dir $(wildcard examples/*/*/Makefile)))
|
||||
PREFIX ?= /usr/local
|
||||
VERSION ?= $(shell cut -d'"' -f2 src/version.h)
|
||||
.PHONY: examples test
|
||||
COMMON_CFLAGS ?= $(C_WARN) $(WARN) $(INCS) $(DEFS) -DMG_ENABLE_IPV6=$(IPV6) $(TFLAGS) -pthread
|
||||
CFLAGS ?= $(OPTS) $(ASAN) $(COMMON_CFLAGS)
|
||||
VALGRIND_CFLAGS ?= $(OPTS) $(COMMON_CFLAGS)
|
||||
VALGRIND_RUN ?= valgrind --tool=memcheck --gen-suppressions=all --leak-check=full --show-leak-kinds=all --leak-resolution=high --track-origins=yes --error-exitcode=1 --exit-on-first-error=yes --fair-sched=yes
|
||||
.PHONY: clean_examples examples mip_test test valgrind
|
||||
|
||||
ifeq "$(findstring ++,$(CC))" ""
|
||||
# $(CC) does not end with ++, i.e. we're using C. Apply C flags
|
||||
C_WARN ?= -Wmissing-prototypes -Wstrict-prototypes
|
||||
else
|
||||
# $(CC) ends with ++, i.e. we're using C++. Apply C++ flags
|
||||
C_WARN ?= -Wno-deprecated
|
||||
endif
|
||||
|
||||
ifeq "$(SSL)" "MBEDTLS"
|
||||
MBEDTLS ?= /usr
|
||||
MBEDTLS ?= /usr/local
|
||||
CFLAGS += -DMG_ENABLE_MBEDTLS=1 -I$(MBEDTLS)/include -I/usr/include
|
||||
LDFLAGS ?= -L$(MBEDTLS)/lib -lmbedtls -lmbedcrypto -lmbedx509
|
||||
endif
|
||||
|
||||
ifeq "$(SSL)" "OPENSSL"
|
||||
OPENSSL ?= /usr
|
||||
OPENSSL ?= /usr/local
|
||||
CFLAGS += -DMG_ENABLE_OPENSSL=1 -I$(OPENSSL)/include
|
||||
LDFLAGS ?= -L$(OPENSSL)/lib -lssl -lcrypto
|
||||
endif
|
||||
|
||||
all: mg_prefix unpacked test test++ arm examples vc98 vc2017 mingw mingw++ linux linux++ fuzz
|
||||
all:
|
||||
$(MAKE) -C examples/http-server
|
||||
|
||||
tall: mg_prefix unamalgamated test mip_test arm examples vc98 vc17 vc22 mingw mingw++ fuzz
|
||||
|
||||
mip_test: test/mip_test.c mongoose.c mongoose.h Makefile mip_tap_test
|
||||
$(CC) test/mip_test.c $(INCS) $(WARN) $(OPTS) $(C_WARN) $(ASAN) -o $@
|
||||
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
|
||||
|
||||
mip_tap_test: test/mip_tap_test.c mongoose.c mongoose.h Makefile
|
||||
$(CC) test/mip_tap_test.c $(INCS) $(WARN) $(OPTS) $(C_WARN) $(ASAN) -o $@
|
||||
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
|
||||
|
||||
examples:
|
||||
@for X in $(EXAMPLES); do $(MAKE) -C $$X example || break; done
|
||||
@for X in $(EXAMPLES); do test -f $$X/Makefile || continue; $(MAKE) -C $$X example || exit 1; done
|
||||
clean_examples:
|
||||
for X in $(EXAMPLES); do test -f $$X/Makefile || continue; $(MAKE) -C $$X clean || exit 1; done
|
||||
|
||||
test/packed_fs.c: Makefile src/fs.h src/ssi.h test/fuzz.c test/data/a.txt
|
||||
examples_mac:
|
||||
for X in $(EXAMPLES_MAC); do test -f $$X/Makefile || continue; $(MAKE) -C $$X example || exit 1; done
|
||||
clean_examples_mac:
|
||||
for X in $(EXAMPLES_MAC); do test -f $$X/Makefile || continue; $(MAKE) -C $$X clean || exit 1; done
|
||||
|
||||
examples_win:
|
||||
$(foreach X, $(EXAMPLES_WIN), $(MAKE) -C $(X) example &)
|
||||
clean_examples_win:
|
||||
$(foreach X, $(EXAMPLES_WIN), $(MAKE) -C $(X) clean &)
|
||||
|
||||
test/packed_fs.c: Makefile src/ssi.h test/fuzz.c test/data/a.txt
|
||||
$(CC) $(CFLAGS) test/pack.c -o pack
|
||||
./pack $? > $@
|
||||
$(RUN) ./pack Makefile src/ssi.h test/fuzz.c test/data/a.txt test/data/range.txt > $@
|
||||
|
||||
DIR ?= test/data
|
||||
OUT ?= packed_fs.c
|
||||
mkfs:
|
||||
$(CC) $(CFLAGS) test/pack.c -o pack
|
||||
$(RUN) ./pack -s $(DIR) `find $(DIR) -type f` > $(OUT)
|
||||
# find $(DIR) -type f | sed -e s,^$(DIR),,g -e s,^/,,g
|
||||
|
||||
# Check that all external (exported) symbols have "mg_" prefix
|
||||
mg_prefix: mongoose.c mongoose.h
|
||||
$(CC) mongoose.c $(CFLAGS) -c -o /tmp/x.o && nm /tmp/x.o | grep ' T' | grep -v 'mg_' ; test $$? = 1
|
||||
$(CC) mongoose.c $(CFLAGS) -c -o /tmp/x.o && nm /tmp/x.o | grep ' T ' | grep -v 'mg_' ; test $$? = 1
|
||||
|
||||
# C++ build
|
||||
test++: CC = g++
|
||||
test++: WARN += -Wno-shadow -Wno-missing-field-initializers -Wno-deprecated
|
||||
test++: test
|
||||
musl: test
|
||||
musl: ASAN =
|
||||
musl: WARN += -Wno-sign-conversion
|
||||
musl: CC = $(DOCKER) mdashnet/cc1 gcc
|
||||
musl: RUN = $(DOCKER) mdashnet/cc1
|
||||
|
||||
# Make sure we can build from an unamalgamated sources
|
||||
unamalgamated: $(HDRS) Makefile test/packed_fs.c
|
||||
$(CC) src/*.c test/packed_fs.c test/unit_test.c $(CFLAGS) $(LDFLAGS) -g -o unit_test
|
||||
|
||||
unpacked:
|
||||
$(CC) -I. mongoose.c test/unit_test.c -o unit_test
|
||||
|
||||
fuzzer: mongoose.c mongoose.h Makefile test/fuzz.c
|
||||
clang mongoose.c test/fuzz.c $(WARN) $(INCS) -DMG_ENABLE_LINES -DMG_ENABLE_LOG=0 -fsanitize=fuzzer,signed-integer-overflow,address -g -o $@
|
||||
|
||||
fuzz: fuzzer
|
||||
fuzz: ASAN = -fsanitize=fuzzer,signed-integer-overflow,address,undefined
|
||||
fuzz: mongoose.c mongoose.h Makefile test/fuzz.c
|
||||
$(CC) test/fuzz.c $(OPTS) $(WARN) $(INCS) $(TFLAGS) $(ASAN) -o fuzzer
|
||||
$(RUN) ./fuzzer
|
||||
|
||||
# make CC=/usr/local/opt/llvm\@8/bin/clang ASAN_OPTIONS=detect_leaks=1
|
||||
test: CFLAGS += -DMG_ENABLE_IPV6=$(IPV6) -fsanitize=address#,undefined
|
||||
test: mongoose.h Makefile $(SRCS)
|
||||
$(CC) $(SRCS) $(CFLAGS) -coverage $(LDFLAGS) -g -o unit_test
|
||||
FUZZDATA ?= /tmp/fuzzdata
|
||||
fuzz2: mongoose.c mongoose.h Makefile test/fuzz.c
|
||||
$(CC) test/fuzz.c -DMAIN $(OPTS) $(WARN) $(ASAN) $(INCS) -o fuzzer
|
||||
$(RUN) ./fuzzer $(FUZZDATA)
|
||||
|
||||
test: Makefile mongoose.h $(SRCS)
|
||||
$(CC) $(SRCS) $(CFLAGS) $(LDFLAGS) -o unit_test
|
||||
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./unit_test
|
||||
|
||||
coverage: CFLAGS += -coverage
|
||||
coverage: test
|
||||
gcov -l -n *.gcno | sed '/^$$/d' | sed 'N;s/\n/ /'
|
||||
gcov mongoose.gcno >/dev/null
|
||||
gcov -t mongoose.c > mongoose.gcov
|
||||
|
||||
upload-coverage: coverage
|
||||
curl -s https://codecov.io/bash | /bin/bash
|
||||
|
||||
infer:
|
||||
infer run -- cc test/unit_test.c -c -W -Wall -Werror -Isrc -I. -O2 -DMG_ENABLE_MBEDTLS=1 -DMG_ENABLE_LINES -I/usr/local/Cellar/mbedtls/2.23.0/include -DMG_ENABLE_IPV6=1 -g -o /dev/null
|
||||
valgrind: Makefile mongoose.h mongoose.c
|
||||
$(CC) $(SRCS) $(VALGRIND_CFLAGS) $(LDFLAGS) -g -o unit_test
|
||||
$(VALGRIND_RUN) ./unit_test
|
||||
|
||||
armhf: ASAN=
|
||||
armhf: IPV6=0
|
||||
armhf: CC = $(DOCKER) mdashnet/armhf cc
|
||||
armhf: RUN = $(DOCKER) mdashnet/armhf
|
||||
armhf: test
|
||||
|
||||
s390: ASAN=
|
||||
s390: IPV6=0
|
||||
s390: CC = $(DOCKER) mdashnet/s390 cc
|
||||
s390: RUN = $(DOCKER) mdashnet/s390
|
||||
s390: test
|
||||
|
||||
arm: DEFS += -DMG_ENABLE_FILE=0 -DMG_ENABLE_TCPIP=1 -DMG_ARCH=MG_ARCH_NEWLIB
|
||||
arm: mongoose.h $(SRCS)
|
||||
$(DOCKER) mdashnet/armgcc arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb $(SRCS) test/mongoose_custom.c -Itest -DMG_ARCH=MG_ARCH_CUSTOM $(OPTS) $(WARN) $(INCS) $(DEFS) -o unit_test -nostartfiles --specs nosys.specs -e 0
|
||||
$(DOCKER) mdashnet/armgcc arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb $(SRCS) $(OPTS) $(WARN) $(INCS) $(DEFS) $(TFLAGS) -o unit_test -nostartfiles --specs nosys.specs -e 0
|
||||
|
||||
riscv: DEFS += -DMG_ENABLE_FILE=0 -DMG_ENABLE_TCPIP=1 -DMG_ARCH=MG_ARCH_NEWLIB
|
||||
riscv: mongoose.h $(SRCS)
|
||||
$(DOCKER) mdashnet/riscv riscv-none-elf-gcc -march=rv32imc -mabi=ilp32 $(SRCS) test/mongoose_custom.c -Itest -DMG_ARCH=MG_ARCH_CUSTOM $(OPTS) $(WARN) $(INCS) $(DEFS) -o unit_test
|
||||
$(DOCKER) mdashnet/riscv riscv-none-elf-gcc -march=rv32imc -mabi=ilp32 $(SRCS) $(OPTS) $(WARN) $(INCS) $(DEFS) $(TFLAGS) -o unit_test
|
||||
|
||||
#vc98: VCFLAGS += -DMG_ENABLE_IPV6=1
|
||||
vc98: Makefile mongoose.h $(SRCS)
|
||||
$(DOCKER) mdashnet/vc98 wine cl $(SRCS) $(VCFLAGS) ws2_32.lib /Fe$@.exe
|
||||
$(DOCKER) mdashnet/vc98 wine $@.exe
|
||||
|
||||
#vc2017: VCFLAGS += -DMG_ENABLE_IPV6=1
|
||||
vc2017: Makefile mongoose.h $(SRCS)
|
||||
$(DOCKER) mdashnet/vc2017 wine64 cl $(SRCS) $(VCFLAGS) ws2_32.lib /Fe$@.exe
|
||||
$(DOCKER) mdashnet/vc2017 wine64 $@.exe
|
||||
vc17: Makefile mongoose.h $(SRCS)
|
||||
$(DOCKER) mdashnet/vc17 wine64 cl $(SRCS) $(VCFLAGS) ws2_32.lib /Fe$@.exe
|
||||
$(DOCKER) mdashnet/vc17 wine64 $@.exe
|
||||
|
||||
vc22: Makefile mongoose.h $(SRCS)
|
||||
$(DOCKER) mdashnet/vc22 wine64 cl $(SRCS) $(VCFLAGS) ws2_32.lib /Fe$@.exe
|
||||
$(DOCKER) mdashnet/vc22 wine64 $@.exe
|
||||
|
||||
mingw: Makefile mongoose.h $(SRCS)
|
||||
$(DOCKER) mdashnet/mingw i686-w64-mingw32-gcc $(SRCS) -W -Wall -Werror -I. $(DEFS) -lwsock32 -o test.exe
|
||||
$(DOCKER) mdashnet/vc98 wine test.exe
|
||||
$(DOCKER) mdashnet/mingw x86_64-w64-mingw32-gcc $(SRCS) -W -Wall -Werror -I. $(DEFS) -lwsock32 -o $@.exe
|
||||
$(DOCKER) mdashnet/mingw wine64 $@.exe
|
||||
|
||||
arduino: ENV = -v $(CWD)/arduino:/root
|
||||
arduino:
|
||||
curl -sL http://downloads.arduino.cc/arduino-1.8.19-linux64.tar.xz | unxz | tar -xf -
|
||||
mv arduino-* $@
|
||||
$(DOCKER) mdashnet/cc2 ./arduino/arduino --pref "boardsmanager.additional.urls=https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json" --save-prefs
|
||||
$(DOCKER) mdashnet/cc2 ./arduino/arduino --pref "compiler.warning_level=all" --save-prefs
|
||||
$(DOCKER) mdashnet/cc2 ./arduino/arduino --install-boards Seeeduino:samd
|
||||
|
||||
arduino-xiao: ENV = -v $(CWD)/arduino:/root
|
||||
arduino-xiao: arduino
|
||||
rm -rf tmp; mkdir tmp
|
||||
cp examples/arduino/w5500/w5500.ino tmp/tmp.ino
|
||||
cp mongoose.c mongoose.h examples/arduino/w5500/mongoose_custom.h tmp/
|
||||
$(DOCKER) mdashnet/cc2 ./arduino/arduino --verbose --verify --board Seeeduino:samd:seeed_XIAO_m0 tmp/tmp.ino
|
||||
|
||||
mingw++: Makefile mongoose.h $(SRCS)
|
||||
$(DOCKER) mdashnet/mingw i686-w64-mingw32-g++ $(SRCS) -W -Wall -Werror -I. $(DEFS) -lwsock32 -o test.exe
|
||||
# Note: for some reason, a binary built with mingw g++, fails to run
|
||||
|
||||
#linux: CFLAGS += -DMG_ENABLE_IPV6=$(IPV6)
|
||||
linux: CFLAGS += -fsanitize=address,undefined
|
||||
linux: Makefile mongoose.h $(SRCS)
|
||||
$(DOCKER) mdashnet/cc2 gcc $(SRCS) $(CFLAGS) $(LDFLAGS) -o unit_test_gcc
|
||||
$(DOCKER) mdashnet/cc2 ./unit_test_gcc
|
||||
|
||||
linux++: CC = g++
|
||||
linux++: WARN += -Wno-missing-field-initializers
|
||||
linux++: linux
|
||||
$(DOCKER) mdashnet/mingw x86_64-w64-mingw32-g++ $(SRCS) -W -Wall -Werror -I. $(DEFS) -lwsock32 -o $@.exe
|
||||
|
||||
linux-libs: CFLAGS += -fPIC
|
||||
linux-libs: mongoose.o
|
||||
@@ -121,12 +190,17 @@ install: linux-libs
|
||||
uninstall:
|
||||
rm -rf $(DESTDIR)$(PREFIX)/lib/libmongoose.a $(DESTDIR)$(PREFIX)/lib/libmongoose.so.$(VERSION) $(DESTDIR)$(PREFIX)/include/mongoose.h $(DESTDIR)$(PREFIX)/lib/libmongoose.so
|
||||
|
||||
mongoose.c: Makefile $(wildcard src/*)
|
||||
(cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/private.h src/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@
|
||||
mongoose.c: Makefile $(wildcard src/*.c) $(wildcard src/tcpip/*.c)
|
||||
(cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/tcpip/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@
|
||||
|
||||
mongoose.h: $(HDRS) Makefile
|
||||
(cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/config.h src/arch.h src/arch_*.h src/str.h src/log.h src/timer.h src/util.h src/fs.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/ws.h src/sntp.h src/mqtt.h src/dns.h | sed -e 's,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
|
||||
(cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/net_*.h src/config.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h src/tcpip/tcpip.h src/tcpip/driver_*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
|
||||
|
||||
|
||||
clean: clean_examples clean_embedded
|
||||
rm -rf $(PROG) *.exe *.o *.dSYM *_test* ut fuzzer *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb slow-unit* _CL_* infer-out data.txt crash-* test/packed_fs.c pack arduino tmp
|
||||
#find examples -maxdepth 3 -name zephyr -prune -o -name Makefile -print | xargs dirname | xargs -n1 make clean -C
|
||||
|
||||
clean_embedded:
|
||||
for X in $(EXAMPLES_EMBEDDED); do test -f $$X/Makefile || continue; $(MAKE) -C $$X clean || exit 1; done
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.o *.dSYM unit_test* ut fuzzer *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb slow-unit* _CL_* infer-out data.txt crash-* test/packed_fs.c pack
|
||||
@for X in $(EXAMPLES); do $(MAKE) -C $$X clean; done
|
||||
@@ -4,26 +4,34 @@
|
||||
[](https://github.com/cesanta/mongoose/actions)
|
||||
[](https://codecov.io/gh/cesanta/mongoose)
|
||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:mongoose)
|
||||
[](https://gitter.im/cesanta/mongoose)
|
||||
|
||||
Mongoose is a networking library for C/C++. It implements event-driven
|
||||
Mongoose is a network library for C/C++.
|
||||
It implements event-driven
|
||||
non-blocking APIs for TCP, UDP, HTTP, WebSocket, MQTT. It is designed for
|
||||
connecting devices and bringing them online. On the market since 2004, used by
|
||||
vast number of open source and commercial products - it even runs on the
|
||||
International Space Station! Mongoose makes embedded network programming fast,
|
||||
robust, and easy. Features include:
|
||||
|
||||
- Cross-platform: works on Linux/UNIX, MacOS, Windows, Android, FreeRTOS, etc.
|
||||
- Supported embedded architectures: ESP32, NRF52, STM32, NXP, and more
|
||||
- Cross-platform:
|
||||
- works on Linux/UNIX, MacOS, Windows, Android
|
||||
- works on STM32, NXP, ESP32, NRF52, TI, Microchip, and other
|
||||
- write code once - and it'll work everywhere
|
||||
- ideal for the unification of the network infrastructure code across company
|
||||
- Built-in protocols: plain TCP/UDP, HTTP, MQTT, Websocket
|
||||
- SSL/TLS support: mbedTLS, OpenSSL or custom (via API)
|
||||
- Asynchronous DNS resolver
|
||||
- Tiny static and run-time footprint
|
||||
- Source code is both ISO C and ISO C++ compliant
|
||||
- Very easy to integrate: just copy `mongoose.c` and `mongoose.h` files to your source tree. See
|
||||
[exact steps](https://mongoose.ws/documentation/#2-minute-integration-guide)
|
||||
- Works with any network stack with socket API, like LwIP or FreeRTOS-Plus-TCP
|
||||
- Very easy to integrate: just copy `mongoose.c` and `mongoose.h` files to your build tree
|
||||
- Detailed [documentation](https://mongoose.ws/documentation/) and
|
||||
[tutorials](https://mongoose.ws/tutorials/)
|
||||
- Provides a built-in TCP/IP stack with drivers for bare metal or RTOS systems
|
||||
- A complete Web device dashboard
|
||||
[bare metal example on Nucleo-F429ZI](examples/stm32/nucleo-f429zi-baremetal)
|
||||
is only 6 files
|
||||
- For comparison, a CubeIDE generated HTTP example is 400+ files
|
||||
- Detailed [user guide, API reference and tons of tutorials](https://mongoose.ws/documentation/) available
|
||||
|
||||
|
||||
# Commercial use
|
||||
@@ -33,15 +41,11 @@ robust, and easy. Features include:
|
||||
interface on devices, RESTful API services, telemetry data exchange, remote
|
||||
control for a product, remote software updates, remote monitoring, and others
|
||||
- Deployed to hundreds of millions devices in production environment worldwide
|
||||
- See [Case Studies](https://cesanta.com/case-studies.html) from our respected
|
||||
customers like Schneider Electric (industrial automation), Broadcom (semiconductors), Schenck Process
|
||||
(industrial engineering), and others
|
||||
- See [Testimonials](https://cesanta.com/testimonials.html) from engineers that
|
||||
integrated Mongoose in their commercial products
|
||||
- We provide [commercial licensing](https://cesanta.com/licensing.html),
|
||||
[support](https://cesanta.com/support.html), consultancy and integration
|
||||
assistance - don't hesitate to
|
||||
[contact us](https://www.cesanta.com/contact.html)
|
||||
- See [Case Studies](https://mongoose.ws/case-studies/) from our respected
|
||||
customers like [Schneider Electric](https://mongoose.ws/case-studies/schneider-electric/) (industrial automation), [Broadcom](https://mongoose.ws/case-studies/broadcom/) (semiconductors), [Pilz](https://mongoose.ws/case-studies/pilz/) (industrial automation), and others
|
||||
- See [Testimonials](https://mongoose.ws/testimonials/) from engineers that integrated Mongoose in their commercial products
|
||||
- We provide [Evaluation and Commercial licensing](https://mongoose.ws/licensing/), [support](https://mongoose.ws/support/), consultancy and integration
|
||||
assistance - don't hesitate to [contact us](https://mongoose.ws/contact/)
|
||||
|
||||
|
||||
# Security
|
||||
@@ -69,30 +73,6 @@ We take security seriously:
|
||||
have specific security requirements and run independent security audits,
|
||||
of which we get notified and in case of any issue, act similar to (3).
|
||||
|
||||
# Supplement software
|
||||
|
||||
This software is often used together with Mongoose:
|
||||
- [mjson](https://github.com/cesanta/mjson) - a JSON parser, emitter and
|
||||
JSON-RPC engine. Used to implement
|
||||
RESTful APIs that use JSON, or implement data exchange (e.g. over MQTT
|
||||
or Websocket) that use JSON for data encapsulation
|
||||
- [elk](https://github.com/cesanta/elk) - a tiny JavaScript engine.
|
||||
Used to implement scripting support
|
||||
|
||||
|
||||
# Precompiled web server binary
|
||||
|
||||
We have built a ready-to-go, precompiled web server executable application for
|
||||
Windows and Mac. It requires no installation and no configuration to start.
|
||||
Simply copy `mongoose.exe` to any directory you want to serve, and double-click
|
||||
on `mongoose.exe` to start it. Mongoose will iconify to a system tray and
|
||||
start serving your directory to a local network.
|
||||
|
||||
It is a great tool for sharing your files, or for local web or game
|
||||
development. It has a unique feature - an ability to share your local files via
|
||||
a global URL.
|
||||
|
||||
Visit [download page](https://mongoose.ws/) to try it out.
|
||||
|
||||
# Contributions
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,24 @@
|
||||
# Mongoose Network Library Examples
|
||||
|
||||
In order to build and run any of the existing examples, please follow:
|
||||
https://mongoose.ws/documentation/#how-to-build-and-run-examples
|
||||
|
||||
## Contributing
|
||||
|
||||
Rules for creating a new example:
|
||||
|
||||
- Makefile golden reference for desktop/server example: [http-server](http-server)
|
||||
- Makefile golden reference for the embedded example:
|
||||
[stm32/nucleo-f746zg-baremetal](stm32/nucleo-f746zg-baremetal)
|
||||
- An example must build on Windows, Mac and Ubuntu Linux systems
|
||||
- Assume that user installed tools according to https://mongoose.ws/tutorials/tools/
|
||||
- Makefile must not include any other make files
|
||||
- Use `CFLAGS` for system-specific compilation options
|
||||
- Use `CFLAGS_MONGOOSE` for mongoose-specific compilation options
|
||||
- Use `$(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA)` to compile
|
||||
- If external repository is required, download it on demand using git
|
||||
shallow clone. See embedded example golden reference
|
||||
- Keep Makefile as short as possible, but verbose to understand it easily
|
||||
- Symlink files when required, like `mongoose.c`, `ca.pem`, etc. Make no copies
|
||||
- Example's README.md should contain only the title and the link to mongoose.ws
|
||||
tutorial page
|
||||
@@ -0,0 +1 @@
|
||||
../../../mongoose.c
|
||||
@@ -0,0 +1 @@
|
||||
../../../mongoose.h
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#define MG_ARCH MG_ARCH_CUSTOM
|
||||
#define MG_ENABLE_SOCKET 0
|
||||
#define MG_ENABLE_TCPIP 1
|
||||
#define mkdir(a, b) (-1)
|
||||
//#define MG_ENABLE_LOG 0
|
||||
@@ -0,0 +1,49 @@
|
||||
#include <SPI.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
#define SS_PIN 3 // Slave select pin
|
||||
struct mg_mgr mgr; // Mongoose event manager
|
||||
struct mg_tcpip_spi spi = {
|
||||
NULL, // SPI data
|
||||
[](void *) { digitalWrite(SS_PIN, LOW); }, // begin transation
|
||||
[](void *) { digitalWrite(SS_PIN, HIGH); }, // end transaction
|
||||
[](void *, uint8_t c) { return SPI.transfer(c); }, // execute transaction
|
||||
};
|
||||
struct mg_tcpip_if mif = {.mac = {2, 0, 1, 2, 3, 5}}; // network interface
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
pinMode(SS_PIN, OUTPUT);
|
||||
SPI.begin();
|
||||
|
||||
// Set logging function to a serial print
|
||||
mg_log_set_fn([](char ch, void *) { Serial.print(ch); }, NULL);
|
||||
mg_mgr_init(&mgr);
|
||||
|
||||
delay(3000);
|
||||
MG_INFO(("Starting TCP/IP stack..."));
|
||||
|
||||
mif.driver = &mg_tcpip_driver_w5500;
|
||||
mif.driver_data = &spi;
|
||||
mg_tcpip_init(&mgr, &mif);
|
||||
|
||||
// Start a 5 sec timer, print status message periodically
|
||||
mg_timer_add(
|
||||
&mgr, 5000, MG_TIMER_REPEAT,
|
||||
[](void *) {
|
||||
MG_INFO(("ethernet: %s", mg_tcpip_driver_w5500.up(&mif) ? "up" : "down"));
|
||||
},
|
||||
NULL);
|
||||
|
||||
// Setup HTTP listener. Respond "ok" on any HTTP request
|
||||
mg_http_listen(
|
||||
&mgr, "http://0.0.0.0",
|
||||
[](struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "ok\n");
|
||||
},
|
||||
&mgr);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mg_mgr_poll(&mgr, 1);
|
||||
}
|
||||
@@ -1,11 +1,25 @@
|
||||
PROG ?= example
|
||||
CFLAGS ?= -DMG_IO_SIZE=8192 -DMG_ENABLE_LINES
|
||||
PROG ?= example # Program we are building
|
||||
DELETE = rm -rf # Command to remove files
|
||||
OUT ?= -o $(PROG) # Compiler argument for output file
|
||||
SOURCES = main.c mongoose.c # Source code files
|
||||
CFLAGS = -W -Wall -Wextra -g -I. # Build options
|
||||
|
||||
all: $(PROG)
|
||||
$(DEBUGGER) ./$(PROG) $(ARGS)
|
||||
# Mongoose build options. See https://mongoose.ws/documentation/#build-options
|
||||
CFLAGS_MONGOOSE += -DMG_IO_SIZE=8192 -DMG_ENABLE_LINES
|
||||
|
||||
$(PROG): main.c
|
||||
$(CC) ../../mongoose.c -I../.. -W -Wall $(CFLAGS) -o $(PROG) main.c
|
||||
ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
|
||||
PROG ?= example.exe # Use .exe suffix for the binary
|
||||
CC = gcc # Use MinGW gcc compiler
|
||||
CFLAGS += -lws2_32 # Link against Winsock library
|
||||
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
|
||||
OUT ?= -o $(PROG) # Build output
|
||||
endif
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb
|
||||
all: $(PROG) # Default target. Build and run program
|
||||
$(RUN) ./$(PROG) $(ARGS)
|
||||
|
||||
$(PROG): $(SOURCES) # Build program from sources
|
||||
$(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT)
|
||||
|
||||
clean: # Cleanup. Delete built program and all build artifacts
|
||||
$(DELETE) $(PROG) *.o *.obj *.exe *.dSYM
|
||||
@@ -0,0 +1 @@
|
||||
See detailed tutorial at https://mongoose.ws/tutorials/captive-dns-server/
|
||||
@@ -21,11 +21,14 @@ uint8_t answer[] = {
|
||||
};
|
||||
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_READ) {
|
||||
if (ev == MG_EV_OPEN) {
|
||||
c->is_hexdumping = 1;
|
||||
} else if (ev == MG_EV_READ) {
|
||||
struct mg_dns_rr rr; // Parse first question, offset 12 is header size
|
||||
size_t n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr);
|
||||
MG_INFO(("DNS request parsed, result=%d", (int) n));
|
||||
if (n > 0) {
|
||||
char buf[12 + n + sizeof(answer)];
|
||||
char buf[512];
|
||||
struct mg_dns_header *h = (struct mg_dns_header *) buf;
|
||||
memset(buf, 0, sizeof(buf)); // Clear the whole datagram
|
||||
h->txnid = ((struct mg_dns_header *) c->recv.buf)->txnid; // Copy tnxid
|
||||
@@ -34,7 +37,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
h->flags = mg_htons(0x8400); // Authoritative response
|
||||
memcpy(buf + sizeof(*h), c->recv.buf + sizeof(*h), n); // Copy question
|
||||
memcpy(buf + sizeof(*h) + n, answer, sizeof(answer)); // And answer
|
||||
mg_send(c, buf, sizeof(buf)); // And send it!
|
||||
mg_send(c, buf, 12 + n + sizeof(answer)); // And send it!
|
||||
}
|
||||
mg_iobuf_del(&c->recv, 0, c->recv.len);
|
||||
}
|
||||
@@ -45,7 +48,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
|
||||
mg_log_set("3"); // Set log level
|
||||
mg_log_set(MG_LL_DEBUG); // Set log level
|
||||
mg_mgr_init(&mgr); // Initialise event manager
|
||||
mg_listen(&mgr, s_listen_url, fn, NULL); // Start DNS server
|
||||
for (;;) mg_mgr_poll(&mgr, 1000); // Event loop
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../../mongoose.c
|
||||
@@ -0,0 +1 @@
|
||||
../../mongoose.h
|
||||
@@ -1,23 +0,0 @@
|
||||
PROG ?= example
|
||||
SOURCES ?= ../../mongoose.c main.c mjson.c packed_fs.c
|
||||
CFLAGS ?= -I../.. -DMG_ENABLE_PACKED_FS=1 $(EXTRA)
|
||||
FILES_TO_EMBED ?= $(wildcard web_root/*.js) $(wildcard web_root/*.css) $(wildcard web_root/*.html) $(wildcard web_root/images/*.png) $(wildcard images/*.jpg)
|
||||
|
||||
all: $(PROG)
|
||||
$(RUN) ./$(PROG)
|
||||
|
||||
$(PROG):
|
||||
$(CC) ../../test/pack.c -o pack
|
||||
./pack $(FILES_TO_EMBED) > packed_fs.c
|
||||
$(CC) -W -Wall -Wextra -Os -g3 $(CFLAGS) -o $(PROG) $(SOURCES)
|
||||
|
||||
ROOT ?= $(realpath $(CURDIR)/../..)
|
||||
DOCKER ?= docker run -it --rm -e Tmp=. -e WINEDEBUG=-all -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/vc98 wine
|
||||
windows:
|
||||
$(DOCKER) cl.exe /nologo $(CFLAGS) ../../test/pack.c /Fepack.exe
|
||||
$(DOCKER) cmd /c 'pack.exe $(FILES_TO_EMBED) > packed_fs.c'
|
||||
$(DOCKER) cl.exe /nologo /O2 $(CFLAGS) $(SOURCES) ws2_32.lib /Fe$(PROG).exe
|
||||
$(DOCKER) $(PROG).exe
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb log.txt packed_fs.c pack
|
||||
@@ -1,34 +0,0 @@
|
||||
# A complete device dashboard
|
||||
|
||||
This example is a demonstration of how Mongoose Library could be integrated
|
||||
into an embedded device and provide a complete device dashboard with the
|
||||
following features:
|
||||
|
||||
- Multiple logins with different permissions (admin and user)
|
||||
- Login screen for non-authenticated connections
|
||||
- A [preact](https://preactjs.com/)-based dashboard with multiple pages
|
||||
- Internal variables
|
||||
- A form that changes those variables
|
||||
- All changes are propagates to all connected clients
|
||||
- Live log stream
|
||||
- Live video stream
|
||||
|
||||
|
||||
In essence, this example is a combination of several other examples
|
||||
pulled together (multiple-logins, dashboard, live-log, video-stream).
|
||||
|
||||
# Screenshots
|
||||
|
||||
This is a login screen that prompts for user/password
|
||||
|
||||

|
||||
|
||||
# Main dashboard
|
||||
|
||||
A main dashboard page shows internal data, live video stream, and a form
|
||||
|
||||

|
||||
|
||||
A live log page shows live log stream coming from device
|
||||
|
||||

|
||||
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 10 KiB |
@@ -1,207 +0,0 @@
|
||||
// Copyright (c) 2020 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "mjson.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
// Authenticated user.
|
||||
// A user can be authenticated by:
|
||||
// - a name:pass pair
|
||||
// - a token
|
||||
// When a user is shown a login screen, she enters a user:pass. If successful,
|
||||
// a server returns user info which includes token. From that point on,
|
||||
// client can use token for authentication. Tokens could be refreshed/changed
|
||||
// on a server side, forcing clients to re-login.
|
||||
struct user {
|
||||
const char *name, *pass, *token;
|
||||
};
|
||||
|
||||
// This is a configuration structure we're going to show on a dashboard
|
||||
static struct config {
|
||||
int value1;
|
||||
char *value2;
|
||||
} s_config = {123, NULL};
|
||||
|
||||
// Stringifies the config. A caller must free() it.
|
||||
static char *stringify_config(struct config *cfg) {
|
||||
char *s = NULL;
|
||||
mjson_printf(mjson_print_dynamic_buf, &s, "{%Q:%d,%Q:%Q}", "value1",
|
||||
cfg->value1, "value2", cfg->value2);
|
||||
return s;
|
||||
}
|
||||
|
||||
// Update config structure. Return true if changed, false otherwise
|
||||
static bool update_config(struct mg_http_message *hm, struct config *cfg) {
|
||||
bool changed = false;
|
||||
char buf[256];
|
||||
double dv;
|
||||
if (mjson_get_number(hm->body.ptr, hm->body.len, "$.value1", &dv)) {
|
||||
cfg->value1 = dv;
|
||||
changed = true;
|
||||
}
|
||||
if (mjson_get_string(hm->body.ptr, hm->body.len, "$.value2", buf,
|
||||
sizeof(buf)) > 0) {
|
||||
free(cfg->value2);
|
||||
cfg->value2 = strdup(buf);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
// Parse HTTP requests, return authenticated user or NULL
|
||||
static struct user *getuser(struct mg_http_message *hm) {
|
||||
// In production, make passwords strong and tokens randomly generated
|
||||
// In this example, user list is kept in RAM. In production, it can
|
||||
// be backed by file, database, or some other method.
|
||||
static struct user users[] = {
|
||||
{"admin", "admin", "admin_token"},
|
||||
{"user1", "pass1", "user1_token"},
|
||||
{"user2", "pass2", "user2_token"},
|
||||
{NULL, NULL, NULL},
|
||||
};
|
||||
char user[256], pass[256];
|
||||
struct user *u;
|
||||
mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass));
|
||||
if (user[0] != '\0' && pass[0] != '\0') {
|
||||
// Both user and password is set, search by user/password
|
||||
for (u = users; u->name != NULL; u++)
|
||||
if (strcmp(user, u->name) == 0 && strcmp(pass, u->pass) == 0) return u;
|
||||
} else if (user[0] == '\0') {
|
||||
// Only password is set, search by token
|
||||
for (u = users; u->name != NULL; u++)
|
||||
if (strcmp(pass, u->token) == 0) return u;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Notify all config watchers about the config change
|
||||
static void notify_config_change(struct mg_mgr *mgr) {
|
||||
struct mg_connection *c;
|
||||
char *s = stringify_config(&s_config);
|
||||
for (c = mgr->conns; c != NULL; c = c->next) {
|
||||
if (c->label[0] == 'W') mg_http_printf_chunk(c, "%s\n", s);
|
||||
}
|
||||
free(s);
|
||||
}
|
||||
|
||||
// HTTP request handler function
|
||||
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
struct user *u = getuser(hm);
|
||||
// LOG(LL_INFO, ("%p [%.*s] auth %s", c->fd, (int) hm->uri.len, hm->uri.ptr,
|
||||
// u ? u->name : "NULL"));
|
||||
if (u == NULL && mg_http_match_uri(hm, "/api/#")) {
|
||||
// All URIs starting with /api/ must be authenticated
|
||||
mg_printf(c, "%s", "HTTP/1.1 403 Denied\r\nContent-Length: 0\r\n\r\n");
|
||||
} else if (mg_http_match_uri(hm, "/api/config/get")) {
|
||||
char *s = stringify_config(&s_config);
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s\n",
|
||||
(int) strlen(s) + 1, s);
|
||||
free(s);
|
||||
} else if (mg_http_match_uri(hm, "/api/config/set")) {
|
||||
// Admins only
|
||||
if (strcmp(u->name, "admin") == 0) {
|
||||
if (update_config(hm, &s_config)) notify_config_change(fn_data);
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
|
||||
} else {
|
||||
mg_printf(c, "%s", "HTTP/1.1 403 Denied\r\nContent-Length: 0\r\n\r\n");
|
||||
}
|
||||
} else if (mg_http_match_uri(hm, "/api/config/watch")) {
|
||||
c->label[0] = 'W'; // Mark ourselves as a config watcher
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
} else if (mg_http_match_uri(hm, "/api/video1")) {
|
||||
c->label[0] = 'S'; // Mark that connection as live streamer
|
||||
mg_printf(
|
||||
c, "%s",
|
||||
"HTTP/1.0 200 OK\r\n"
|
||||
"Cache-Control: no-cache\r\n"
|
||||
"Pragma: no-cache\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\n"
|
||||
"Content-Type: multipart/x-mixed-replace; boundary=--foo\r\n\r\n");
|
||||
} else if (mg_http_match_uri(hm, "/api/log/static")) {
|
||||
struct mg_http_serve_opts opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
mg_http_serve_file(c, hm, "log.txt", &opts);
|
||||
} else if (mg_http_match_uri(hm, "/api/log/live")) {
|
||||
c->label[0] = 'L'; // Mark that connection as live log listener
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
} else if (mg_http_match_uri(hm, "/api/login")) {
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
mg_http_printf_chunk(c, "{\"user\":\"%s\",\"token\":\"%s\"}\n", u->name,
|
||||
u->token);
|
||||
mg_http_printf_chunk(c, "");
|
||||
} else {
|
||||
struct mg_http_serve_opts opts = {0};
|
||||
opts.root_dir = "web_root";
|
||||
mg_http_serve_dir(c, ev_data, &opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The image stream is simulated by sending MJPEG frames specified by the
|
||||
// "files" array of file names.
|
||||
static void broadcast_mjpeg_frame(struct mg_mgr *mgr) {
|
||||
const char *files[] = {"images/1.jpg", "images/2.jpg", "images/3.jpg",
|
||||
"images/4.jpg", "images/5.jpg", "images/6.jpg"};
|
||||
size_t nfiles = sizeof(files) / sizeof(files[0]);
|
||||
static size_t i;
|
||||
const char *path = files[i++ % nfiles];
|
||||
size_t size = 0;
|
||||
char *data = mg_file_read(path, &size); // Read next file
|
||||
struct mg_connection *c;
|
||||
for (c = mgr->conns; c != NULL; c = c->next) {
|
||||
if (c->label[0] != 'S') continue; // Skip non-stream connections
|
||||
if (data == NULL || size == 0) continue; // Skip on file read error
|
||||
mg_printf(c,
|
||||
"--foo\r\nContent-Type: image/jpeg\r\n"
|
||||
"Content-Length: %lu\r\n\r\n",
|
||||
(unsigned long) size);
|
||||
mg_send(c, data, size);
|
||||
mg_send(c, "\r\n", 2);
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void mjpeg_cb(void *arg) {
|
||||
broadcast_mjpeg_frame(arg);
|
||||
}
|
||||
|
||||
static void log_message(const char *filename, const char *message) {
|
||||
FILE *fp = fopen(filename, "a");
|
||||
if (fp != NULL) {
|
||||
fprintf(fp, "%s", message);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
static void broadcast_message(struct mg_mgr *mgr, const char *message) {
|
||||
struct mg_connection *c;
|
||||
for (c = mgr->conns; c != NULL; c = c->next) {
|
||||
if (c->label[0] == 'L') mg_http_printf_chunk(c, "%s", message);
|
||||
}
|
||||
}
|
||||
|
||||
// Timer function - called periodically.
|
||||
// Prepare log message. Save it to a file, and broadcast.
|
||||
static void log_cb(void *arg) {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "Time is: %lu\n", (unsigned long) time(NULL));
|
||||
log_message("log.txt", buf);
|
||||
broadcast_message(arg, buf);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_timer t1, t2;
|
||||
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://localhost:8000", cb, &mgr);
|
||||
mg_timer_init(&t1, 500, MG_TIMER_REPEAT, mjpeg_cb, &mgr);
|
||||
mg_timer_init(&t2, 1000, MG_TIMER_REPEAT, log_cb, &mgr);
|
||||
for (;;) mg_mgr_poll(&mgr, 50);
|
||||
mg_timer_free(&t1);
|
||||
mg_timer_free(&t2);
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,981 +0,0 @@
|
||||
// Copyright (c) 2018-2020 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#include "mjson.h"
|
||||
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
#else
|
||||
#define va_copy(x, y) (x) = (y)
|
||||
#define snprintf _snprintf
|
||||
#define alloca _alloca
|
||||
#endif
|
||||
|
||||
static int mjson_esc(int c, int esc) {
|
||||
const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
|
||||
for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
|
||||
if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mjson_pass_string(const char *s, int len) {
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (s[i] == '\\' && i + 1 < len && mjson_esc(s[i + 1], 1)) {
|
||||
i++;
|
||||
} else if (s[i] == '\0') {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
} else if (s[i] == '"') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int mjson(const char *s, int len, mjson_cb_t cb, void *ud) {
|
||||
enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
|
||||
unsigned char nesting[MJSON_MAX_DEPTH];
|
||||
int i, depth = 0;
|
||||
#define MJSONCALL(ev) \
|
||||
if (cb != NULL && cb(ev, s, start, i - start + 1, ud)) return i + 1;
|
||||
|
||||
// In the ascii table, the distance between `[` and `]` is 2.
|
||||
// Ditto for `{` and `}`. Hence +2 in the code below.
|
||||
#define MJSONEOO() \
|
||||
do { \
|
||||
if (c != nesting[depth - 1] + 2) return MJSON_ERROR_INVALID_INPUT; \
|
||||
depth--; \
|
||||
if (depth == 0) { \
|
||||
MJSONCALL(tok); \
|
||||
return i + 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
int start = i;
|
||||
unsigned char c = ((unsigned char *) s)[i];
|
||||
int tok = c;
|
||||
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
|
||||
// printf("- %c [%.*s] %d %d\n", c, i, s, depth, expecting);
|
||||
switch (expecting) {
|
||||
case S_VALUE:
|
||||
if (c == '{') {
|
||||
if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP;
|
||||
nesting[depth++] = c;
|
||||
expecting = S_KEY;
|
||||
break;
|
||||
} else if (c == '[') {
|
||||
if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP;
|
||||
nesting[depth++] = c;
|
||||
break;
|
||||
} else if (c == ']' && depth > 0) { // Empty array
|
||||
MJSONEOO();
|
||||
} else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
|
||||
i += 3;
|
||||
tok = MJSON_TOK_TRUE;
|
||||
} else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
|
||||
i += 3;
|
||||
tok = MJSON_TOK_NULL;
|
||||
} else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
|
||||
i += 4;
|
||||
tok = MJSON_TOK_FALSE;
|
||||
} else if (c == '-' || ((c >= '0' && c <= '9'))) {
|
||||
char *end = NULL;
|
||||
strtod(&s[i], &end);
|
||||
if (end != NULL) i += end - &s[i] - 1;
|
||||
tok = MJSON_TOK_NUMBER;
|
||||
} else if (c == '"') {
|
||||
int n = mjson_pass_string(&s[i + 1], len - i - 1);
|
||||
if (n < 0) return n;
|
||||
i += n + 1;
|
||||
tok = MJSON_TOK_STRING;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
if (depth == 0) {
|
||||
MJSONCALL(tok);
|
||||
return i + 1;
|
||||
}
|
||||
expecting = S_COMMA_OR_EOO;
|
||||
break;
|
||||
|
||||
case S_KEY:
|
||||
if (c == '"') {
|
||||
int n = mjson_pass_string(&s[i + 1], len - i - 1);
|
||||
if (n < 0) return n;
|
||||
i += n + 1;
|
||||
tok = MJSON_TOK_KEY;
|
||||
expecting = S_COLON;
|
||||
} else if (c == '}') { // Empty object
|
||||
MJSONEOO();
|
||||
expecting = S_COMMA_OR_EOO;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_COLON:
|
||||
if (c == ':') {
|
||||
expecting = S_VALUE;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_COMMA_OR_EOO:
|
||||
if (depth <= 0) return MJSON_ERROR_INVALID_INPUT;
|
||||
if (c == ',') {
|
||||
expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
|
||||
} else if (c == ']' || c == '}') {
|
||||
MJSONEOO();
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
MJSONCALL(tok);
|
||||
}
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
struct msjon_get_data {
|
||||
const char *path; // Lookup json path
|
||||
int pos; // Current path index
|
||||
int d1; // Current depth of traversal
|
||||
int d2; // Expected depth of traversal
|
||||
int i1; // Index in an array
|
||||
int i2; // Expected index in an array
|
||||
int obj; // If the value is array/object, offset where it starts
|
||||
const char **tokptr; // Destination
|
||||
int *toklen; // Destination length
|
||||
int tok; // Returned token
|
||||
};
|
||||
|
||||
static int mjson_plen(const char *s) {
|
||||
int i = 0;
|
||||
while (s[i] != '\0' && s[i] != '.' && s[i] != '[') i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) {
|
||||
struct msjon_get_data *data = (struct msjon_get_data *) ud;
|
||||
// printf("--> %2x %2d %2d %2d %2d\t'%s'\t'%.*s'\t\t'%.*s'\n", tok, data->d1,
|
||||
// data->d2, data->i1, data->i2, data->path + data->pos, off, s, len,
|
||||
// s + off);
|
||||
if (data->tok != MJSON_TOK_INVALID) return 1; // Found
|
||||
|
||||
if (tok == '{') {
|
||||
if (!data->path[data->pos] && data->d1 == data->d2) data->obj = off;
|
||||
data->d1++;
|
||||
} else if (tok == '[') {
|
||||
if (data->d1 == data->d2 && data->path[data->pos] == '[') {
|
||||
data->i1 = 0;
|
||||
data->i2 = (int) strtod(&data->path[data->pos + 1], NULL);
|
||||
if (data->i1 == data->i2) {
|
||||
data->d2++;
|
||||
data->pos += 3;
|
||||
}
|
||||
}
|
||||
if (!data->path[data->pos] && data->d1 == data->d2) data->obj = off;
|
||||
data->d1++;
|
||||
} else if (tok == ',') {
|
||||
if (data->d1 == data->d2 + 1) {
|
||||
data->i1++;
|
||||
if (data->i1 == data->i2) {
|
||||
while (data->path[data->pos] != ']') data->pos++;
|
||||
data->pos++;
|
||||
data->d2++;
|
||||
}
|
||||
}
|
||||
} else if (tok == MJSON_TOK_KEY && data->d1 == data->d2 + 1 &&
|
||||
data->path[data->pos] == '.' && s[off] == '"' &&
|
||||
s[off + len - 1] == '"' &&
|
||||
mjson_plen(&data->path[data->pos + 1]) == len - 2 &&
|
||||
!memcmp(s + off + 1, &data->path[data->pos + 1], len - 2)) {
|
||||
data->d2++;
|
||||
data->pos += len - 1;
|
||||
} else if (tok == MJSON_TOK_KEY && data->d1 == data->d2) {
|
||||
return 1; // Exhausted path, not found
|
||||
} else if (tok == '}' || tok == ']') {
|
||||
data->d1--;
|
||||
// data->d2--;
|
||||
if (!data->path[data->pos] && data->d1 == data->d2 && data->obj != -1) {
|
||||
data->tok = tok - 2;
|
||||
if (data->tokptr) *data->tokptr = s + data->obj;
|
||||
if (data->toklen) *data->toklen = off - data->obj + 1;
|
||||
return 1;
|
||||
}
|
||||
} else if (MJSON_TOK_IS_VALUE(tok)) {
|
||||
// printf("TOK --> %d\n", tok);
|
||||
if (data->d1 == data->d2 && !data->path[data->pos]) {
|
||||
data->tok = tok;
|
||||
if (data->tokptr) *data->tokptr = s + off;
|
||||
if (data->toklen) *data->toklen = len;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum mjson_tok mjson_find(const char *s, int len, const char *jp,
|
||||
const char **tokptr, int *toklen) {
|
||||
struct msjon_get_data data = {jp, 1, 0, 0, 0,
|
||||
0, -1, tokptr, toklen, MJSON_TOK_INVALID};
|
||||
if (jp[0] != '$') return MJSON_TOK_INVALID;
|
||||
if (mjson(s, len, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID;
|
||||
return (enum mjson_tok) data.tok;
|
||||
}
|
||||
|
||||
int mjson_get_number(const char *s, int len, const char *path, double *v) {
|
||||
const char *p;
|
||||
int tok, n;
|
||||
if ((tok = mjson_find(s, len, path, &p, &n)) == MJSON_TOK_NUMBER) {
|
||||
if (v != NULL) *v = strtod(p, NULL);
|
||||
}
|
||||
return tok == MJSON_TOK_NUMBER ? 1 : 0;
|
||||
}
|
||||
|
||||
int mjson_get_bool(const char *s, int len, const char *path, int *v) {
|
||||
int tok = mjson_find(s, len, path, NULL, NULL);
|
||||
if (tok == MJSON_TOK_TRUE && v != NULL) *v = 1;
|
||||
if (tok == MJSON_TOK_FALSE && v != NULL) *v = 0;
|
||||
return tok == MJSON_TOK_TRUE || tok == MJSON_TOK_FALSE ? 1 : 0;
|
||||
}
|
||||
|
||||
static int mjson_unescape(const char *s, int len, char *to, int n) {
|
||||
int i, j;
|
||||
for (i = 0, j = 0; i < len && j < n; i++, j++) {
|
||||
if (s[i] == '\\' && i + 1 < len) {
|
||||
int c = mjson_esc(s[i + 1], 0);
|
||||
if (c == 0) return -1;
|
||||
to[j] = c;
|
||||
i++;
|
||||
} else {
|
||||
to[j] = s[i];
|
||||
}
|
||||
}
|
||||
if (j >= n) return -1;
|
||||
if (n > 0) to[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
|
||||
int mjson_get_string(const char *s, int len, const char *path, char *to,
|
||||
int n) {
|
||||
const char *p;
|
||||
int sz;
|
||||
if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return -1;
|
||||
return mjson_unescape(p + 1, sz - 2, to, n);
|
||||
}
|
||||
|
||||
int mjson_get_hex(const char *s, int len, const char *x, char *to, int n) {
|
||||
const char *p;
|
||||
int i, j, sz;
|
||||
if (mjson_find(s, len, x, &p, &sz) != MJSON_TOK_STRING) return -1;
|
||||
for (i = j = 0; i < sz - 3 && j < n; i += 2, j++) {
|
||||
#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W')
|
||||
unsigned char a = *(const unsigned char *) (p + i + 1);
|
||||
unsigned char b = *(const unsigned char *) (p + i + 2);
|
||||
((unsigned char *) to)[j] = (HEXTOI(a) << 4) | HEXTOI(b);
|
||||
}
|
||||
if (j < n) to[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
static int mjson_base64rev(int c) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
return c - 'A';
|
||||
} else if (c >= 'a' && c <= 'z') {
|
||||
return c + 26 - 'a';
|
||||
} else if (c >= '0' && c <= '9') {
|
||||
return c + 52 - '0';
|
||||
} else if (c == '+') {
|
||||
return 62;
|
||||
} else if (c == '/') {
|
||||
return 63;
|
||||
} else {
|
||||
return 64;
|
||||
}
|
||||
}
|
||||
|
||||
int mjson_base64_dec(const char *src, int n, char *dst, int dlen) {
|
||||
const char *end = src + n;
|
||||
int len = 0;
|
||||
while (src + 3 < end && len < dlen) {
|
||||
int a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]),
|
||||
c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]);
|
||||
dst[len++] = (a << 2) | (b >> 4);
|
||||
if (src[2] != '=' && len < dlen) {
|
||||
dst[len++] = (b << 4) | (c >> 2);
|
||||
if (src[3] != '=' && len < dlen) {
|
||||
dst[len++] = (c << 6) | d;
|
||||
}
|
||||
}
|
||||
src += 4;
|
||||
}
|
||||
if (len < dlen) dst[len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
int mjson_get_base64(const char *s, int len, const char *path, char *to,
|
||||
int n) {
|
||||
const char *p;
|
||||
int sz;
|
||||
if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return 0;
|
||||
return mjson_base64_dec(p + 1, sz - 2, to, n);
|
||||
}
|
||||
#endif // MJSON_ENABLE_BASE64
|
||||
|
||||
#if MJSON_ENABLE_NEXT
|
||||
struct nextdata {
|
||||
int off, len, depth, t, vo, arrayindex;
|
||||
int *koff, *klen, *voff, *vlen, *vtype;
|
||||
};
|
||||
|
||||
static int next_cb(int tok, const char *s, int off, int len, void *ud) {
|
||||
struct nextdata *d = (struct nextdata *) ud;
|
||||
// int i;
|
||||
switch (tok) {
|
||||
case '{':
|
||||
case '[':
|
||||
if (d->depth == 0 && tok == '[') d->arrayindex = 0;
|
||||
if (d->depth == 1 && off > d->off) {
|
||||
d->vo = off;
|
||||
d->t = tok == '{' ? MJSON_TOK_OBJECT : MJSON_TOK_ARRAY;
|
||||
if (d->voff) *d->voff = off;
|
||||
if (d->vtype) *d->vtype = d->t;
|
||||
}
|
||||
d->depth++;
|
||||
break;
|
||||
case '}':
|
||||
case ']':
|
||||
d->depth--;
|
||||
if (d->depth == 1 && d->vo) {
|
||||
d->len = off + len;
|
||||
if (d->vlen) *d->vlen = d->len - d->vo;
|
||||
if (d->arrayindex >= 0) {
|
||||
if (d->koff) *d->koff = d->arrayindex; // koff holds array index
|
||||
if (d->klen) *d->klen = 0; // klen holds 0
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (d->depth == 1 && d->arrayindex >= 0) d->arrayindex++;
|
||||
break;
|
||||
case ',':
|
||||
case ':':
|
||||
break;
|
||||
case MJSON_TOK_KEY:
|
||||
if (d->depth == 1 && d->off < off) {
|
||||
if (d->koff) *d->koff = off; // And report back to the user
|
||||
if (d->klen) *d->klen = len; // If we have to
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (d->depth != 1) break;
|
||||
// If we're iterating over the array
|
||||
if (off > d->off) {
|
||||
d->len = off + len;
|
||||
if (d->vlen) *d->vlen = len; // value length
|
||||
if (d->voff) *d->voff = off; // value offset
|
||||
if (d->vtype) *d->vtype = tok; // value type
|
||||
if (d->arrayindex >= 0) {
|
||||
if (d->koff) *d->koff = d->arrayindex; // koff holds array index
|
||||
if (d->klen) *d->klen = 0; // klen holds 0
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (d->arrayindex >= 0) d->arrayindex++;
|
||||
break;
|
||||
}
|
||||
(void) s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff,
|
||||
int *vlen, int *vtype) {
|
||||
struct nextdata d = {off, 0, 0, 0, 0, -1, koff, klen, voff, vlen, vtype};
|
||||
mjson(s, n, next_cb, &d);
|
||||
return d.len;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_PRINT
|
||||
int mjson_print_fixed_buf(const char *ptr, int len, void *fndata) {
|
||||
struct mjson_fixedbuf *fb = (struct mjson_fixedbuf *) fndata;
|
||||
int i, left = fb->size - 1 - fb->len;
|
||||
if (left < len) len = left;
|
||||
for (i = 0; i < len; i++) fb->ptr[fb->len + i] = ptr[i];
|
||||
fb->len += len;
|
||||
fb->ptr[fb->len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
int mjson_print_dynamic_buf(const char *ptr, int len, void *fndata) {
|
||||
char *s, *buf = *(char **) fndata;
|
||||
int curlen = buf == NULL ? 0 : strlen(buf);
|
||||
if ((s = (char *) realloc(buf, curlen + len + 1)) == NULL) {
|
||||
return 0;
|
||||
} else {
|
||||
memcpy(s + curlen, ptr, len);
|
||||
s[curlen + len] = '\0';
|
||||
*(char **) fndata = s;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
int mjson_print_null(const char *ptr, int len, void *userdata) {
|
||||
(void) ptr;
|
||||
(void) userdata;
|
||||
return len;
|
||||
}
|
||||
|
||||
int mjson_print_file(const char *ptr, int len, void *userdata) {
|
||||
return fwrite(ptr, 1, len, (FILE *) userdata);
|
||||
}
|
||||
|
||||
int mjson_print_buf(mjson_print_fn_t fn, void *fndata, const char *buf,
|
||||
int len) {
|
||||
return fn(buf, len, fndata);
|
||||
}
|
||||
|
||||
int mjson_print_int(mjson_print_fn_t fn, void *fndata, int value,
|
||||
int is_signed) {
|
||||
char buf[20];
|
||||
int len = snprintf(buf, sizeof(buf), is_signed ? "%d" : "%u", value);
|
||||
return fn(buf, len, fndata);
|
||||
}
|
||||
|
||||
int mjson_print_long(mjson_print_fn_t fn, void *fndata, long value,
|
||||
int is_signed) {
|
||||
char buf[20];
|
||||
const char *fmt = (is_signed ? "%ld" : "%lu");
|
||||
int len = snprintf(buf, sizeof(buf), fmt, value);
|
||||
return fn(buf, len, fndata);
|
||||
}
|
||||
|
||||
int mjson_print_dbl(mjson_print_fn_t fn, void *fndata, double d,
|
||||
const char *fmt) {
|
||||
char buf[40];
|
||||
int n = snprintf(buf, sizeof(buf), fmt, d);
|
||||
return fn(buf, n, fndata);
|
||||
}
|
||||
|
||||
int mjson_print_str(mjson_print_fn_t fn, void *fndata, const char *s, int len) {
|
||||
int i, n = fn("\"", 1, fndata);
|
||||
for (i = 0; i < len; i++) {
|
||||
char c = mjson_esc(s[i], 1);
|
||||
if (c) {
|
||||
n += fn("\\", 1, fndata);
|
||||
n += fn(&c, 1, fndata);
|
||||
} else {
|
||||
n += fn(&s[i], 1, fndata);
|
||||
}
|
||||
}
|
||||
return n + fn("\"", 1, fndata);
|
||||
}
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
int mjson_print_b64(mjson_print_fn_t fn, void *fndata, const unsigned char *s,
|
||||
int n) {
|
||||
const char *t =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
int i, len = fn("\"", 1, fndata);
|
||||
for (i = 0; i < n; i += 3) {
|
||||
int a = s[i], b = i + 1 < n ? s[i + 1] : 0, c = i + 2 < n ? s[i + 2] : 0;
|
||||
char buf[4] = {t[a >> 2], t[(a & 3) << 4 | (b >> 4)], '=', '='};
|
||||
if (i + 1 < n) buf[2] = t[(b & 15) << 2 | (c >> 6)];
|
||||
if (i + 2 < n) buf[3] = t[c & 63];
|
||||
len += fn(buf, sizeof(buf), fndata);
|
||||
}
|
||||
return len + fn("\"", 1, fndata);
|
||||
}
|
||||
#endif /* MJSON_ENABLE_BASE64 */
|
||||
|
||||
int mjson_vprintf(mjson_print_fn_t fn, void *fndata, const char *fmt,
|
||||
va_list xap) {
|
||||
int i = 0, n = 0;
|
||||
va_list ap;
|
||||
va_copy(ap, xap);
|
||||
while (fmt[i] != '\0') {
|
||||
if (fmt[i] == '%') {
|
||||
char fc = fmt[++i];
|
||||
int is_long = 0;
|
||||
if (fc == 'l') {
|
||||
is_long = 1;
|
||||
fc = fmt[i + 1];
|
||||
}
|
||||
if (fc == 'Q') {
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_str(fn, fndata, buf ? buf : "", buf ? strlen(buf) : 0);
|
||||
} else if (strncmp(&fmt[i], ".*Q", 3) == 0) {
|
||||
int len = va_arg(ap, int);
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_str(fn, fndata, buf, len);
|
||||
i += 2;
|
||||
} else if (fc == 'd' || fc == 'u') {
|
||||
int is_signed = (fc == 'd');
|
||||
if (is_long) {
|
||||
long val = va_arg(ap, long);
|
||||
n += mjson_print_long(fn, fndata, val, is_signed);
|
||||
i++;
|
||||
} else {
|
||||
int val = va_arg(ap, int);
|
||||
n += mjson_print_int(fn, fndata, val, is_signed);
|
||||
}
|
||||
} else if (fc == 'B') {
|
||||
const char *s = va_arg(ap, int) ? "true" : "false";
|
||||
n += mjson_print_buf(fn, fndata, s, strlen(s));
|
||||
} else if (fc == 's') {
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_buf(fn, fndata, buf, strlen(buf));
|
||||
} else if (strncmp(&fmt[i], ".*s", 3) == 0) {
|
||||
int len = va_arg(ap, int);
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_buf(fn, fndata, buf, len);
|
||||
i += 2;
|
||||
} else if (fc == 'g') {
|
||||
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%g");
|
||||
} else if (fc == 'f') {
|
||||
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%f");
|
||||
#if MJSON_ENABLE_BASE64
|
||||
} else if (fc == 'V') {
|
||||
int len = va_arg(ap, int);
|
||||
const char *buf = va_arg(ap, const char *);
|
||||
n += mjson_print_b64(fn, fndata, (unsigned char *) buf, len);
|
||||
#endif
|
||||
} else if (fc == 'H') {
|
||||
const char *hex = "0123456789abcdef";
|
||||
int i, len = va_arg(ap, int);
|
||||
const unsigned char *p = va_arg(ap, const unsigned char *);
|
||||
n += fn("\"", 1, fndata);
|
||||
for (i = 0; i < len; i++) {
|
||||
n += fn(&hex[(p[i] >> 4) & 15], 1, fndata);
|
||||
n += fn(&hex[p[i] & 15], 1, fndata);
|
||||
}
|
||||
n += fn("\"", 1, fndata);
|
||||
} else if (fc == 'M') {
|
||||
mjson_vprint_fn_t vfn = va_arg(ap, mjson_vprint_fn_t);
|
||||
n += vfn(fn, fndata, &ap);
|
||||
}
|
||||
i++;
|
||||
} else {
|
||||
n += mjson_print_buf(fn, fndata, &fmt[i++], 1);
|
||||
}
|
||||
}
|
||||
va_end(xap);
|
||||
va_end(ap);
|
||||
return n;
|
||||
}
|
||||
|
||||
int mjson_printf(mjson_print_fn_t fn, void *fndata, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
int len;
|
||||
va_start(ap, fmt);
|
||||
len = mjson_vprintf(fn, fndata, fmt, ap);
|
||||
va_end(ap);
|
||||
return len;
|
||||
}
|
||||
#endif /* MJSON_ENABLE_PRINT */
|
||||
|
||||
#if MJSON_IMPLEMENT_STRTOD
|
||||
static inline int is_digit(int c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
/* NOTE: strtod() implementation by Yasuhiro Matsumoto. */
|
||||
double strtod(const char *str, char **end) {
|
||||
double d = 0.0;
|
||||
int sign = 1, n = 0;
|
||||
const char *p = str, *a = str;
|
||||
|
||||
/* decimal part */
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
} else if (*p == '+')
|
||||
++p;
|
||||
if (is_digit(*p)) {
|
||||
d = (double) (*p++ - '0');
|
||||
while (*p && is_digit(*p)) {
|
||||
d = d * 10.0 + (double) (*p - '0');
|
||||
++p;
|
||||
++n;
|
||||
}
|
||||
a = p;
|
||||
} else if (*p != '.')
|
||||
goto done;
|
||||
d *= sign;
|
||||
|
||||
/* fraction part */
|
||||
if (*p == '.') {
|
||||
double f = 0.0;
|
||||
double base = 0.1;
|
||||
++p;
|
||||
|
||||
if (is_digit(*p)) {
|
||||
while (*p && is_digit(*p)) {
|
||||
f += base * (*p - '0');
|
||||
base /= 10.0;
|
||||
++p;
|
||||
++n;
|
||||
}
|
||||
}
|
||||
d += f * sign;
|
||||
a = p;
|
||||
}
|
||||
|
||||
/* exponential part */
|
||||
if ((*p == 'E') || (*p == 'e')) {
|
||||
int e = 0;
|
||||
++p;
|
||||
|
||||
sign = 1;
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
} else if (*p == '+')
|
||||
++p;
|
||||
|
||||
if (is_digit(*p)) {
|
||||
while (*p == '0') ++p;
|
||||
e = (int) (*p++ - '0');
|
||||
while (*p && is_digit(*p)) {
|
||||
e = e * 10 + (int) (*p - '0');
|
||||
++p;
|
||||
}
|
||||
e *= sign;
|
||||
} else if (!is_digit(*(a - 1))) {
|
||||
a = str;
|
||||
goto done;
|
||||
} else if (*p == 0)
|
||||
goto done;
|
||||
|
||||
if (d == 2.2250738585072011 && e == -308) {
|
||||
d = 0.0;
|
||||
a = p;
|
||||
goto done;
|
||||
}
|
||||
if (d == 2.2250738585072012 && e <= -308) {
|
||||
d *= 1.0e-308;
|
||||
a = p;
|
||||
goto done;
|
||||
}
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 10; i++) d *= 10;
|
||||
}
|
||||
a = p;
|
||||
} else if (p > str && !is_digit(*(p - 1))) {
|
||||
a = str;
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
if (end) *end = (char *) a;
|
||||
return d;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_MERGE
|
||||
int mjson_merge(const char *s, int n, const char *s2, int n2,
|
||||
mjson_print_fn_t fn, void *userdata) {
|
||||
int koff, klen, voff, vlen, t, t2, k, off = 0, len = 0, comma = 0;
|
||||
if (n < 2) return len;
|
||||
len += fn("{", 1, userdata);
|
||||
while ((off = mjson_next(s, n, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
char path[klen + 1];
|
||||
#else
|
||||
char *path = (char *) alloca(klen + 1);
|
||||
#endif
|
||||
const char *val;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s + koff + 1, klen - 2);
|
||||
path[klen] = '\0';
|
||||
if ((t2 = mjson_find(s2, n2, path, &val, &k)) != MJSON_TOK_INVALID) {
|
||||
if (t2 == MJSON_TOK_NULL) continue; // null deletes the key
|
||||
} else {
|
||||
val = s + voff; // Key is not found in the update. Copy the old value.
|
||||
}
|
||||
if (comma) len += fn(",", 1, userdata);
|
||||
len += fn(s + koff, klen, userdata);
|
||||
len += fn(":", 1, userdata);
|
||||
if (t == MJSON_TOK_OBJECT && t2 == MJSON_TOK_OBJECT) {
|
||||
len += mjson_merge(s + voff, vlen, val, k, fn, userdata);
|
||||
} else {
|
||||
if (t2 != MJSON_TOK_INVALID) vlen = k;
|
||||
len += fn(val, vlen, userdata);
|
||||
}
|
||||
comma = 1;
|
||||
}
|
||||
// Add missing keys
|
||||
off = 0;
|
||||
while ((off = mjson_next(s2, n2, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
char path[klen + 1];
|
||||
#else
|
||||
char *path = (char *) alloca(klen + 1);
|
||||
#endif
|
||||
const char *val;
|
||||
if (t == MJSON_TOK_NULL) continue;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s2 + koff + 1, klen - 2);
|
||||
path[klen] = '\0';
|
||||
if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue;
|
||||
if (comma) len += fn(",", 1, userdata);
|
||||
len += fn(s2 + koff, klen, userdata);
|
||||
len += fn(":", 1, userdata);
|
||||
len += fn(s2 + voff, vlen, userdata);
|
||||
comma = 1;
|
||||
}
|
||||
len += fn("}", 1, userdata);
|
||||
return len;
|
||||
}
|
||||
#endif // MJSON_ENABLE_MERGE
|
||||
|
||||
#if MJSON_ENABLE_PRETTY
|
||||
struct prettydata {
|
||||
int level;
|
||||
int len;
|
||||
int prev;
|
||||
const char *pad;
|
||||
int padlen;
|
||||
mjson_print_fn_t fn;
|
||||
void *userdata;
|
||||
};
|
||||
|
||||
static int pretty_cb(int ev, const char *s, int off, int len, void *ud) {
|
||||
struct prettydata *d = (struct prettydata *) ud;
|
||||
int i;
|
||||
switch (ev) {
|
||||
case '{':
|
||||
case '[':
|
||||
d->level++;
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
case '}':
|
||||
case ']':
|
||||
d->level--;
|
||||
if (d->prev != '[' && d->prev != '{' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
case ',':
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
if (d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
if (d->padlen > 0) d->len += d->fn(" ", 1, d->userdata);
|
||||
break;
|
||||
case MJSON_TOK_KEY:
|
||||
if (d->prev == '{' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
default:
|
||||
if (d->prev == '[' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
}
|
||||
d->prev = ev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mjson_pretty(const char *s, int n, const char *pad, mjson_print_fn_t fn,
|
||||
void *userdata) {
|
||||
struct prettydata d = {0, 0, 0, pad, strlen(pad), fn, userdata};
|
||||
if (mjson(s, n, pretty_cb, &d) < 0) return -1;
|
||||
return d.len;
|
||||
}
|
||||
#endif // MJSON_ENABLE_PRETTY
|
||||
|
||||
#if MJSON_ENABLE_RPC
|
||||
struct jsonrpc_ctx jsonrpc_default_context;
|
||||
struct jsonrpc_userdata {
|
||||
mjson_print_fn_t fn;
|
||||
void *fndata;
|
||||
};
|
||||
|
||||
int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) {
|
||||
int i = 0, j = 0, ni = 0, nj = 0;
|
||||
while (i < n1 || j < n2) {
|
||||
if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) {
|
||||
i++, j++;
|
||||
} else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) {
|
||||
ni = i, nj = j + 1, i++;
|
||||
} else if (nj > 0 && nj <= n2 && (s1[i - 1] == '#' || s2[j] != '/')) {
|
||||
i = ni, j = nj;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int jsonrpc_printer(const char *buf, int len, void *userdata) {
|
||||
struct jsonrpc_userdata *u = (struct jsonrpc_userdata *) userdata;
|
||||
return u->fn(buf, len, u->fndata);
|
||||
}
|
||||
|
||||
void jsonrpc_return_errorv(struct jsonrpc_request *r, int code,
|
||||
const char *message, const char *data_fmt,
|
||||
va_list ap) {
|
||||
if (r->id_len == 0) return;
|
||||
mjson_printf(r->fn, r->fndata,
|
||||
"{\"id\":%.*s,\"error\":{\"code\":%d,\"message\":%Q", r->id_len,
|
||||
r->id, code, message == NULL ? "" : message);
|
||||
if (data_fmt != NULL) {
|
||||
mjson_printf(r->fn, r->fndata, ",\"data\":");
|
||||
mjson_vprintf(r->fn, r->fndata, data_fmt, ap);
|
||||
}
|
||||
mjson_printf(r->fn, r->fndata, "}}\n");
|
||||
}
|
||||
|
||||
void jsonrpc_return_error(struct jsonrpc_request *r, int code,
|
||||
const char *message, const char *data_fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, data_fmt);
|
||||
jsonrpc_return_errorv(r, code, message, data_fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt,
|
||||
va_list ap) {
|
||||
if (r->id_len == 0) return;
|
||||
mjson_printf(r->fn, r->fndata, "{\"id\":%.*s,\"result\":", r->id_len, r->id);
|
||||
if (result_fmt != NULL) {
|
||||
mjson_vprintf(r->fn, r->fndata, result_fmt, ap);
|
||||
} else {
|
||||
mjson_printf(r->fn, r->fndata, "%s", "null");
|
||||
}
|
||||
mjson_printf(r->fn, r->fndata, "}\n");
|
||||
}
|
||||
|
||||
void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
|
||||
...) {
|
||||
va_list ap;
|
||||
va_start(ap, result_fmt);
|
||||
jsonrpc_return_successv(r, result_fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
mjson_print_fn_t fn, void *fndata) {
|
||||
const char *result = NULL, *error = NULL;
|
||||
int result_sz = 0, error_sz = 0;
|
||||
struct jsonrpc_method *m = NULL;
|
||||
struct jsonrpc_userdata d;
|
||||
struct jsonrpc_request r = {req, req_sz, 0, 0, 0, 0, 0,
|
||||
0, &jsonrpc_printer, NULL, NULL};
|
||||
|
||||
d.fn = fn;
|
||||
d.fndata = fndata;
|
||||
r.fndata = &d;
|
||||
|
||||
// Is is a response frame?
|
||||
mjson_find(req, req_sz, "$.result", &result, &result_sz);
|
||||
if (result == NULL) mjson_find(req, req_sz, "$.error", &error, &error_sz);
|
||||
if (result_sz > 0 || error_sz > 0) {
|
||||
if (ctx->response_cb != NULL) ctx->response_cb(req, req_sz, ctx->userdata);
|
||||
return;
|
||||
}
|
||||
|
||||
// Method must exist and must be a string
|
||||
if (mjson_find(req, req_sz, "$.method", &r.method, &r.method_len) !=
|
||||
MJSON_TOK_STRING) {
|
||||
mjson_printf(fn, fndata, "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n",
|
||||
req_sz, req);
|
||||
ctx->in_len = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// id and params are optional
|
||||
mjson_find(req, req_sz, "$.id", &r.id, &r.id_len);
|
||||
mjson_find(req, req_sz, "$.params", &r.params, &r.params_len);
|
||||
|
||||
for (m = ctx->methods; m != NULL; m = m->next) {
|
||||
if (mjson_globmatch(m->method, m->method_sz, r.method + 1,
|
||||
r.method_len - 2) > 0) {
|
||||
if (r.params == NULL) r.params = "";
|
||||
r.userdata = m->cbdata;
|
||||
m->cb(&r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m == NULL) {
|
||||
jsonrpc_return_error(&r, JSONRPC_ERROR_NOT_FOUND, "method not found", NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static int jsonrpc_print_methods(mjson_print_fn_t fn, void *fndata,
|
||||
va_list *ap) {
|
||||
struct jsonrpc_ctx *ctx = va_arg(*ap, struct jsonrpc_ctx *);
|
||||
struct jsonrpc_method *m;
|
||||
int len = 0;
|
||||
for (m = ctx->methods; m != NULL; m = m->next) {
|
||||
if (m != ctx->methods) len += mjson_print_buf(fn, fndata, ",", 1);
|
||||
len += mjson_print_str(fn, fndata, m->method, strlen(m->method));
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static void rpclist(struct jsonrpc_request *r) {
|
||||
jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->userdata);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t response_cb,
|
||||
void *userdata) {
|
||||
ctx->response_cb = response_cb;
|
||||
ctx->userdata = userdata;
|
||||
jsonrpc_ctx_export(ctx, MJSON_RPC_LIST_NAME, rpclist, ctx);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_process_byte(struct jsonrpc_ctx *ctx, unsigned char ch,
|
||||
mjson_print_fn_t fn, void *p) {
|
||||
if (ctx->in_len >= (int) sizeof(ctx->in)) ctx->in_len = 0; // Overflow
|
||||
if (ch == '\n') { // If new line, parse frame
|
||||
if (ctx->in_len > 1) jsonrpc_ctx_process(ctx, ctx->in, ctx->in_len, fn, p);
|
||||
ctx->in_len = 0;
|
||||
} else {
|
||||
ctx->in[ctx->in_len] = ch; // Append to the buffer
|
||||
ctx->in_len++;
|
||||
}
|
||||
}
|
||||
|
||||
void jsonrpc_init(mjson_print_fn_t response_cb, void *userdata) {
|
||||
jsonrpc_ctx_init(&jsonrpc_default_context, response_cb, userdata);
|
||||
}
|
||||
#endif // MJSON_ENABLE_RPC
|
||||
@@ -1,220 +0,0 @@
|
||||
// Copyright (c) 2018-2020 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#ifndef MJSON_H
|
||||
#define MJSON_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef MJSON_ENABLE_PRINT
|
||||
#define MJSON_ENABLE_PRINT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_RPC
|
||||
#define MJSON_ENABLE_RPC 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_BASE64
|
||||
#define MJSON_ENABLE_BASE64 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_MERGE 0
|
||||
#elif MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_PRETTY 0
|
||||
#elif MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_NEXT
|
||||
#define MJSON_ENABLE_NEXT 0
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef MJSON_RPC_IN_BUF_SIZE
|
||||
#define MJSON_RPC_IN_BUF_SIZE 256
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_RPC_LIST_NAME
|
||||
#define MJSON_RPC_LIST_NAME "rpc.list"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum {
|
||||
MJSON_ERROR_INVALID_INPUT = -1,
|
||||
MJSON_ERROR_TOO_DEEP = -2,
|
||||
};
|
||||
|
||||
enum mjson_tok {
|
||||
MJSON_TOK_INVALID = 0,
|
||||
MJSON_TOK_KEY = 1,
|
||||
MJSON_TOK_STRING = 11,
|
||||
MJSON_TOK_NUMBER = 12,
|
||||
MJSON_TOK_TRUE = 13,
|
||||
MJSON_TOK_FALSE = 14,
|
||||
MJSON_TOK_NULL = 15,
|
||||
MJSON_TOK_ARRAY = 91,
|
||||
MJSON_TOK_OBJECT = 123,
|
||||
};
|
||||
#define MJSON_TOK_IS_VALUE(t) ((t) > 10 && (t) < 20)
|
||||
|
||||
typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud);
|
||||
|
||||
#ifndef MJSON_MAX_DEPTH
|
||||
#define MJSON_MAX_DEPTH 20
|
||||
#endif
|
||||
|
||||
int mjson(const char *s, int len, mjson_cb_t cb, void *ud);
|
||||
enum mjson_tok mjson_find(const char *s, int len, const char *jp,
|
||||
const char **tokptr, int *toklen);
|
||||
int mjson_get_number(const char *s, int len, const char *path, double *v);
|
||||
int mjson_get_bool(const char *s, int len, const char *path, int *v);
|
||||
int mjson_get_string(const char *s, int len, const char *path, char *to, int n);
|
||||
int mjson_get_hex(const char *s, int len, const char *path, char *to, int n);
|
||||
|
||||
#if MJSON_ENABLE_NEXT
|
||||
int mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff,
|
||||
int *vlen, int *vtype);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
int mjson_get_base64(const char *s, int len, const char *path, char *to, int n);
|
||||
int mjson_base64_dec(const char *src, int n, char *dst, int dlen);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_PRINT
|
||||
typedef int (*mjson_print_fn_t)(const char *buf, int len, void *userdata);
|
||||
typedef int (*mjson_vprint_fn_t)(mjson_print_fn_t, void *, va_list *);
|
||||
|
||||
struct mjson_fixedbuf {
|
||||
char *ptr;
|
||||
int size, len;
|
||||
};
|
||||
|
||||
int mjson_printf(mjson_print_fn_t, void *, const char *fmt, ...);
|
||||
int mjson_vprintf(mjson_print_fn_t, void *, const char *fmt, va_list ap);
|
||||
int mjson_print_str(mjson_print_fn_t, void *, const char *s, int len);
|
||||
int mjson_print_int(mjson_print_fn_t, void *, int value, int is_signed);
|
||||
int mjson_print_long(mjson_print_fn_t, void *, long value, int is_signed);
|
||||
int mjson_print_buf(mjson_print_fn_t fn, void *, const char *buf, int len);
|
||||
|
||||
int mjson_print_null(const char *ptr, int len, void *userdata);
|
||||
int mjson_print_file(const char *ptr, int len, void *userdata);
|
||||
int mjson_print_fixed_buf(const char *ptr, int len, void *userdata);
|
||||
int mjson_print_dynamic_buf(const char *ptr, int len, void *userdata);
|
||||
|
||||
#if MJSON_ENABLE_PRETTY
|
||||
int mjson_pretty(const char *, int, const char *, mjson_print_fn_t, void *);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_MERGE
|
||||
int mjson_merge(const char *, int, const char *, int, mjson_print_fn_t, void *);
|
||||
#endif
|
||||
|
||||
#endif // MJSON_ENABLE_PRINT
|
||||
|
||||
#if MJSON_ENABLE_RPC
|
||||
|
||||
void jsonrpc_init(mjson_print_fn_t, void *userdata);
|
||||
int mjson_globmatch(const char *s1, int n1, const char *s2, int n2);
|
||||
|
||||
struct jsonrpc_request {
|
||||
const char *frame; // Points to the whole frame
|
||||
int frame_len; // Frame length
|
||||
const char *params; // Points to the "params" in the request frame
|
||||
int params_len; // Length of the "params"
|
||||
const char *id; // Points to the "id" in the request frame
|
||||
int id_len; // Length of the "id"
|
||||
const char *method; // Points to the "method" in the request frame
|
||||
int method_len; // Length of the "method"
|
||||
mjson_print_fn_t fn; // Printer function
|
||||
void *fndata; // Printer function data
|
||||
void *userdata; // Callback's user data as specified at export time
|
||||
};
|
||||
|
||||
struct jsonrpc_method {
|
||||
const char *method;
|
||||
int method_sz;
|
||||
void (*cb)(struct jsonrpc_request *);
|
||||
void *cbdata;
|
||||
struct jsonrpc_method *next;
|
||||
};
|
||||
|
||||
// Main RPC context, stores current request information and a list of
|
||||
// exported RPC methods.
|
||||
struct jsonrpc_ctx {
|
||||
struct jsonrpc_method *methods;
|
||||
void *userdata;
|
||||
mjson_print_fn_t response_cb;
|
||||
int in_len;
|
||||
char in[MJSON_RPC_IN_BUF_SIZE];
|
||||
};
|
||||
|
||||
// Registers function fn under the given name within the given RPC context
|
||||
#define jsonrpc_ctx_export(ctx, name, fn, ud) \
|
||||
do { \
|
||||
static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0, 0}; \
|
||||
m.cbdata = (ud); \
|
||||
m.next = (ctx)->methods; \
|
||||
(ctx)->methods = &m; \
|
||||
} while (0)
|
||||
|
||||
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t, void *);
|
||||
void jsonrpc_return_error(struct jsonrpc_request *r, int code,
|
||||
const char *message, const char *data_fmt, ...);
|
||||
void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
|
||||
...);
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
mjson_print_fn_t fn, void *fndata);
|
||||
void jsonrpc_ctx_process_byte(struct jsonrpc_ctx *ctx, unsigned char ch,
|
||||
mjson_print_fn_t fn, void *fndata);
|
||||
|
||||
extern struct jsonrpc_ctx jsonrpc_default_context;
|
||||
|
||||
#define jsonrpc_export(name, fn, ud) \
|
||||
jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn), (ud))
|
||||
|
||||
#define jsonrpc_process(buf, len, fn, data) \
|
||||
jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (data))
|
||||
|
||||
#define jsonrpc_process_byte(x, fn, data) \
|
||||
jsonrpc_ctx_process_byte(&jsonrpc_default_context, (x), (fn), (data))
|
||||
|
||||
#define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */
|
||||
#define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */
|
||||
#define JSONRPC_ERROR_BAD_PARAMS -32602 /* Invalid params passed */
|
||||
#define JSONRPC_ERROR_INTERNAL -32603 /* Internal JSON-RPC error */
|
||||
|
||||
#endif // MJSON_ENABLE_RPC
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // MJSON_H
|
||||
|
Before Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 122 KiB |
@@ -1,312 +0,0 @@
|
||||
const {h, render, Component} = preact;
|
||||
const html = htm.bind(h);
|
||||
|
||||
class Login extends Component {
|
||||
state = {user: '', pass: ''};
|
||||
onclick = (app) => {
|
||||
const authhdr = 'Basic ' + btoa(this.state.user + ':' + this.state.pass);
|
||||
const headers = {Authorization: authhdr};
|
||||
return axios.get('/api/login', {headers})
|
||||
.then(res => app.setUser(res.data.token))
|
||||
.catch(err => alert('Login failed'));
|
||||
};
|
||||
onpassinput = (ev) => this.setState({pass: ev.target.value});
|
||||
onuserinput = (ev) => this.setState({user: ev.target.value});
|
||||
render({app}, {user, pass, signup}) {
|
||||
return html`
|
||||
<div class='mx-auto bg-light rounded border my-5' style='max-width: 480px;'>
|
||||
<div class='form p-5 rounded form-sm'>
|
||||
<h4 class="text-muted mb-4">Login </h4>
|
||||
<input type='email' placeholder='Email' class='my-2 form-control'
|
||||
oninput=${this.onuserinput} value=${user} />
|
||||
<input type="password" placeholder="Password" class="my-2 form-control"
|
||||
oninput=${this.onpassinput} value=${pass}
|
||||
onchange=${ev => this.onclick(app)} />
|
||||
<div class="mb-4">
|
||||
<button class="btn btn-info btn-block"
|
||||
disabled=${!user || !pass} onclick="${ev => this.onclick(app)}"
|
||||
> Login </button>
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
Valid logins: admin:admin, user1:pass1, user2:pass2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
class Dropdown extends Component {
|
||||
state = {show: false};
|
||||
render(props, state) {
|
||||
const onclick = x => this.setState({show: x});
|
||||
const show = state.show ? 'show' : '';
|
||||
return html`
|
||||
<div class="dropdown autoexpand ${props.cls}">
|
||||
<div type="buttonx" onclick=${() => onclick(!state.show)}
|
||||
class="dropdown-toggle my-0 ${props.bcls}"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
${props.title}
|
||||
</div>
|
||||
<div onclick=${() => onclick(false)} style=${props.dstyle}
|
||||
class="dropdown-menu ${props.dcls} ${show}">
|
||||
${props.children}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
};
|
||||
|
||||
const NavLink = ({href, title, url}) => html`<a class="nav-item nav-link
|
||||
${url == href ? 'active' : ''}"
|
||||
target=${href.match(/^http/) ? '_blank' : ''}
|
||||
href="${href.match(/^http/) ? '' : '#'}${href}">${title}</a>`;
|
||||
|
||||
class Header extends Component {
|
||||
state = {expanded: false};
|
||||
ontoggle = () => this.setState({expanded: !this.state.expanded});
|
||||
render(props, state) {
|
||||
const u = props.app.state.user || {};
|
||||
return html`
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="#">
|
||||
<img src="images/logo.png" width="26" height="26" alt="" class="mr-2" />
|
||||
MyProduct
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"
|
||||
onclick=${() => this.ontoggle()} >
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="navbar-collapse ${state.expanded ? '' : 'collapse'}"
|
||||
id="navbarNav">
|
||||
<div class="nav navbar-nav mr-auto">
|
||||
<${NavLink} href="/" title="Dashboard" url=${props.url} />
|
||||
<${NavLink} href="/logs" title="Logs" url=${props.url} />
|
||||
</div>
|
||||
</div>
|
||||
<form class="form-inline">
|
||||
<${Dropdown} title="${u.user}" cls="mr-2"
|
||||
bcls="btn btn-sm btn-outline-light pointer" dcls="m-0 dropdown-menu-right">
|
||||
<div onclick=${() => props.app.setUser('')}
|
||||
class="dropdown-item small pointer text-center">logout</div>
|
||||
</${Dropdown}>
|
||||
<img src="images/user.png" class="rounded-circle nav-item mr-2" width="30" />
|
||||
</form>
|
||||
</nav>
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const Info = () => html`<div class="alert alert-secondary my-3">
|
||||
This dashboard shows values kept in server memory. Many clients
|
||||
can open this page. As soon as any client changes any value,
|
||||
all clients update automatically.
|
||||
The JS code that watches state changes, reconnects on network failures.
|
||||
That means if server restarts, dashboard on all connected clients
|
||||
refresh automatically.
|
||||
You can use <code>curl</code> command-line utility:
|
||||
<br/><code>curl localhost:8000/api/config/get</code>
|
||||
<br/><code>curl localhost:8000/api/config/watch</code>
|
||||
<br/><code>curl localhost:8000/api/config/set -d '{"a":123}'</code>
|
||||
<br/><span class="badge badge-danger">NOTE:</span> administrators
|
||||
can change settings values, whilst users cannot.
|
||||
</div>`;
|
||||
|
||||
class DashVideo extends Component {
|
||||
render(props, state) {
|
||||
const onclick = ev => axios.post(
|
||||
'/api/config/set', {value1: +state.value1, value2: state.value2});
|
||||
// alert(JSON.stringify(state));
|
||||
return html`<div id="form">
|
||||
<div><b>Change values</b></div>
|
||||
<table>
|
||||
<tr><td>Value 1 (number):</td><td><input value=${state.value1}
|
||||
onInput=${linkState(this, 'value1')} /></td></tr>
|
||||
<tr><td>Value 2 (string):</td><td><input value=${state.value2}
|
||||
onInput=${linkState(this, 'value2')}/></td></tr>
|
||||
<tr><td></td>
|
||||
<td><button onClick=${onclick}>Save values</button></td></tr>
|
||||
</table>
|
||||
</div>`
|
||||
}
|
||||
};
|
||||
|
||||
class DashForm extends Component {
|
||||
render(props, state) {
|
||||
const onclick = ev => axios.post(
|
||||
'/api/config/set', {value1: +state.value1, value2: state.value2});
|
||||
// alert(JSON.stringify(state));
|
||||
return html`<div id="form">
|
||||
<div><b>Change values</b></div>
|
||||
<table>
|
||||
<tr><td>Value 1 (number):</td><td><input class="form-control form-control-sm"
|
||||
value=${state.value1}
|
||||
onInput=${linkState(this, 'value1')} /></td></tr>
|
||||
<tr><td>Value 2 (string):</td><td><input class="form-control form-control-sm"
|
||||
value=${state.value2}
|
||||
onInput=${linkState(this, 'value2')}/></td></tr>
|
||||
<tr><td></td>
|
||||
<td><button class="btn btn-primary btn-block"
|
||||
onClick=${onclick}>Save values</button></td></tr>
|
||||
</table>
|
||||
</div>`
|
||||
}
|
||||
};
|
||||
|
||||
class DashSettings extends Component {
|
||||
state = {value1: null, value2: null};
|
||||
componentDidMount() {
|
||||
axios.get('/api/config/get')
|
||||
.then(r => this.setState(r.data))
|
||||
.catch(e => console.log(e));
|
||||
var self = this;
|
||||
var f = function(reader) {
|
||||
return reader.read().then(function(result) {
|
||||
var data = String.fromCharCode.apply(null, result.value);
|
||||
self.setState(JSON.parse(data));
|
||||
// console.log(JSON.parse(data));
|
||||
if (!result.done) return f(reader);
|
||||
});
|
||||
};
|
||||
fetch('/api/config/watch')
|
||||
.then(r => r.body.getReader())
|
||||
.then(f)
|
||||
.catch(e => setTimeout(x => self.componentDidMount(), 1000));
|
||||
}
|
||||
render(props, state) {
|
||||
return html
|
||||
`<div id="dashboard" style="display: flex; justify-content: flex-evenly;">
|
||||
<div style="width:50%;text-align:center;">Value 1 (number):
|
||||
<div style="font-size:140%;"><b>${state.value1}</b></div></div>
|
||||
<div style="width:50%;text-align:center;">Value 2 (string):
|
||||
<div style="font-size:140%;"><b>${state.value2}</b></div></div>
|
||||
</div>`
|
||||
}
|
||||
};
|
||||
|
||||
class AdminDashboard extends Component {
|
||||
render(props, state) {
|
||||
return html`<div class="container-fluid">
|
||||
<${Info} />
|
||||
<div class="card-deck">
|
||||
<div class="card">
|
||||
<div class="card-header">Settings</div>
|
||||
<div class="card-body"><${DashSettings} /></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">Video stream</div>
|
||||
<div class="card-body"><img src="/api/video1" class="mw-100" /></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">Change settings</div>
|
||||
<div class="card-body"><${DashForm} /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
|
||||
class UserDashboard extends Component {
|
||||
render(props, state) {
|
||||
return html`<div class="container-fluid">
|
||||
<${Info} />
|
||||
<div class="card-deck">
|
||||
<div class="card">
|
||||
<div class="card-header">Settings</div>
|
||||
<div class="card-body"><${DashSettings} /></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">Video stream</div>
|
||||
<div class="card-body"><img src="/api/video1" class="mw-100" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
|
||||
class Logs extends Component {
|
||||
state = {live: '', static: ''};
|
||||
componentDidMount() {
|
||||
var self = this;
|
||||
var f = function(r) {
|
||||
return r.read().then(function(result) {
|
||||
var data = String.fromCharCode.apply(null, result.value);
|
||||
var live = self.state.live + data;
|
||||
self.setState({live}); // Append live log
|
||||
if (!result.done) f(r); // Read next chunk
|
||||
});
|
||||
};
|
||||
fetch('/api/log/static')
|
||||
.then(r => r.text())
|
||||
.then(r => {
|
||||
self.setState({static: r});
|
||||
})
|
||||
.then(r => fetch('/api/log/live'))
|
||||
.then(r => r.body.getReader())
|
||||
.then(f);
|
||||
}
|
||||
render(props, state) {
|
||||
return html`<div class="container-fluid">
|
||||
<div class="alert alert-secondary my-3">
|
||||
A div below shows file log.txt, which is produced by the server.
|
||||
The server appends a new log message to that file every second,
|
||||
and a div below also shows that, implementing a "live log" feature.
|
||||
JS code on this page first fetches /api/log/static that returns
|
||||
log.txt contents, then fetches /api/log/live that does not return,
|
||||
but feeds chunks of data as they arrive (live log).
|
||||
<br/><br/>
|
||||
You can also use <code>curl</code> command-line utility to see
|
||||
live logs:
|
||||
<br/><code>curl localhost:8000/api/log/live</code>
|
||||
</div>
|
||||
<div class="card-deck">
|
||||
<div class="card">
|
||||
<div class="card-header">Static</div>
|
||||
<div class="card-body"><pre class="log">${state.static}</pre></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">Live</div>
|
||||
<div class="card-body"><pre class="log">${state.live}</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
|
||||
class App extends Component {
|
||||
state = {user: null, url: '/'};
|
||||
setUser(token) {
|
||||
const maxAge = token ? 86400 : 0;
|
||||
document.cookie = `access_token=${token};path=/;max-age=${maxAge}`;
|
||||
// this.setState({token});
|
||||
return axios.get('/api/login')
|
||||
.then(res => this.setState({user: res.data}))
|
||||
.catch(err => this.setState({user: {}}));
|
||||
}
|
||||
componentDidMount() {
|
||||
const getCookie = name => {
|
||||
var v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
|
||||
return v ? v[2] : '';
|
||||
};
|
||||
this.setUser(getCookie('access_token')); // Move from state1 to 2 or 3
|
||||
}
|
||||
render(props, state) {
|
||||
// We have three states:
|
||||
// 1. Beginning. We don't know if we have a valid auth token or not
|
||||
// 2. We sent an /api/login request, and failed to login
|
||||
// 3. We sent an /api/login request, and succeeded to login
|
||||
|
||||
if (!state.user) return ''; // State 1
|
||||
if (!state.user.user) return h(Login, {app: this}); // State 2
|
||||
return h( // State 3
|
||||
'div', {}, h(Header, {url: state.url, app: this}),
|
||||
h(preactRouter.Router, {
|
||||
history: History.createHashHistory(),
|
||||
onChange: ev => this.setState({url: ev.url}),
|
||||
},
|
||||
h(state.user.user == 'admin' ? AdminDashboard : UserDashboard,
|
||||
{default: true, app: this}),
|
||||
h(Logs, {path: 'logs', app: this})));
|
||||
}
|
||||
};
|
||||
|
||||
window.onload = () => render(h(App), document.body);
|
||||
@@ -1,3 +0,0 @@
|
||||
/* axios v0.21.1 | (c) 2020 by Matt Zabriskie */
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new i(e),n=s(i.prototype.request,t);return o.extend(n,i.prototype,t),o.extend(n,t),n}var o=n(2),s=n(3),i=n(4),a=n(22),u=n(10),c=r(u);c.Axios=i,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),c.isAxiosError=n(26),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"undefined"==typeof e}function s(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function i(e){return"[object ArrayBuffer]"===R.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){if("[object Object]"!==R.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function l(e){return"[object Date]"===R.call(e)}function h(e){return"[object File]"===R.call(e)}function m(e){return"[object Blob]"===R.call(e)}function y(e){return"[object Function]"===R.call(e)}function g(e){return p(e)&&y(e.pipe)}function v(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function x(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function b(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n<o;n++)t.call(null,e[n],n,e);else for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&t.call(null,e[s],s,e)}function E(){function e(e,n){d(t[n])&&d(e)?t[n]=E(t[n],e):d(e)?t[n]=E({},e):r(e)?t[n]=e.slice():t[n]=e}for(var t={},n=0,o=arguments.length;n<o;n++)b(arguments[n],e);return t}function j(e,t,n){return b(t,function(t,r){n&&"function"==typeof t?e[r]=S(t,n):e[r]=t}),e}function C(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e}var S=n(3),R=Object.prototype.toString;e.exports={isArray:r,isArrayBuffer:i,isBuffer:s,isFormData:a,isArrayBufferView:u,isString:c,isNumber:f,isObject:p,isPlainObject:d,isUndefined:o,isDate:l,isFile:h,isBlob:m,isFunction:y,isStream:g,isURLSearchParams:v,isStandardBrowserEnv:w,forEach:b,merge:E,extend:j,trim:x,stripBOM:C}},function(e,t){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r<n.length;r++)n[r]=arguments[r];return e.apply(t,n)}}},function(e,t,n){"use strict";function r(e){this.defaults=e,this.interceptors={request:new i,response:new i}}var o=n(2),s=n(5),i=n(6),a=n(7),u=n(22);r.prototype.request=function(e){"string"==typeof e?(e=arguments[1]||{},e.url=arguments[0]):e=e||{},e=u(this.defaults,e),e.method?e.method=e.method.toLowerCase():this.defaults.method?e.method=this.defaults.method.toLowerCase():e.method="get";var t=[a,void 0],n=Promise.resolve(e);for(this.interceptors.request.forEach(function(e){t.unshift(e.fulfilled,e.rejected)}),this.interceptors.response.forEach(function(e){t.push(e.fulfilled,e.rejected)});t.length;)n=n.then(t.shift(),t.shift());return n},r.prototype.getUri=function(e){return e=u(this.defaults,e),s(e.url,e.params,e.paramsSerializer).replace(/^\?/,"")},o.forEach(["delete","get","head","options"],function(e){r.prototype[e]=function(t,n){return this.request(u(n||{},{method:e,url:t,data:(n||{}).data}))}}),o.forEach(["post","put","patch"],function(e){r.prototype[e]=function(t,n,r){return this.request(u(r||{},{method:e,url:t,data:n}))}}),e.exports=r},function(e,t,n){"use strict";function r(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var o=n(2);e.exports=function(e,t,n){if(!t)return e;var s;if(n)s=n(t);else if(o.isURLSearchParams(t))s=t.toString();else{var i=[];o.forEach(t,function(e,t){null!==e&&"undefined"!=typeof e&&(o.isArray(e)?t+="[]":e=[e],o.forEach(e,function(e){o.isDate(e)?e=e.toISOString():o.isObject(e)&&(e=JSON.stringify(e)),i.push(r(t)+"="+r(e))}))}),s=i.join("&")}if(s){var a=e.indexOf("#");a!==-1&&(e=e.slice(0,a)),e+=(e.indexOf("?")===-1?"?":"&")+s}return e}},function(e,t,n){"use stricLine truncated
|
||||
//# sourceMappingURL=axios.min.map
|
||||
@@ -1,7 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap v4.3.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2019 The Bootstrap Authors
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,Line truncated
|
||||
/*# sourceMappingURL=bootstrap.min.css.map */
|
||||
@@ -1 +0,0 @@
|
||||
!function(){function n(n){var t=e.get(this);return t||(t=new Map,e.set(this,t)),1<(t=a(this,t.get(n)||(t.set(n,t=function(n){function t(n){1===u&&(n||(r=r.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?p.push(0,n,r):3===u&&(n||r)?(p.push(3,n,r),u=2):2===u&&"..."===r&&n?p.push(4,n,0):2===u&&r&&!n?p.push(5,0,!0,r):5<=u&&((r||!n&&5===u)&&(p.push(u,0,r,s),u=6),n&&(p.push(u,n,0,s),u=6)),r=""}for(var e,s,u=1,r="",h="",p=[0],a=0;a<n.length;a++){a&&(1===u&&t(),t(a));for(var o=0;o<n[a].length;o++)e=n[a][o],1===u?"<"===e?(t(),p=[p],u=3):r+=e:4===u?r="--"===r&&">"===e?(u=1,""):e+r[0]:h?e===h?h="":r+=e:'"'===e||"'"===e?h=e:">"===e?(t(),u=1):u&&("="===e?(u=5,s=r,r=""):"/"===e&&(u<5||">"===n[a][o+1])?(t(),3===u&&(p=p[0]),(p=(u=p)[0]).push(2,0,u),u=0):" "===e||"\t"===e||"\n"===e||"\r"===e?(t(),u=2):r+=e),3===u&&"!--"===r&&(u=4,p=p[0])}return t(),p}(n)),t),arguments,[])).length?t:t[0]}var a=function(n,t,e,s){var u;t[0]=0;for(var r=1;r<t.length;r++){var h=t[r++],p=t[r]?(t[0]|=h?1:2,e[t[r++]]):t[++r];3===h?s[0]=p:4===h?s[1]=Object.assign(s[1]||{},p):5===h?(s[1]=s[1]||{})[t[++r]]=p:6===h?s[1][t[++r]]+=p+"":h?(u=n.apply(p,a(n,p,e,["",null])),s.push(u),p[0]?t[0]|=2:(t[r-2]=0,t[r]=u)):s.push(p)}return s},e=new Map;"undefined"!=typeof module?module.exports=n:self.htm=n}();
|
||||
|
Before Width: | Height: | Size: 3.2 KiB |
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>example</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script src="preact.min.js"></script>
|
||||
<script src="preact-router.min.js"></script>
|
||||
<script src="htm.min.js"></script>
|
||||
<script src="history.min.js"></script>
|
||||
<script src="linkstate.min.js"></script>
|
||||
<script src="axios.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -1,18 +0,0 @@
|
||||
function linkState(component, key, eventPath) {
|
||||
let path = key.split('.'), cache = component.__lsc || (component.__lsc = {});
|
||||
|
||||
return cache[key + eventPath] || (cache[key + eventPath] = function(e) {
|
||||
let t = e && e.target || this, state = {}, obj = state,
|
||||
v = typeof eventPath === 'string' ?
|
||||
delve(e, eventPath) :
|
||||
t.nodeName ? (t.type.match(/^che|rad/) ? t.checked : t.value) :
|
||||
e,
|
||||
i = 0;
|
||||
for (; i < path.length - 1; i++) {
|
||||
obj = obj[path[i]] ||
|
||||
(obj[path[i]] = !i && component.state[path[i]] || {});
|
||||
}
|
||||
obj[path[i]] = v;
|
||||
component.setState(state);
|
||||
});
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("preact")):"function"==typeof define&&define.amd?define(["preact"],e):t.preactRouter=e(t.preact)}(this,function(p){function i(t,e){for(var n in e)t[n]=e[n];return t}function u(t,e,n){var r,o=/(?:\?([^#]*))?(#.*)?$/,i=t.match(o),u={};if(i&&i[1])for(var a=i[1].split("&"),p=0;p<a.length;p++){var c=a[p].split("=");u[decodeURIComponent(c[0])]=decodeURIComponent(c.slice(1).join("="))}t=y(t.replace(o,"")),e=y(e||"");for(var f=Math.max(t.length,e.length),l=0;l<f;l++)if(e[l]&&":"===e[l].charAt(0)){var s=e[l].replace(/(^:|[+*?]+$)/g,""),h=(e[l].match(/[+*?]+$/)||v)[0]||"",d=~h.indexOf("+"),g=~h.indexOf("*"),m=t[l]||"";if(!m&&!g&&(h.indexOf("?")<0||d)){r=!1;break}if(u[s]=decodeURIComponent(m),d||g){u[s]=t.slice(l).map(decodeURIComponent).join("/");break}}else if(e[l]!==t[l]){r=!1;break}return(!0===n.default||!1!==r)&&u}function e(t,e){return t.rank<e.rank?1:t.rank>e.rank?-1:t.index-e.index}function n(t,e){return t.index=e,t.rank=(n=t).props.default?0:function(t){return y(t).map(r).join("")}(n.props.path),t.props;var n}function y(t){return t.replace(/(^\/+|\/+$)/g,"").split("/")}function r(t){return":"==t.charAt(0)?1+"*+?".indexOf(t.charAt(t.length-1))||4:5}function o(){var t;return""+((t=g&&g.location?g.location:g&&g.getCurrentLocation?g.getCurrentLocation():"undefined"!=typeof location?location:b).pathname||"")+(t.search||"")}function a(t,e){return void 0===e&&(e=!1),"string"!=typeof t&&t.url&&(e=t.replace,t=t.url),function(t){for(var e=m.length;e--;)if(m[e].canRoute(t))return 1;return}(t)&&(n=t,void 0===(r=e?"replace":"push")&&(r="push"),g&&g[r]?g[r](n):"undefined"!=typeof history&&history[r+"State"]&&history[r+"State"](null,null,n)),c(t);var n,r}function c(t){for(var e=!1,n=0;n<m.length;n++)!0===m[n].routeTo(t)&&(e=!0);for(var r=C.length;r--;)C[r](t);return e}function f(t){if(t&&t.getAttribute){var e=t.getAttribute("href"),n=t.getAttribute("target");if(e&&e.match(/^\//g)&&(!n||n.match(/^_?self$/i)))return a(e)}}function l(t){if(!(t.ctrlKey||t.metaKey||t.altKey||t.shiftKey||0!==t.button))return f(t.currentTarget||t.target||this),s(t)}function s(t){return t&&(t.stopImmediatePropagation&&t.stopImmediatePropagation(),t.stopPropagation&&t.stopPropagation(),t.preventDefault()),!1}function h(t){if(!(t.ctrlKey||t.metaKey||t.altKey||t.shiftKey||0!==t.button)){var e=t.target;do{if("A"===(e.nodeName+"").toUpperCase()&&e.getAttribute("href")){if(e.hasAttribute("native"))return;if(f(e))return s(t)}}while(e=e.parentNode)}}var d,v={},g=null,m=[],C=[],b={},U=!1,t=((d=p.Component)&&(k.__proto__=d),((k.prototype=Object.create(d&&d.prototype)).constructor=k).prototype.shouldComponentUpdate=function(t){return!0!==t.static||t.url!==this.props.url||t.onChange!==this.props.onChange},k.prototype.canRoute=function(t){return 0<this.getMatchingChildren(p.toChildArray(this.props.children),t,!1).length},k.prototype.routeTo=function(t){this.setState({url:t});var e=this.canRoute(t);return this.updating||this.forceUpdate(),e},k.prototype.componentWillMount=function(){m.push(this),this.updating=!0},k.prototype.componentDidMount=function(){var e=this;g&&(this.unlisten=g.listen(function(t){e.routeTo(""+(t.pathname||"")+(t.search||""))})),this.updating=!1},k.prototype.componentWillUnmount=function(){"function"==typeof this.unlisten&&this.unlisten(),m.splice(m.indexOf(this),1)},k.prototype.componentWillUpdate=function(){this.updating=!0},k.prototype.componentDidUpdate=function(){this.updating=!1},k.prototype.getMatchingChildren=function(t,r,o){return t.filter(n).sort(e).map(function(t){var e=u(r,t.props.path,t.props);if(e){if(!1===o)return t;var n={url:r,matches:e};return i(n,e),delete n.ref,delete n.key,p.cloneElement(t,n)}}).filter(Boolean)},k.prototype.render=function(t,e){var n=t.children,r=t.onChange,o=e.url,i=this.getMatchingChildren(p.toChildArray(n),o,!0),u=i[0]||null,a=this.previousUrl;return o!==a&&(this.previousUrl=o,"function"==typeof r&&r({router:this,url:o,previous:a,active:i,current:u})),u},k);function k(t){d.call(this,t),t.history&&(g=t.history),this.state={url:t.url||o()},U||("function"==typeof addEventListener&&(g||addEventListener("popstate",function(){c(o())}),addEventListener("click",h)),U=!0)}return t.subscribers=C,t.getCurrentUrl=o,t.route=a,(t.Router=t).Route=function(t){return p.createElement(t.component,t)},t.Link=function(t){return p.createElement("a",i({onClick:l},t))},t.exec=u,t});
|
||||
@@ -1,2 +0,0 @@
|
||||
!function(){var n,l,u,t,i,o,r,e,f={},c=[],a=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function s(n,l){for(var u in l)n[u]=l[u];return n}function v(n){var l=n.parentNode;l&&l.removeChild(n)}function h(n,l,u){var t,i=arguments,o={};for(t in l)"key"!==t&&"ref"!==t&&(o[t]=l[t]);if(arguments.length>3)for(u=[u],t=3;t<arguments.length;t++)u.push(i[t]);if(null!=u&&(o.children=u),"function"==typeof n&&null!=n.defaultProps)for(t in n.defaultProps)void 0===o[t]&&(o[t]=n.defaultProps[t]);return y(n,o,l&&l.key,l&&l.ref,null)}function y(l,u,t,i,o){var r={type:l,props:u,key:t,ref:i,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,constructor:void 0,__v:o};return null==o&&(r.__v=r),n.vnode&&n.vnode(r),r}function p(n){return n.children}function d(n,l){this.props=n,this.context=l}function _(n,l){if(null==l)return n.__?_(n.__,n.__.__k.indexOf(n)+1):null;for(var u;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e)return u.__e;return"function"==typeof n.type?_(n):null}function m(n){var l,u;if(null!=(n=n.__)&&null!=n.__c){for(n.__e=n.__c.base=null,l=0;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e){n.__e=n.__c.base=u.__e;break}return m(n)}}function k(l){(!l.__d&&(l.__d=!0)&&u.push(l)&&!w.__r++||i!==n.debounceRendering)&&((i=n.debounceRendering)||t)(w)}function w(){for(var n;w.__r=u.length;)n=u.sort(function(n,l){return n.__v.__b-l.__v.__b}),u=[],n.some(function(n){var l,u,t,i,o,r,e;n.__d&&(r=(o=(l=n).__v).__e,(e=l.__P)&&(u=[],(t=s({},o)).__v=t,i=N(e,o,t,l.__n,void 0!==e.ownerSVGElement,null,u,null==r?_(o):r),z(u,o),i!=r&&m(o)))})}function g(n,l,u,t,i,o,r,e,a,s){var h,d,m,k,w,g,x,C=t&&t.__k||c,A=C.length;for(a==f&&(a=null!=r?r[0]:A?_(t,0):null),u.__k=[],h=0;h<l.length;h++)if(null!=(k=u.__k[h]=null==(k=l[h])||"boolean"==typeof k?null:"string"==typeof k||"number"==typeof k?y(null,k,null,null,k):Array.isArray(k)?y(p,{children:k},null,null,null):null!=k.__e||null!=k.__c?y(k.type,k.props,k.key,null,k.__v):k)){if(k.__=u,k.__b=u.__b+1,null===(m=C[h])||m&&k.key==m.key&&k.type===m.type)C[h]=void 0;else for(d=0;d<A;d++){if((m=C[d])&&k.key==m.key&&k.type===m.type){C[d]=void 0;break}m=null}w=N(n,k,m=m||f,i,o,r,e,a,s),(d=k.ref)&&m.ref!=d&&(x||(x=[]),m.ref&&x.push(m.ref,null,k),x.push(d,k.__c||w,k)),null!=w?(null==g&&(g=w),a=b(n,k,m,C,r,w,a),"option"==u.type?n.value="":"function"==typeof u.type&&(u.__d=a)):a&&m.__e==a&&a.parentNode!=n&&(a=_(m))}if(u.__e=g,null!=r&&"function"!=typeof u.type)for(h=r.length;h--;)null!=r[h]&&v(r[h]);for(h=A;h--;)null!=C[h]&&j(C[h],C[h]);if(x)for(h=0;h<x.length;h++)$(x[h],x[++h],x[++h])}function b(n,l,u,t,i,o,r){var e,f,c;if(void 0!==l.__d)e=l.__d,l.__d=void 0;else if(i==u||o!=r||null==o.parentNode)n:if(null==r||r.parentNode!==n)n.appendChild(o),e=null;else{for(f=r,c=0;(f=f.nextSibling)&&c<t.length;c+=2)if(f==o)break n;n.insertBefore(o,r),e=r}return void 0!==e?e:o.nextSibling}function x(n,l,u,t,i){var o;for(o in u)"children"===o||"key"===o||o in l||A(n,o,null,u[o],t);for(o in l)i&&"function"!=typeof l[o]||"children"===o||"key"===o||"value"===o||"checked"===o||u[o]===l[o]||A(n,o,l[o],u[o],t)}function C(n,l,u){"-"===l[0]?n.setProperty(l,u):n[l]="number"==typeof u&&!1===a.test(l)?u+"px":null==u?"":u}function A(n,l,u,t,i){var o,r,e,f,c;if(i?"className"===l&&(l="class"):"class"===l&&(l="className"),"style"===l)if(o=n.style,"string"==typeof u)o.cssText=u;else{if("string"==typeof t&&(o.cssText="",t=null),t)for(f in t)u&&f in u||C(o,f,"");if(u)for(c in u)t&&u[c]===t[c]||C(o,c,u[c])}else"o"===l[0]&&"n"===l[1]?(r=l!==(l=l.replace(/Capture$/,"")),e=l.toLowerCase(),l=(e in n?e:l).slice(2),u?(t||n.addEventListener(l,P,r),(n.l||(n.l={}))[l]=u):n.removeEventListener(l,P,r)):"list"!==l&&"tagName"!==l&&"form"!==l&&"type"!==l&&"size"!==l&&!i&&l in n?n[l]=null==u?"":u:"function"!=typeof u&&"dangerouslySetInnerHTML"!==l&&(l!==(l=l.replace(/^xlink:?/,""))?null==u||!1===u?n.removeAttributeNS("http://www.w3.org/1999/xlink",l.toLowerCase()):n.setAttributeNS("http://www.w3.org/1999/xlink",l.toLowerCase(),u):null==u||!1===u&&!/^ar/.test(l)?n.removeAttribute(l):n.setAttribute(l,u))}function P(l){this.l[l.type](n.event?n.event(l):l)}function E(n,l,u){var t,i;for(t=0;t<n.__k.length;t++)(i=n.__k[t])&&(i.__=n,i.__e&&("function"==typeof i.type&&i.__k.length>1&&E(i,l,u),l=b(u,i,i,n.__k,null,i.__e,l),"function"==typeof n.type&&(n.__d=l)))}function N(l,u,t,i,o,r,e,f,c){var a,v,h,y,_,m,k,w,b,x,C,A=u.type;if(void 0!==u.constructor)return null;(a=n.__b)&&a(u);try{n:if("function"==typeof A){if(w=u.props,b=(a=A.contextType)&&i[a.__c],x=a?b?b.props.value:a.__:i,t.__c?k=(v=u.__c=t.__c).__=v.__E:("prototype"in A&&A.prototype.render?u.__c=v=new A(w,x):(u.__c=v=new d(w,x),v.constructor=A,v.render=F),b&&b.sub(v),v.props=w,v.state||(v.state={}),v.context=x,v.__n=i,h=v.__d=!0,v.__h=[]),null==v.__s&&(v.__s=v.state),null!=A.getDerivedStateFromProps&&(v.__s==v.state&&(v.__s=s({},v.__s)),s(v.__s,A.getDerivedStateFromProps(w,v.__s))),y=v.props,_=v.state,h)null==A.getDerivedStateFromProps&&null!=v.componentWillMount&&v.componenLine truncated
|
||||
//# sourceMappingURL=preact.min.js.map
|
||||
@@ -1,7 +0,0 @@
|
||||
html, body { margin: 0; padding: 0; height: 100%; }
|
||||
select, input, label::before, textarea { outline: none; box-shadow:none !important; border: 1px solid #ccc !important; }
|
||||
.btn:focus,.btn:active:focus,.btn.active:focus,
|
||||
.btn.focus,.btn:active.focus,.btn.active.focus { outline: none; box-shadow:none !important; }
|
||||
.dropdown.autoexpand:hover>.dropdown-menu { display: block; }
|
||||
.pointer { cursor: pointer; }
|
||||
.log { max-height: 10em; overflow: auto; height: 100%; }
|
||||
@@ -1,10 +0,0 @@
|
||||
PROG ?= example
|
||||
|
||||
all: $(PROG)
|
||||
$(DEBUGGER) ./$(PROG)
|
||||
|
||||
$(PROG):
|
||||
$(CC) ../../mongoose.c -I../.. $(CFLAGS) $(EXTRA) -o $(PROG) main.c mjson.c
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb log.txt
|
||||
@@ -1,82 +0,0 @@
|
||||
// Copyright (c) 2020 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "mjson.h" // JSON parsing and printing
|
||||
#include "mongoose.h"
|
||||
|
||||
// This is a configuration structure we're going to show on a dashboard
|
||||
static struct config {
|
||||
int value1;
|
||||
char *value2;
|
||||
} s_config = {123, NULL};
|
||||
|
||||
// Stringifies the config. A caller must free() it.
|
||||
static char *stringify_config(struct config *cfg) {
|
||||
char *s = NULL;
|
||||
mjson_printf(mjson_print_dynamic_buf, &s, "{%Q:%d,%Q:%Q}", "value1",
|
||||
cfg->value1, "value2", cfg->value2);
|
||||
return s;
|
||||
}
|
||||
|
||||
// Update config structure. Return true if changed, false otherwise
|
||||
static bool update_config(struct mg_http_message *hm, struct config *cfg) {
|
||||
bool changed = false;
|
||||
char buf[256];
|
||||
double dv;
|
||||
if (mjson_get_number(hm->body.ptr, hm->body.len, "$.value1", &dv)) {
|
||||
s_config.value1 = dv;
|
||||
changed = true;
|
||||
}
|
||||
if (mjson_get_string(hm->body.ptr, hm->body.len, "$.value2", buf,
|
||||
sizeof(buf)) > 0) {
|
||||
free(s_config.value2);
|
||||
s_config.value2 = strdup(buf);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
// Notify all config watchers about the config change
|
||||
static void notify_config_change(struct mg_mgr *mgr) {
|
||||
struct mg_connection *c;
|
||||
char *s = stringify_config(&s_config);
|
||||
for (c = mgr->conns; c != NULL; c = c->next) {
|
||||
if (c->label[0] == 'W') mg_http_printf_chunk(c, "%s\n", s);
|
||||
}
|
||||
free(s);
|
||||
}
|
||||
|
||||
// HTTP request handler function. It implements the following endpoints:
|
||||
// /api/config/get - returns current config
|
||||
// /api/config/set - updates current config
|
||||
// /api/config/watch - does not return. Sends config as it changes in
|
||||
// chunks all other URI - serves web_root/ directory
|
||||
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_http_match_uri(hm, "/api/config/get")) {
|
||||
char *s = stringify_config(&s_config);
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s\n",
|
||||
(int) strlen(s) + 1, s);
|
||||
free(s);
|
||||
} else if (mg_http_match_uri(hm, "/api/config/set")) {
|
||||
if (update_config(hm, &s_config)) notify_config_change(fn_data);
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
|
||||
} else if (mg_http_match_uri(hm, "/api/config/watch")) {
|
||||
c->label[0] = 'W'; // Mark ourselves as a config watcher
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
} else {
|
||||
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
|
||||
mg_http_serve_dir(c, ev_data, &opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://localhost:8000", cb, &mgr);
|
||||
for (;;) mg_mgr_poll(&mgr, 1000);
|
||||
mg_mgr_free(&mgr);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,981 +0,0 @@
|
||||
// Copyright (c) 2018-2020 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#include "mjson.h"
|
||||
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
#else
|
||||
#define va_copy(x, y) (x) = (y)
|
||||
#define snprintf _snprintf
|
||||
#define alloca _alloca
|
||||
#endif
|
||||
|
||||
static int mjson_esc(int c, int esc) {
|
||||
const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
|
||||
for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
|
||||
if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mjson_pass_string(const char *s, int len) {
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (s[i] == '\\' && i + 1 < len && mjson_esc(s[i + 1], 1)) {
|
||||
i++;
|
||||
} else if (s[i] == '\0') {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
} else if (s[i] == '"') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int mjson(const char *s, int len, mjson_cb_t cb, void *ud) {
|
||||
enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
|
||||
unsigned char nesting[MJSON_MAX_DEPTH];
|
||||
int i, depth = 0;
|
||||
#define MJSONCALL(ev) \
|
||||
if (cb != NULL && cb(ev, s, start, i - start + 1, ud)) return i + 1;
|
||||
|
||||
// In the ascii table, the distance between `[` and `]` is 2.
|
||||
// Ditto for `{` and `}`. Hence +2 in the code below.
|
||||
#define MJSONEOO() \
|
||||
do { \
|
||||
if (c != nesting[depth - 1] + 2) return MJSON_ERROR_INVALID_INPUT; \
|
||||
depth--; \
|
||||
if (depth == 0) { \
|
||||
MJSONCALL(tok); \
|
||||
return i + 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
int start = i;
|
||||
unsigned char c = ((unsigned char *) s)[i];
|
||||
int tok = c;
|
||||
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
|
||||
// printf("- %c [%.*s] %d %d\n", c, i, s, depth, expecting);
|
||||
switch (expecting) {
|
||||
case S_VALUE:
|
||||
if (c == '{') {
|
||||
if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP;
|
||||
nesting[depth++] = c;
|
||||
expecting = S_KEY;
|
||||
break;
|
||||
} else if (c == '[') {
|
||||
if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP;
|
||||
nesting[depth++] = c;
|
||||
break;
|
||||
} else if (c == ']' && depth > 0) { // Empty array
|
||||
MJSONEOO();
|
||||
} else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
|
||||
i += 3;
|
||||
tok = MJSON_TOK_TRUE;
|
||||
} else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
|
||||
i += 3;
|
||||
tok = MJSON_TOK_NULL;
|
||||
} else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
|
||||
i += 4;
|
||||
tok = MJSON_TOK_FALSE;
|
||||
} else if (c == '-' || ((c >= '0' && c <= '9'))) {
|
||||
char *end = NULL;
|
||||
strtod(&s[i], &end);
|
||||
if (end != NULL) i += end - &s[i] - 1;
|
||||
tok = MJSON_TOK_NUMBER;
|
||||
} else if (c == '"') {
|
||||
int n = mjson_pass_string(&s[i + 1], len - i - 1);
|
||||
if (n < 0) return n;
|
||||
i += n + 1;
|
||||
tok = MJSON_TOK_STRING;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
if (depth == 0) {
|
||||
MJSONCALL(tok);
|
||||
return i + 1;
|
||||
}
|
||||
expecting = S_COMMA_OR_EOO;
|
||||
break;
|
||||
|
||||
case S_KEY:
|
||||
if (c == '"') {
|
||||
int n = mjson_pass_string(&s[i + 1], len - i - 1);
|
||||
if (n < 0) return n;
|
||||
i += n + 1;
|
||||
tok = MJSON_TOK_KEY;
|
||||
expecting = S_COLON;
|
||||
} else if (c == '}') { // Empty object
|
||||
MJSONEOO();
|
||||
expecting = S_COMMA_OR_EOO;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_COLON:
|
||||
if (c == ':') {
|
||||
expecting = S_VALUE;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_COMMA_OR_EOO:
|
||||
if (depth <= 0) return MJSON_ERROR_INVALID_INPUT;
|
||||
if (c == ',') {
|
||||
expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
|
||||
} else if (c == ']' || c == '}') {
|
||||
MJSONEOO();
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
MJSONCALL(tok);
|
||||
}
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
struct msjon_get_data {
|
||||
const char *path; // Lookup json path
|
||||
int pos; // Current path index
|
||||
int d1; // Current depth of traversal
|
||||
int d2; // Expected depth of traversal
|
||||
int i1; // Index in an array
|
||||
int i2; // Expected index in an array
|
||||
int obj; // If the value is array/object, offset where it starts
|
||||
const char **tokptr; // Destination
|
||||
int *toklen; // Destination length
|
||||
int tok; // Returned token
|
||||
};
|
||||
|
||||
static int mjson_plen(const char *s) {
|
||||
int i = 0;
|
||||
while (s[i] != '\0' && s[i] != '.' && s[i] != '[') i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) {
|
||||
struct msjon_get_data *data = (struct msjon_get_data *) ud;
|
||||
// printf("--> %2x %2d %2d %2d %2d\t'%s'\t'%.*s'\t\t'%.*s'\n", tok, data->d1,
|
||||
// data->d2, data->i1, data->i2, data->path + data->pos, off, s, len,
|
||||
// s + off);
|
||||
if (data->tok != MJSON_TOK_INVALID) return 1; // Found
|
||||
|
||||
if (tok == '{') {
|
||||
if (!data->path[data->pos] && data->d1 == data->d2) data->obj = off;
|
||||
data->d1++;
|
||||
} else if (tok == '[') {
|
||||
if (data->d1 == data->d2 && data->path[data->pos] == '[') {
|
||||
data->i1 = 0;
|
||||
data->i2 = (int) strtod(&data->path[data->pos + 1], NULL);
|
||||
if (data->i1 == data->i2) {
|
||||
data->d2++;
|
||||
data->pos += 3;
|
||||
}
|
||||
}
|
||||
if (!data->path[data->pos] && data->d1 == data->d2) data->obj = off;
|
||||
data->d1++;
|
||||
} else if (tok == ',') {
|
||||
if (data->d1 == data->d2 + 1) {
|
||||
data->i1++;
|
||||
if (data->i1 == data->i2) {
|
||||
while (data->path[data->pos] != ']') data->pos++;
|
||||
data->pos++;
|
||||
data->d2++;
|
||||
}
|
||||
}
|
||||
} else if (tok == MJSON_TOK_KEY && data->d1 == data->d2 + 1 &&
|
||||
data->path[data->pos] == '.' && s[off] == '"' &&
|
||||
s[off + len - 1] == '"' &&
|
||||
mjson_plen(&data->path[data->pos + 1]) == len - 2 &&
|
||||
!memcmp(s + off + 1, &data->path[data->pos + 1], len - 2)) {
|
||||
data->d2++;
|
||||
data->pos += len - 1;
|
||||
} else if (tok == MJSON_TOK_KEY && data->d1 == data->d2) {
|
||||
return 1; // Exhausted path, not found
|
||||
} else if (tok == '}' || tok == ']') {
|
||||
data->d1--;
|
||||
// data->d2--;
|
||||
if (!data->path[data->pos] && data->d1 == data->d2 && data->obj != -1) {
|
||||
data->tok = tok - 2;
|
||||
if (data->tokptr) *data->tokptr = s + data->obj;
|
||||
if (data->toklen) *data->toklen = off - data->obj + 1;
|
||||
return 1;
|
||||
}
|
||||
} else if (MJSON_TOK_IS_VALUE(tok)) {
|
||||
// printf("TOK --> %d\n", tok);
|
||||
if (data->d1 == data->d2 && !data->path[data->pos]) {
|
||||
data->tok = tok;
|
||||
if (data->tokptr) *data->tokptr = s + off;
|
||||
if (data->toklen) *data->toklen = len;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum mjson_tok mjson_find(const char *s, int len, const char *jp,
|
||||
const char **tokptr, int *toklen) {
|
||||
struct msjon_get_data data = {jp, 1, 0, 0, 0,
|
||||
0, -1, tokptr, toklen, MJSON_TOK_INVALID};
|
||||
if (jp[0] != '$') return MJSON_TOK_INVALID;
|
||||
if (mjson(s, len, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID;
|
||||
return (enum mjson_tok) data.tok;
|
||||
}
|
||||
|
||||
int mjson_get_number(const char *s, int len, const char *path, double *v) {
|
||||
const char *p;
|
||||
int tok, n;
|
||||
if ((tok = mjson_find(s, len, path, &p, &n)) == MJSON_TOK_NUMBER) {
|
||||
if (v != NULL) *v = strtod(p, NULL);
|
||||
}
|
||||
return tok == MJSON_TOK_NUMBER ? 1 : 0;
|
||||
}
|
||||
|
||||
int mjson_get_bool(const char *s, int len, const char *path, int *v) {
|
||||
int tok = mjson_find(s, len, path, NULL, NULL);
|
||||
if (tok == MJSON_TOK_TRUE && v != NULL) *v = 1;
|
||||
if (tok == MJSON_TOK_FALSE && v != NULL) *v = 0;
|
||||
return tok == MJSON_TOK_TRUE || tok == MJSON_TOK_FALSE ? 1 : 0;
|
||||
}
|
||||
|
||||
static int mjson_unescape(const char *s, int len, char *to, int n) {
|
||||
int i, j;
|
||||
for (i = 0, j = 0; i < len && j < n; i++, j++) {
|
||||
if (s[i] == '\\' && i + 1 < len) {
|
||||
int c = mjson_esc(s[i + 1], 0);
|
||||
if (c == 0) return -1;
|
||||
to[j] = c;
|
||||
i++;
|
||||
} else {
|
||||
to[j] = s[i];
|
||||
}
|
||||
}
|
||||
if (j >= n) return -1;
|
||||
if (n > 0) to[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
|
||||
int mjson_get_string(const char *s, int len, const char *path, char *to,
|
||||
int n) {
|
||||
const char *p;
|
||||
int sz;
|
||||
if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return -1;
|
||||
return mjson_unescape(p + 1, sz - 2, to, n);
|
||||
}
|
||||
|
||||
int mjson_get_hex(const char *s, int len, const char *x, char *to, int n) {
|
||||
const char *p;
|
||||
int i, j, sz;
|
||||
if (mjson_find(s, len, x, &p, &sz) != MJSON_TOK_STRING) return -1;
|
||||
for (i = j = 0; i < sz - 3 && j < n; i += 2, j++) {
|
||||
#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W')
|
||||
unsigned char a = *(const unsigned char *) (p + i + 1);
|
||||
unsigned char b = *(const unsigned char *) (p + i + 2);
|
||||
((unsigned char *) to)[j] = (HEXTOI(a) << 4) | HEXTOI(b);
|
||||
}
|
||||
if (j < n) to[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
static int mjson_base64rev(int c) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
return c - 'A';
|
||||
} else if (c >= 'a' && c <= 'z') {
|
||||
return c + 26 - 'a';
|
||||
} else if (c >= '0' && c <= '9') {
|
||||
return c + 52 - '0';
|
||||
} else if (c == '+') {
|
||||
return 62;
|
||||
} else if (c == '/') {
|
||||
return 63;
|
||||
} else {
|
||||
return 64;
|
||||
}
|
||||
}
|
||||
|
||||
int mjson_base64_dec(const char *src, int n, char *dst, int dlen) {
|
||||
const char *end = src + n;
|
||||
int len = 0;
|
||||
while (src + 3 < end && len < dlen) {
|
||||
int a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]),
|
||||
c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]);
|
||||
dst[len++] = (a << 2) | (b >> 4);
|
||||
if (src[2] != '=' && len < dlen) {
|
||||
dst[len++] = (b << 4) | (c >> 2);
|
||||
if (src[3] != '=' && len < dlen) {
|
||||
dst[len++] = (c << 6) | d;
|
||||
}
|
||||
}
|
||||
src += 4;
|
||||
}
|
||||
if (len < dlen) dst[len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
int mjson_get_base64(const char *s, int len, const char *path, char *to,
|
||||
int n) {
|
||||
const char *p;
|
||||
int sz;
|
||||
if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return 0;
|
||||
return mjson_base64_dec(p + 1, sz - 2, to, n);
|
||||
}
|
||||
#endif // MJSON_ENABLE_BASE64
|
||||
|
||||
#if MJSON_ENABLE_NEXT
|
||||
struct nextdata {
|
||||
int off, len, depth, t, vo, arrayindex;
|
||||
int *koff, *klen, *voff, *vlen, *vtype;
|
||||
};
|
||||
|
||||
static int next_cb(int tok, const char *s, int off, int len, void *ud) {
|
||||
struct nextdata *d = (struct nextdata *) ud;
|
||||
// int i;
|
||||
switch (tok) {
|
||||
case '{':
|
||||
case '[':
|
||||
if (d->depth == 0 && tok == '[') d->arrayindex = 0;
|
||||
if (d->depth == 1 && off > d->off) {
|
||||
d->vo = off;
|
||||
d->t = tok == '{' ? MJSON_TOK_OBJECT : MJSON_TOK_ARRAY;
|
||||
if (d->voff) *d->voff = off;
|
||||
if (d->vtype) *d->vtype = d->t;
|
||||
}
|
||||
d->depth++;
|
||||
break;
|
||||
case '}':
|
||||
case ']':
|
||||
d->depth--;
|
||||
if (d->depth == 1 && d->vo) {
|
||||
d->len = off + len;
|
||||
if (d->vlen) *d->vlen = d->len - d->vo;
|
||||
if (d->arrayindex >= 0) {
|
||||
if (d->koff) *d->koff = d->arrayindex; // koff holds array index
|
||||
if (d->klen) *d->klen = 0; // klen holds 0
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (d->depth == 1 && d->arrayindex >= 0) d->arrayindex++;
|
||||
break;
|
||||
case ',':
|
||||
case ':':
|
||||
break;
|
||||
case MJSON_TOK_KEY:
|
||||
if (d->depth == 1 && d->off < off) {
|
||||
if (d->koff) *d->koff = off; // And report back to the user
|
||||
if (d->klen) *d->klen = len; // If we have to
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (d->depth != 1) break;
|
||||
// If we're iterating over the array
|
||||
if (off > d->off) {
|
||||
d->len = off + len;
|
||||
if (d->vlen) *d->vlen = len; // value length
|
||||
if (d->voff) *d->voff = off; // value offset
|
||||
if (d->vtype) *d->vtype = tok; // value type
|
||||
if (d->arrayindex >= 0) {
|
||||
if (d->koff) *d->koff = d->arrayindex; // koff holds array index
|
||||
if (d->klen) *d->klen = 0; // klen holds 0
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (d->arrayindex >= 0) d->arrayindex++;
|
||||
break;
|
||||
}
|
||||
(void) s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff,
|
||||
int *vlen, int *vtype) {
|
||||
struct nextdata d = {off, 0, 0, 0, 0, -1, koff, klen, voff, vlen, vtype};
|
||||
mjson(s, n, next_cb, &d);
|
||||
return d.len;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_PRINT
|
||||
int mjson_print_fixed_buf(const char *ptr, int len, void *fndata) {
|
||||
struct mjson_fixedbuf *fb = (struct mjson_fixedbuf *) fndata;
|
||||
int i, left = fb->size - 1 - fb->len;
|
||||
if (left < len) len = left;
|
||||
for (i = 0; i < len; i++) fb->ptr[fb->len + i] = ptr[i];
|
||||
fb->len += len;
|
||||
fb->ptr[fb->len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
int mjson_print_dynamic_buf(const char *ptr, int len, void *fndata) {
|
||||
char *s, *buf = *(char **) fndata;
|
||||
int curlen = buf == NULL ? 0 : strlen(buf);
|
||||
if ((s = (char *) realloc(buf, curlen + len + 1)) == NULL) {
|
||||
return 0;
|
||||
} else {
|
||||
memcpy(s + curlen, ptr, len);
|
||||
s[curlen + len] = '\0';
|
||||
*(char **) fndata = s;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
int mjson_print_null(const char *ptr, int len, void *userdata) {
|
||||
(void) ptr;
|
||||
(void) userdata;
|
||||
return len;
|
||||
}
|
||||
|
||||
int mjson_print_file(const char *ptr, int len, void *userdata) {
|
||||
return fwrite(ptr, 1, len, (FILE *) userdata);
|
||||
}
|
||||
|
||||
int mjson_print_buf(mjson_print_fn_t fn, void *fndata, const char *buf,
|
||||
int len) {
|
||||
return fn(buf, len, fndata);
|
||||
}
|
||||
|
||||
int mjson_print_int(mjson_print_fn_t fn, void *fndata, int value,
|
||||
int is_signed) {
|
||||
char buf[20];
|
||||
int len = snprintf(buf, sizeof(buf), is_signed ? "%d" : "%u", value);
|
||||
return fn(buf, len, fndata);
|
||||
}
|
||||
|
||||
int mjson_print_long(mjson_print_fn_t fn, void *fndata, long value,
|
||||
int is_signed) {
|
||||
char buf[20];
|
||||
const char *fmt = (is_signed ? "%ld" : "%lu");
|
||||
int len = snprintf(buf, sizeof(buf), fmt, value);
|
||||
return fn(buf, len, fndata);
|
||||
}
|
||||
|
||||
int mjson_print_dbl(mjson_print_fn_t fn, void *fndata, double d,
|
||||
const char *fmt) {
|
||||
char buf[40];
|
||||
int n = snprintf(buf, sizeof(buf), fmt, d);
|
||||
return fn(buf, n, fndata);
|
||||
}
|
||||
|
||||
int mjson_print_str(mjson_print_fn_t fn, void *fndata, const char *s, int len) {
|
||||
int i, n = fn("\"", 1, fndata);
|
||||
for (i = 0; i < len; i++) {
|
||||
char c = mjson_esc(s[i], 1);
|
||||
if (c) {
|
||||
n += fn("\\", 1, fndata);
|
||||
n += fn(&c, 1, fndata);
|
||||
} else {
|
||||
n += fn(&s[i], 1, fndata);
|
||||
}
|
||||
}
|
||||
return n + fn("\"", 1, fndata);
|
||||
}
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
int mjson_print_b64(mjson_print_fn_t fn, void *fndata, const unsigned char *s,
|
||||
int n) {
|
||||
const char *t =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
int i, len = fn("\"", 1, fndata);
|
||||
for (i = 0; i < n; i += 3) {
|
||||
int a = s[i], b = i + 1 < n ? s[i + 1] : 0, c = i + 2 < n ? s[i + 2] : 0;
|
||||
char buf[4] = {t[a >> 2], t[(a & 3) << 4 | (b >> 4)], '=', '='};
|
||||
if (i + 1 < n) buf[2] = t[(b & 15) << 2 | (c >> 6)];
|
||||
if (i + 2 < n) buf[3] = t[c & 63];
|
||||
len += fn(buf, sizeof(buf), fndata);
|
||||
}
|
||||
return len + fn("\"", 1, fndata);
|
||||
}
|
||||
#endif /* MJSON_ENABLE_BASE64 */
|
||||
|
||||
int mjson_vprintf(mjson_print_fn_t fn, void *fndata, const char *fmt,
|
||||
va_list xap) {
|
||||
int i = 0, n = 0;
|
||||
va_list ap;
|
||||
va_copy(ap, xap);
|
||||
while (fmt[i] != '\0') {
|
||||
if (fmt[i] == '%') {
|
||||
char fc = fmt[++i];
|
||||
int is_long = 0;
|
||||
if (fc == 'l') {
|
||||
is_long = 1;
|
||||
fc = fmt[i + 1];
|
||||
}
|
||||
if (fc == 'Q') {
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_str(fn, fndata, buf ? buf : "", buf ? strlen(buf) : 0);
|
||||
} else if (strncmp(&fmt[i], ".*Q", 3) == 0) {
|
||||
int len = va_arg(ap, int);
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_str(fn, fndata, buf, len);
|
||||
i += 2;
|
||||
} else if (fc == 'd' || fc == 'u') {
|
||||
int is_signed = (fc == 'd');
|
||||
if (is_long) {
|
||||
long val = va_arg(ap, long);
|
||||
n += mjson_print_long(fn, fndata, val, is_signed);
|
||||
i++;
|
||||
} else {
|
||||
int val = va_arg(ap, int);
|
||||
n += mjson_print_int(fn, fndata, val, is_signed);
|
||||
}
|
||||
} else if (fc == 'B') {
|
||||
const char *s = va_arg(ap, int) ? "true" : "false";
|
||||
n += mjson_print_buf(fn, fndata, s, strlen(s));
|
||||
} else if (fc == 's') {
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_buf(fn, fndata, buf, strlen(buf));
|
||||
} else if (strncmp(&fmt[i], ".*s", 3) == 0) {
|
||||
int len = va_arg(ap, int);
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_buf(fn, fndata, buf, len);
|
||||
i += 2;
|
||||
} else if (fc == 'g') {
|
||||
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%g");
|
||||
} else if (fc == 'f') {
|
||||
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%f");
|
||||
#if MJSON_ENABLE_BASE64
|
||||
} else if (fc == 'V') {
|
||||
int len = va_arg(ap, int);
|
||||
const char *buf = va_arg(ap, const char *);
|
||||
n += mjson_print_b64(fn, fndata, (unsigned char *) buf, len);
|
||||
#endif
|
||||
} else if (fc == 'H') {
|
||||
const char *hex = "0123456789abcdef";
|
||||
int i, len = va_arg(ap, int);
|
||||
const unsigned char *p = va_arg(ap, const unsigned char *);
|
||||
n += fn("\"", 1, fndata);
|
||||
for (i = 0; i < len; i++) {
|
||||
n += fn(&hex[(p[i] >> 4) & 15], 1, fndata);
|
||||
n += fn(&hex[p[i] & 15], 1, fndata);
|
||||
}
|
||||
n += fn("\"", 1, fndata);
|
||||
} else if (fc == 'M') {
|
||||
mjson_vprint_fn_t vfn = va_arg(ap, mjson_vprint_fn_t);
|
||||
n += vfn(fn, fndata, &ap);
|
||||
}
|
||||
i++;
|
||||
} else {
|
||||
n += mjson_print_buf(fn, fndata, &fmt[i++], 1);
|
||||
}
|
||||
}
|
||||
va_end(xap);
|
||||
va_end(ap);
|
||||
return n;
|
||||
}
|
||||
|
||||
int mjson_printf(mjson_print_fn_t fn, void *fndata, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
int len;
|
||||
va_start(ap, fmt);
|
||||
len = mjson_vprintf(fn, fndata, fmt, ap);
|
||||
va_end(ap);
|
||||
return len;
|
||||
}
|
||||
#endif /* MJSON_ENABLE_PRINT */
|
||||
|
||||
#if MJSON_IMPLEMENT_STRTOD
|
||||
static inline int is_digit(int c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
/* NOTE: strtod() implementation by Yasuhiro Matsumoto. */
|
||||
double strtod(const char *str, char **end) {
|
||||
double d = 0.0;
|
||||
int sign = 1, n = 0;
|
||||
const char *p = str, *a = str;
|
||||
|
||||
/* decimal part */
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
} else if (*p == '+')
|
||||
++p;
|
||||
if (is_digit(*p)) {
|
||||
d = (double) (*p++ - '0');
|
||||
while (*p && is_digit(*p)) {
|
||||
d = d * 10.0 + (double) (*p - '0');
|
||||
++p;
|
||||
++n;
|
||||
}
|
||||
a = p;
|
||||
} else if (*p != '.')
|
||||
goto done;
|
||||
d *= sign;
|
||||
|
||||
/* fraction part */
|
||||
if (*p == '.') {
|
||||
double f = 0.0;
|
||||
double base = 0.1;
|
||||
++p;
|
||||
|
||||
if (is_digit(*p)) {
|
||||
while (*p && is_digit(*p)) {
|
||||
f += base * (*p - '0');
|
||||
base /= 10.0;
|
||||
++p;
|
||||
++n;
|
||||
}
|
||||
}
|
||||
d += f * sign;
|
||||
a = p;
|
||||
}
|
||||
|
||||
/* exponential part */
|
||||
if ((*p == 'E') || (*p == 'e')) {
|
||||
int e = 0;
|
||||
++p;
|
||||
|
||||
sign = 1;
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
} else if (*p == '+')
|
||||
++p;
|
||||
|
||||
if (is_digit(*p)) {
|
||||
while (*p == '0') ++p;
|
||||
e = (int) (*p++ - '0');
|
||||
while (*p && is_digit(*p)) {
|
||||
e = e * 10 + (int) (*p - '0');
|
||||
++p;
|
||||
}
|
||||
e *= sign;
|
||||
} else if (!is_digit(*(a - 1))) {
|
||||
a = str;
|
||||
goto done;
|
||||
} else if (*p == 0)
|
||||
goto done;
|
||||
|
||||
if (d == 2.2250738585072011 && e == -308) {
|
||||
d = 0.0;
|
||||
a = p;
|
||||
goto done;
|
||||
}
|
||||
if (d == 2.2250738585072012 && e <= -308) {
|
||||
d *= 1.0e-308;
|
||||
a = p;
|
||||
goto done;
|
||||
}
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 10; i++) d *= 10;
|
||||
}
|
||||
a = p;
|
||||
} else if (p > str && !is_digit(*(p - 1))) {
|
||||
a = str;
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
if (end) *end = (char *) a;
|
||||
return d;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_MERGE
|
||||
int mjson_merge(const char *s, int n, const char *s2, int n2,
|
||||
mjson_print_fn_t fn, void *userdata) {
|
||||
int koff, klen, voff, vlen, t, t2, k, off = 0, len = 0, comma = 0;
|
||||
if (n < 2) return len;
|
||||
len += fn("{", 1, userdata);
|
||||
while ((off = mjson_next(s, n, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
char path[klen + 1];
|
||||
#else
|
||||
char *path = (char *) alloca(klen + 1);
|
||||
#endif
|
||||
const char *val;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s + koff + 1, klen - 2);
|
||||
path[klen] = '\0';
|
||||
if ((t2 = mjson_find(s2, n2, path, &val, &k)) != MJSON_TOK_INVALID) {
|
||||
if (t2 == MJSON_TOK_NULL) continue; // null deletes the key
|
||||
} else {
|
||||
val = s + voff; // Key is not found in the update. Copy the old value.
|
||||
}
|
||||
if (comma) len += fn(",", 1, userdata);
|
||||
len += fn(s + koff, klen, userdata);
|
||||
len += fn(":", 1, userdata);
|
||||
if (t == MJSON_TOK_OBJECT && t2 == MJSON_TOK_OBJECT) {
|
||||
len += mjson_merge(s + voff, vlen, val, k, fn, userdata);
|
||||
} else {
|
||||
if (t2 != MJSON_TOK_INVALID) vlen = k;
|
||||
len += fn(val, vlen, userdata);
|
||||
}
|
||||
comma = 1;
|
||||
}
|
||||
// Add missing keys
|
||||
off = 0;
|
||||
while ((off = mjson_next(s2, n2, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
char path[klen + 1];
|
||||
#else
|
||||
char *path = (char *) alloca(klen + 1);
|
||||
#endif
|
||||
const char *val;
|
||||
if (t == MJSON_TOK_NULL) continue;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s2 + koff + 1, klen - 2);
|
||||
path[klen] = '\0';
|
||||
if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue;
|
||||
if (comma) len += fn(",", 1, userdata);
|
||||
len += fn(s2 + koff, klen, userdata);
|
||||
len += fn(":", 1, userdata);
|
||||
len += fn(s2 + voff, vlen, userdata);
|
||||
comma = 1;
|
||||
}
|
||||
len += fn("}", 1, userdata);
|
||||
return len;
|
||||
}
|
||||
#endif // MJSON_ENABLE_MERGE
|
||||
|
||||
#if MJSON_ENABLE_PRETTY
|
||||
struct prettydata {
|
||||
int level;
|
||||
int len;
|
||||
int prev;
|
||||
const char *pad;
|
||||
int padlen;
|
||||
mjson_print_fn_t fn;
|
||||
void *userdata;
|
||||
};
|
||||
|
||||
static int pretty_cb(int ev, const char *s, int off, int len, void *ud) {
|
||||
struct prettydata *d = (struct prettydata *) ud;
|
||||
int i;
|
||||
switch (ev) {
|
||||
case '{':
|
||||
case '[':
|
||||
d->level++;
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
case '}':
|
||||
case ']':
|
||||
d->level--;
|
||||
if (d->prev != '[' && d->prev != '{' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
case ',':
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
if (d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
if (d->padlen > 0) d->len += d->fn(" ", 1, d->userdata);
|
||||
break;
|
||||
case MJSON_TOK_KEY:
|
||||
if (d->prev == '{' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
default:
|
||||
if (d->prev == '[' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
}
|
||||
d->prev = ev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mjson_pretty(const char *s, int n, const char *pad, mjson_print_fn_t fn,
|
||||
void *userdata) {
|
||||
struct prettydata d = {0, 0, 0, pad, strlen(pad), fn, userdata};
|
||||
if (mjson(s, n, pretty_cb, &d) < 0) return -1;
|
||||
return d.len;
|
||||
}
|
||||
#endif // MJSON_ENABLE_PRETTY
|
||||
|
||||
#if MJSON_ENABLE_RPC
|
||||
struct jsonrpc_ctx jsonrpc_default_context;
|
||||
struct jsonrpc_userdata {
|
||||
mjson_print_fn_t fn;
|
||||
void *fndata;
|
||||
};
|
||||
|
||||
int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) {
|
||||
int i = 0, j = 0, ni = 0, nj = 0;
|
||||
while (i < n1 || j < n2) {
|
||||
if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) {
|
||||
i++, j++;
|
||||
} else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) {
|
||||
ni = i, nj = j + 1, i++;
|
||||
} else if (nj > 0 && nj <= n2 && (s1[i - 1] == '#' || s2[j] != '/')) {
|
||||
i = ni, j = nj;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int jsonrpc_printer(const char *buf, int len, void *userdata) {
|
||||
struct jsonrpc_userdata *u = (struct jsonrpc_userdata *) userdata;
|
||||
return u->fn(buf, len, u->fndata);
|
||||
}
|
||||
|
||||
void jsonrpc_return_errorv(struct jsonrpc_request *r, int code,
|
||||
const char *message, const char *data_fmt,
|
||||
va_list ap) {
|
||||
if (r->id_len == 0) return;
|
||||
mjson_printf(r->fn, r->fndata,
|
||||
"{\"id\":%.*s,\"error\":{\"code\":%d,\"message\":%Q", r->id_len,
|
||||
r->id, code, message == NULL ? "" : message);
|
||||
if (data_fmt != NULL) {
|
||||
mjson_printf(r->fn, r->fndata, ",\"data\":");
|
||||
mjson_vprintf(r->fn, r->fndata, data_fmt, ap);
|
||||
}
|
||||
mjson_printf(r->fn, r->fndata, "}}\n");
|
||||
}
|
||||
|
||||
void jsonrpc_return_error(struct jsonrpc_request *r, int code,
|
||||
const char *message, const char *data_fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, data_fmt);
|
||||
jsonrpc_return_errorv(r, code, message, data_fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt,
|
||||
va_list ap) {
|
||||
if (r->id_len == 0) return;
|
||||
mjson_printf(r->fn, r->fndata, "{\"id\":%.*s,\"result\":", r->id_len, r->id);
|
||||
if (result_fmt != NULL) {
|
||||
mjson_vprintf(r->fn, r->fndata, result_fmt, ap);
|
||||
} else {
|
||||
mjson_printf(r->fn, r->fndata, "%s", "null");
|
||||
}
|
||||
mjson_printf(r->fn, r->fndata, "}\n");
|
||||
}
|
||||
|
||||
void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
|
||||
...) {
|
||||
va_list ap;
|
||||
va_start(ap, result_fmt);
|
||||
jsonrpc_return_successv(r, result_fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
mjson_print_fn_t fn, void *fndata) {
|
||||
const char *result = NULL, *error = NULL;
|
||||
int result_sz = 0, error_sz = 0;
|
||||
struct jsonrpc_method *m = NULL;
|
||||
struct jsonrpc_userdata d;
|
||||
struct jsonrpc_request r = {req, req_sz, 0, 0, 0, 0, 0,
|
||||
0, &jsonrpc_printer, NULL, NULL};
|
||||
|
||||
d.fn = fn;
|
||||
d.fndata = fndata;
|
||||
r.fndata = &d;
|
||||
|
||||
// Is is a response frame?
|
||||
mjson_find(req, req_sz, "$.result", &result, &result_sz);
|
||||
if (result == NULL) mjson_find(req, req_sz, "$.error", &error, &error_sz);
|
||||
if (result_sz > 0 || error_sz > 0) {
|
||||
if (ctx->response_cb != NULL) ctx->response_cb(req, req_sz, ctx->userdata);
|
||||
return;
|
||||
}
|
||||
|
||||
// Method must exist and must be a string
|
||||
if (mjson_find(req, req_sz, "$.method", &r.method, &r.method_len) !=
|
||||
MJSON_TOK_STRING) {
|
||||
mjson_printf(fn, fndata, "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n",
|
||||
req_sz, req);
|
||||
ctx->in_len = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// id and params are optional
|
||||
mjson_find(req, req_sz, "$.id", &r.id, &r.id_len);
|
||||
mjson_find(req, req_sz, "$.params", &r.params, &r.params_len);
|
||||
|
||||
for (m = ctx->methods; m != NULL; m = m->next) {
|
||||
if (mjson_globmatch(m->method, m->method_sz, r.method + 1,
|
||||
r.method_len - 2) > 0) {
|
||||
if (r.params == NULL) r.params = "";
|
||||
r.userdata = m->cbdata;
|
||||
m->cb(&r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m == NULL) {
|
||||
jsonrpc_return_error(&r, JSONRPC_ERROR_NOT_FOUND, "method not found", NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static int jsonrpc_print_methods(mjson_print_fn_t fn, void *fndata,
|
||||
va_list *ap) {
|
||||
struct jsonrpc_ctx *ctx = va_arg(*ap, struct jsonrpc_ctx *);
|
||||
struct jsonrpc_method *m;
|
||||
int len = 0;
|
||||
for (m = ctx->methods; m != NULL; m = m->next) {
|
||||
if (m != ctx->methods) len += mjson_print_buf(fn, fndata, ",", 1);
|
||||
len += mjson_print_str(fn, fndata, m->method, strlen(m->method));
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static void rpclist(struct jsonrpc_request *r) {
|
||||
jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->userdata);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t response_cb,
|
||||
void *userdata) {
|
||||
ctx->response_cb = response_cb;
|
||||
ctx->userdata = userdata;
|
||||
jsonrpc_ctx_export(ctx, MJSON_RPC_LIST_NAME, rpclist, ctx);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_process_byte(struct jsonrpc_ctx *ctx, unsigned char ch,
|
||||
mjson_print_fn_t fn, void *p) {
|
||||
if (ctx->in_len >= (int) sizeof(ctx->in)) ctx->in_len = 0; // Overflow
|
||||
if (ch == '\n') { // If new line, parse frame
|
||||
if (ctx->in_len > 1) jsonrpc_ctx_process(ctx, ctx->in, ctx->in_len, fn, p);
|
||||
ctx->in_len = 0;
|
||||
} else {
|
||||
ctx->in[ctx->in_len] = ch; // Append to the buffer
|
||||
ctx->in_len++;
|
||||
}
|
||||
}
|
||||
|
||||
void jsonrpc_init(mjson_print_fn_t response_cb, void *userdata) {
|
||||
jsonrpc_ctx_init(&jsonrpc_default_context, response_cb, userdata);
|
||||
}
|
||||
#endif // MJSON_ENABLE_RPC
|
||||
@@ -1,220 +0,0 @@
|
||||
// Copyright (c) 2018-2020 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#ifndef MJSON_H
|
||||
#define MJSON_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef MJSON_ENABLE_PRINT
|
||||
#define MJSON_ENABLE_PRINT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_RPC
|
||||
#define MJSON_ENABLE_RPC 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_BASE64
|
||||
#define MJSON_ENABLE_BASE64 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_MERGE 0
|
||||
#elif MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_PRETTY 0
|
||||
#elif MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_NEXT
|
||||
#define MJSON_ENABLE_NEXT 0
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef MJSON_RPC_IN_BUF_SIZE
|
||||
#define MJSON_RPC_IN_BUF_SIZE 256
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_RPC_LIST_NAME
|
||||
#define MJSON_RPC_LIST_NAME "rpc.list"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum {
|
||||
MJSON_ERROR_INVALID_INPUT = -1,
|
||||
MJSON_ERROR_TOO_DEEP = -2,
|
||||
};
|
||||
|
||||
enum mjson_tok {
|
||||
MJSON_TOK_INVALID = 0,
|
||||
MJSON_TOK_KEY = 1,
|
||||
MJSON_TOK_STRING = 11,
|
||||
MJSON_TOK_NUMBER = 12,
|
||||
MJSON_TOK_TRUE = 13,
|
||||
MJSON_TOK_FALSE = 14,
|
||||
MJSON_TOK_NULL = 15,
|
||||
MJSON_TOK_ARRAY = 91,
|
||||
MJSON_TOK_OBJECT = 123,
|
||||
};
|
||||
#define MJSON_TOK_IS_VALUE(t) ((t) > 10 && (t) < 20)
|
||||
|
||||
typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud);
|
||||
|
||||
#ifndef MJSON_MAX_DEPTH
|
||||
#define MJSON_MAX_DEPTH 20
|
||||
#endif
|
||||
|
||||
int mjson(const char *s, int len, mjson_cb_t cb, void *ud);
|
||||
enum mjson_tok mjson_find(const char *s, int len, const char *jp,
|
||||
const char **tokptr, int *toklen);
|
||||
int mjson_get_number(const char *s, int len, const char *path, double *v);
|
||||
int mjson_get_bool(const char *s, int len, const char *path, int *v);
|
||||
int mjson_get_string(const char *s, int len, const char *path, char *to, int n);
|
||||
int mjson_get_hex(const char *s, int len, const char *path, char *to, int n);
|
||||
|
||||
#if MJSON_ENABLE_NEXT
|
||||
int mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff,
|
||||
int *vlen, int *vtype);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
int mjson_get_base64(const char *s, int len, const char *path, char *to, int n);
|
||||
int mjson_base64_dec(const char *src, int n, char *dst, int dlen);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_PRINT
|
||||
typedef int (*mjson_print_fn_t)(const char *buf, int len, void *userdata);
|
||||
typedef int (*mjson_vprint_fn_t)(mjson_print_fn_t, void *, va_list *);
|
||||
|
||||
struct mjson_fixedbuf {
|
||||
char *ptr;
|
||||
int size, len;
|
||||
};
|
||||
|
||||
int mjson_printf(mjson_print_fn_t, void *, const char *fmt, ...);
|
||||
int mjson_vprintf(mjson_print_fn_t, void *, const char *fmt, va_list ap);
|
||||
int mjson_print_str(mjson_print_fn_t, void *, const char *s, int len);
|
||||
int mjson_print_int(mjson_print_fn_t, void *, int value, int is_signed);
|
||||
int mjson_print_long(mjson_print_fn_t, void *, long value, int is_signed);
|
||||
int mjson_print_buf(mjson_print_fn_t fn, void *, const char *buf, int len);
|
||||
|
||||
int mjson_print_null(const char *ptr, int len, void *userdata);
|
||||
int mjson_print_file(const char *ptr, int len, void *userdata);
|
||||
int mjson_print_fixed_buf(const char *ptr, int len, void *userdata);
|
||||
int mjson_print_dynamic_buf(const char *ptr, int len, void *userdata);
|
||||
|
||||
#if MJSON_ENABLE_PRETTY
|
||||
int mjson_pretty(const char *, int, const char *, mjson_print_fn_t, void *);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_MERGE
|
||||
int mjson_merge(const char *, int, const char *, int, mjson_print_fn_t, void *);
|
||||
#endif
|
||||
|
||||
#endif // MJSON_ENABLE_PRINT
|
||||
|
||||
#if MJSON_ENABLE_RPC
|
||||
|
||||
void jsonrpc_init(mjson_print_fn_t, void *userdata);
|
||||
int mjson_globmatch(const char *s1, int n1, const char *s2, int n2);
|
||||
|
||||
struct jsonrpc_request {
|
||||
const char *frame; // Points to the whole frame
|
||||
int frame_len; // Frame length
|
||||
const char *params; // Points to the "params" in the request frame
|
||||
int params_len; // Length of the "params"
|
||||
const char *id; // Points to the "id" in the request frame
|
||||
int id_len; // Length of the "id"
|
||||
const char *method; // Points to the "method" in the request frame
|
||||
int method_len; // Length of the "method"
|
||||
mjson_print_fn_t fn; // Printer function
|
||||
void *fndata; // Printer function data
|
||||
void *userdata; // Callback's user data as specified at export time
|
||||
};
|
||||
|
||||
struct jsonrpc_method {
|
||||
const char *method;
|
||||
int method_sz;
|
||||
void (*cb)(struct jsonrpc_request *);
|
||||
void *cbdata;
|
||||
struct jsonrpc_method *next;
|
||||
};
|
||||
|
||||
// Main RPC context, stores current request information and a list of
|
||||
// exported RPC methods.
|
||||
struct jsonrpc_ctx {
|
||||
struct jsonrpc_method *methods;
|
||||
void *userdata;
|
||||
mjson_print_fn_t response_cb;
|
||||
int in_len;
|
||||
char in[MJSON_RPC_IN_BUF_SIZE];
|
||||
};
|
||||
|
||||
// Registers function fn under the given name within the given RPC context
|
||||
#define jsonrpc_ctx_export(ctx, name, fn, ud) \
|
||||
do { \
|
||||
static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0, 0}; \
|
||||
m.cbdata = (ud); \
|
||||
m.next = (ctx)->methods; \
|
||||
(ctx)->methods = &m; \
|
||||
} while (0)
|
||||
|
||||
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t, void *);
|
||||
void jsonrpc_return_error(struct jsonrpc_request *r, int code,
|
||||
const char *message, const char *data_fmt, ...);
|
||||
void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
|
||||
...);
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
mjson_print_fn_t fn, void *fndata);
|
||||
void jsonrpc_ctx_process_byte(struct jsonrpc_ctx *ctx, unsigned char ch,
|
||||
mjson_print_fn_t fn, void *fndata);
|
||||
|
||||
extern struct jsonrpc_ctx jsonrpc_default_context;
|
||||
|
||||
#define jsonrpc_export(name, fn, ud) \
|
||||
jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn), (ud))
|
||||
|
||||
#define jsonrpc_process(buf, len, fn, data) \
|
||||
jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (data))
|
||||
|
||||
#define jsonrpc_process_byte(x, fn, data) \
|
||||
jsonrpc_ctx_process_byte(&jsonrpc_default_context, (x), (fn), (data))
|
||||
|
||||
#define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */
|
||||
#define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */
|
||||
#define JSONRPC_ERROR_BAD_PARAMS -32602 /* Invalid params passed */
|
||||
#define JSONRPC_ERROR_INTERNAL -32603 /* Internal JSON-RPC error */
|
||||
|
||||
#endif // MJSON_ENABLE_RPC
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // MJSON_H
|
||||
@@ -1,76 +0,0 @@
|
||||
const {h, render, Component} = preact;
|
||||
const html = htm.bind(h);
|
||||
|
||||
const Info = () => html`<div id="info">
|
||||
This dashboard shows values kept in server memory. Many clients
|
||||
can open this page. As soon as any client changes any value,
|
||||
all clients update automatically.
|
||||
<br/><br/>
|
||||
The JS code that watches state changes, reconnects on network failures.
|
||||
That means if server restarts, dashboard on all connected clients
|
||||
refresh automatically.
|
||||
<br/><br/>
|
||||
|
||||
You can use <code>curl</code> command-line utility:
|
||||
<br/><code>curl localhost:8000/api/config/get</code>
|
||||
<br/><code>curl localhost:8000/api/config/watch</code>
|
||||
<br/><code>curl localhost:8000/api/config/set -d '{"a":123}'</code>
|
||||
</div>`;
|
||||
|
||||
class Dashboard extends Component {
|
||||
state = {value1: null, value2: null};
|
||||
componentDidMount() {
|
||||
axios.get('/api/config/get')
|
||||
.then(r => this.setState(r.data))
|
||||
.catch(e => console.log(e));
|
||||
var self = this;
|
||||
var f = function(reader) {
|
||||
return reader.read().then(function(result) {
|
||||
var data = String.fromCharCode.apply(null, result.value);
|
||||
self.setState(JSON.parse(data));
|
||||
// console.log(JSON.parse(data));
|
||||
if (!result.done) return f(reader);
|
||||
});
|
||||
};
|
||||
fetch('/api/config/watch')
|
||||
.then(r => r.body.getReader())
|
||||
.then(f)
|
||||
.catch(e => setTimeout(x => self.componentDidMount(), 1000));
|
||||
}
|
||||
render(props, state) {
|
||||
return html
|
||||
`<div id="dashboard" style="display: flex; justify-content: flex-evenly;">
|
||||
<div style="width:50%;text-align:center;">Value 1 (number):
|
||||
<div style="font-size:140%;"><b>${state.value1}</b></div></div>
|
||||
<div style="width:50%;text-align:center;">Value 2 (string):
|
||||
<div style="font-size:140%;"><b>${state.value2}</b></div></div>
|
||||
</div>`
|
||||
}
|
||||
};
|
||||
|
||||
class Form extends Component {
|
||||
render(props, state) {
|
||||
const onclick = ev => axios.post(
|
||||
'/api/config/set', {value1: +state.value1, value2: state.value2});
|
||||
// alert(JSON.stringify(state));
|
||||
return html`<div id="form">
|
||||
<div><b>Change values</b></div>
|
||||
<table>
|
||||
<tr><td>Value 1 (number):</td><td><input value=${state.value1}
|
||||
onInput=${linkState(this, 'value1')} /></td></tr>
|
||||
<tr><td>Value 2 (string):</td><td><input value=${state.value2}
|
||||
onInput=${linkState(this, 'value2')}/></td></tr>
|
||||
<tr><td></td>
|
||||
<td><button onClick=${onclick}>Save values</button></td></tr>
|
||||
</table>
|
||||
</div>`
|
||||
}
|
||||
};
|
||||
|
||||
const App = () => html`<div id="container">
|
||||
<${Info} />
|
||||
<${Dashboard} />
|
||||
<${Form} />
|
||||
</div>`;
|
||||
|
||||
window.onload = () => render(h(App), document.body);
|
||||
@@ -1,3 +0,0 @@
|
||||
/* axios v0.21.1 | (c) 2020 by Matt Zabriskie */
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new i(e),n=s(i.prototype.request,t);return o.extend(n,i.prototype,t),o.extend(n,t),n}var o=n(2),s=n(3),i=n(4),a=n(22),u=n(10),c=r(u);c.Axios=i,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),c.isAxiosError=n(26),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"undefined"==typeof e}function s(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function i(e){return"[object ArrayBuffer]"===R.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){if("[object Object]"!==R.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function l(e){return"[object Date]"===R.call(e)}function h(e){return"[object File]"===R.call(e)}function m(e){return"[object Blob]"===R.call(e)}function y(e){return"[object Function]"===R.call(e)}function g(e){return p(e)&&y(e.pipe)}function v(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function x(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function b(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n<o;n++)t.call(null,e[n],n,e);else for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&t.call(null,e[s],s,e)}function E(){function e(e,n){d(t[n])&&d(e)?t[n]=E(t[n],e):d(e)?t[n]=E({},e):r(e)?t[n]=e.slice():t[n]=e}for(var t={},n=0,o=arguments.length;n<o;n++)b(arguments[n],e);return t}function j(e,t,n){return b(t,function(t,r){n&&"function"==typeof t?e[r]=S(t,n):e[r]=t}),e}function C(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e}var S=n(3),R=Object.prototype.toString;e.exports={isArray:r,isArrayBuffer:i,isBuffer:s,isFormData:a,isArrayBufferView:u,isString:c,isNumber:f,isObject:p,isPlainObject:d,isUndefined:o,isDate:l,isFile:h,isBlob:m,isFunction:y,isStream:g,isURLSearchParams:v,isStandardBrowserEnv:w,forEach:b,merge:E,extend:j,trim:x,stripBOM:C}},function(e,t){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r<n.length;r++)n[r]=arguments[r];return e.apply(t,n)}}},function(e,t,n){"use strict";function r(e){this.defaults=e,this.interceptors={request:new i,response:new i}}var o=n(2),s=n(5),i=n(6),a=n(7),u=n(22);r.prototype.request=function(e){"string"==typeof e?(e=arguments[1]||{},e.url=arguments[0]):e=e||{},e=u(this.defaults,e),e.method?e.method=e.method.toLowerCase():this.defaults.method?e.method=this.defaults.method.toLowerCase():e.method="get";var t=[a,void 0],n=Promise.resolve(e);for(this.interceptors.request.forEach(function(e){t.unshift(e.fulfilled,e.rejected)}),this.interceptors.response.forEach(function(e){t.push(e.fulfilled,e.rejected)});t.length;)n=n.then(t.shift(),t.shift());return n},r.prototype.getUri=function(e){return e=u(this.defaults,e),s(e.url,e.params,e.paramsSerializer).replace(/^\?/,"")},o.forEach(["delete","get","head","options"],function(e){r.prototype[e]=function(t,n){return this.request(u(n||{},{method:e,url:t,data:(n||{}).data}))}}),o.forEach(["post","put","patch"],function(e){r.prototype[e]=function(t,n,r){return this.request(u(r||{},{method:e,url:t,data:n}))}}),e.exports=r},function(e,t,n){"use strict";function r(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var o=n(2);e.exports=function(e,t,n){if(!t)return e;var s;if(n)s=n(t);else if(o.isURLSearchParams(t))s=t.toString();else{var i=[];o.forEach(t,function(e,t){null!==e&&"undefined"!=typeof e&&(o.isArray(e)?t+="[]":e=[e],o.forEach(e,function(e){o.isDate(e)?e=e.toISOString():o.isObject(e)&&(e=JSON.stringify(e)),i.push(r(t)+"="+r(e))}))}),s=i.join("&")}if(s){var a=e.indexOf("#");a!==-1&&(e=e.slice(0,a)),e+=(e.indexOf("?")===-1?"?":"&")+s}return e}},function(e,t,n){"use stricLine truncated
|
||||
//# sourceMappingURL=axios.min.map
|
||||
@@ -1 +0,0 @@
|
||||
!function(){function n(n){var t=e.get(this);return t||(t=new Map,e.set(this,t)),1<(t=a(this,t.get(n)||(t.set(n,t=function(n){function t(n){1===u&&(n||(r=r.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?p.push(0,n,r):3===u&&(n||r)?(p.push(3,n,r),u=2):2===u&&"..."===r&&n?p.push(4,n,0):2===u&&r&&!n?p.push(5,0,!0,r):5<=u&&((r||!n&&5===u)&&(p.push(u,0,r,s),u=6),n&&(p.push(u,n,0,s),u=6)),r=""}for(var e,s,u=1,r="",h="",p=[0],a=0;a<n.length;a++){a&&(1===u&&t(),t(a));for(var o=0;o<n[a].length;o++)e=n[a][o],1===u?"<"===e?(t(),p=[p],u=3):r+=e:4===u?r="--"===r&&">"===e?(u=1,""):e+r[0]:h?e===h?h="":r+=e:'"'===e||"'"===e?h=e:">"===e?(t(),u=1):u&&("="===e?(u=5,s=r,r=""):"/"===e&&(u<5||">"===n[a][o+1])?(t(),3===u&&(p=p[0]),(p=(u=p)[0]).push(2,0,u),u=0):" "===e||"\t"===e||"\n"===e||"\r"===e?(t(),u=2):r+=e),3===u&&"!--"===r&&(u=4,p=p[0])}return t(),p}(n)),t),arguments,[])).length?t:t[0]}var a=function(n,t,e,s){var u;t[0]=0;for(var r=1;r<t.length;r++){var h=t[r++],p=t[r]?(t[0]|=h?1:2,e[t[r++]]):t[++r];3===h?s[0]=p:4===h?s[1]=Object.assign(s[1]||{},p):5===h?(s[1]=s[1]||{})[t[++r]]=p:6===h?s[1][t[++r]]+=p+"":h?(u=n.apply(p,a(n,p,e,["",null])),s.push(u),p[0]?t[0]|=2:(t[r-2]=0,t[r]=u)):s.push(p)}return s},e=new Map;"undefined"!=typeof module?module.exports=n:self.htm=n}();
|
||||
@@ -1,18 +0,0 @@
|
||||
function linkState(component, key, eventPath) {
|
||||
let path = key.split('.'), cache = component.__lsc || (component.__lsc = {});
|
||||
|
||||
return cache[key + eventPath] || (cache[key + eventPath] = function(e) {
|
||||
let t = e && e.target || this, state = {}, obj = state,
|
||||
v = typeof eventPath === 'string' ?
|
||||
delve(e, eventPath) :
|
||||
t.nodeName ? (t.type.match(/^che|rad/) ? t.checked : t.value) :
|
||||
e,
|
||||
i = 0;
|
||||
for (; i < path.length - 1; i++) {
|
||||
obj = obj[path[i]] ||
|
||||
(obj[path[i]] = !i && component.state[path[i]] || {});
|
||||
}
|
||||
obj[path[i]] = v;
|
||||
component.setState(state);
|
||||
});
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
!function(){var n,l,u,t,i,o,r,e,f={},c=[],a=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function s(n,l){for(var u in l)n[u]=l[u];return n}function v(n){var l=n.parentNode;l&&l.removeChild(n)}function h(n,l,u){var t,i=arguments,o={};for(t in l)"key"!==t&&"ref"!==t&&(o[t]=l[t]);if(arguments.length>3)for(u=[u],t=3;t<arguments.length;t++)u.push(i[t]);if(null!=u&&(o.children=u),"function"==typeof n&&null!=n.defaultProps)for(t in n.defaultProps)void 0===o[t]&&(o[t]=n.defaultProps[t]);return y(n,o,l&&l.key,l&&l.ref,null)}function y(l,u,t,i,o){var r={type:l,props:u,key:t,ref:i,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,constructor:void 0,__v:o};return null==o&&(r.__v=r),n.vnode&&n.vnode(r),r}function p(n){return n.children}function d(n,l){this.props=n,this.context=l}function _(n,l){if(null==l)return n.__?_(n.__,n.__.__k.indexOf(n)+1):null;for(var u;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e)return u.__e;return"function"==typeof n.type?_(n):null}function m(n){var l,u;if(null!=(n=n.__)&&null!=n.__c){for(n.__e=n.__c.base=null,l=0;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e){n.__e=n.__c.base=u.__e;break}return m(n)}}function k(l){(!l.__d&&(l.__d=!0)&&u.push(l)&&!w.__r++||i!==n.debounceRendering)&&((i=n.debounceRendering)||t)(w)}function w(){for(var n;w.__r=u.length;)n=u.sort(function(n,l){return n.__v.__b-l.__v.__b}),u=[],n.some(function(n){var l,u,t,i,o,r,e;n.__d&&(r=(o=(l=n).__v).__e,(e=l.__P)&&(u=[],(t=s({},o)).__v=t,i=N(e,o,t,l.__n,void 0!==e.ownerSVGElement,null,u,null==r?_(o):r),z(u,o),i!=r&&m(o)))})}function g(n,l,u,t,i,o,r,e,a,s){var h,d,m,k,w,g,x,C=t&&t.__k||c,A=C.length;for(a==f&&(a=null!=r?r[0]:A?_(t,0):null),u.__k=[],h=0;h<l.length;h++)if(null!=(k=u.__k[h]=null==(k=l[h])||"boolean"==typeof k?null:"string"==typeof k||"number"==typeof k?y(null,k,null,null,k):Array.isArray(k)?y(p,{children:k},null,null,null):null!=k.__e||null!=k.__c?y(k.type,k.props,k.key,null,k.__v):k)){if(k.__=u,k.__b=u.__b+1,null===(m=C[h])||m&&k.key==m.key&&k.type===m.type)C[h]=void 0;else for(d=0;d<A;d++){if((m=C[d])&&k.key==m.key&&k.type===m.type){C[d]=void 0;break}m=null}w=N(n,k,m=m||f,i,o,r,e,a,s),(d=k.ref)&&m.ref!=d&&(x||(x=[]),m.ref&&x.push(m.ref,null,k),x.push(d,k.__c||w,k)),null!=w?(null==g&&(g=w),a=b(n,k,m,C,r,w,a),"option"==u.type?n.value="":"function"==typeof u.type&&(u.__d=a)):a&&m.__e==a&&a.parentNode!=n&&(a=_(m))}if(u.__e=g,null!=r&&"function"!=typeof u.type)for(h=r.length;h--;)null!=r[h]&&v(r[h]);for(h=A;h--;)null!=C[h]&&j(C[h],C[h]);if(x)for(h=0;h<x.length;h++)$(x[h],x[++h],x[++h])}function b(n,l,u,t,i,o,r){var e,f,c;if(void 0!==l.__d)e=l.__d,l.__d=void 0;else if(i==u||o!=r||null==o.parentNode)n:if(null==r||r.parentNode!==n)n.appendChild(o),e=null;else{for(f=r,c=0;(f=f.nextSibling)&&c<t.length;c+=2)if(f==o)break n;n.insertBefore(o,r),e=r}return void 0!==e?e:o.nextSibling}function x(n,l,u,t,i){var o;for(o in u)"children"===o||"key"===o||o in l||A(n,o,null,u[o],t);for(o in l)i&&"function"!=typeof l[o]||"children"===o||"key"===o||"value"===o||"checked"===o||u[o]===l[o]||A(n,o,l[o],u[o],t)}function C(n,l,u){"-"===l[0]?n.setProperty(l,u):n[l]="number"==typeof u&&!1===a.test(l)?u+"px":null==u?"":u}function A(n,l,u,t,i){var o,r,e,f,c;if(i?"className"===l&&(l="class"):"class"===l&&(l="className"),"style"===l)if(o=n.style,"string"==typeof u)o.cssText=u;else{if("string"==typeof t&&(o.cssText="",t=null),t)for(f in t)u&&f in u||C(o,f,"");if(u)for(c in u)t&&u[c]===t[c]||C(o,c,u[c])}else"o"===l[0]&&"n"===l[1]?(r=l!==(l=l.replace(/Capture$/,"")),e=l.toLowerCase(),l=(e in n?e:l).slice(2),u?(t||n.addEventListener(l,P,r),(n.l||(n.l={}))[l]=u):n.removeEventListener(l,P,r)):"list"!==l&&"tagName"!==l&&"form"!==l&&"type"!==l&&"size"!==l&&!i&&l in n?n[l]=null==u?"":u:"function"!=typeof u&&"dangerouslySetInnerHTML"!==l&&(l!==(l=l.replace(/^xlink:?/,""))?null==u||!1===u?n.removeAttributeNS("http://www.w3.org/1999/xlink",l.toLowerCase()):n.setAttributeNS("http://www.w3.org/1999/xlink",l.toLowerCase(),u):null==u||!1===u&&!/^ar/.test(l)?n.removeAttribute(l):n.setAttribute(l,u))}function P(l){this.l[l.type](n.event?n.event(l):l)}function E(n,l,u){var t,i;for(t=0;t<n.__k.length;t++)(i=n.__k[t])&&(i.__=n,i.__e&&("function"==typeof i.type&&i.__k.length>1&&E(i,l,u),l=b(u,i,i,n.__k,null,i.__e,l),"function"==typeof n.type&&(n.__d=l)))}function N(l,u,t,i,o,r,e,f,c){var a,v,h,y,_,m,k,w,b,x,C,A=u.type;if(void 0!==u.constructor)return null;(a=n.__b)&&a(u);try{n:if("function"==typeof A){if(w=u.props,b=(a=A.contextType)&&i[a.__c],x=a?b?b.props.value:a.__:i,t.__c?k=(v=u.__c=t.__c).__=v.__E:("prototype"in A&&A.prototype.render?u.__c=v=new A(w,x):(u.__c=v=new d(w,x),v.constructor=A,v.render=F),b&&b.sub(v),v.props=w,v.state||(v.state={}),v.context=x,v.__n=i,h=v.__d=!0,v.__h=[]),null==v.__s&&(v.__s=v.state),null!=A.getDerivedStateFromProps&&(v.__s==v.state&&(v.__s=s({},v.__s)),s(v.__s,A.getDerivedStateFromProps(w,v.__s))),y=v.props,_=v.state,h)null==A.getDerivedStateFromProps&&null!=v.componentWillMount&&v.componenLine truncated
|
||||
//# sourceMappingURL=preact.min.js.map
|
||||
@@ -1,6 +0,0 @@
|
||||
#container { margin-right: auto; margin-left: auto; max-width: 480px; }
|
||||
#info { background: #d0f0f0; border-radius: .5em; padding: 2em; }
|
||||
#dashboard { background: #f0f0f0; border-radius: .5em; padding: 0.5em; max-height: 10em; overflow: auto; height: 100%; margin-top: 1em; }
|
||||
#form { background: #d0c0f0; border-radius: .5em; padding: 0.5em; max-height: 10em; overflow: auto; height: 100%; margin-top: 1em; }
|
||||
button { width: 100%; }
|
||||
button, select, input, label::before, textarea { outline: none; box-shadow:none !important; border: 1px solid #ccc !important; }
|
||||
@@ -0,0 +1,47 @@
|
||||
PROG ?= example # Program we are building
|
||||
PACK ?= ./pack # Packing executable
|
||||
DELETE = rm -rf # Command to remove files
|
||||
OUT ?= -o $(PROG) # Compiler argument for output file
|
||||
SOURCES = main.c mongoose.c net.c packed_fs.c # Source code files
|
||||
CFLAGS = -W -Wall -Wextra -g -I. # Build options
|
||||
|
||||
# Mongoose build options. See https://mongoose.ws/documentation/#build-options
|
||||
CFLAGS_MONGOOSE += -DMG_ENABLE_PACKED_FS=1
|
||||
|
||||
FILES_TO_EMBED ?= $(wildcard web_root/*)
|
||||
|
||||
ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
|
||||
PROG ?= example.exe # Use .exe suffix for the binary
|
||||
PACK = pack.exe # Packing executable
|
||||
CC = gcc # Use MinGW gcc compiler
|
||||
CFLAGS += -lws2_32 # Link against Winsock library
|
||||
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
|
||||
OUT ?= -o $(PROG) # Build output
|
||||
MAKE += WINDOWS=1 CC=$(CC)
|
||||
endif
|
||||
|
||||
all: $(PROG) # Default target. Build and run program
|
||||
$(RUN) ./$(PROG) $(ARGS)
|
||||
|
||||
# Before embedding files, gzip them to save space
|
||||
packed_fs.c: $(FILES_TO_EMBED) Makefile
|
||||
$(CC) ../../test/pack.c -o $(PACK)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
$(PACK) $(FILES_TO_EMBED) > $@
|
||||
else
|
||||
rm -rf tmp/web_root && mkdir tmp && cp -r web_root tmp/
|
||||
find tmp -type f | xargs -n1 gzip
|
||||
cd tmp && ../pack `find web_root -type f` > ../$@
|
||||
endif
|
||||
|
||||
$(PROG): $(SOURCES) # Build program from sources
|
||||
$(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT)
|
||||
|
||||
clean: # Cleanup. Delete built program and all build artifacts
|
||||
$(DELETE) $(PROG) *.o *.obj *.exe *.dSYM pack tmp mbedtls
|
||||
|
||||
# see https://mongoose.ws/tutorials/tls/#how-to-build for TLS build options
|
||||
|
||||
mbedtls: # Pull and build mbedTLS library
|
||||
git clone --depth 1 -b v2.28.2 https://github.com/mbed-tls/mbedtls $@
|
||||
$(MAKE) -C mbedtls/library
|
||||
@@ -0,0 +1,29 @@
|
||||
# A complete device dashboard
|
||||
|
||||
This example is a demonstration of how Mongoose Library could be integrated
|
||||
into an embedded device and provide a complete device dashboard with the
|
||||
following features:
|
||||
|
||||
- Authentication: login-protected dashboard
|
||||
- Multiple logins with different permissions (admin and user)
|
||||
- Web UI is fully embedded into the server/firmware binary, and does not
|
||||
need a filesystem to serve it. UI is resilient to FS problems
|
||||
- Administrators can change server settings
|
||||
- All changes are propagated to all connected clients
|
||||
- The device is connected to the external MQTT server
|
||||
- Logged in clients can send/receive messages via MQTT
|
||||
|
||||
## Screenshots
|
||||
|
||||
This is a login screen that prompts for user/password
|
||||
|
||||

|
||||
|
||||
## Main dashboard
|
||||
|
||||
The main dashboard page shows the interactive MQTT console
|
||||
|
||||

|
||||
|
||||
|
||||
See a detailed tutorial at https://mongoose.ws/tutorials/device-dashboard/
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2020-2022 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
const char *s_listening_url = "http://0.0.0.0:8000";
|
||||
const char *s_listening_surl = "https://0.0.0.0:8443";
|
||||
|
||||
void device_dashboard_fn(struct mg_connection *, int, void *, void *);
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
mg_log_set(MG_LL_DEBUG); // Set debug log level
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, s_listening_url, device_dashboard_fn, NULL);
|
||||
MG_INFO(("Listening on %s", s_listening_url));
|
||||
#if MG_ENABLE_MBEDTLS || MG_ENABLE_OPENSSL
|
||||
mg_http_listen(&mgr, s_listening_surl, device_dashboard_fn, "");
|
||||
MG_INFO(("Listening on %s", s_listening_surl));
|
||||
#endif
|
||||
while (mgr.conns != NULL) mg_mgr_poll(&mgr, 500);
|
||||
mg_mgr_free(&mgr);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../mongoose.c
|
||||
@@ -0,0 +1 @@
|
||||
../../mongoose.h
|
||||
@@ -0,0 +1,284 @@
|
||||
// Copyright (c) 2020-2022 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
#if !defined(MQTT_SERVER)
|
||||
#if MG_ENABLE_MBEDTLS || MG_ENABLE_OPENSSL
|
||||
#define MQTT_SERVER "mqtts://broker.hivemq.com:8883"
|
||||
#else
|
||||
#define MQTT_SERVER "mqtt://broker.hivemq.com:1883"
|
||||
#endif
|
||||
#endif
|
||||
#define MQTT_PUBLISH_TOPIC "mg/my_device"
|
||||
#define MQTT_SUBSCRIBE_TOPIC "mg/#"
|
||||
|
||||
// Certificate generation procedure:
|
||||
// openssl ecparam -name prime256v1 -genkey -noout -out key.pem
|
||||
// openssl req -new -key key.pem -x509 -nodes -days 3650 -out cert.pem
|
||||
static const char *s_ssl_cert =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIBCTCBsAIJAK9wbIDkHnAoMAoGCCqGSM49BAMCMA0xCzAJBgNVBAYTAklFMB4X\n"
|
||||
"DTIzMDEyOTIxMjEzOFoXDTMzMDEyNjIxMjEzOFowDTELMAkGA1UEBhMCSUUwWTAT\n"
|
||||
"BgcqhkjOPQIBBggqhkjOPQMBBwNCAARzSQS5OHd17lUeNI+6kp9WYu0cxuEIi/JT\n"
|
||||
"jphbCmdJD1cUvhmzM9/phvJT9ka10Z9toZhgnBq0o0xfTQ4jC1vwMAoGCCqGSM49\n"
|
||||
"BAMCA0gAMEUCIQCe0T2E0GOiVe9KwvIEPeX1J1J0T7TNacgR0Ya33HV9VgIgNvdn\n"
|
||||
"aEWiBp1xshs4iz6WbpxrS1IHucrqkZuJLfNZGZI=\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
static const char *s_ssl_key =
|
||||
"-----BEGIN EC PRIVATE KEY-----\n"
|
||||
"MHcCAQEEICBz3HOkQLPBDtdknqC7k1PNsWj6HfhyNB5MenfjmqiooAoGCCqGSM49\n"
|
||||
"AwEHoUQDQgAEc0kEuTh3de5VHjSPupKfVmLtHMbhCIvyU46YWwpnSQ9XFL4ZszPf\n"
|
||||
"6YbyU/ZGtdGfbaGYYJwatKNMX00OIwtb8A==\n"
|
||||
"-----END EC PRIVATE KEY-----\n";
|
||||
|
||||
static time_t s_boot_timestamp = 0; // Updated by SNTP
|
||||
#ifndef DISABLE_ROUTING
|
||||
static struct mg_connection *s_sntp_conn = NULL; // SNTP connection
|
||||
#endif
|
||||
|
||||
// Define a system time alternative
|
||||
time_t ourtime(time_t *tp) {
|
||||
time_t t = s_boot_timestamp + (time_t) (mg_millis() / 1000);
|
||||
if (tp != NULL) *tp = t;
|
||||
return t;
|
||||
}
|
||||
|
||||
// Authenticated user.
|
||||
// A user can be authenticated by:
|
||||
// - a name:pass pair
|
||||
// - a token
|
||||
// When a user is shown a login screen, she enters a user:pass. If successful,
|
||||
// a server returns user info which includes token. From that point on,
|
||||
// client can use token for authentication. Tokens could be refreshed/changed
|
||||
// on a server side, forcing clients to re-login.
|
||||
struct user {
|
||||
const char *name, *pass, *token;
|
||||
};
|
||||
|
||||
// This is a configuration structure we're going to show on a dashboard
|
||||
static struct config {
|
||||
char *url, *pub, *sub; // MQTT settings
|
||||
} s_config;
|
||||
|
||||
static struct mg_connection *s_mqtt = NULL; // MQTT connection
|
||||
static bool s_connected = false; // MQTT connection established
|
||||
|
||||
// Try to update a single configuration value
|
||||
static void update_config(struct mg_str *body, const char *name, char **value) {
|
||||
char buf[256];
|
||||
if (mg_http_get_var(body, name, buf, sizeof(buf)) > 0) {
|
||||
free(*value);
|
||||
*value = strdup(buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse HTTP requests, return authenticated user or NULL
|
||||
static struct user *getuser(struct mg_http_message *hm) {
|
||||
// In production, make passwords strong and tokens randomly generated
|
||||
// In this example, user list is kept in RAM. In production, it can
|
||||
// be backed by file, database, or some other method.
|
||||
static struct user users[] = {
|
||||
{"admin", "pass0", "admin_token"},
|
||||
{"user1", "pass1", "user1_token"},
|
||||
{"user2", "pass2", "user2_token"},
|
||||
{NULL, NULL, NULL},
|
||||
};
|
||||
char user[256], pass[256];
|
||||
struct user *u;
|
||||
mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass));
|
||||
if (user[0] != '\0' && pass[0] != '\0') {
|
||||
// Both user and password is set, search by user/password
|
||||
for (u = users; u->name != NULL; u++)
|
||||
if (strcmp(user, u->name) == 0 && strcmp(pass, u->pass) == 0) return u;
|
||||
} else if (user[0] == '\0') {
|
||||
// Only password is set, search by token
|
||||
for (u = users; u->name != NULL; u++)
|
||||
if (strcmp(pass, u->token) == 0) return u;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Notify all config watchers about the config change
|
||||
static void send_notification(struct mg_mgr *mgr, const char *fmt, ...) {
|
||||
struct mg_connection *c;
|
||||
for (c = mgr->conns; c != NULL; c = c->next) {
|
||||
if (c->data[0] == 'W') {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
mg_ws_vprintf(c, WEBSOCKET_OP_TEXT, fmt, &ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send simulated metrics data to the dashboard, for chart rendering
|
||||
static void timer_metrics_fn(void *param) {
|
||||
send_notification(param, "{%m:%m,%m:[%lu, %d]}", MG_ESC("name"),
|
||||
MG_ESC("metrics"), MG_ESC("data"),
|
||||
(unsigned long) ourtime(NULL),
|
||||
10 + (int) ((double) rand() * 10 / RAND_MAX));
|
||||
}
|
||||
|
||||
#ifndef DISABLE_ROUTING
|
||||
|
||||
// MQTT event handler function
|
||||
static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data, void *fnd) {
|
||||
if (ev == MG_EV_CONNECT && mg_url_is_ssl(s_config.url)) {
|
||||
struct mg_tls_opts opts = {.ca = "ca.pem",
|
||||
.srvname = mg_url_host(s_config.url)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_MQTT_OPEN) {
|
||||
s_connected = true;
|
||||
c->is_hexdumping = 1;
|
||||
struct mg_mqtt_opts sub_opts;
|
||||
memset(&sub_opts, 0, sizeof(sub_opts));
|
||||
sub_opts.topic = mg_str(s_config.sub);
|
||||
sub_opts.qos = 2;
|
||||
|
||||
mg_mqtt_sub(s_mqtt, &sub_opts);
|
||||
send_notification(c->mgr, "{%m:%m,%m:null}", MG_ESC("name"),
|
||||
MG_ESC("config"), MG_ESC("data"));
|
||||
MG_INFO(("MQTT connected, server %s", MQTT_SERVER));
|
||||
} else if (ev == MG_EV_MQTT_MSG) {
|
||||
struct mg_mqtt_message *mm = ev_data;
|
||||
send_notification(
|
||||
c->mgr, "{%m:%m,%m:{%m: %m, %m: %m, %m: %d}}", MG_ESC("name"),
|
||||
MG_ESC("message"), MG_ESC("data"), MG_ESC("topic"), mg_print_esc,
|
||||
(int) mm->topic.len, mm->topic.ptr, MG_ESC("data"), mg_print_esc,
|
||||
(int) mm->data.len, mm->data.ptr, MG_ESC("qos"), (int) mm->qos);
|
||||
} else if (ev == MG_EV_MQTT_CMD) {
|
||||
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
|
||||
MG_DEBUG(("%lu cmd %d qos %d", c->id, mm->cmd, mm->qos));
|
||||
} else if (ev == MG_EV_CLOSE) {
|
||||
s_mqtt = NULL;
|
||||
if (s_connected) {
|
||||
s_connected = false;
|
||||
send_notification(c->mgr, "{%m:%m,%m:null}", MG_ESC("name"),
|
||||
MG_ESC("config"), MG_ESC("data"));
|
||||
}
|
||||
}
|
||||
(void) fnd;
|
||||
}
|
||||
|
||||
// Keep MQTT connection open - reconnect if closed
|
||||
static void timer_mqtt_fn(void *param) {
|
||||
struct mg_mgr *mgr = (struct mg_mgr *) param;
|
||||
if (s_mqtt == NULL) {
|
||||
struct mg_mqtt_opts opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
s_mqtt = mg_mqtt_connect(mgr, s_config.url, &opts, mqtt_fn, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// SNTP connection event handler. When we get a response from an SNTP server,
|
||||
// adjust s_boot_timestamp. We'll get a valid time from that point on
|
||||
static void sfn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_SNTP_TIME) {
|
||||
uint64_t t = *(uint64_t *) ev_data;
|
||||
s_boot_timestamp = (time_t) ((t - mg_millis()) / 1000);
|
||||
c->is_closing = 1;
|
||||
} else if (ev == MG_EV_CLOSE) {
|
||||
s_sntp_conn = NULL;
|
||||
}
|
||||
(void) fn_data;
|
||||
}
|
||||
|
||||
static void timer_sntp_fn(void *param) { // SNTP timer function. Sync up time
|
||||
struct mg_mgr *mgr = (struct mg_mgr *) param;
|
||||
if (s_sntp_conn == NULL && s_boot_timestamp == 0) {
|
||||
s_sntp_conn = mg_sntp_connect(mgr, NULL, sfn, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// HTTP request handler function
|
||||
void device_dashboard_fn(struct mg_connection *c, int ev, void *ev_data,
|
||||
void *fn_data) {
|
||||
if (ev == MG_EV_OPEN && c->is_listening) {
|
||||
mg_timer_add(c->mgr, 1000, MG_TIMER_REPEAT, timer_metrics_fn, c->mgr);
|
||||
#ifndef DISABLE_ROUTING
|
||||
mg_timer_add(c->mgr, 1000, MG_TIMER_REPEAT, timer_mqtt_fn, c->mgr);
|
||||
mg_timer_add(c->mgr, 1000, MG_TIMER_REPEAT, timer_sntp_fn, c->mgr);
|
||||
#endif
|
||||
s_config.url = strdup(MQTT_SERVER);
|
||||
s_config.pub = strdup(MQTT_PUBLISH_TOPIC);
|
||||
s_config.sub = strdup(MQTT_SUBSCRIBE_TOPIC);
|
||||
} else if (ev == MG_EV_ACCEPT && fn_data != NULL) {
|
||||
struct mg_tls_opts opts = {.cert = s_ssl_cert, .certkey = s_ssl_key};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
struct user *u = getuser(hm);
|
||||
// MG_INFO(("%p [%.*s] auth %s", c->fd, (int) hm->uri.len, hm->uri.ptr,
|
||||
// u ? u->name : "NULL"));
|
||||
if (mg_http_match_uri(hm, "/api/hi")) {
|
||||
mg_http_reply(c, 200, "", "hi\n"); // Testing endpoint
|
||||
} else if (mg_http_match_uri(hm, "/api/debug")) {
|
||||
int level = mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG);
|
||||
mg_log_set(level);
|
||||
mg_http_reply(c, 200, "", "Debug level set to %d\n", level);
|
||||
} else if (u == NULL && mg_http_match_uri(hm, "/api/#")) {
|
||||
// All URIs starting with /api/ must be authenticated
|
||||
mg_http_reply(c, 403, "", "Denied\n");
|
||||
} else if (mg_http_match_uri(hm, "/api/config/get")) {
|
||||
#ifdef DISABLE_ROUTING
|
||||
mg_http_reply(c, 200, NULL, "{%m:%m,%m:%m,%m:%m}\n", MG_ESC("url"),
|
||||
MG_ESC(s_config.url), MG_ESC("pub"), MG_ESC(s_config.pub),
|
||||
MG_ESC("sub"), MG_ESC(s_config.sub));
|
||||
#else
|
||||
mg_http_reply(c, 200, NULL, "{%m:%m,%m:%m,%m:%m,%m:%s}\n", MG_ESC("url"),
|
||||
MG_ESC(s_config.url), MG_ESC("pub"), MG_ESC(s_config.pub),
|
||||
MG_ESC("sub"), MG_ESC(s_config.sub), MG_ESC("connected"),
|
||||
s_connected ? "true" : "false");
|
||||
#endif
|
||||
} else if (mg_http_match_uri(hm, "/api/config/set")) {
|
||||
// Admins only
|
||||
if (strcmp(u->name, "admin") == 0) {
|
||||
update_config(&hm->body, "url", &s_config.url);
|
||||
update_config(&hm->body, "pub", &s_config.pub);
|
||||
update_config(&hm->body, "sub", &s_config.sub);
|
||||
if (s_mqtt) s_mqtt->is_closing = 1; // Ask to disconnect from MQTT
|
||||
send_notification(c->mgr, "{%m:%m,%m:null}", MG_ESC("name"),
|
||||
MG_ESC("config"), MG_ESC("data"));
|
||||
mg_http_reply(c, 200, "", "ok\n");
|
||||
} else {
|
||||
mg_http_reply(c, 403, "", "Denied\n");
|
||||
}
|
||||
} else if (mg_http_match_uri(hm, "/api/message/send")) {
|
||||
char buf[256];
|
||||
if (s_connected &&
|
||||
mg_http_get_var(&hm->body, "message", buf, sizeof(buf)) > 0) {
|
||||
struct mg_mqtt_opts pub_opts;
|
||||
memset(&pub_opts, 0, sizeof(pub_opts));
|
||||
pub_opts.topic = mg_str(s_config.pub);
|
||||
pub_opts.message = mg_str(buf);
|
||||
pub_opts.qos = 2, pub_opts.retain = false;
|
||||
|
||||
mg_mqtt_pub(s_mqtt, &pub_opts);
|
||||
}
|
||||
mg_http_reply(c, 200, "", "ok\n");
|
||||
} else if (mg_http_match_uri(hm, "/api/watch")) {
|
||||
c->data[0] = 'W'; // Mark ourselves as a event listener
|
||||
mg_ws_upgrade(c, hm, NULL);
|
||||
} else if (mg_http_match_uri(hm, "/api/login")) {
|
||||
mg_http_reply(c, 200, NULL, "{%m:%m,%m:%m}\n", MG_ESC("user"),
|
||||
MG_ESC(u->name), MG_ESC("token"), MG_ESC(u->token));
|
||||
} else {
|
||||
struct mg_http_serve_opts opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
#if 1
|
||||
opts.root_dir = "/web_root";
|
||||
opts.fs = &mg_fs_packed;
|
||||
#else
|
||||
opts.root_dir = "web_root";
|
||||
#endif
|
||||
mg_http_serve_dir(c, ev_data, &opts);
|
||||
}
|
||||
MG_DEBUG(("%lu %.*s %.*s -> %.*s", c->id, (int) hm->method.len,
|
||||
hm->method.ptr, (int) hm->uri.len, hm->uri.ptr, (int) 3,
|
||||
&c->send.buf[9]));
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 53 KiB |
@@ -1,16 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>example</title>
|
||||
<title>Device Dashboard</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script src="preact.min.js"></script>
|
||||
<script src="htm.min.js"></script>
|
||||
<script src="linkstate.min.js"></script>
|
||||
<script src="axios.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
<script type="module" src="main.js"></script>
|
||||
</html>
|
||||
@@ -0,0 +1,385 @@
|
||||
'use strict';
|
||||
import {Component, h, html, render, useEffect, useState, useRef} from './preact.min.js';
|
||||
|
||||
var devaddr = "address:port";
|
||||
|
||||
const MaxMetricsDataPoints = 50;
|
||||
|
||||
// This simple publish/subscribe is used to pass notifications that were
|
||||
// received from the server, to all child components of the app.
|
||||
var PubSub = (function() {
|
||||
var handlers = {}, id = 0;
|
||||
return {
|
||||
subscribe: function(fn) {
|
||||
handlers[id++] = fn;
|
||||
},
|
||||
unsubscribe: function(id) {
|
||||
delete handlers[id];
|
||||
},
|
||||
publish: function(data) {
|
||||
for (var k in handlers) handlers[k](data);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
const Nav = props => html`
|
||||
<div style="background: #333; padding: 0.5em; color: #fff;">
|
||||
<div class="container d-flex">
|
||||
<div style="flex: 1 1 auto; display: flex; align-items: center;">
|
||||
<b>Your Product</b>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; flex: 0 0 auto; ">
|
||||
<span>Logged in as:</span>
|
||||
<span style="padding: 0 0.5em;"><img src="user.png" height="22" /></span>
|
||||
<span>${props.user}</span>
|
||||
<a class="btn" onclick=${props.logout}
|
||||
style="margin-left: 1em; font-size: 0.8em; background: #8aa;">logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
||||
const Hero = props => html`
|
||||
<div class="section">
|
||||
<div style="margin-top: 1em; background: #eee; padding: 1em; border-radius: 0.5em; color: #777; ">
|
||||
<h1 style="margin: 0.2em 0;">Interactive Device Dashboard</h1>
|
||||
|
||||
<p>
|
||||
This device dashboard is developed using the modern and compact Preact framework,
|
||||
in order to fit on very small devices. This is
|
||||
a <a href="https://mongoose.ws/tutorials/http-server/">hybrid server</a> which
|
||||
provides both static and dynamic content. Static files, like CSS/JS/HTML
|
||||
or images, are compiled into the server binary.
|
||||
|
||||
This UI uses the REST API implemented by the device, which you can examine
|
||||
using <code>curl</code> command-line utility:
|
||||
</p>
|
||||
|
||||
<div><code>curl -u admin:pass0 ${devaddr}/api/config/get</code> </div>
|
||||
<div><code>curl -u admin:pass0 ${devaddr}/api/config/set -d 'pub=mg/topic'</code> </div>
|
||||
<div><code>curl -u admin:pass0 ${devaddr}/api/message/send -d 'message=hello'</code> </div>
|
||||
|
||||
<p>
|
||||
The device can send notifications to this dashboard at anytime. Notifications
|
||||
are sent over WebSocket at URI <code>/api/watch</code> as JSON strings: <code>{"name": "..", "data": ...}</code>
|
||||
<div>Try <code>wscat --auth user1:pass1 --connect ws://${devaddr}/api/watch</code></div>
|
||||
</p>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
const Login = function(props) {
|
||||
const [user, setUser] = useState('');
|
||||
const [pass, setPass] = useState('');
|
||||
const login = ev =>
|
||||
fetch(
|
||||
'/api/login',
|
||||
{headers: {Authorization: 'Basic ' + btoa(user + ':' + pass)}})
|
||||
.then(r => r.json())
|
||||
.then(r => r && props.login(r))
|
||||
.catch(err => err);
|
||||
return html`
|
||||
<div class="rounded border" style="max-width: 480px; margin: 0 auto; margin-top: 5em; background: #eee; ">
|
||||
<div style="padding: 2em; ">
|
||||
<h1 style="color: #666;">Device Dashboard Login </h1>
|
||||
<div style="margin: 0.5em 0;">
|
||||
<input type='text' placeholder='Name' style="width: 100%;"
|
||||
oninput=${ev => setUser(ev.target.value)} value=${user} />
|
||||
</div>
|
||||
<div style="margin: 0.5em 0;">
|
||||
<input type="password" placeholder="Password" style="width: 100%;"
|
||||
oninput=${ev => setPass(ev.target.value)} value=${pass}
|
||||
onchange=${login} />
|
||||
</div>
|
||||
<div style="margin: 1em 0;">
|
||||
<button class="btn" style="width: 100%; background: #8aa;"
|
||||
disabled=${!user || !pass} onclick=${login}> Login </button>
|
||||
</div>
|
||||
<div style="color: #777; margin-top: 2em;">
|
||||
Valid logins: admin:pass0, user1:pass1, user2:pass2
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
|
||||
const Configuration = function(props) {
|
||||
const [url, setUrl] = useState(props.config.url || '');
|
||||
const [pub, setPub] = useState(props.config.pub || '');
|
||||
const [sub, setSub] = useState(props.config.sub || '');
|
||||
|
||||
useEffect(() => {
|
||||
setUrl(props.config.url);
|
||||
setPub(props.config.pub);
|
||||
setSub(props.config.sub);
|
||||
}, [props.config]);
|
||||
|
||||
const update = (name, val) => fetch('/api/config/set', {
|
||||
method: 'post',
|
||||
body: `${name}=${encodeURIComponent(val)}`
|
||||
}).catch(err => err);
|
||||
const updateurl = ev => update('url', url);
|
||||
const updatepub = ev => update('pub', pub);
|
||||
const updatesub = ev => update('sub', sub);
|
||||
|
||||
// console.log(props, [url, pub, sub]);
|
||||
return html`
|
||||
<div class="section">
|
||||
<h3 style="background: #c03434; color: #fff; padding: 0.4em;">
|
||||
Device Configuration</h3>
|
||||
<div style="margin: 0.5em 0; display: flex;">
|
||||
<span class="addon nowrap">MQTT server:</span>
|
||||
<input type="text" style="flex: 1 100%;"
|
||||
value=${url} onchange=${updateurl}
|
||||
oninput=${ev => setUrl(ev.target.value)} />
|
||||
<button class="btn" disabled=${!url} onclick=${updateurl}
|
||||
style="margin-left: 1em; background: #8aa;">Update</button>
|
||||
</div>
|
||||
<div style="margin: 0.5em 0; display: flex; ">
|
||||
<span class="addon nowrap">Subscribe topic:</span>
|
||||
<input type="text" style="flex: 1 100%;"
|
||||
value=${sub} onchange=${updatesub}
|
||||
oninput=${ev => setSub(ev.target.value)} />
|
||||
<button class="btn" disabled=${!sub} onclick=${updatesub}
|
||||
style="margin-left: 1em; background: #8aa;">Update</button>
|
||||
</div>
|
||||
<div style="margin: 0.5em 0; display: flex;">
|
||||
<span class="addon nowrap">Publish topic:</span>
|
||||
<input type="text" style="flex: 1 100%;"
|
||||
value=${pub} onchange=${updatepub}
|
||||
oninput=${ev => setPub(ev.target.value)} />
|
||||
<button class="btn" disabled=${!pub} onclick=${updatepub}
|
||||
style="margin-left: 1em; background: #8aa;">Update</button>
|
||||
</div>
|
||||
<div>
|
||||
You can use <a href="http://www.hivemq.com/demos/websocket-client/">
|
||||
HiveMQ Websocket web client</a> to send messages to this console.
|
||||
</div>
|
||||
<div class="msg">
|
||||
The device keeps a persistent connection to the configured MQTT server.
|
||||
Changes to this configuration are propagated to all dashboards: try
|
||||
changing them in this dashboard and observe changes in other opened
|
||||
dashboards.
|
||||
</div><div class="msg">
|
||||
Note: administrators can see this section and can change device
|
||||
configuration, whilst users cannot.
|
||||
</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
|
||||
const Message = m => html`<div style="margin: 0.5em 0;">
|
||||
<span class="qos">qos: ${m.message.qos} </span>
|
||||
<span class="topic">topic: ${m.message.topic} </span>
|
||||
<span class="data">data: ${m.message.data}</span>
|
||||
</div>`;
|
||||
|
||||
const Messages = function(props) {
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [txt, setTxt] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const id = PubSub.subscribe(function(msg) {
|
||||
if (msg.name == 'message') setMessages(x => x.concat([msg.data]));
|
||||
});
|
||||
return PubSub.unsubscribe(id);
|
||||
}, []);
|
||||
|
||||
const sendmessage = ev => fetch('/api/message/send', {
|
||||
method: 'post',
|
||||
body: `message=${encodeURIComponent(txt)}`
|
||||
}).then(r => setTxt(''));
|
||||
const routing = "connected" in props.config;
|
||||
const connstatus = !routing ? 'This device has no MQTT functionality' : props.config.connected ? 'connected' : 'disconnected';
|
||||
return html`
|
||||
<div class="section">
|
||||
<h3 style="background: #30c040; color: #fff; padding: 0.4em;">MQTT messages</h3>
|
||||
<div>
|
||||
MQTT server status: <b>${connstatus}</b>
|
||||
</div>
|
||||
<div style="height: 10em; overflow: auto; padding: 0.5em; " class="border">
|
||||
${messages.map(message => h(Message, {message}))}
|
||||
</div>
|
||||
<div style="margin: 0.5em 0; display: flex">
|
||||
<span class="addon nowrap">Publish message:</span>
|
||||
<input placeholder="type and press enter..." style="flex: 1 100%;"
|
||||
value=${txt} onchange=${sendmessage} disabled=${!routing}
|
||||
oninput=${ev => setTxt(ev.target.value)} />
|
||||
</div>
|
||||
<div class="msg">
|
||||
The message gets passed to the device via REST. Then the device sends it to
|
||||
the MQTT server over MQTT. All MQTT messages on a subscribed topic
|
||||
received by the device, are propagated to this dashboard via /api/watch.
|
||||
</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
// Expected arguments:
|
||||
// data: timeseries, e.g. [ [1654361352, 19], [1654361353, 18], ... ]
|
||||
// width, height, yticks, xticks, ymin, ymax, xmin, xmax
|
||||
const SVG = function(props) {
|
||||
// w
|
||||
// +---------------------+
|
||||
// | h1 |
|
||||
// | +-----------+ |
|
||||
// | | | | h
|
||||
// | w1 | | w2 |
|
||||
// | +-----------+ |
|
||||
// | h2 |
|
||||
// +---------------------+
|
||||
//
|
||||
let w = props.width, h = props.height, w1 = 30, w2 = 0, h1 = 8, h2 = 18;
|
||||
let yticks = props.yticks || 4, xticks = props.xticks || 5;
|
||||
let data = props.data || [];
|
||||
let ymin = props.ymin || 0;
|
||||
let ymax = props.ymax || Math.max.apply(null, data.map(p => p[1]));
|
||||
let xmin = props.xmin || Math.min.apply(null, data.map(p => p[0]));
|
||||
let xmax = props.xmax || Math.max.apply(null, data.map(p => p[0]));
|
||||
|
||||
// Y-axis tick lines and labels
|
||||
let yta = (new Array(yticks + 1)).fill(0).map((_, i) => i); // indices
|
||||
let yti = i => h - h2 - (h - h1 - h2) * i / yticks; // index's Y
|
||||
let ytv = i => (ymax - ymin) * i / yticks;
|
||||
let ytl = y => html`<line x1=${w1} y1=${y} x2=${w} y2=${y} class="tick"/>`;
|
||||
let ytt = (y, v) => html`<text x=0 y=${y + 5} class="label">${v}</text>`;
|
||||
|
||||
// X-axis tick lines and labels
|
||||
let datefmt = unix => (new Date(unix * 1000)).toISOString().substr(14, 5);
|
||||
let xta = (new Array(xticks + 1)).fill(0).map((_, i) => i); // indices
|
||||
let xti = i => w1 + (w - w1 - w2) * i / xticks; // index's X
|
||||
let xtv = i => datefmt(xmin + (xmax - xmin) * i / xticks);
|
||||
let xtl = x => html`<path d="M ${x},${h1} L ${x},${h - h2}" class="tick"/>`;
|
||||
let xtt = (x, v) =>
|
||||
html`<text x=${x - 15} y=${h - 2} class="label">${v}</text>`;
|
||||
|
||||
// Transform data points array into coordinate
|
||||
let dx = v => w1 + (v - xmin) / ((xmax - xmin) || 1) * (w - w1 - w2);
|
||||
let dy = v => h - h2 - (v - ymin) / ((ymax - ymin) || 1) * (h - h1 - h2);
|
||||
let dd = data.map(p => [Math.round(dx(p[0])), Math.round(dy(p[1]))]);
|
||||
let ddl = dd.length;
|
||||
// And plot the data as <path> element
|
||||
let begin0 = ddl ? `M ${dd[0][0]},${dd[0][1]}` : `M 0,0`;
|
||||
let begin = `M ${w1},${h - h2}`; // Initial point
|
||||
let end = ddl ? `L ${dd[ddl - 1][0]},${h - h2}` : `L ${w1},${h - h2}`;
|
||||
let series = ddl ? dd.map(p => `L ${p[0]} ${p[1]}`) : [];
|
||||
|
||||
return html`
|
||||
<svg viewBox="0 0 ${w} ${h}">
|
||||
<style>
|
||||
.axis { stroke: #aaa; fill: none; }
|
||||
.label { stroke: #aaa; font-size: 13px; }
|
||||
.tick { stroke: #ccc; fill: none; stroke-dasharray: 5; }
|
||||
.seriesbg { stroke: none; fill: rgba(200,225,255, 0.25)}
|
||||
.series { stroke: #25a; fill: none; }
|
||||
</style>
|
||||
${yta.map(i => ytl(yti(i)))}
|
||||
${yta.map(i => ytt(yti(i), ytv(i)))}
|
||||
${xta.map(i => xtl(xti(i)))}
|
||||
${data.length ? xta.map(i => xtt(xti(i), xtv(i))) : ''}
|
||||
<path d="${begin} ${series.join(' ')} ${end}" class="seriesbg" />
|
||||
<path d="${begin0} ${series.join(' ')}" class="series" />
|
||||
</svg>`;
|
||||
};
|
||||
|
||||
|
||||
const Chart = function(props) {
|
||||
const [data, setData] = useState([]);
|
||||
useEffect(() => {
|
||||
const id = PubSub.subscribe(function(msg) {
|
||||
if (msg.name != 'metrics') return;
|
||||
setData(x => x.concat([msg.data]).splice(-MaxMetricsDataPoints));
|
||||
});
|
||||
return PubSub.unsubscribe(id);
|
||||
}, []);
|
||||
|
||||
let xmax = 0, missing = MaxMetricsDataPoints - data.length;
|
||||
if (missing > 0) xmax = Math.round(Date.now() / 1000) + missing;
|
||||
return html`
|
||||
<div class="section">
|
||||
<h3 style="background: #ec3; color: #fff; padding: 0.4em;">Data Chart</h3>
|
||||
<div style="overflow: auto; padding: 0.5em;" class="">
|
||||
<${SVG} height=240 width=600 ymin=0 ymax=20 xmax=${xmax} data=${data} />
|
||||
</div>
|
||||
<div class="msg">
|
||||
This chart plots live sensor data, sent by the device via /api/watch.
|
||||
</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
const App = function(props) {
|
||||
const [user, setUser] = useState('');
|
||||
const [config, setConfig] = useState({});
|
||||
|
||||
const getconfig = () =>
|
||||
fetch('/api/config/get', {headers: {Authorization: ''}})
|
||||
.then(r => r.json())
|
||||
.then(r => setConfig(r))
|
||||
.catch(err => console.log(err));
|
||||
|
||||
// Watch for notifications. As soon as a notification arrives, pass it on
|
||||
// to all subscribed components
|
||||
const watch = function() {
|
||||
var l = window.location, proto = l.protocol.replace('http', 'ws');
|
||||
var tid, wsURI = proto + '//' + l.host + '/api/watch'
|
||||
var reconnect = function() {
|
||||
var ws = new WebSocket(wsURI);
|
||||
// ws.onopen = () => console.log('ws connected');
|
||||
ws.onmessage = function(ev) {
|
||||
try {
|
||||
var msg = JSON.parse(ev.data);
|
||||
PubSub.publish(msg);
|
||||
// if (msg.name != 'metrics') console.log('ws->', msg);
|
||||
} catch (e) {
|
||||
console.log('Invalid ws frame:', ev.data); // eslint-disable-line
|
||||
}
|
||||
};
|
||||
ws.onclose = function() {
|
||||
clearTimeout(tid);
|
||||
tid = setTimeout(reconnect, 1000);
|
||||
console.log('ws disconnected');
|
||||
};
|
||||
};
|
||||
reconnect();
|
||||
};
|
||||
|
||||
const login = function(u) {
|
||||
document.cookie = `access_token=${u.token}; Secure, HttpOnly; SameSite=Lax; path=/; max-age=3600`;
|
||||
setUser(u.user);
|
||||
if (location.search.substring(1) == 'nows') {
|
||||
// If query string is ?nows, then do not connect to websocket. For debug.
|
||||
} else {
|
||||
watch(); // Connect to websocket, receive constant graph updates
|
||||
}
|
||||
return getconfig();
|
||||
};
|
||||
|
||||
const logout = ev => {
|
||||
document.cookie = `access_token=; Secure, HttpOnly; SameSite=Lax; path=/; max-age=0`;
|
||||
setUser('');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Called once at init time
|
||||
PubSub.subscribe(msg => msg.name == 'config' && getconfig());
|
||||
fetch('/api/login', {headers: {Authorization: ''}})
|
||||
.then(r => r.json())
|
||||
.then(r => login(r))
|
||||
.catch(err => setUser(''));
|
||||
}, []);
|
||||
|
||||
if (!user) return html`<${Login} login=${login} />`;
|
||||
|
||||
return html`
|
||||
<${Nav} user=${user} logout=${logout} />
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col col-6"><${Hero} /></div>
|
||||
<div class="col col-6"><${Chart} /></div>
|
||||
<div class="col col-6">
|
||||
${user == 'admin' && h(Configuration, {config})}
|
||||
</div>
|
||||
<div class="col col-6"><${Messages} config=${config} /></div>
|
||||
</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
window.onload = () => render(h(App), document.body);
|
||||
@@ -0,0 +1 @@
|
||||
var e,n,_,t,o,r,u,l={},i=[],c=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function s(e,n){for(var _ in n)e[_]=n[_];return e}function f(e){var n=e.parentNode;n&&n.removeChild(e)}function a(n,_,t){var o,r,u,l={};for(u in _)"key"==u?o=_[u]:"ref"==u?r=_[u]:l[u]=_[u];if(arguments.length>2&&(l.children=arguments.length>3?e.call(arguments,2):t),"function"==typeof n&&null!=n.defaultProps)for(u in n.defaultProps)void 0===l[u]&&(l[u]=n.defaultProps[u]);return p(n,l,o,r,null)}function p(e,t,o,r,u){var l={type:e,props:t,key:o,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==u?++_:u};return null!=n.vnode&&n.vnode(l),l}function h(e){return e.children}function d(e,n){this.props=e,this.context=n}function v(e,n){if(null==n)return e.__?v(e.__,e.__.__k.indexOf(e)+1):null;for(var _;n<e.__k.length;n++)if(null!=(_=e.__k[n])&&null!=_.__e)return _.__e;return"function"==typeof e.type?v(e):null}function y(e){var n,_;if(null!=(e=e.__)&&null!=e.__c){for(e.__e=e.__c.base=null,n=0;n<e.__k.length;n++)if(null!=(_=e.__k[n])&&null!=_.__e){e.__e=e.__c.base=_.__e;break}return y(e)}}function m(e){(!e.__d&&(e.__d=!0)&&t.push(e)&&!g.__r++||r!==n.debounceRendering)&&((r=n.debounceRendering)||o)(g)}function g(){for(var e;g.__r=t.length;)e=t.sort(function(e,n){return e.__v.__b-n.__v.__b}),t=[],e.some(function(e){var n,_,t,o,r,u;e.__d&&(r=(o=(n=e).__v).__e,(u=n.__P)&&(_=[],(t=s({},o)).__v=o.__v+1,P(u,o,t,n.__n,void 0!==u.ownerSVGElement,null!=o.__h?[r]:null,_,null==r?v(o):r,o.__h),D(_,o),o.__e!=r&&y(o)))})}function k(e,n,_,t,o,r,u,c,s,f){var a,d,y,m,g,k,x,H=t&&t.__k||i,E=H.length;for(_.__k=[],a=0;a<n.length;a++)if(null!=(m=_.__k[a]=null==(m=n[a])||"boolean"==typeof m?null:"string"==typeof m||"number"==typeof m||"bigint"==typeof m?p(null,m,null,null,m):Array.isArray(m)?p(h,{children:m},null,null,null):m.__b>0?p(m.type,m.props,m.key,null,m.__v):m)){if(m.__=_,m.__b=_.__b+1,null===(y=H[a])||y&&m.key==y.key&&m.type===y.type)H[a]=void 0;else for(d=0;d<E;d++){if((y=H[d])&&m.key==y.key&&m.type===y.type){H[d]=void 0;break}y=null}P(e,m,y=y||l,o,r,u,c,s,f),g=m.__e,(d=m.ref)&&y.ref!=d&&(x||(x=[]),y.ref&&x.push(y.ref,null,m),x.push(d,m.__c||g,m)),null!=g?(null==k&&(k=g),"function"==typeof m.type&&null!=m.__k&&m.__k===y.__k?m.__d=s=b(m,s,e):s=C(e,m,y,H,g,s),f||"option"!==_.type?"function"==typeof _.type&&(_.__d=s):e.value=""):s&&y.__e==s&&s.parentNode!=e&&(s=v(y))}for(_.__e=k,a=E;a--;)null!=H[a]&&("function"==typeof _.type&&null!=H[a].__e&&H[a].__e==_.__d&&(_.__d=v(t,a+1)),U(H[a],H[a]));if(x)for(a=0;a<x.length;a++)T(x[a],x[++a],x[++a])}function b(e,n,_){var t,o;for(t=0;t<e.__k.length;t++)(o=e.__k[t])&&(o.__=e,n="function"==typeof o.type?b(o,n,_):C(_,o,o,e.__k,o.__e,n));return n}function C(e,n,_,t,o,r){var u,l,i;if(void 0!==n.__d)u=n.__d,n.__d=void 0;else if(null==_||o!=r||null==o.parentNode)e:if(null==r||r.parentNode!==e)e.appendChild(o),u=null;else{for(l=r,i=0;(l=l.nextSibling)&&i<t.length;i+=2)if(l==o)break e;e.insertBefore(o,r),u=r}return void 0!==u?u:o.nextSibling}function x(e,n,_){"-"===n[0]?e.setProperty(n,_):e[n]=null==_?"":"number"!=typeof _||c.test(n)?_:_+"px"}function H(e,n,_,t,o){var r;e:if("style"===n)if("string"==typeof _)e.style.cssText=_;else{if("string"==typeof t&&(e.style.cssText=t=""),t)for(n in t)_&&n in _||x(e.style,n,"");if(_)for(n in _)t&&_[n]===t[n]||x(e.style,n,_[n])}else if("o"===n[0]&&"n"===n[1])r=n!==(n=n.replace(/Capture$/,"")),n=n.toLowerCase()in e?n.toLowerCase().slice(2):n.slice(2),e.l||(e.l={}),e.l[n+r]=_,_?t||e.addEventListener(n,r?S:E,r):e.removeEventListener(n,r?S:E,r);else if("dangerouslySetInnerHTML"!==n){if(o)n=n.replace(/xlink[H:h]/,"h").replace(/sName$/,"s");else if("href"!==n&&"list"!==n&&"form"!==n&&"tabIndex"!==n&&"download"!==n&&n in e)try{e[n]=null==_?"":_;break e}catch(e){}"function"==typeof _||(null!=_&&(!1!==_||"a"===n[0]&&"r"===n[1])?e.setAttribute(n,_):e.removeAttribute(n))}}function E(e){this.l[e.type+!1](n.event?n.event(e):e)}function S(e){this.l[e.type+!0](n.event?n.event(e):e)}function P(e,_,t,o,r,u,l,i,c){var f,a,p,v,y,m,g,b,C,x,H,E=_.type;if(void 0!==_.constructor)return null;null!=t.__h&&(c=t.__h,i=_.__e=t.__e,_.__h=null,u=[i]),(f=n.__b)&&f(_);try{e:if("function"==typeof E){if(b=_.props,C=(f=E.contextType)&&o[f.__c],x=f?C?C.props.value:f.__:o,t.__c?g=(a=_.__c=t.__c).__=a.__E:("prototype"in E&&E.prototype.render?_.__c=a=new E(b,x):(_.__c=a=new d(b,x),a.constructor=E,a.render=A),C&&C.sub(a),a.props=b,a.state||(a.state={}),a.context=x,a.__n=o,p=a.__d=!0,a.__h=[]),null==a.__s&&(a.__s=a.state),null!=E.getDerivedStateFromProps&&(a.__s==a.state&&(a.__s=s({},a.__s)),s(a.__s,E.getDerivedStateFromProps(b,a.__s))),v=a.props,y=a.state,p)null==E.getDerivedStateFromProps&&null!=a.componentWillMount&&a.componentWillMount(),null!=a.componentDidMount&&a.__h.push(a.componentDidMount);else{if(null==E.getDerivedStateFromProps&&b!==v&&null!=a.componentWillReceiveProps&&a.componentWillReceiveProps(b,x),!a.__e&&null!=a.shouldComponentUpdate&&!1===a.shouldCompLine truncated
|
||||
@@ -0,0 +1,43 @@
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; height: 100%; font: 16px sans-serif; }
|
||||
select, input, label::before, textarea { outline: none; box-shadow:none !important; border: 1px solid #ccc !important; }
|
||||
code, pre { color: #373; font-family: monospace; font-weight: bolder; font-size: smaller; background: #ddd; padding: 0.1em 0.3em; border-radius: 0.2em; }
|
||||
textarea, input, .addon { font-size: 15px; border: 1px solid #ccc; padding: 0.5em; }
|
||||
a, a:visited, a:active { color: #55f; }
|
||||
.addon { background: #eee; min-width: 9em;}
|
||||
.btn {
|
||||
background: #ccc; border-radius: 0.3em; border: 0; color: #fff; cursor: pointer;
|
||||
display: inline-block; padding: 0.6em 2em; font-weight: bolder;
|
||||
}
|
||||
.btn[disabled] { opacity: 0.5; cursor: auto;}
|
||||
.smooth { transition: all .2s; }
|
||||
.container { margin: 0 20px; width: auto; }
|
||||
.d-flex { display: flex; }
|
||||
.d-none { display: none; }
|
||||
.border { border: 1px solid #ddd; }
|
||||
.rounded { border-radius: 0.5em; }
|
||||
.nowrap { white-space: nowrap; }
|
||||
.msg { background: #def; border-left: 5px solid #59d; padding: 0.5em; font-size: 90%; margin: 1em 0; }
|
||||
.section { margin: 0 1em; }
|
||||
.topic, .data, .qos { padding: 0.2em 0.5em; border-radius: 0.4em; margin-right: 0.5em; }
|
||||
.qos { background: #efa; }
|
||||
.topic { background: #fea; }
|
||||
.data { background: #aef; }
|
||||
|
||||
/* Grid */
|
||||
.row { display: flex; flex-wrap: wrap; }
|
||||
.col { margin: 0; padding: 0; overflow: auto; }
|
||||
.col-12 { width: 100%; }
|
||||
.col-11 { width: 91.66%; }
|
||||
.col-10 { width: 83.33%; }
|
||||
.col-9 { width: 75%; }
|
||||
.col-8 { width: 66.66%; }
|
||||
.col-7 { width: 58.33%; }
|
||||
.col-6 { width: 50%; }
|
||||
.col-5 { width: 41.66%; }
|
||||
.col-4 { width: 33.33%; }
|
||||
.col-3 { width: 25%; }
|
||||
.col-2 { width: 16.66%; }
|
||||
.col-1 { width: 8.33%; }
|
||||
@media (min-width: 1310px) { .container { margin: auto; width: 1270px; } }
|
||||
@media (max-width: 920px) { .row .col { width: 100%; } }
|
||||
@@ -0,0 +1,51 @@
|
||||
PROG ?= example # Program we are building
|
||||
DELETE = rm -rf # Command to remove files
|
||||
OUT ?= -o $(PROG) # Compiler argument for output file
|
||||
SOURCES = main.c mongoose.c packed_fs.c # Source code files
|
||||
CFLAGS = -W -Wall -Wextra -g -I. # Build options
|
||||
PACK = ./pack # Utility to pack files into a packed filesystem
|
||||
|
||||
# Mongoose build options. See https://mongoose.ws/documentation/#build-options
|
||||
CFLAGS_MONGOOSE += -DMG_ENABLE_PACKED_FS=1
|
||||
|
||||
FILES_TO_EMBED ?= $(wildcard web_root/*)
|
||||
|
||||
ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
|
||||
PROG ?= example.exe # Use .exe suffix for the binary
|
||||
CC = gcc # Use MinGW gcc compiler
|
||||
CFLAGS += -lws2_32 # Link against Winsock library
|
||||
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
|
||||
OUT ?= -o $(PROG) # Build output
|
||||
PACK = pack.exe
|
||||
endif
|
||||
|
||||
all: $(PROG) # Default target. Build and run program
|
||||
$(RUN) ./$(PROG) $(ARGS)
|
||||
|
||||
$(PROG): $(SOURCES) $(FILES_TO_EMBED) # Build program from sources
|
||||
$(CC) ../../test/pack.c -o $(PACK)
|
||||
$(PACK) $(FILES_TO_EMBED) > packed_fs.c
|
||||
$(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT)
|
||||
|
||||
clean: # Cleanup. Delete built program and all build artifacts
|
||||
$(DELETE) $(PROG) *.dSYM $(PACK)
|
||||
|
||||
.PHONY: compress expand gzipped plain all
|
||||
|
||||
# See tutorial at https://mongoose.ws/tutorials/embedded-filesystem/#build-and-try
|
||||
gzipped: compress
|
||||
$(MAKE) all
|
||||
|
||||
plain: expand
|
||||
$(MAKE) all
|
||||
|
||||
compress:
|
||||
for file in $(FILES_TO_EMBED) ; do \
|
||||
gzip -q $$file ; \
|
||||
done
|
||||
|
||||
expand:
|
||||
for file in $(FILES_TO_EMBED) ; do \
|
||||
gunzip -q $$file ; \
|
||||
done
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
See detailed tutorial at https://mongoose.ws/tutorials/embedded-filesystem/
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2022 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
const char *s_listening_url = "http://0.0.0.0:8000";
|
||||
|
||||
// HTTP request handler function
|
||||
void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_serve_opts opts = {
|
||||
.root_dir = "/web_root",
|
||||
.fs = &mg_fs_packed
|
||||
};
|
||||
mg_http_serve_dir(c, ev_data, &opts);
|
||||
}
|
||||
(void) fn_data;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
mg_log_set(MG_LL_INFO);
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, s_listening_url, fn, NULL);
|
||||
while (true) mg_mgr_poll(&mgr, 500);
|
||||
mg_mgr_free(&mgr);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../mongoose.c
|
||||
@@ -0,0 +1 @@
|
||||
../../mongoose.h
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="description" content="Mongoose Embedded Filesytem example" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Mongoose Embedded Filesytem example</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body></body>
|
||||
<script type="module" src="main.js"></script>
|
||||
</html>
|
||||
@@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
import { h, html, render } from './preact.min.js';
|
||||
|
||||
|
||||
const X = function () {
|
||||
|
||||
return html`
|
||||
<div class="container">
|
||||
<h2 class="section">ABOUT US</h2>
|
||||
<div class="row">
|
||||
<div class="col-7">
|
||||
<p>
|
||||
Cesanta Software Ltd. is headquartered in Dublin, Republic of Ireland.
|
||||
</p>
|
||||
<p></p>
|
||||
Our story roots back to 2004, when Mongoose Web Server Library development started. <br>
|
||||
|
||||
As Mongoose Web Server grew in popularity and matured over the following years, in 2013 Cesanta was established to continue its development and
|
||||
provide support to our valued customers.
|
||||
</p>
|
||||
<p>We are proud to have among our customers many <i>Fortune 500</i> companies as well as medium and small size businesses.
|
||||
Security and quality of our solutions is a paramount for us and the fact that Mongoose Web Server is used by NASA aboard the International Space Station is
|
||||
the best confirmation to it.</p>
|
||||
<p>Since 2013, Cesanta has expanded its product portfolio. We develop and distribute embedded software and hardware with focus on connected products and the Internet of Things.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
const Y = function () {
|
||||
|
||||
return html`
|
||||
<div class="container">
|
||||
<h3 class="section">Among our products are:</h3>
|
||||
<div class="row">
|
||||
<div class="col-7">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://mongoose.ws/">Mongoose Web Server</a>
|
||||
- an embedded web server and networking library
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://vcon.io/">VCON.io</a>
|
||||
- Arduino-compatible boards with built-in firmware OTA updates
|
||||
and management dashboard
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://mdash.net/">mDash.net</a>
|
||||
- an all-in-one IoT Platform
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://mongoose-os.com">Mongoose OS</a>
|
||||
- an operating system for low-power microcontrollers
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="https://github.com/cesanta/mjs">mJS</a>
|
||||
- an embedded JavaScript engine for C/C++
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p><b>Our solutions are:</b></p>
|
||||
<ul>
|
||||
<li>integrated into thousands of commercial products</li>
|
||||
<li>deployed to hundreds of millions devices in production environments</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
const App = function (props) {
|
||||
|
||||
return html`
|
||||
<h1>Basic Embedded Filesystem demo</h1>
|
||||
<div>
|
||||
${h(X)}
|
||||
</div>
|
||||
<div>
|
||||
${h(Y)}
|
||||
</div>`;
|
||||
};
|
||||
|
||||
window.onload = () => render(h(App), document.body);
|
||||
@@ -0,0 +1 @@
|
||||
var e,n,_,t,o,r,u,l={},i=[],c=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function s(e,n){for(var _ in n)e[_]=n[_];return e}function f(e){var n=e.parentNode;n&&n.removeChild(e)}function a(n,_,t){var o,r,u,l={};for(u in _)"key"==u?o=_[u]:"ref"==u?r=_[u]:l[u]=_[u];if(arguments.length>2&&(l.children=arguments.length>3?e.call(arguments,2):t),"function"==typeof n&&null!=n.defaultProps)for(u in n.defaultProps)void 0===l[u]&&(l[u]=n.defaultProps[u]);return p(n,l,o,r,null)}function p(e,t,o,r,u){var l={type:e,props:t,key:o,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==u?++_:u};return null!=n.vnode&&n.vnode(l),l}function h(e){return e.children}function d(e,n){this.props=e,this.context=n}function v(e,n){if(null==n)return e.__?v(e.__,e.__.__k.indexOf(e)+1):null;for(var _;n<e.__k.length;n++)if(null!=(_=e.__k[n])&&null!=_.__e)return _.__e;return"function"==typeof e.type?v(e):null}function y(e){var n,_;if(null!=(e=e.__)&&null!=e.__c){for(e.__e=e.__c.base=null,n=0;n<e.__k.length;n++)if(null!=(_=e.__k[n])&&null!=_.__e){e.__e=e.__c.base=_.__e;break}return y(e)}}function m(e){(!e.__d&&(e.__d=!0)&&t.push(e)&&!g.__r++||r!==n.debounceRendering)&&((r=n.debounceRendering)||o)(g)}function g(){for(var e;g.__r=t.length;)e=t.sort(function(e,n){return e.__v.__b-n.__v.__b}),t=[],e.some(function(e){var n,_,t,o,r,u;e.__d&&(r=(o=(n=e).__v).__e,(u=n.__P)&&(_=[],(t=s({},o)).__v=o.__v+1,P(u,o,t,n.__n,void 0!==u.ownerSVGElement,null!=o.__h?[r]:null,_,null==r?v(o):r,o.__h),D(_,o),o.__e!=r&&y(o)))})}function k(e,n,_,t,o,r,u,c,s,f){var a,d,y,m,g,k,x,H=t&&t.__k||i,E=H.length;for(_.__k=[],a=0;a<n.length;a++)if(null!=(m=_.__k[a]=null==(m=n[a])||"boolean"==typeof m?null:"string"==typeof m||"number"==typeof m||"bigint"==typeof m?p(null,m,null,null,m):Array.isArray(m)?p(h,{children:m},null,null,null):m.__b>0?p(m.type,m.props,m.key,null,m.__v):m)){if(m.__=_,m.__b=_.__b+1,null===(y=H[a])||y&&m.key==y.key&&m.type===y.type)H[a]=void 0;else for(d=0;d<E;d++){if((y=H[d])&&m.key==y.key&&m.type===y.type){H[d]=void 0;break}y=null}P(e,m,y=y||l,o,r,u,c,s,f),g=m.__e,(d=m.ref)&&y.ref!=d&&(x||(x=[]),y.ref&&x.push(y.ref,null,m),x.push(d,m.__c||g,m)),null!=g?(null==k&&(k=g),"function"==typeof m.type&&null!=m.__k&&m.__k===y.__k?m.__d=s=b(m,s,e):s=C(e,m,y,H,g,s),f||"option"!==_.type?"function"==typeof _.type&&(_.__d=s):e.value=""):s&&y.__e==s&&s.parentNode!=e&&(s=v(y))}for(_.__e=k,a=E;a--;)null!=H[a]&&("function"==typeof _.type&&null!=H[a].__e&&H[a].__e==_.__d&&(_.__d=v(t,a+1)),U(H[a],H[a]));if(x)for(a=0;a<x.length;a++)T(x[a],x[++a],x[++a])}function b(e,n,_){var t,o;for(t=0;t<e.__k.length;t++)(o=e.__k[t])&&(o.__=e,n="function"==typeof o.type?b(o,n,_):C(_,o,o,e.__k,o.__e,n));return n}function C(e,n,_,t,o,r){var u,l,i;if(void 0!==n.__d)u=n.__d,n.__d=void 0;else if(null==_||o!=r||null==o.parentNode)e:if(null==r||r.parentNode!==e)e.appendChild(o),u=null;else{for(l=r,i=0;(l=l.nextSibling)&&i<t.length;i+=2)if(l==o)break e;e.insertBefore(o,r),u=r}return void 0!==u?u:o.nextSibling}function x(e,n,_){"-"===n[0]?e.setProperty(n,_):e[n]=null==_?"":"number"!=typeof _||c.test(n)?_:_+"px"}function H(e,n,_,t,o){var r;e:if("style"===n)if("string"==typeof _)e.style.cssText=_;else{if("string"==typeof t&&(e.style.cssText=t=""),t)for(n in t)_&&n in _||x(e.style,n,"");if(_)for(n in _)t&&_[n]===t[n]||x(e.style,n,_[n])}else if("o"===n[0]&&"n"===n[1])r=n!==(n=n.replace(/Capture$/,"")),n=n.toLowerCase()in e?n.toLowerCase().slice(2):n.slice(2),e.l||(e.l={}),e.l[n+r]=_,_?t||e.addEventListener(n,r?S:E,r):e.removeEventListener(n,r?S:E,r);else if("dangerouslySetInnerHTML"!==n){if(o)n=n.replace(/xlink[H:h]/,"h").replace(/sName$/,"s");else if("href"!==n&&"list"!==n&&"form"!==n&&"tabIndex"!==n&&"download"!==n&&n in e)try{e[n]=null==_?"":_;break e}catch(e){}"function"==typeof _||(null!=_&&(!1!==_||"a"===n[0]&&"r"===n[1])?e.setAttribute(n,_):e.removeAttribute(n))}}function E(e){this.l[e.type+!1](n.event?n.event(e):e)}function S(e){this.l[e.type+!0](n.event?n.event(e):e)}function P(e,_,t,o,r,u,l,i,c){var f,a,p,v,y,m,g,b,C,x,H,E=_.type;if(void 0!==_.constructor)return null;null!=t.__h&&(c=t.__h,i=_.__e=t.__e,_.__h=null,u=[i]),(f=n.__b)&&f(_);try{e:if("function"==typeof E){if(b=_.props,C=(f=E.contextType)&&o[f.__c],x=f?C?C.props.value:f.__:o,t.__c?g=(a=_.__c=t.__c).__=a.__E:("prototype"in E&&E.prototype.render?_.__c=a=new E(b,x):(_.__c=a=new d(b,x),a.constructor=E,a.render=A),C&&C.sub(a),a.props=b,a.state||(a.state={}),a.context=x,a.__n=o,p=a.__d=!0,a.__h=[]),null==a.__s&&(a.__s=a.state),null!=E.getDerivedStateFromProps&&(a.__s==a.state&&(a.__s=s({},a.__s)),s(a.__s,E.getDerivedStateFromProps(b,a.__s))),v=a.props,y=a.state,p)null==E.getDerivedStateFromProps&&null!=a.componentWillMount&&a.componentWillMount(),null!=a.componentDidMount&&a.__h.push(a.componentDidMount);else{if(null==E.getDerivedStateFromProps&&b!==v&&null!=a.componentWillReceiveProps&&a.componentWillReceiveProps(b,x),!a.__e&&null!=a.shouldComponentUpdate&&!1===a.shouldCompLine truncated
|
||||
@@ -0,0 +1,43 @@
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; height: 100%; font: 16px sans-serif; }
|
||||
select, input, label::before, textarea { outline: none; box-shadow:none !important; border: 1px solid #ccc !important; }
|
||||
code, pre { color: #373; font-family: monospace; font-weight: bolder; font-size: smaller; background: #ddd; padding: 0.1em 0.3em; border-radius: 0.2em; }
|
||||
textarea, input, .addon { font-size: 15px; border: 1px solid #ccc; padding: 0.5em; }
|
||||
a, a:visited, a:active { color: #55f; }
|
||||
.addon { background: #eee; min-width: 9em;}
|
||||
.btn {
|
||||
background: #ccc; border-radius: 0.3em; border: 0; color: #fff; cursor: pointer;
|
||||
display: inline-block; padding: 0.6em 2em; font-weight: bolder;
|
||||
}
|
||||
.btn[disabled] { opacity: 0.5; cursor: auto;}
|
||||
.smooth { transition: all .2s; }
|
||||
.container { margin: 0 20px; width: auto; }
|
||||
.d-flex { display: flex; }
|
||||
.d-none { display: none; }
|
||||
.border { border: 1px solid #ddd; }
|
||||
.rounded { border-radius: 0.5em; }
|
||||
.nowrap { white-space: nowrap; }
|
||||
.msg { background: #def; border-left: 5px solid #59d; padding: 0.5em; font-size: 90%; margin: 1em 0; }
|
||||
.section { margin: 0 1em; }
|
||||
.topic, .data, .qos { padding: 0.2em 0.5em; border-radius: 0.4em; margin-right: 0.5em; }
|
||||
.qos { background: #efa; }
|
||||
.topic { background: #fea; }
|
||||
.data { background: #aef; }
|
||||
|
||||
/* Grid */
|
||||
.row { display: flex; flex-wrap: wrap; }
|
||||
.col { margin: 0; padding: 0; overflow: auto; }
|
||||
.col-12 { width: 100%; }
|
||||
.col-11 { width: 91.66%; }
|
||||
.col-10 { width: 83.33%; }
|
||||
.col-9 { width: 75%; }
|
||||
.col-8 { width: 66.66%; }
|
||||
.col-7 { width: 58.33%; }
|
||||
.col-6 { width: 50%; }
|
||||
.col-5 { width: 41.66%; }
|
||||
.col-4 { width: 33.33%; }
|
||||
.col-3 { width: 25%; }
|
||||
.col-2 { width: 16.66%; }
|
||||
.col-1 { width: 8.33%; }
|
||||
@media (min-width: 1310px) { .container { margin: auto; width: 1270px; } }
|
||||
@media (max-width: 920px) { .row .col { width: 100%; } }
|
||||
@@ -1,18 +0,0 @@
|
||||
THISDIR = $(realpath $(CURDIR))
|
||||
ROOTDIR = $(realpath $(CURDIR)/../..)
|
||||
|
||||
all: example
|
||||
|
||||
example:
|
||||
true
|
||||
|
||||
build: main/main.c Makefile
|
||||
docker run --rm -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) espressif/idf idf.py build
|
||||
|
||||
COMPORT ?= /dev/cu.SLAB_USBtoUART
|
||||
ESPTOOL ?= esptool.py
|
||||
flash:
|
||||
cd build && $(ESPTOOL) --chip esp32 -p $(COMPORT) -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 40m --flash_size 2MB 0x8000 partition_table/partition-table.bin 0x1000 bootloader/bootloader.bin 0x100000 mongoose-esp32-example.bin
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
@@ -1,28 +0,0 @@
|
||||
# An example ESP32 application
|
||||
|
||||
To build this application, follow these steps:
|
||||
|
||||
1. Make sure you have Docker installed
|
||||
2. Clone whole mongoose repository on your machine:
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/cesanta/mongoose.git
|
||||
```
|
||||
3. Start command line or terminal, and go into the example's directory:
|
||||
```sh
|
||||
cd mongoose/examples/esp32
|
||||
```
|
||||
4. Run `make`. This invokes a Docker-based build. A firmware will be built
|
||||
in the `build/` directory:
|
||||
```sh
|
||||
make
|
||||
```
|
||||
|
||||
# Flashing built firmware
|
||||
|
||||
If you have a real board to flash to, you can flash a built firmware.
|
||||
First, make sure to have `esptool.py` script installed on your workstation.
|
||||
Then, connect the board to your workstation and run the following command:
|
||||
|
||||
```sh
|
||||
make flash COMPORT=YOUR_USB_PORT
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
THISDIR = $(realpath $(CURDIR))
|
||||
ROOTDIR = $(realpath $(CURDIR)/../../..)
|
||||
DOCKER ?= docker run --rm $(DA) -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) espressif/idf
|
||||
CMD ?= idf.py build
|
||||
PORT ?= /dev/ttyUSB0
|
||||
|
||||
all: example
|
||||
|
||||
example:
|
||||
true
|
||||
|
||||
build: Makefile $(wildcard main/*)
|
||||
$(DOCKER) $(CMD)
|
||||
|
||||
flash:
|
||||
flash: CMD = idf.py flash
|
||||
flash: DA = --device $(PORT)
|
||||
flash: build
|
||||
|
||||
.PHONY: build
|
||||
|
||||
dashboard.hex: build
|
||||
esputil mkhex \
|
||||
0x8000 build/partition_table/partition-table.bin \
|
||||
0x1000 build/bootloader/bootloader.bin \
|
||||
0x100000 build/mongoose-esp32-example.bin > $@
|
||||
|
||||
flash2: dashboard.hex
|
||||
esputil -p $(PORT) -b 921600 -fp 0x220 flash dashboard.hex
|
||||
esputil -p $(PORT) monitor
|
||||
|
||||
ESPTOOL ?= esptool.py
|
||||
|
||||
flash3:
|
||||
cd build && $(ESPTOOL) --chip esp32 -p $(PORT) -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 40m --flash_size 2MB 0x8000 partition_table/partition-table.bin 0x1000 bootloader/bootloader.bin 0x100000 mongoose-esp32-example.bin
|
||||
|
||||
clean:
|
||||
test -d build && $(DOCKER) rm -rf build sdkconfig || true
|
||||
@@ -0,0 +1 @@
|
||||
See detailed tutorial at https://mongoose.ws/tutorials/esp32/device-dashboard
|
||||
@@ -0,0 +1,7 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
"wifi.c"
|
||||
"net.c"
|
||||
"packed_fs.c"
|
||||
"mongoose.c")
|
||||
component_compile_options(-DMG_ENABLE_LINES)
|
||||
component_compile_options(-DMG_ENABLE_PACKED_FS)
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2020 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "esp_spiffs.h"
|
||||
//#include "freertos/FreeRTOS.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
const char *s_listening_url = "http://0.0.0.0:80";
|
||||
void device_dashboard_fn(struct mg_connection *, int, void *, void *);
|
||||
|
||||
#define WIFI_SSID "YOUR_WIFI_NETWORK_NAME" // SET THIS!
|
||||
#define WIFI_PASS "YOUR_WIFI_PASSWORD" // SET THIS!
|
||||
#define FS_ROOT "/spiffs"
|
||||
|
||||
void app_main(void) {
|
||||
// Mount filesystem
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = FS_ROOT, .max_files = 20, .format_if_mount_failed = true};
|
||||
int res = esp_vfs_spiffs_register(&conf);
|
||||
MG_INFO(("FS %s, %d", conf.base_path, res));
|
||||
mg_file_printf(&mg_fs_posix, FS_ROOT "/hello.txt", "%s", "hello from ESP");
|
||||
|
||||
// Setup wifi. This function is implemented in wifi.c
|
||||
// It blocks until connected to the configured WiFi network
|
||||
void wifi_init(const char *ssid, const char *pass);
|
||||
wifi_init(WIFI_SSID, WIFI_PASS);
|
||||
|
||||
// Connected to WiFi, now start HTTP server
|
||||
struct mg_mgr mgr;
|
||||
mg_log_set(MG_LL_DEBUG); // Set log level
|
||||
mg_mgr_init(&mgr);
|
||||
MG_INFO(("Mongoose v%s on %s", MG_VERSION, s_listening_url));
|
||||
mg_http_listen(&mgr, s_listening_url, device_dashboard_fn, NULL);
|
||||
for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../mongoose.h
|
||||
@@ -0,0 +1 @@
|
||||
../../../device-dashboard/net.c
|
||||
@@ -0,0 +1 @@
|
||||
../../../device-dashboard/packed_fs.c
|
||||