Compare commits
948 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
53bc33fc3e | |
![]() |
1a7ee04d0b | |
![]() |
4a7a683ba7 | |
![]() |
8700d50c81 | |
![]() |
3bab5694b8 | |
![]() |
e205214ba4 | |
![]() |
73e9daed05 | |
![]() |
426f97ddc2 | |
![]() |
88e526cce3 | |
![]() |
9e14c2125d | |
![]() |
889c78c77a | |
![]() |
3b1dbe2209 | |
![]() |
980d39f70d | |
![]() |
461f4c2509 | |
![]() |
d2176fd6ea | |
![]() |
c9511593a3 | |
![]() |
66553d143d | |
![]() |
696350343b | |
![]() |
1f773c44fc | |
![]() |
87c22d4a96 | |
![]() |
9f35dd9992 | |
![]() |
14bf5216e0 | |
![]() |
1d196d633a | |
![]() |
de607e3e3a | |
![]() |
d931cd1b84 | |
![]() |
b3f93dd678 | |
![]() |
7f9ecd450d | |
![]() |
69462e4b60 | |
![]() |
d3732aed20 | |
![]() |
6213028f0f | |
![]() |
281813369e | |
![]() |
1014eece5f | |
![]() |
6f484ab2e3 | |
![]() |
0bd6d9a967 | |
![]() |
25b2f18c4c | |
![]() |
1556084dbd | |
![]() |
0571100229 | |
![]() |
d6909f439c | |
![]() |
36b5c90b65 | |
![]() |
f3ac957485 | |
![]() |
6f834517ca | |
![]() |
44c3f7dc4e | |
![]() |
5bb77c1c14 | |
![]() |
3ee1478a58 | |
![]() |
df9e0ef26b | |
![]() |
0e02ca06ee | |
![]() |
643bcb1a37 | |
![]() |
88b20a73ea | |
![]() |
e8a8165635 | |
![]() |
588d4eb36e | |
![]() |
efea80b593 | |
![]() |
c75d93f3c4 | |
![]() |
bee1e0e074 | |
![]() |
4375a935f0 | |
![]() |
7d5fe7c9f6 | |
![]() |
e801d53d51 | |
![]() |
bf0a03d11d | |
![]() |
ec757b1b29 | |
![]() |
3c3b120b9b | |
![]() |
046035ab9e | |
![]() |
52b481e831 | |
![]() |
73422eb8ea | |
![]() |
b087e08f13 | |
![]() |
1e1b405c32 | |
![]() |
dccea4e474 | |
![]() |
91d86bad0c | |
![]() |
bfe4df68a8 | |
![]() |
dd6a43d901 | |
![]() |
726cd6d4e7 | |
![]() |
d730cd9260 | |
![]() |
eb8cfd3c91 | |
![]() |
8f9bd4a5e7 | |
![]() |
3ebf434945 | |
![]() |
92a7194c6e | |
![]() |
24ad841d7f | |
![]() |
b45f806309 | |
![]() |
cf7ec86106 | |
![]() |
37f7517c51 | |
![]() |
2a04803dc6 | |
![]() |
3700406687 | |
![]() |
1ba0ff7871 | |
![]() |
be23cd44b5 | |
![]() |
43d1b9da33 | |
![]() |
bda30bae09 | |
![]() |
de21cb16da | |
![]() |
0a153bc656 | |
![]() |
149451c270 | |
![]() |
d7de315c20 | |
![]() |
61c09b69b9 | |
![]() |
863071824b | |
![]() |
82f2fbef90 | |
![]() |
c736348bed | |
![]() |
309737dd33 | |
![]() |
3a9ea9af79 | |
![]() |
bc68f3e79f | |
![]() |
85be07efe5 | |
![]() |
fe9ff346a8 | |
![]() |
ec878e0dae | |
![]() |
b1c2b36a37 | |
![]() |
11f349ac89 | |
![]() |
67373e4f7e | |
![]() |
dd46830b9d | |
![]() |
63b333746d | |
![]() |
2c6033f57a | |
![]() |
4d2ea00130 | |
![]() |
c8c57d5340 | |
![]() |
4013291787 | |
![]() |
25d944e4b7 | |
![]() |
de6f886586 | |
![]() |
800625e8a1 | |
![]() |
bce0700cd9 | |
![]() |
cccecf58f6 | |
![]() |
c2308cb987 | |
![]() |
ffbf660143 | |
![]() |
edb96d3ca8 | |
![]() |
a49e5e67ba | |
![]() |
90ddd78bb8 | |
![]() |
9bd98aca1e | |
![]() |
49fdced812 | |
![]() |
6bc0034d67 | |
![]() |
8e0cf2603c | |
![]() |
4a0035b9eb | |
![]() |
19eebde8e2 | |
![]() |
cd52670c0b | |
![]() |
fd62ccd9aa | |
![]() |
1a4a106179 | |
![]() |
ee8851b0af | |
![]() |
40150a307c | |
![]() |
a5b1d5631e | |
![]() |
26a4f66a75 | |
![]() |
73222de627 | |
![]() |
ddb480921e | |
![]() |
5945d2a38d | |
![]() |
a04ec59ba5 | |
![]() |
310686508f | |
![]() |
66b5e3c962 | |
![]() |
cf0af59e42 | |
![]() |
9dab0c4bc5 | |
![]() |
94ee85bd83 | |
![]() |
672aae441b | |
![]() |
3d0223e56a | |
![]() |
93d2521511 | |
![]() |
aafd84fff7 | |
![]() |
8f8056a3e3 | |
![]() |
c1a9bc63bf | |
![]() |
2a6fe06ba3 | |
![]() |
6c3e60999c | |
![]() |
10a025369d | |
![]() |
e490aa83f6 | |
![]() |
eec9790a0b | |
![]() |
de140d02a5 | |
![]() |
c0fab4a38c | |
![]() |
313008b21b | |
![]() |
2aed62b9f8 | |
![]() |
4fe9d89912 | |
![]() |
e989e66d9b | |
![]() |
3367bc667b | |
![]() |
93f5d59bf4 | |
![]() |
1af5af0696 | |
![]() |
e46b4de0bb | |
![]() |
464b730cd8 | |
![]() |
837cbe8a85 | |
![]() |
8f3a674f9d | |
![]() |
3e300defa1 | |
![]() |
cd685d6590 | |
![]() |
05f6c52f67 | |
![]() |
a565261517 | |
![]() |
e5a154e3a0 | |
![]() |
286e6b5bbb | |
![]() |
ea031aaf4d | |
![]() |
4119865370 | |
![]() |
da00ca4d98 | |
![]() |
9288757f97 | |
![]() |
66962e1bdb | |
![]() |
0b4ea1e385 | |
![]() |
11d08aea99 | |
![]() |
1934e4ba7c | |
![]() |
1cfa4f9e9e | |
![]() |
13b4e159df | |
![]() |
e84e05a45e | |
![]() |
313e92b1c3 | |
![]() |
9dfd31da10 | |
![]() |
c3029d06ed | |
![]() |
7e1cad0f9f | |
![]() |
f0eadf2c98 | |
![]() |
9ccb0fab68 | |
![]() |
e5b20c15c9 | |
![]() |
f11a553231 | |
![]() |
bbd711eb24 | |
![]() |
981d005423 | |
![]() |
3b2422607f | |
![]() |
e05b4a3d1c | |
![]() |
39e5418b3e | |
![]() |
fc7f98b79b | |
![]() |
4451c4decd | |
![]() |
6fbe9ec1df | |
![]() |
77ee927ce8 | |
![]() |
d829c4e3dd | |
![]() |
b8eb47b0cc | |
![]() |
ff596c6f1f | |
![]() |
393f6c3eed | |
![]() |
f6b67052ca | |
![]() |
9edba110f6 | |
![]() |
612042efaa | |
![]() |
3a3e52d46e | |
![]() |
0fe2329809 | |
![]() |
ab7c360304 | |
![]() |
024964d380 | |
![]() |
e19efe31e9 | |
![]() |
6220a85bd5 | |
![]() |
1e5ebb9c9b | |
![]() |
070a882173 | |
![]() |
6227b17d16 | |
![]() |
927ad8c54e | |
![]() |
339ec2f3c0 | |
![]() |
a31c21bb4d | |
![]() |
6f699a72a2 | |
![]() |
547a73727f | |
![]() |
db040aa301 | |
![]() |
bc08514f7e | |
![]() |
7f0892d8b2 | |
![]() |
a13272d6be | |
![]() |
d63b9ef39c | |
![]() |
800fa28f83 | |
![]() |
37d4d27dce | |
![]() |
4549ecf405 | |
![]() |
e2838539b4 | |
![]() |
72dfcb23ee | |
![]() |
6f2b74dc52 | |
![]() |
8b8b198d93 | |
![]() |
a10267507b | |
![]() |
271884f2d0 | |
![]() |
781ac33b10 | |
![]() |
9d7833b71c | |
![]() |
a38e7b6b91 | |
![]() |
a32d9f025b | |
![]() |
4b06a8ae11 | |
![]() |
6c6057c97f | |
![]() |
8b14c32028 | |
![]() |
69ed6e89b7 | |
![]() |
ab94c0098c | |
![]() |
1383a5bb1a | |
![]() |
721fea39b2 | |
![]() |
2226d3b34b | |
![]() |
d5bbc95b1f | |
![]() |
44b4604552 | |
![]() |
f51e9ad4c9 | |
![]() |
3f86376d09 | |
![]() |
0d9bf3ccc5 | |
![]() |
f9e5559ee0 | |
![]() |
eeabe48cef | |
![]() |
d306b65909 | |
![]() |
19798b894f | |
![]() |
b7c618aec8 | |
![]() |
44c1e0b964 | |
![]() |
47a2e5b1e3 | |
![]() |
6aff7445f1 | |
![]() |
51f061a325 | |
![]() |
1f6a71e6af | |
![]() |
aed825a7ad | |
![]() |
afd35f7291 | |
![]() |
b8707f90cb | |
![]() |
31127c4edd | |
![]() |
53b09e70a4 | |
![]() |
d854cb9b22 | |
![]() |
f83a345227 | |
![]() |
f2ca8fc6fa | |
![]() |
4ea82828d4 | |
![]() |
3677fb8a37 | |
![]() |
1063b38626 | |
![]() |
ebe8831900 | |
![]() |
c3f5423823 | |
![]() |
2ad20ea54d | |
![]() |
56002e5555 | |
![]() |
6bc6e6ffec | |
![]() |
ea8428621a | |
![]() |
1455bebba2 | |
![]() |
22465a5558 | |
![]() |
4fa48cb74b | |
![]() |
c2d80bd55c | |
![]() |
fea40c1d9a | |
![]() |
a810743fa7 | |
![]() |
79690a90a2 | |
![]() |
5e52d3c6dd | |
![]() |
4dd1c04ae8 | |
![]() |
071ffe39b5 | |
![]() |
9c3340be56 | |
![]() |
cd20fcf943 | |
![]() |
8d5218f67c | |
![]() |
307105c96e | |
![]() |
f5554a4e00 | |
![]() |
f5be061c4d | |
![]() |
6a8bd52902 | |
![]() |
a0abdcb198 | |
![]() |
ac5cc41b95 | |
![]() |
ee20868cb4 | |
![]() |
545c7ca4c1 | |
![]() |
9646426964 | |
![]() |
fa7fc0024e | |
![]() |
434d6e5d79 | |
![]() |
98a0abd7fa | |
![]() |
f803dfd6cf | |
![]() |
4b12828262 | |
![]() |
209b355481 | |
![]() |
114673d756 | |
![]() |
4d92f37a98 | |
![]() |
34b3a69e9e | |
![]() |
077894bb4d | |
![]() |
a2ae2ea38e | |
![]() |
df92e1d8db | |
![]() |
2fa3aa55f4 | |
![]() |
d2549c0208 | |
![]() |
8e43cb894c | |
![]() |
2099d7acbb | |
![]() |
f9ac3e659f | |
![]() |
e02673aa79 | |
![]() |
08e15087cd | |
![]() |
1607449bb7 | |
![]() |
1d7534adae | |
![]() |
205121da43 | |
![]() |
129309e910 | |
![]() |
3cd5754b63 | |
![]() |
2db87096a3 | |
![]() |
0c9a04c6d8 | |
![]() |
f31c38158f | |
![]() |
8d91e18650 | |
![]() |
b0718c2e9a | |
![]() |
ed0661917e | |
![]() |
74b0373c51 | |
![]() |
cb3f858fcf | |
![]() |
fc7d0882b2 | |
![]() |
2a701bdc63 | |
![]() |
27e7b3f30a | |
![]() |
0f8f8d63a4 | |
![]() |
8d6c14cb21 | |
![]() |
a74d389862 | |
![]() |
3fb1487cdb | |
![]() |
88b7b1ffae | |
![]() |
4c7902c0a2 | |
![]() |
6c6e488295 | |
![]() |
4490bbc531 | |
![]() |
cad3839e87 | |
![]() |
566f23b01f | |
![]() |
6ba8d6612d | |
![]() |
15f5b64005 | |
![]() |
e454c7e412 | |
![]() |
7661f97a98 | |
![]() |
97889e7021 | |
![]() |
2ed25887e1 | |
![]() |
f8575fb7eb | |
![]() |
17c33ba7bc | |
![]() |
814cb4451e | |
![]() |
edff93a66d | |
![]() |
858bfeb457 | |
![]() |
76828b0ba0 | |
![]() |
da5bed9fce | |
![]() |
04db845e94 | |
![]() |
938789e554 | |
![]() |
92374ed20f | |
![]() |
8dc809380e | |
![]() |
88bb6fa2aa | |
![]() |
bbd5f14cfe | |
![]() |
b1b2cf23bc | |
![]() |
bc847b3611 | |
![]() |
a75a358a96 | |
![]() |
6092abc03f | |
![]() |
0ef7833d81 | |
![]() |
f71ff37caf | |
![]() |
a964b14418 | |
![]() |
8ae678ce07 | |
![]() |
70118937fa | |
![]() |
c145fd3de9 | |
![]() |
d2a9130cfb | |
![]() |
5cef28bf15 | |
![]() |
0c1fc4d2f3 | |
![]() |
96b508be1e | |
![]() |
d84b33a203 | |
![]() |
cef9a3e6f3 | |
![]() |
2a0e390472 | |
![]() |
3a4fe9cd60 | |
![]() |
f88f3fd858 | |
![]() |
dabd8032a1 | |
![]() |
6f5f1f84a7 | |
![]() |
a2bb562c3e | |
![]() |
d129b028f1 | |
![]() |
fbcbf3bf92 | |
![]() |
f1c3fb5baf | |
![]() |
ccc9a98e96 | |
![]() |
32bbe8ad96 | |
![]() |
eeb199b816 | |
![]() |
3904c7d1f8 | |
![]() |
7f6449ece2 | |
![]() |
f4b59ea0d5 | |
![]() |
ea88ad4c26 | |
![]() |
b08826708a | |
![]() |
0614e81ed4 | |
![]() |
4dae2a283f | |
![]() |
bdce179f88 | |
![]() |
8b15c7fecd | |
![]() |
2adf28b6dc | |
![]() |
4037f6bab1 | |
![]() |
606bc19def | |
![]() |
85b3b0051c | |
![]() |
7994801daa | |
![]() |
0625a20853 | |
![]() |
c08b9f4682 | |
![]() |
240f86c7c1 | |
![]() |
bb9566345a | |
![]() |
92869beb32 | |
![]() |
f0dcbcd54f | |
![]() |
c47651282f | |
![]() |
e26d8e870f | |
![]() |
13f020901a | |
![]() |
947d205c09 | |
![]() |
9a4755c30b | |
![]() |
f54fb07d61 | |
![]() |
897ed8206a | |
![]() |
fd884ad9fa | |
![]() |
4e87effeba | |
![]() |
142611deaa | |
![]() |
06d0bd1c82 | |
![]() |
b3eb1fff13 | |
![]() |
d7e4373db7 | |
![]() |
13b3971daf | |
![]() |
f60f82cb06 | |
![]() |
9541970fb3 | |
![]() |
4f0373a3a2 | |
![]() |
2e1ce7d3fa | |
![]() |
8d50fcdf96 | |
![]() |
5c6fb08a5d | |
![]() |
076bdbdb5d | |
![]() |
b0c31b2e61 | |
![]() |
1fedc1917d | |
![]() |
fc74f2c717 | |
![]() |
435273994a | |
![]() |
b48029528f | |
![]() |
dc9bbe6f66 | |
![]() |
8eb348202a | |
![]() |
249184661e | |
![]() |
b190fe3609 | |
![]() |
d1496ddb1e | |
![]() |
eb231231c7 | |
![]() |
2782a3e517 | |
![]() |
0fa65f0eac | |
![]() |
be16f15917 | |
![]() |
66afa66287 | |
![]() |
a94c666d76 | |
![]() |
c5a6e2052e | |
![]() |
d28c9d67ed | |
![]() |
e00a2cf793 | |
![]() |
2a3a244024 | |
![]() |
8b6c03216e | |
![]() |
7813e88a69 | |
![]() |
976a912c07 | |
![]() |
e2d311e3ea | |
![]() |
755ce7f178 | |
![]() |
c3bb45e0a2 | |
![]() |
eada674e50 | |
![]() |
627e46d807 | |
![]() |
fbc63ace3b | |
![]() |
1f84001e6e | |
![]() |
dc86f82795 | |
![]() |
3030344426 | |
![]() |
4f1c767f2c | |
![]() |
402b9eda3e | |
![]() |
a0e8165361 | |
![]() |
24bccc522b | |
![]() |
04b9f1c2e7 | |
![]() |
3c42868b9e | |
![]() |
2073d2022b | |
![]() |
0eb8a82f2b | |
![]() |
c30c312783 | |
![]() |
4f8e344c63 | |
![]() |
e8c6257a7f | |
![]() |
83932c5aaa | |
![]() |
56b9de1893 | |
![]() |
62246fbe20 | |
![]() |
43826465ec | |
![]() |
1aa316ee62 | |
![]() |
7f2c174d29 | |
![]() |
90e97d7bbb | |
![]() |
97a9ee8d57 | |
![]() |
fd06e84e56 | |
![]() |
06af09c939 | |
![]() |
a003beb8f7 | |
![]() |
2de8e737eb | |
![]() |
b8196d5214 | |
![]() |
187b3bcbab | |
![]() |
2ee4268048 | |
![]() |
a579ace0e2 | |
![]() |
531280a927 | |
![]() |
d07dd5eb15 | |
![]() |
7f5759382a | |
![]() |
254adfa765 | |
![]() |
6b70eccf67 | |
![]() |
f5a9be23de | |
![]() |
f13bead10e | |
![]() |
982956ca91 | |
![]() |
906a62596f | |
![]() |
95e346558c | |
![]() |
2300341153 | |
![]() |
7f60abcaea | |
![]() |
a1c2b16d67 | |
![]() |
63cb166444 | |
![]() |
76b3f9692e | |
![]() |
7be0569a89 | |
![]() |
38a63981eb | |
![]() |
7676370be0 | |
![]() |
d99f598784 | |
![]() |
53d203372c | |
![]() |
98a9eb539b | |
![]() |
6b9209edad | |
![]() |
5c7bcac281 | |
![]() |
c88abc010e | |
![]() |
69249dc471 | |
![]() |
21fe23b5a4 | |
![]() |
1559ee0f0f | |
![]() |
91c9c6d2f7 | |
![]() |
32d7b95961 | |
![]() |
741e06a2cc | |
![]() |
fa1ec452c0 | |
![]() |
026078a6d2 | |
![]() |
71a5c5c122 | |
![]() |
502edf6221 | |
![]() |
6305e41e8f | |
![]() |
18cb99f1f3 | |
![]() |
86d0a9238b | |
![]() |
fd867fca70 | |
![]() |
27c66e31fc | |
![]() |
fee91ee2b2 | |
![]() |
8422f446a9 | |
![]() |
a821afd290 | |
![]() |
98c22d5835 | |
![]() |
84a18ffde6 | |
![]() |
73276a323c | |
![]() |
e8bf8e3587 | |
![]() |
e70695536a | |
![]() |
a0e69bdb4a | |
![]() |
8ac0c0625e | |
![]() |
574be119b1 | |
![]() |
a6b6b872f1 | |
![]() |
290eb11441 | |
![]() |
aa6326c3b2 | |
![]() |
0a4db75572 | |
![]() |
4eb8ac28a8 | |
![]() |
b8929d6de5 | |
![]() |
db135676dd | |
![]() |
27214fa0a4 | |
![]() |
8f626c2833 | |
![]() |
b251dbdf8c | |
![]() |
839928a1fe | |
![]() |
5ccdc30654 | |
![]() |
19c7ef2f36 | |
![]() |
22f90b08fa | |
![]() |
60fc32fd7e | |
![]() |
dbb330efcf | |
![]() |
354e51b265 | |
![]() |
085f165318 | |
![]() |
efb2eeab00 | |
![]() |
9c0bbc3f81 | |
![]() |
ad5b8cca37 | |
![]() |
0c687233c7 | |
![]() |
9dcdf1a000 | |
![]() |
79085e32a0 | |
![]() |
e711183cf0 | |
![]() |
e249949738 | |
![]() |
7b8cfe9f4b | |
![]() |
8127f5e529 | |
![]() |
80f20cf7f4 | |
![]() |
0518f7e40e | |
![]() |
be854a047b | |
![]() |
652b5e1358 | |
![]() |
f86182f46a | |
![]() |
8a903b7463 | |
![]() |
9c83b01e4a | |
![]() |
97bad3c217 | |
![]() |
232dccaf5d | |
![]() |
1d14585ccb | |
![]() |
5b809926ad | |
![]() |
7f00a1fe6e | |
![]() |
313b129e7f | |
![]() |
0a2c9a7b74 | |
![]() |
8661241fd8 | |
![]() |
47942fb9c8 | |
![]() |
55ed669de7 | |
![]() |
0240da91db | |
![]() |
784ddda2aa | |
![]() |
84d32ae347 | |
![]() |
d979ee6381 | |
![]() |
1cf8a3c76d | |
![]() |
b8867cc165 | |
![]() |
2f4f470499 | |
![]() |
0e2c016ef4 | |
![]() |
e1780991d4 | |
![]() |
12b6d1918d | |
![]() |
5f90caa19a | |
![]() |
ea0aebcdc9 | |
![]() |
f4bd323db9 | |
![]() |
1f9caf057d | |
![]() |
dd077d0cf7 | |
![]() |
b6d3852a0f | |
![]() |
da8cf5ea0d | |
![]() |
f04da2a91c | |
![]() |
b6c4f296cc | |
![]() |
5c9d312ea2 | |
![]() |
a5cbb3f0fc | |
![]() |
e0bfcb4b42 | |
![]() |
50829d9b02 | |
![]() |
87f59f5075 | |
![]() |
ac1f28c2cb | |
![]() |
ea64565c21 | |
![]() |
1aaff4c40d | |
![]() |
774732d37f | |
![]() |
6bc9ca53ff | |
![]() |
de7af7c867 | |
![]() |
8d67f98d2a | |
![]() |
d57c020ec0 | |
![]() |
4c406dfab7 | |
![]() |
19a6cabf70 | |
![]() |
ed8f1fa961 | |
![]() |
405c5e02db | |
![]() |
8c559f345f | |
![]() |
079149af3b | |
![]() |
507a4fd5dd | |
![]() |
2ba8f7a6df | |
![]() |
da788fab41 | |
![]() |
8d6eae6a4d | |
![]() |
6bd32c243c | |
![]() |
dedb98c15f | |
![]() |
4b14d370b4 | |
![]() |
c657df777f | |
![]() |
a977afc5a8 | |
![]() |
8215abc978 | |
![]() |
212fa65a4e | |
![]() |
3495cf9cf6 | |
![]() |
d5f8c00eef | |
![]() |
6efe519d70 | |
![]() |
6712efc5fd | |
![]() |
c4025dab40 | |
![]() |
8eaf643de2 | |
![]() |
faafb0d6f1 | |
![]() |
6f94c9f4f9 | |
![]() |
87e271ce89 | |
![]() |
6e46c10e3f | |
![]() |
8555acfbed | |
![]() |
33ebb628f5 | |
![]() |
fa6c6196bb | |
![]() |
2cdef7f569 | |
![]() |
245a70f736 | |
![]() |
98251fbb02 | |
![]() |
8af5fa5ac3 | |
![]() |
9432137e94 | |
![]() |
71545c6e94 | |
![]() |
693b5ecc3f | |
![]() |
6e57d80bdf | |
![]() |
4e9fe4ae7a | |
![]() |
e41fbca364 | |
![]() |
69d87b259c | |
![]() |
c7d44453aa | |
![]() |
3c5771e4bf | |
![]() |
011e900aee | |
![]() |
ddb7ec451b | |
![]() |
e57d476f4b | |
![]() |
71bb473a1a | |
![]() |
7d3a525847 | |
![]() |
fc593899a5 | |
![]() |
2abdc41c42 | |
![]() |
104c6e349f | |
![]() |
085de4c049 | |
![]() |
5072ad0175 | |
![]() |
864215ad3c | |
![]() |
c418e4b263 | |
![]() |
294528b6a9 | |
![]() |
c385dcc875 | |
![]() |
7bc4e56220 | |
![]() |
f9aa0c8c63 | |
![]() |
9a09b7581c | |
![]() |
448a97c199 | |
![]() |
e7af24d3e4 | |
![]() |
de09cad3b4 | |
![]() |
e065834e95 | |
![]() |
c2688aab0a | |
![]() |
4e3aa00030 | |
![]() |
7ff6a499e0 | |
![]() |
2a621e2f00 | |
![]() |
cb2e97cbb2 | |
![]() |
a501decb1c | |
![]() |
3152d46f55 | |
![]() |
5d81693e5a | |
![]() |
a4c544d651 | |
![]() |
f16014bfd1 | |
![]() |
dbfec00e68 | |
![]() |
e0620d40d8 | |
![]() |
f49f84d82d | |
![]() |
bd87b5b198 | |
![]() |
8801d7eaa0 | |
![]() |
1fe846a86c | |
![]() |
6010e65af9 | |
![]() |
4cad4e39c2 | |
![]() |
afd76cc15e | |
![]() |
7410ac6504 | |
![]() |
ee31e44c1a | |
![]() |
8f7e043535 | |
![]() |
0096adf9ee | |
![]() |
a757b3be13 | |
![]() |
3797242dfe | |
![]() |
ed43424a9d | |
![]() |
f612f9cb45 | |
![]() |
fc38737b7e | |
![]() |
c67f0d1710 | |
![]() |
f2b0a1bdbc | |
![]() |
571ef34a1d | |
![]() |
d4b07cca61 | |
![]() |
42868887cf | |
![]() |
6fd340f12b | |
![]() |
6f620d174e | |
![]() |
a8dee31134 | |
![]() |
dec4f8ffb1 | |
![]() |
4cbb463a23 | |
![]() |
d825ec5540 | |
![]() |
bf418ead32 | |
![]() |
504ea18bcc | |
![]() |
9d8308d6f7 | |
![]() |
641ba988de | |
![]() |
3921a69cf2 | |
![]() |
9d17e5f83c | |
![]() |
5bb7719f56 | |
![]() |
d27117216b | |
![]() |
39d461296e | |
![]() |
746738a25c | |
![]() |
93d0428e7f | |
![]() |
bd4d88ade4 | |
![]() |
5e00b20072 | |
![]() |
3e3ad18f40 | |
![]() |
ce04296919 | |
![]() |
b66731a1f4 | |
![]() |
0bbcc8c786 | |
![]() |
e21676afc2 | |
![]() |
cb49531eb5 | |
![]() |
493c439ebc | |
![]() |
59b31a85d7 | |
![]() |
987e0754ec | |
![]() |
ef15148c5b | |
![]() |
2f331f0f9d | |
![]() |
d80309f129 | |
![]() |
6d77bf42e5 | |
![]() |
0445717cbb | |
![]() |
fce9acd721 | |
![]() |
604e985447 | |
![]() |
7d989e82b2 | |
![]() |
08f7e7373c | |
![]() |
fd5ce8084a | |
![]() |
209a88c5e4 | |
![]() |
5d76123682 | |
![]() |
d4824a5e63 | |
![]() |
ba8f326502 | |
![]() |
da2b764be0 | |
![]() |
f225a4279a | |
![]() |
b473884621 | |
![]() |
29ecee243c | |
![]() |
3871a0b083 | |
![]() |
46bd4f2b32 | |
![]() |
fb388cc103 | |
![]() |
fed514c2e3 | |
![]() |
bfa2f2fd38 | |
![]() |
638821cf66 | |
![]() |
1bed755e88 | |
![]() |
8f593ea098 | |
![]() |
49bbee536e | |
![]() |
71eda445df | |
![]() |
da5820f23b | |
![]() |
4e3d499ccd | |
![]() |
d038f1268f | |
![]() |
8d83215bd9 | |
![]() |
66817cc21a | |
![]() |
1901dd3a20 | |
![]() |
75cabbf90a | |
![]() |
3c09d0bb1a | |
![]() |
1c5e03d54c | |
![]() |
16d4966361 | |
![]() |
5bfa90e53d | |
![]() |
9a249b3416 | |
![]() |
d44e3e5944 | |
![]() |
65226a32e4 | |
![]() |
a2487d9b2e | |
![]() |
e353eda31f | |
![]() |
f84fa4275d | |
![]() |
98baa520e9 | |
![]() |
2792281043 | |
![]() |
a460e6e0c0 | |
![]() |
4931d96d74 | |
![]() |
7358fa1b65 | |
![]() |
fd8cddf051 | |
![]() |
2f5dd155c1 | |
![]() |
26a98f486a | |
![]() |
c1c2bbf250 | |
![]() |
344cb2582a | |
![]() |
7a86a1aab3 | |
![]() |
a172c4c003 | |
![]() |
6a05e140b0 | |
![]() |
49e7f85746 | |
![]() |
3656428ea3 | |
![]() |
ee3c9a081c | |
![]() |
b3e2830cce | |
![]() |
8d3c537332 | |
![]() |
0e85a10dc5 | |
![]() |
05c521e02a | |
![]() |
b6da1752a1 | |
![]() |
7dc7409a15 | |
![]() |
f30d3fc4eb | |
![]() |
43c8108e3c | |
![]() |
5342432c2a | |
![]() |
b3f3923e64 | |
![]() |
77aa69977f | |
![]() |
86497b5187 | |
![]() |
a475c38fc9 | |
![]() |
364c29d1ce | |
![]() |
fe52fedaf5 | |
![]() |
961d47dbd6 | |
![]() |
6a9ce98a34 | |
![]() |
4e677792c1 | |
![]() |
9697498b04 | |
![]() |
b0ec64bb92 | |
![]() |
1ee9f713d5 | |
![]() |
6a314d11e4 | |
![]() |
8dc1be228b | |
![]() |
fad467530c | |
![]() |
f62bbf4aec | |
![]() |
b432163b25 | |
![]() |
91283ca628 | |
![]() |
51a090fee6 | |
![]() |
f09b1eba87 | |
![]() |
42d931ad3e | |
![]() |
1507eb51f8 | |
![]() |
7bc0f89ea3 | |
![]() |
30b895b4d4 | |
![]() |
13f1ac2225 | |
![]() |
4cb735b098 | |
![]() |
5c56a2b764 | |
![]() |
0b4ce5dd44 | |
![]() |
23db485363 | |
![]() |
7f55338e54 | |
![]() |
5342c2eae7 | |
![]() |
a466a2997f | |
![]() |
ae9465873e | |
![]() |
bbb9dcdfcc | |
![]() |
20ce166d46 | |
![]() |
0d67345d8d | |
![]() |
e52e7c8169 | |
![]() |
a2154a10e1 | |
![]() |
144fa34e29 | |
![]() |
8300086835 | |
![]() |
e03dc38160 | |
![]() |
2e79d97e5e | |
![]() |
2ef6480816 | |
![]() |
1222ce54fe | |
![]() |
1a9c7f3508 | |
![]() |
b5a8d53b02 | |
![]() |
6b1db0c51c | |
![]() |
9213d4f37e | |
![]() |
66bd0a9c27 | |
![]() |
ee85a8e6e3 | |
![]() |
6a1ee3830b | |
![]() |
d2e1a84005 | |
![]() |
8f41f7d87d | |
![]() |
1c83bf4ad8 | |
![]() |
ab6472510d | |
![]() |
22665da324 | |
![]() |
7549312b1c | |
![]() |
61c9fe62b4 | |
![]() |
8934b10832 | |
![]() |
5547dd69f7 | |
![]() |
3f5159267c | |
![]() |
2e15b74ce9 | |
![]() |
e0e88119db | |
![]() |
d124c58251 | |
![]() |
0785ee979a | |
![]() |
ba0503ef00 | |
![]() |
34c632cad3 | |
![]() |
58aa3d8606 | |
![]() |
3d83f01f5b | |
![]() |
dcb12a89c9 | |
![]() |
82b7f4cf16 | |
![]() |
7b046af751 | |
![]() |
52ab40b189 | |
![]() |
a8cf0b455b | |
![]() |
4e999e68da | |
![]() |
d576656fbd | |
![]() |
fc1f80c4fc | |
![]() |
9a59395715 | |
![]() |
1acb46486c | |
![]() |
a3d73b35c6 | |
![]() |
b11e5a0e15 | |
![]() |
8361f5d96e | |
![]() |
9edd2c21fd | |
![]() |
87d2a0f524 | |
![]() |
066f304ea3 | |
![]() |
fa5f9ed1ff | |
![]() |
f5982bf1af | |
![]() |
1e267e751c | |
![]() |
161cf07116 | |
![]() |
5a10be3ed9 | |
![]() |
37bcde3050 | |
![]() |
c1ca5cb753 | |
![]() |
af29d10a82 | |
![]() |
ab1613aeb2 | |
![]() |
6bd619c669 | |
![]() |
33942d98cd | |
![]() |
a0337e0ae3 | |
![]() |
79d5974dde | |
![]() |
78f449218e | |
![]() |
4005dbefee | |
![]() |
ea55b67173 | |
![]() |
549c97deb3 | |
![]() |
9297f3ae95 | |
![]() |
e5bfa4997c | |
![]() |
84affe3464 | |
![]() |
f295add527 | |
![]() |
11ad1789f2 | |
![]() |
d9fa2ba6ac | |
![]() |
fc2c42161f | |
![]() |
bce57f2552 | |
![]() |
7843a1e93c | |
![]() |
f52248537e | |
![]() |
c1b5de325e | |
![]() |
ae273593d2 | |
![]() |
13bf921d07 | |
![]() |
96981df093 | |
![]() |
bec1cacdba | |
![]() |
ba3a6d3d1e | |
![]() |
f2439292e8 | |
![]() |
6edbfa33c0 | |
![]() |
4dde181488 | |
![]() |
d8a5458142 | |
![]() |
f387fa54ce | |
![]() |
80d6ff4c48 | |
![]() |
b13a61641a | |
![]() |
99b8b823aa | |
![]() |
69ff481f91 | |
![]() |
2aa84d264d | |
![]() |
321d818454 | |
![]() |
f47f4754f4 | |
![]() |
0488c36a1c | |
![]() |
ab4287937f | |
![]() |
83b1698288 | |
![]() |
d1c56ab75a | |
![]() |
576c27c9c5 | |
![]() |
c6d00ab30e |
|
@ -1,34 +1,77 @@
|
|||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
version: 2.1
|
||||
|
||||
executors:
|
||||
build-executor:
|
||||
machine: true
|
||||
working_directory: /home/circleci/project
|
||||
|
||||
commands:
|
||||
build-commands:
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- cache-v1-{{ .Revision }}-{{ .Environment.CIRCLE_WORKFLOW_ID }}
|
||||
- run:
|
||||
name: After cache restore
|
||||
command: |
|
||||
git clean -xdn
|
||||
mkdir -p docker_cache
|
||||
ls docker_cache
|
||||
#sudo docker images
|
||||
#if [ -f docker_cache/images.tar.gz ]; then gunzip -c docker_cache/images.tar.gz | sudo docker load; fi
|
||||
- run:
|
||||
name: Build and Install Deps
|
||||
command: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
mv .circleci/circle_envs .env
|
||||
echo -e '\ndocker_volumes/db/pg_wal/*' >> .dockerignore
|
||||
sudo docker-compose run web gem install bundler
|
||||
sudo docker-compose run web bundle install
|
||||
sudo docker-compose run web npm install
|
||||
sudo docker-compose run web bundle exec rails db:create
|
||||
sudo docker-compose run web bundle exec rails db:migrate
|
||||
sudo docker-compose run web rake keys:generate
|
||||
- run:
|
||||
name: After cache update
|
||||
command: |
|
||||
git clean -xdn
|
||||
ls docker_cache
|
||||
#sudo docker images
|
||||
#if [ ! -f docker_cache/images.tar.gz ]; then sudo docker save $(sudo docker images ruby -q) | gzip > docker_cache/images.tar.gz; fi
|
||||
- save_cache:
|
||||
key: cache-v1-{{ .Revision }}-{{ .Environment.CIRCLE_WORKFLOW_ID }}
|
||||
paths:
|
||||
- docker_volumes
|
||||
- node_modules
|
||||
- docker_cache
|
||||
rspec-commands:
|
||||
steps:
|
||||
- run:
|
||||
name: Run Ruby Tests
|
||||
command: |
|
||||
sudo docker-compose run web rspec spec
|
||||
sudo docker-compose run web rspec spec --format progress --format RspecJunitFormatter --out test-results/rspec/rspec.xml
|
||||
jest-commands:
|
||||
steps:
|
||||
- run:
|
||||
name: Run JS tests
|
||||
command: |
|
||||
sudo docker-compose run web npm run test-very-slow -- -c .circleci/jest-ci.config.js
|
||||
echo 'export COVERAGE_AVAILABLE=true' >> $BASH_ENV
|
||||
lint-commands:
|
||||
steps:
|
||||
- run:
|
||||
name: Run JS Linters
|
||||
command: |
|
||||
sudo docker-compose run web npm run linters
|
||||
when: always
|
||||
coverage-commands:
|
||||
steps:
|
||||
- run:
|
||||
name: Run JS Tests
|
||||
name: Check coverage status
|
||||
command: |
|
||||
sudo docker-compose run web npm run test-very-slow
|
||||
echo 'export COVERAGE_AVAILABLE=true' >> $BASH_ENV
|
||||
sudo docker-compose run -e CIRCLE_SHA1="$CIRCLE_SHA1" -e CIRCLE_BRANCH="$CIRCLE_BRANCH" -e CIRCLE_PULL_REQUEST="$CIRCLE_PULL_REQUEST" web rake coverage:run
|
||||
no_output_timeout: 15m
|
||||
when: always
|
||||
- run:
|
||||
name: Report coverage to Coveralls
|
||||
command: |
|
||||
|
@ -37,3 +80,60 @@ jobs:
|
|||
sudo docker-compose run -e COVERALLS_REPO_TOKEN=lEX6nkql7y2YFCcIXVq5ORvdvMtYzfZdG web npm run coverage
|
||||
fi
|
||||
when: always # change to `on_success` for a stricter comparison
|
||||
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_test:
|
||||
jobs:
|
||||
#- build
|
||||
- all
|
||||
#- test-api:
|
||||
# requires:
|
||||
# - build
|
||||
#- run-linters:
|
||||
# requires:
|
||||
# - build
|
||||
#- test-fe:
|
||||
# requires:
|
||||
# - build
|
||||
|
||||
jobs:
|
||||
build:
|
||||
executor: build-executor
|
||||
steps:
|
||||
- build-commands
|
||||
all:
|
||||
executor: build-executor
|
||||
steps:
|
||||
- build-commands
|
||||
- rspec-commands
|
||||
- lint-commands
|
||||
- jest-commands
|
||||
- store_test_results:
|
||||
path: test-results
|
||||
- coverage-commands
|
||||
test-api:
|
||||
executor: build-executor
|
||||
steps:
|
||||
- build-commands
|
||||
- rspec-commands
|
||||
- store_test_results:
|
||||
path: test-results
|
||||
run-linters:
|
||||
executor: build-executor
|
||||
steps:
|
||||
- build-commands
|
||||
- lint-commands
|
||||
test-fe:
|
||||
executor: build-executor
|
||||
parallelism: 4
|
||||
steps:
|
||||
- build-commands
|
||||
- run:
|
||||
name: Run JS Tests
|
||||
command: |
|
||||
circleci tests glob **/__tests__/**/*.ts* | circleci tests split > /tmp/tests-to-run
|
||||
sudo docker-compose run web npm run test-very-slow -- -c .circleci/jest-ci.config.js $(cat /tmp/tests-to-run)
|
||||
- store_test_results:
|
||||
path: test-results
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
const baseConfig = require("../jest.config");
|
||||
|
||||
baseConfig.rootDir = "..";
|
||||
baseConfig.reporters.push([
|
||||
"jest-junit",
|
||||
{
|
||||
outputDirectory: "test-results/jest"
|
||||
}
|
||||
]);
|
||||
|
||||
module.exports = baseConfig;
|
|
@ -1,6 +1,7 @@
|
|||
.cache
|
||||
.env
|
||||
.vscode
|
||||
.idea
|
||||
*.log
|
||||
*.pem
|
||||
coverage_api
|
||||
|
@ -18,7 +19,6 @@ api_docs.md
|
|||
erd_diagram.png
|
||||
erd.pdf
|
||||
*scratchpad*
|
||||
scratchpad.rb
|
||||
/config/master.key
|
||||
config/credentials.yml.enc
|
||||
# ActiveStorage blobs:
|
||||
|
|
|
@ -1 +1 @@
|
|||
2.6.3
|
||||
2.6.5
|
||||
|
|
7
Gemfile
7
Gemfile
|
@ -1,6 +1,7 @@
|
|||
source "https://rubygems.org"
|
||||
ruby "~> 2.6.3"
|
||||
ruby "~> 2.6.5"
|
||||
|
||||
gem "rails"
|
||||
gem "active_model_serializers"
|
||||
gem "bunny"
|
||||
gem "delayed_job_active_record" # TODO: Get off of SQL backed jobs. Use Redis
|
||||
|
@ -16,7 +17,6 @@ gem "rabbitmq_http_api_client"
|
|||
gem "rack-attack"
|
||||
gem "rack-cors"
|
||||
gem "rails_12factor"
|
||||
gem "rails", "5.2.3" # TODO: Upgrade to Rails 6
|
||||
gem "redis", "~> 4.0"
|
||||
gem "request_store"
|
||||
gem "rollbar"
|
||||
|
@ -24,7 +24,7 @@ gem "scenic"
|
|||
gem "secure_headers"
|
||||
gem "tzinfo" # For validation of user selected timezone names
|
||||
gem "valid_url"
|
||||
gem "zero_downtime_migrations"
|
||||
gem "kaminari"
|
||||
|
||||
group :development, :test do
|
||||
gem "climate_control"
|
||||
|
@ -37,6 +37,7 @@ group :development, :test do
|
|||
gem "pry"
|
||||
gem "rspec-rails"
|
||||
gem "rspec"
|
||||
gem 'rspec_junit_formatter'
|
||||
gem "simplecov"
|
||||
gem "smarf_doc", git: "https://github.com/RickCarlino/smarf_doc.git"
|
||||
end
|
||||
|
|
309
Gemfile.lock
309
Gemfile.lock
|
@ -7,71 +7,84 @@ GIT
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actioncable (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailer (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activejob (= 5.2.3)
|
||||
actionmailbox (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
activejob (= 6.0.2.2)
|
||||
activerecord (= 6.0.2.2)
|
||||
activestorage (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
actionview (= 6.0.2.2)
|
||||
activejob (= 6.0.2.2)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
rack (~> 2.0)
|
||||
actionpack (6.0.2.2)
|
||||
actionview (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
rack (~> 2.0, >= 2.0.8)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
activerecord (= 6.0.2.2)
|
||||
activestorage (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
active_model_serializers (0.10.10)
|
||||
actionpack (>= 4.1, < 6.1)
|
||||
activemodel (>= 4.1, < 6.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
activejob (6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
activerecord (5.2.3)
|
||||
activemodel (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
arel (>= 9.0)
|
||||
activestorage (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
activerecord (= 5.2.3)
|
||||
activemodel (6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
activerecord (6.0.2.2)
|
||||
activemodel (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
activestorage (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
activejob (= 6.0.2.2)
|
||||
activerecord (= 6.0.2.2)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (5.2.3)
|
||||
activesupport (6.0.2.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.6.0)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
amq-protocol (2.3.0)
|
||||
arel (9.0.0)
|
||||
zeitwerk (~> 2.2)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
amq-protocol (2.3.1)
|
||||
bcrypt (3.1.13)
|
||||
builder (3.2.3)
|
||||
bunny (2.14.2)
|
||||
amq-protocol (~> 2.3, >= 2.3.0)
|
||||
builder (3.2.4)
|
||||
bunny (2.15.0)
|
||||
amq-protocol (~> 2.3, >= 2.3.1)
|
||||
case_transform (0.2)
|
||||
activesupport
|
||||
climate_control (0.2.0)
|
||||
codecov (0.1.14)
|
||||
codecov (0.1.16)
|
||||
json
|
||||
simplecov
|
||||
url
|
||||
coderay (1.1.2)
|
||||
concurrent-ruby (1.1.5)
|
||||
crass (1.0.4)
|
||||
database_cleaner (1.7.0)
|
||||
concurrent-ruby (1.1.6)
|
||||
crass (1.0.6)
|
||||
database_cleaner (1.8.3)
|
||||
declarative (0.0.10)
|
||||
declarative-option (0.1.0)
|
||||
delayed_job (4.1.8)
|
||||
|
@ -86,190 +99,208 @@ GEM
|
|||
responders
|
||||
warden (~> 1.2.3)
|
||||
diff-lcs (1.3)
|
||||
digest-crc (0.4.1)
|
||||
discard (1.1.0)
|
||||
digest-crc (0.5.1)
|
||||
discard (1.2.0)
|
||||
activerecord (>= 4.2, < 7)
|
||||
docile (1.3.2)
|
||||
erubi (1.8.0)
|
||||
factory_bot (5.0.2)
|
||||
erubi (1.9.0)
|
||||
factory_bot (5.1.2)
|
||||
activesupport (>= 4.2.0)
|
||||
factory_bot_rails (5.0.2)
|
||||
factory_bot (~> 5.0.2)
|
||||
factory_bot_rails (5.1.1)
|
||||
factory_bot (~> 5.1.0)
|
||||
railties (>= 4.2.0)
|
||||
faker (2.3.0)
|
||||
i18n (~> 1.6.0)
|
||||
faraday (0.15.4)
|
||||
faker (2.11.0)
|
||||
i18n (>= 1.6, < 2)
|
||||
faraday (0.17.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday_middleware (0.13.1)
|
||||
faraday_middleware (0.14.0)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
font-awesome-rails (4.7.0.5)
|
||||
railties (>= 3.2, < 6.1)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
google-api-client (0.30.8)
|
||||
google-api-client (0.37.2)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.5, < 0.10.0)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.10)
|
||||
google-cloud-core (1.3.0)
|
||||
signet (~> 0.12)
|
||||
google-cloud-core (1.5.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-env (1.2.0)
|
||||
faraday (~> 0.11)
|
||||
google-cloud-storage (1.21.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.3.1)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.0.0)
|
||||
google-cloud-storage (1.25.1)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.26)
|
||||
google-api-client (~> 0.33)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (>= 0.6.2, < 0.10.0)
|
||||
googleauth (~> 0.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.9.0)
|
||||
faraday (~> 0.12)
|
||||
googleauth (0.11.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.7)
|
||||
hashdiff (1.0.0)
|
||||
signet (~> 0.12)
|
||||
hashdiff (1.0.1)
|
||||
hashie (3.6.0)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.6.0)
|
||||
i18n (1.8.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.2.0)
|
||||
json (2.3.0)
|
||||
jsonapi-renderer (0.2.2)
|
||||
jwt (2.2.1)
|
||||
loofah (2.2.3)
|
||||
kaminari (1.2.0)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.0)
|
||||
kaminari-activerecord (= 1.2.0)
|
||||
kaminari-core (= 1.2.0)
|
||||
kaminari-actionview (1.2.0)
|
||||
actionview
|
||||
kaminari-core (= 1.2.0)
|
||||
kaminari-activerecord (1.2.0)
|
||||
activerecord
|
||||
kaminari-core (= 1.2.0)
|
||||
kaminari-core (1.2.0)
|
||||
loofah (2.5.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
memoist (0.16.0)
|
||||
method_source (0.9.2)
|
||||
mimemagic (0.3.3)
|
||||
memoist (0.16.2)
|
||||
method_source (1.0.0)
|
||||
mimemagic (0.3.4)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.11.3)
|
||||
minitest (5.14.0)
|
||||
multi_json (1.13.1)
|
||||
multipart-post (2.1.1)
|
||||
mutations (0.9.0)
|
||||
mutations (0.9.1)
|
||||
activesupport
|
||||
nio4r (2.4.0)
|
||||
nokogiri (1.10.4)
|
||||
nio4r (2.5.2)
|
||||
nokogiri (1.10.9)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.0.1)
|
||||
passenger (6.0.2)
|
||||
passenger (6.0.4)
|
||||
rack
|
||||
rake (>= 0.8.1)
|
||||
pg (1.1.4)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
pg (1.2.3)
|
||||
pry (0.13.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (3.1.1)
|
||||
rabbitmq_http_api_client (1.12.0)
|
||||
faraday (~> 0.15.4)
|
||||
faraday_middleware (~> 0.13.0)
|
||||
public_suffix (4.0.3)
|
||||
rabbitmq_http_api_client (1.13.0)
|
||||
faraday (>= 0.15, < 1)
|
||||
faraday_middleware (>= 0.13.0, < 1)
|
||||
hashie (~> 3.6)
|
||||
multi_json (~> 1.13.1)
|
||||
rack (2.0.7)
|
||||
rack-attack (6.1.0)
|
||||
rack (2.2.2)
|
||||
rack-attack (6.2.2)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.0.3)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (5.2.3)
|
||||
actioncable (= 5.2.3)
|
||||
actionmailer (= 5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activejob (= 5.2.3)
|
||||
activemodel (= 5.2.3)
|
||||
activerecord (= 5.2.3)
|
||||
activestorage (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
rails (6.0.2.2)
|
||||
actioncable (= 6.0.2.2)
|
||||
actionmailbox (= 6.0.2.2)
|
||||
actionmailer (= 6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
actiontext (= 6.0.2.2)
|
||||
actionview (= 6.0.2.2)
|
||||
activejob (= 6.0.2.2)
|
||||
activemodel (= 6.0.2.2)
|
||||
activerecord (= 6.0.2.2)
|
||||
activestorage (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.2.3)
|
||||
railties (= 6.0.2.2)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.2.0)
|
||||
loofah (~> 2.2, >= 2.2.2)
|
||||
rails-html-sanitizer (1.3.0)
|
||||
loofah (~> 2.3)
|
||||
rails_12factor (0.0.3)
|
||||
rails_serve_static_assets
|
||||
rails_stdout_logging
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
railties (6.0.2.2)
|
||||
actionpack (= 6.0.2.2)
|
||||
activesupport (= 6.0.2.2)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
rake (12.3.3)
|
||||
redis (4.1.2)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
rake (13.0.1)
|
||||
redis (4.1.3)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
request_store (1.4.1)
|
||||
request_store (1.5.0)
|
||||
rack (>= 1.4)
|
||||
responders (3.0.0)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
retriable (3.1.2)
|
||||
rollbar (2.22.1)
|
||||
rspec (3.8.0)
|
||||
rspec-core (~> 3.8.0)
|
||||
rspec-expectations (~> 3.8.0)
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-core (3.8.2)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-expectations (3.8.4)
|
||||
rollbar (2.24.0)
|
||||
rspec (3.9.0)
|
||||
rspec-core (~> 3.9.0)
|
||||
rspec-expectations (~> 3.9.0)
|
||||
rspec-mocks (~> 3.9.0)
|
||||
rspec-core (3.9.1)
|
||||
rspec-support (~> 3.9.1)
|
||||
rspec-expectations (3.9.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-mocks (3.8.1)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-mocks (3.9.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-rails (3.8.2)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.8.0)
|
||||
rspec-expectations (~> 3.8.0)
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-support (3.8.2)
|
||||
scenic (1.5.1)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-rails (4.0.0)
|
||||
actionpack (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
railties (>= 4.2)
|
||||
rspec-core (~> 3.9)
|
||||
rspec-expectations (~> 3.9)
|
||||
rspec-mocks (~> 3.9)
|
||||
rspec-support (~> 3.9)
|
||||
rspec-support (3.9.2)
|
||||
rspec_junit_formatter (0.4.1)
|
||||
rspec-core (>= 2, < 4, != 2.12.0)
|
||||
scenic (1.5.2)
|
||||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
secure_headers (6.1.1)
|
||||
signet (0.11.0)
|
||||
secure_headers (6.3.0)
|
||||
signet (0.13.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simplecov (0.17.0)
|
||||
simplecov (0.18.5)
|
||||
docile (~> 1.1)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
sprockets (3.7.2)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov-html (0.12.2)
|
||||
sprockets (4.0.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.1)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
thor (0.20.3)
|
||||
thor (1.0.1)
|
||||
thread_safe (0.3.6)
|
||||
tzinfo (1.2.5)
|
||||
tzinfo (1.2.7)
|
||||
thread_safe (~> 0.1)
|
||||
uber (0.1.0)
|
||||
url (0.3.2)
|
||||
|
@ -281,8 +312,7 @@ GEM
|
|||
websocket-driver (0.7.1)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.4)
|
||||
zero_downtime_migrations (0.0.7)
|
||||
activerecord
|
||||
zeitwerk (2.3.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -303,6 +333,7 @@ DEPENDENCIES
|
|||
google-cloud-storage (~> 1.11)
|
||||
hashdiff
|
||||
jwt
|
||||
kaminari
|
||||
mutations
|
||||
passenger
|
||||
pg
|
||||
|
@ -311,23 +342,23 @@ DEPENDENCIES
|
|||
rabbitmq_http_api_client
|
||||
rack-attack
|
||||
rack-cors
|
||||
rails (= 5.2.3)
|
||||
rails
|
||||
rails_12factor
|
||||
redis (~> 4.0)
|
||||
request_store
|
||||
rollbar
|
||||
rspec
|
||||
rspec-rails
|
||||
rspec_junit_formatter
|
||||
scenic
|
||||
secure_headers
|
||||
simplecov
|
||||
smarf_doc!
|
||||
tzinfo
|
||||
valid_url
|
||||
zero_downtime_migrations
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.6.3p62
|
||||
ruby 2.6.5p114
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.2
|
||||
2.1.4
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
//= link application.css
|
|
@ -33,6 +33,7 @@ module Api
|
|||
rescue_from(JWT::VerificationError) { |e| auth_err }
|
||||
|
||||
rescue_from(ActionDispatch::Http::Parameters::ParseError) { sorry NOT_JSON }
|
||||
rescue_from(JSON::ParserError) { sorry NOT_JSON }
|
||||
|
||||
rescue_from(ActiveRecord::ValueTooLong) do
|
||||
sorry "Please use reasonable lengths on string inputs"
|
||||
|
@ -79,10 +80,25 @@ module Api
|
|||
{ root: false, user: current_user }
|
||||
end
|
||||
|
||||
def maybe_paginate(collection)
|
||||
page = params[:page]
|
||||
per = params[:per]
|
||||
|
||||
if page && per
|
||||
render json: collection.page(page).per(per)
|
||||
else
|
||||
render json: collection
|
||||
end
|
||||
end
|
||||
private
|
||||
|
||||
def clean_expired_farm_events
|
||||
FarmEvents::CleanExpired.run!(device: current_device)
|
||||
# TODO: The app is leaking `Fragment` records, creating
|
||||
# orphaned DB entries. This should be fixable via
|
||||
# ActiveRecord config. Most likely a misconfiguration.
|
||||
# - RC 4 OCT 19
|
||||
Fragment.remove_old_fragments_for_device(current_device)
|
||||
end
|
||||
|
||||
# Rails 5 params are no longer simple hashes. This was for security reasons.
|
||||
|
@ -131,7 +147,7 @@ module Api
|
|||
strategy = Auth::DetermineAuthStrategy.run!(context)
|
||||
case strategy
|
||||
when :jwt
|
||||
sign_in(Auth::FromJWT.run!(context).require_consent!)
|
||||
sign_in(Auth::FromJwt.run!(context).require_consent!)
|
||||
when :already_connected
|
||||
# Probably provided a cookie.
|
||||
# 9 times out of 10, it's a unit test.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Api
|
||||
class AlertsController < Api::AbstractController
|
||||
def index
|
||||
render json: current_device.alerts
|
||||
maybe_paginate current_device.alerts
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
|
|
@ -44,7 +44,7 @@ module Api
|
|||
end
|
||||
|
||||
def update_fields
|
||||
user.update_attributes!(confirmed_at: Time.now)
|
||||
user.update!(confirmed_at: Time.now)
|
||||
end
|
||||
|
||||
def seed_user
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
module Api
|
||||
class DiagnosticDumpsController < Api::AbstractController
|
||||
|
||||
def index
|
||||
render json: diagnostic_dumps
|
||||
end
|
||||
|
||||
def create
|
||||
Rollbar.info("Device #{current_device.id} created a diagnostic")
|
||||
mutate DiagnosticDumps::Create.run(raw_json, device: current_device)
|
||||
end
|
||||
|
||||
def destroy
|
||||
diagnostic_dump.destroy!
|
||||
render json: ""
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def diagnostic_dumps
|
||||
current_device.diagnostic_dumps
|
||||
end
|
||||
|
||||
def diagnostic_dump
|
||||
@diagnostic_dump ||= diagnostic_dumps.find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,7 +3,7 @@ module Api
|
|||
before_action :clean_expired_farm_events, only: [:index]
|
||||
|
||||
def index
|
||||
render json: current_device.farm_events
|
||||
maybe_paginate current_device.farm_events
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -10,7 +10,7 @@ module Api
|
|||
end
|
||||
|
||||
def index
|
||||
render json: farmware_envs
|
||||
maybe_paginate farmware_envs
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Api
|
||||
class FarmwareInstallationsController < Api::AbstractController
|
||||
def index
|
||||
render json: farmware_installations
|
||||
maybe_paginate farmware_installations
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
module Api
|
||||
class FoldersController < Api::AbstractController
|
||||
def create
|
||||
mutate Folders::Create.run(raw_json, device: current_device)
|
||||
end
|
||||
|
||||
def index
|
||||
render json: folders
|
||||
end
|
||||
|
||||
def show
|
||||
render json: folder
|
||||
end
|
||||
|
||||
def update
|
||||
mutate Folders::Update.run(raw_json,
|
||||
folder: folder,
|
||||
device: current_device)
|
||||
end
|
||||
|
||||
def destroy
|
||||
mutate Folders::Destroy.run(folder: folder, device: current_device)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def folder
|
||||
folders.find(params[:id])
|
||||
end
|
||||
|
||||
def folders
|
||||
current_device.folders
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
module Api
|
||||
class PeripheralsController < Api::AbstractController
|
||||
def index
|
||||
render json: current_device.peripherals
|
||||
maybe_paginate current_device.peripherals
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Api
|
||||
class PinBindingsController < Api::AbstractController
|
||||
def index
|
||||
render json: pin_bindings
|
||||
maybe_paginate pin_bindings
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Api
|
||||
class PlantTemplatesController < Api::AbstractController
|
||||
def index
|
||||
render json: current_device.plant_templates
|
||||
maybe_paginate current_device.plant_templates
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
|
@ -3,7 +3,11 @@ module Api
|
|||
before_action :clean_expired_farm_events, only: [:destroy]
|
||||
|
||||
def index
|
||||
render json: your_point_groups
|
||||
maybe_paginate your_point_groups
|
||||
end
|
||||
|
||||
def show
|
||||
render json: the_point_group
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require_relative "../../lib/hstore_filter"
|
||||
# require_relative "../../lib/mutations/hstore_filter"
|
||||
|
||||
module Api
|
||||
class PointsController < Api::AbstractController
|
||||
|
@ -20,7 +20,7 @@ module Api
|
|||
.where("discarded_at < ?", Time.now - HARD_DELETE_AFTER)
|
||||
.destroy_all
|
||||
|
||||
render json: points(params.fetch(:filter) { "kept" })
|
||||
maybe_paginate points(params.fetch(:filter) { "kept" })
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -3,7 +3,7 @@ module Api
|
|||
before_action :clean_expired_farm_events, only: [:destroy]
|
||||
|
||||
def index
|
||||
render json: your_regimens
|
||||
maybe_paginate your_regimens
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -57,6 +57,7 @@ module Api
|
|||
status
|
||||
status_v8
|
||||
sync
|
||||
telemetry
|
||||
\\#
|
||||
\\*
|
||||
).map { |x| x + "(\\.|\\z)" }.join("|")
|
||||
|
@ -78,7 +79,7 @@ module Api
|
|||
class PasswordFailure < Exception; end
|
||||
|
||||
rescue_from PasswordFailure, with: :report_suspicious_behavior
|
||||
rescue_from BrokerConnectionLimiter::RateLimit, with: :deny
|
||||
rescue_from BrokerConnectionLimiter::RateLimit, with: :do_rate_limit
|
||||
|
||||
skip_before_action :check_fbos_version, except: []
|
||||
skip_before_action :authenticate_user!, except: []
|
||||
|
@ -96,13 +97,13 @@ module Api
|
|||
# "farmbot_demo". We intentionally
|
||||
# differentiate to avoid accidental
|
||||
# security issues. -RC
|
||||
when "guest" then deny
|
||||
when "guest" then deny("Can't use guest account on this server.")
|
||||
when "admin" then authenticate_admin
|
||||
when FARMBOT_DEMO_USER
|
||||
with_rate_limit { allow }
|
||||
else
|
||||
is_ok = device_id_in_username == current_device.id
|
||||
is_ok ? (with_rate_limit { allow }) : deny
|
||||
is_ok ? (with_rate_limit { allow }) : deny("Guests are rate limited")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -111,7 +112,7 @@ module Api
|
|||
# "username" => "admin",
|
||||
# "vhost" => "/",
|
||||
# "ip" => "::ffff:172.23.0.1",
|
||||
vhost_param == VHOST ? allow : deny
|
||||
vhost_param == VHOST ? allow : deny("Bad vhost")
|
||||
end
|
||||
|
||||
def resource_action
|
||||
|
@ -122,7 +123,7 @@ module Api
|
|||
# "name" => "mqtt-subscription-MQTT_FX_Clientqos0",
|
||||
# "permission" => "configure",
|
||||
ok = RESOURCES.include?(resource_param) && PERMISSIONS.include?(permission_param)
|
||||
ok ? allow : deny
|
||||
ok ? allow : deny("Bad resource action")
|
||||
end
|
||||
|
||||
def topic_action # Called during subscribe
|
||||
|
@ -135,10 +136,14 @@ module Api
|
|||
# "vhost" => "/",
|
||||
case routing_key_param
|
||||
when *PUBLIC_CHANNELS
|
||||
permission_param == "read" ? allow : deny
|
||||
permission_param == "read" ? allow : deny("Topic is read only")
|
||||
else
|
||||
if_topic_is_safe do
|
||||
device_id_in_topic == device_id_in_username ? allow : deny
|
||||
if device_id_in_topic == device_id_in_username
|
||||
allow
|
||||
else
|
||||
deny("Unsafe topic")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -176,10 +181,15 @@ module Api
|
|||
|
||||
def report_suspicious_behavior
|
||||
Rollbar.error("Failed password attempt on RMQ: " + password_param)
|
||||
deny
|
||||
deny("Failed password attempt")
|
||||
end
|
||||
|
||||
def deny
|
||||
def do_rate_limit
|
||||
deny("Device is rate limited")
|
||||
end
|
||||
|
||||
def deny(reason)
|
||||
maybe_alert_user(reason)
|
||||
render json: "deny", status: 403
|
||||
end
|
||||
|
||||
|
@ -216,32 +226,32 @@ module Api
|
|||
a, b, c = (routing_key_param || "").split(".")
|
||||
|
||||
if !(permission_param == "read")
|
||||
deny
|
||||
deny("!(permission_param == read)")
|
||||
return
|
||||
end
|
||||
|
||||
if !(a == DEMO_REGISTRY_ROOT)
|
||||
deny
|
||||
deny("!(a == DEMO_REGISTRY_ROOT)")
|
||||
return
|
||||
end
|
||||
|
||||
if b.nil?
|
||||
deny
|
||||
deny("b.nil?")
|
||||
return
|
||||
end
|
||||
|
||||
if b.include?("*")
|
||||
deny
|
||||
deny("b.include?(*)")
|
||||
return
|
||||
end
|
||||
|
||||
if b.include?("#")
|
||||
deny
|
||||
deny("b.include?(#)")
|
||||
return
|
||||
end
|
||||
|
||||
if c.present?
|
||||
deny
|
||||
deny("c.present?")
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -254,6 +264,9 @@ module Api
|
|||
return
|
||||
end
|
||||
|
||||
msg = "Subscribed to illegal topic #{routing_key_param || ""}"
|
||||
maybe_alert_user(msg)
|
||||
|
||||
render json: MALFORMED_TOPIC, status: 422
|
||||
end
|
||||
|
||||
|
@ -266,13 +279,14 @@ module Api
|
|||
end
|
||||
|
||||
def with_rate_limit
|
||||
# TODO: Replace this with `ThrottlePolicy`.
|
||||
BrokerConnectionLimiter
|
||||
.current
|
||||
.maybe_continue(username_param) { yield }
|
||||
end
|
||||
|
||||
def current_device
|
||||
@current_device ||= Auth::FromJWT.run!(jwt: password_param).device
|
||||
@current_device ||= Auth::FromJwt.run!(jwt: password_param).device
|
||||
rescue Mutations::ValidationException => e
|
||||
raise JWT::VerificationError, "RMQ Provided bad token"
|
||||
end
|
||||
|
@ -280,5 +294,13 @@ module Api
|
|||
def device_id_in_username
|
||||
@device_id ||= username_param.gsub("device_", "").to_i
|
||||
end
|
||||
|
||||
def maybe_alert_user(reason)
|
||||
msg = "MQTT ACCESS DENIED #{reason}"
|
||||
puts msg unless Rails.env.test?
|
||||
if device_id_in_username > 0
|
||||
Device.find(device_id_in_username).delay.tell(msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Api
|
||||
class SavedGardensController < Api::AbstractController
|
||||
def index
|
||||
render json: current_device.saved_gardens
|
||||
maybe_paginate current_device.saved_gardens
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -19,7 +19,7 @@ module Api
|
|||
end
|
||||
|
||||
def snapshot
|
||||
mutate SavedGardens::Snapshot.run(device: current_device)
|
||||
mutate SavedGardens::Snapshot.run(raw_json, device: current_device)
|
||||
end
|
||||
|
||||
def apply
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
module Api
|
||||
class SensorReadingsController < Api::AbstractController
|
||||
LIMIT = 5000
|
||||
before_action :clean_old
|
||||
|
||||
def create
|
||||
mutate SensorReadings::Create.run(raw_json, device: current_device)
|
||||
mutate SensorReadings::Create.run(raw_json, device: current_device)
|
||||
end
|
||||
|
||||
def index
|
||||
render json: readings
|
||||
maybe_paginate(readings)
|
||||
end
|
||||
|
||||
def show
|
||||
|
@ -17,10 +20,23 @@ module Api
|
|||
render json: ""
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def clean_old
|
||||
if current_device.sensor_readings.count > LIMIT
|
||||
current_device
|
||||
.sensor_readings
|
||||
.where
|
||||
.not(id: readings.pluck(:id))
|
||||
.delete_all
|
||||
end
|
||||
end
|
||||
|
||||
def readings
|
||||
SensorReading.where(device: current_device)
|
||||
@readings ||= SensorReading
|
||||
.where(device: current_device)
|
||||
.order(created_at: :desc)
|
||||
.limit(LIMIT)
|
||||
end
|
||||
|
||||
def reading
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Api
|
||||
class SensorsController < Api::AbstractController
|
||||
def index
|
||||
render json: current_device.sensors
|
||||
maybe_paginate current_device.sensors
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -2,7 +2,7 @@ module Api
|
|||
class ToolsController < Api::AbstractController
|
||||
|
||||
def index
|
||||
render json: tools
|
||||
maybe_paginate tools
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -7,7 +7,7 @@ module Api
|
|||
end
|
||||
|
||||
def index
|
||||
render json: webcams
|
||||
maybe_paginate webcams
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -7,6 +7,7 @@ class DashboardController < ApplicationController
|
|||
:main_app,
|
||||
:password_reset,
|
||||
:tos_update,
|
||||
:os_download,
|
||||
:demo]
|
||||
|
||||
OUTPUT_URL = "/" + File.join("assets", "parcel") # <= served from public/ dir
|
||||
|
@ -24,6 +25,7 @@ class DashboardController < ApplicationController
|
|||
password_reset: "/password_reset/index.tsx",
|
||||
tos_update: "/tos_update/index.tsx",
|
||||
demo: "/demo/index.tsx",
|
||||
os_download: "/os_download/index.tsx"
|
||||
}.with_indifferent_access
|
||||
|
||||
# === THESE CONSTANTS ARE NON-CONFIGURABLE. ===
|
||||
|
@ -94,9 +96,6 @@ class DashboardController < ApplicationController
|
|||
rescue
|
||||
report = { problem: "Crashed while parsing report" }
|
||||
end
|
||||
# We get too many CSP reports.
|
||||
# Rollbar.info("CSP Violation", report)
|
||||
|
||||
render json: report
|
||||
end
|
||||
|
||||
|
@ -110,6 +109,9 @@ class DashboardController < ApplicationController
|
|||
render json: ""
|
||||
end
|
||||
|
||||
def logout
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_css_assets
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# Recurring task that deletes inactive accounts.
|
||||
class InactiveAccountJob < ApplicationJob
|
||||
queue_as :default
|
||||
LIMIT = 1000
|
||||
ORDER = "RANDOM()"
|
||||
INACTIVE_WITH_DEVICE = 11.months + 15.days
|
||||
INACTIVE_NO_DEVICE = 2.months + 15.days
|
||||
WARNING_TIME = 14.days
|
||||
|
||||
def users
|
||||
User.includes(:device)
|
||||
end
|
||||
|
||||
# They signed up for an account, but never configured a device.
|
||||
def no_device
|
||||
users
|
||||
.where("devices.fbos_version" => nil)
|
||||
.references(:devices)
|
||||
end
|
||||
|
||||
# They signed up for an account and once had a working device.
|
||||
def ok_device
|
||||
users
|
||||
.where
|
||||
.not("devices.fbos_version" => nil)
|
||||
.references(:devices)
|
||||
end
|
||||
|
||||
def inactive_3mo
|
||||
no_device
|
||||
.where("last_sign_in_at < ?", INACTIVE_NO_DEVICE.ago)
|
||||
end
|
||||
|
||||
def inactive_11mo
|
||||
ok_device
|
||||
.where("last_sign_in_at < ?", INACTIVE_WITH_DEVICE.ago)
|
||||
end
|
||||
|
||||
# Returns a Map. Key is the number of warnings sent, value is a User object
|
||||
# (not a device, but device is preloaded)
|
||||
def all_inactive
|
||||
@all_inactive ||= inactive_11mo.or(inactive_3mo).order(ORDER).limit(LIMIT)
|
||||
end
|
||||
|
||||
def notify_old_accounts
|
||||
all_inactive
|
||||
.where(inactivity_warning_sent_at: nil)
|
||||
.map(&:send_inactivity_warning)
|
||||
end
|
||||
|
||||
def delete_old_accounts
|
||||
all_inactive
|
||||
.where
|
||||
.not(inactivity_warning_sent_at: nil)
|
||||
.where("inactivity_warning_sent_at < ?", WARNING_TIME.ago)
|
||||
.map(&:deactivate_account)
|
||||
end
|
||||
|
||||
# EDGE CASE: Accounts that register but never sign in.
|
||||
def cleanup_nils
|
||||
User
|
||||
.where(last_sign_in_at: nil)
|
||||
.limit(LIMIT)
|
||||
.map { |x| x.update!(last_sign_in_at: x.created_at) }
|
||||
end
|
||||
|
||||
def perform
|
||||
cleanup_nils
|
||||
notify_old_accounts
|
||||
delete_old_accounts
|
||||
end
|
||||
end
|
|
@ -7,20 +7,22 @@ class AmqpLogParser < Mutations::Command
|
|||
TOO_OLD = "fbos version is out of date"
|
||||
DISCARD = "message type field is not the kind that gets saved in the DB"
|
||||
NOT_HASH = "logs must be a hash"
|
||||
|
||||
NOT_JSON = "Invalid JSON. Use a JSON validator."
|
||||
# I keep a Ruby copy of the JSON here for reference.
|
||||
# This is what a log will look like after JSON.parse()
|
||||
EXAMPLE_JSON = {
|
||||
"meta" => {
|
||||
"x" => 0,
|
||||
"y" => 0,
|
||||
"z" => 0,
|
||||
"type" => "info",
|
||||
},
|
||||
"major_version" => 6, # <= up-to-date bots do this
|
||||
"message" => "HQ FarmBot TEST 123 Pin 13 is 0",
|
||||
"created_at" => 1512585641,
|
||||
"channels" => [],
|
||||
"created_at" => 1572015955,
|
||||
"major_version" => 8,
|
||||
"message" => "Syncing",
|
||||
"meta" => {},
|
||||
"minor_version" => 1,
|
||||
"patch_version" => 1,
|
||||
"type" => "info",
|
||||
"verbosity" => 3,
|
||||
"x" => 0.0,
|
||||
"y" => 0.0,
|
||||
"z" => 0.0,
|
||||
}
|
||||
|
||||
required do
|
||||
|
@ -48,7 +50,7 @@ class AmqpLogParser < Mutations::Command
|
|||
# Prevents "runaway" bots from flooding the server with frivolous database
|
||||
# hits by using in memory cache of results for 150 seconds.
|
||||
def device
|
||||
Device.cached_find(device_id)
|
||||
Device.find(device_id)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -75,6 +77,8 @@ class AmqpLogParser < Mutations::Command
|
|||
def set_payload!
|
||||
# Parse from string to a Ruby hash (JSON)
|
||||
@output.payload = JSON.parse(payload)
|
||||
rescue JSON::ParserError
|
||||
add_error :json, :not_json, NOT_JSON
|
||||
end
|
||||
|
||||
def log
|
||||
|
@ -100,8 +104,19 @@ class AmqpLogParser < Mutations::Command
|
|||
end
|
||||
|
||||
def find_problems!
|
||||
@output.problems.push(NOT_HASH) and return if not_hash?
|
||||
@output.problems.push(TOO_OLD) and return if major_version < 6
|
||||
@output.problems.push(DISCARD) and return if discard?
|
||||
if not_hash?
|
||||
@output.problems.push(NOT_HASH)
|
||||
return
|
||||
end
|
||||
|
||||
if (major_version || 0) < 7
|
||||
@output.problems.push(TOO_OLD)
|
||||
return
|
||||
end
|
||||
|
||||
if discard?
|
||||
@output.problems.push(DISCARD)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,7 +26,7 @@ module CeleryScript
|
|||
|
||||
def maybe_initialize(parent, leaf_or_node, key = NEVER)
|
||||
if is_node?(leaf_or_node)
|
||||
AstNode.new(parent, leaf_or_node)
|
||||
AstNode.new(parent, **leaf_or_node)
|
||||
else
|
||||
raise TypeCheckError, LEAVES_NEED_KEYS if key == NEVER
|
||||
AstLeaf.new(parent, leaf_or_node, key)
|
||||
|
|
|
@ -26,6 +26,7 @@ module CeleryScript
|
|||
FRIENDLY_ERRORS = {
|
||||
nothing: {
|
||||
write_pin: "You must select a Peripheral in the Control Peripheral step.",
|
||||
toggle_pin: "You must select a Peripheral in the Toggle Peripheral step.",
|
||||
variable_declaration: MISSING_VAR,
|
||||
parameter_declaration: MISSING_PARAM,
|
||||
read_pin: "You must select a Sensor in the Read Sensor step.",
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
# * You need to create "traces" of where you are in a sequence (using numbers)
|
||||
# MORE INFO: https://github.com/FarmBot-Labs/Celery-Slicer
|
||||
module CeleryScript
|
||||
# Supporting class for CSHeap (below this class)
|
||||
# PROBLEM: CSHeap uses numbers to address sibling/parent nodes.
|
||||
# Supporting class for CsHeap (below this class)
|
||||
# PROBLEM: CsHeap uses numbers to address sibling/parent nodes.
|
||||
# PROBLEM: Numbers are very easy to mix up. Is it an array index? A SQL
|
||||
# primary key? A primitive value? It's not always easy to say.
|
||||
# SOLUTION: Create a `HeapAddress` value type to remove ambiguity.
|
||||
|
@ -60,21 +60,22 @@ module CeleryScript
|
|||
end
|
||||
end
|
||||
|
||||
class CSHeap
|
||||
class BadAddress < Exception; end;
|
||||
class CsHeap
|
||||
class BadAddress < Exception; end
|
||||
|
||||
BAD_ADDR = "Bad node address: "
|
||||
# Nodes that point to other nodes rather than primitive data types (eg:
|
||||
# `locals` and friends) will be prepended with a LINK.
|
||||
LINK = "__"
|
||||
LINK = "__"
|
||||
# Points to the originator (parent) of an `arg` or `body` node.
|
||||
PARENT = (LINK + "parent").to_sym
|
||||
PARENT = (LINK + "parent").to_sym
|
||||
# Points to the first element in the `body``
|
||||
BODY = (LINK + "body").to_sym
|
||||
BODY = (LINK + "body").to_sym
|
||||
# Points to the next node in the body chain. Pointing to NOTHING indicates
|
||||
# the end of the body linked list.
|
||||
NEXT = (LINK + "next").to_sym
|
||||
NEXT = (LINK + "next").to_sym
|
||||
# Unique key name. See `celery_script_settings_bag.rb`
|
||||
KIND = :__KIND__
|
||||
KIND = :__KIND__
|
||||
COMMENT = :__COMMENT__
|
||||
|
||||
# Keys that primary nodes must have
|
||||
|
@ -82,17 +83,16 @@ module CeleryScript
|
|||
|
||||
# Index 0 of the heap represents a null pointer of sorts.
|
||||
# If a field points to this address, it is considered empty.
|
||||
NULL = HeapAddress[0]
|
||||
NULL = HeapAddress[0]
|
||||
|
||||
# What you will find at index 0 of the heap:
|
||||
NOTHING = {
|
||||
KIND => "nothing",
|
||||
KIND => "nothing",
|
||||
PARENT => NULL,
|
||||
BODY => NULL,
|
||||
NEXT => NULL
|
||||
BODY => NULL,
|
||||
NEXT => NULL,
|
||||
}
|
||||
|
||||
|
||||
# A dictionary of nodes in the CeleryScript tree, as stored in the heap.
|
||||
# Nodes will have:
|
||||
# * A `KIND` field - What kind of node is it?
|
||||
|
@ -113,7 +113,7 @@ module CeleryScript
|
|||
|
||||
# Set "here" to "null". Prepopulates "here" with an empty entry.
|
||||
def initialize
|
||||
@here = NULL
|
||||
@here = NULL
|
||||
@entries = { @here => NOTHING }
|
||||
end
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
require_relative "./csheap"
|
||||
require_relative "./cs_heap"
|
||||
|
||||
# Service object that:
|
||||
# 1. Pulls out all PrimaryNodes and EdgeNodes for a sequence node (AST Flat IR form)
|
||||
|
@ -8,7 +8,7 @@ require_relative "./csheap"
|
|||
# DEFAULT.
|
||||
module CeleryScript
|
||||
class FetchCelery < Mutations::Command
|
||||
private # = = = = = = =
|
||||
private # = = = = = = =
|
||||
# This class is too CPU intensive to make multiple SQL requests.
|
||||
# To speed up querying, we create an in-memory index for frequently
|
||||
# looked up attributes such as :id, :kind, :parent_id, :primary_node_id
|
||||
|
@ -60,7 +60,7 @@ module CeleryScript
|
|||
# that node's children (or an empty array, since body is always optional).
|
||||
def get_body_elements(origin)
|
||||
next_node = find_by_id_in_memory(origin.body_id)
|
||||
results = []
|
||||
results = []
|
||||
until next_node.kind == "nothing"
|
||||
results.push(next_node)
|
||||
next_node = find_by_id_in_memory(next_node[:next_id])
|
||||
|
@ -71,7 +71,7 @@ module CeleryScript
|
|||
# Top level function call for converting a single EdgeNode into a JSON
|
||||
# document. Returns Ruby hash that conforms to CeleryScript semantics.
|
||||
def recurse_into_node(node)
|
||||
out = { kind: node.kind, args: recurse_into_args(node) }
|
||||
out = { kind: node.kind, args: recurse_into_args(node) }
|
||||
body = get_body_elements(node)
|
||||
if body.empty?
|
||||
# Legacy sequences *must* have body on sequence. Others are fine.
|
||||
|
@ -87,16 +87,18 @@ module CeleryScript
|
|||
# Eg: color, id, etc.
|
||||
def misc_fields
|
||||
return {
|
||||
id: sequence.id,
|
||||
created_at: sequence.created_at,
|
||||
updated_at: sequence.updated_at,
|
||||
args: Sequence::DEFAULT_ARGS,
|
||||
color: sequence.color,
|
||||
name: sequence.name
|
||||
}
|
||||
id: sequence.id,
|
||||
created_at: sequence.created_at,
|
||||
updated_at: sequence.updated_at,
|
||||
folder_id: sequence.folder_id,
|
||||
args: Sequence::DEFAULT_ARGS,
|
||||
color: sequence.color,
|
||||
name: sequence.name,
|
||||
}
|
||||
end
|
||||
|
||||
public # = = = = = = =
|
||||
public # = = = = = = =
|
||||
|
||||
NO_SEQUENCE = "You must have a root node `sequence` at a minimum."
|
||||
|
||||
required do
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
require_relative "./csheap"
|
||||
require_relative "./cs_heap"
|
||||
|
||||
# ABOUT THIS CLASS:
|
||||
# CSHeap creates an in memory representation of a Flat IR tree using array
|
||||
# CsHeap creates an in memory representation of a Flat IR tree using array
|
||||
# indexes (HeapAddress instances, really). This class takes a flat IR tree
|
||||
# from memory and converts `HeapAddress`es to SQL primary/foreign keys.
|
||||
module CeleryScript
|
||||
|
@ -10,14 +10,14 @@ module CeleryScript
|
|||
# The following constants are abbreviations of the full name, since the
|
||||
# full name is quite long and they are referenced frequently in the code.
|
||||
# Just remember that "B" is "BODY", "K" is "KIND", etc...
|
||||
B = CeleryScript::CSHeap::BODY
|
||||
C = CeleryScript::CSHeap::COMMENT
|
||||
K = CeleryScript::CSHeap::KIND
|
||||
L = CeleryScript::CSHeap::LINK
|
||||
N = CeleryScript::CSHeap::NEXT
|
||||
P = CeleryScript::CSHeap::PARENT
|
||||
NULL = CeleryScript::CSHeap::NULL
|
||||
I = :instance
|
||||
B = CeleryScript::CsHeap::BODY
|
||||
C = CeleryScript::CsHeap::COMMENT
|
||||
K = CeleryScript::CsHeap::KIND
|
||||
L = CeleryScript::CsHeap::LINK
|
||||
N = CeleryScript::CsHeap::NEXT
|
||||
P = CeleryScript::CsHeap::PARENT
|
||||
NULL = CeleryScript::CsHeap::NULL
|
||||
I = :instance
|
||||
|
||||
required do
|
||||
model :sequence, class: Sequence
|
||||
|
@ -28,7 +28,7 @@ module CeleryScript
|
|||
def validate
|
||||
# IF YOU REMOVE THIS BAD STUFF WILL HAPPEN:
|
||||
# version is never user definable!
|
||||
sequence_hash[:args] = \
|
||||
sequence_hash[:args] =
|
||||
Sequence::DEFAULT_ARGS.merge(sequence_hash[:args] || {})
|
||||
# See comment above ^ TODO: Investigate removal now that EdgeNodes exist.
|
||||
end
|
||||
|
@ -37,67 +37,67 @@ module CeleryScript
|
|||
Sequence.transaction do
|
||||
flat_ir
|
||||
.each do |node|
|
||||
# Step 1- instantiate records.
|
||||
node[I] = PrimaryNode.create!(kind: node[K],
|
||||
sequence: sequence,
|
||||
comment: node[C] || nil)
|
||||
end
|
||||
# Step 1- instantiate records.
|
||||
node[I] = PrimaryNode.create!(kind: node[K],
|
||||
sequence: sequence,
|
||||
comment: node[C] || nil)
|
||||
end
|
||||
.each_with_index do |node, index|
|
||||
# Step 2- Assign SQL ids (not to be confused with array index IDs or
|
||||
# instances of HeapAddress), also sets parent_arg_name
|
||||
model = node[I]
|
||||
model.parent_arg_name = parent_arg_name_for(node, index)
|
||||
model.body_id = fetch_sql_id_for(B, node)
|
||||
model.parent_id = fetch_sql_id_for(P, node)
|
||||
model.next_id = fetch_sql_id_for(N, node)
|
||||
node
|
||||
end
|
||||
# Step 2- Assign SQL ids (not to be confused with array index IDs or
|
||||
# instances of HeapAddress), also sets parent_arg_name
|
||||
model = node[I]
|
||||
model.parent_arg_name = parent_arg_name_for(node, index)
|
||||
model.body_id = fetch_sql_id_for(B, node)
|
||||
model.parent_id = fetch_sql_id_for(P, node)
|
||||
model.next_id = fetch_sql_id_for(N, node)
|
||||
node
|
||||
end
|
||||
.map do |node|
|
||||
# Step 3- Set edge nodes
|
||||
pairs = node
|
||||
.to_a
|
||||
.select do |x|
|
||||
key = x.first.to_s
|
||||
(x.first != I) && !key.starts_with?(L)
|
||||
end
|
||||
.map do |(key, value)|
|
||||
EdgeNode.create!(kind: key,
|
||||
value: value,
|
||||
sequence_id: sequence.id,
|
||||
primary_node_id: node[:instance].id)
|
||||
end
|
||||
node[:instance]
|
||||
# Step 3- Set edge nodes
|
||||
pairs = node
|
||||
.to_a
|
||||
.select do |x|
|
||||
key = x.first.to_s
|
||||
(x.first != I) && !key.starts_with?(L)
|
||||
end
|
||||
.tap { |x| sequence.update_attributes(migrated_nodes: true) unless sequence.migrated_nodes }
|
||||
.map do |(key, value)|
|
||||
EdgeNode.create!(kind: key,
|
||||
value: value,
|
||||
sequence_id: sequence.id,
|
||||
primary_node_id: node[:instance].id)
|
||||
end
|
||||
node[:instance]
|
||||
end
|
||||
.tap { |x| sequence.update(migrated_nodes: true) unless sequence.migrated_nodes }
|
||||
.map { |x|
|
||||
x.save! if x.changed?
|
||||
x
|
||||
}
|
||||
x.save! if x.changed?
|
||||
x
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
# Index every primary node in memory by its `HeapAddress`.
|
||||
# We need this info in order to fill out the `parent_arg_name` of a node.
|
||||
def every_primary_link
|
||||
@every_primary_link ||= flat_ir
|
||||
.map do |x|
|
||||
x
|
||||
.except(B,C,I,K,L,N,P)
|
||||
.invert
|
||||
.to_a
|
||||
.select{|(k,v)| k.is_a?(HeapAddress)}
|
||||
end
|
||||
x
|
||||
.except(B, C, I, K, L, N, P)
|
||||
.invert
|
||||
.to_a
|
||||
.select { |(k, v)| k.is_a?(HeapAddress) }
|
||||
end
|
||||
.map(&:to_h)
|
||||
.reduce({}, :merge)
|
||||
end
|
||||
|
||||
def parent_arg_name_for(node, index)
|
||||
resides_in_args = (node[N] == NULL) && (node[P] != NULL)
|
||||
link_symbol = every_primary_link[HeapAddress[index]]
|
||||
resides_in_args = (node[N] == NULL) && (node[P] != NULL)
|
||||
link_symbol = every_primary_link[HeapAddress[index]]
|
||||
needs_p_arg_name = (resides_in_args && link_symbol)
|
||||
parent_arg_name = (needs_p_arg_name ? link_symbol.to_s.gsub(L, "") : nil)
|
||||
parent_arg_name = (needs_p_arg_name ? link_symbol.to_s.gsub(L, "") : nil)
|
||||
return parent_arg_name
|
||||
end
|
||||
|
||||
|
@ -107,7 +107,7 @@ private
|
|||
end
|
||||
|
||||
def sequence_hash
|
||||
@sequence_hash ||= \
|
||||
@sequence_hash ||=
|
||||
HashWithIndifferentAccess.new(kind: "sequence", args: args, body: body)
|
||||
end
|
||||
|
||||
|
|
|
@ -2,18 +2,18 @@ module CeleryScript
|
|||
# THIS IS A MORE MINIMAL VERSION OF CeleryScript::TreeClimber.
|
||||
# It is a NON-VALIDATING tree climber.
|
||||
# Don't use this on unverified data structures.
|
||||
class JSONClimber
|
||||
class JsonClimber
|
||||
HASH_ONLY = "Expected a Hash."
|
||||
NOT_NODE = "Expected hash with at least a `kind` and `args` prop."
|
||||
NOT_NODE = "Expected hash with at least a `kind` and `args` prop."
|
||||
|
||||
def self.climb(thing, &callable)
|
||||
raise HASH_ONLY unless thing.is_a?(Hash)
|
||||
raise NOT_NODE unless is_node?(thing)
|
||||
raise NOT_NODE unless is_node?(thing)
|
||||
go(thing, callable)
|
||||
thing
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def self.is_node?(maybe)
|
||||
maybe.is_a?(Hash) &&
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require_relative "./csheap.rb"
|
||||
require_relative "./cs_heap.rb"
|
||||
# ORIGINAL IMPLEMENTATION HERE: https://github.com/FarmBot-Labs/Celery-Slicer
|
||||
# Take a nested ("canonical") representation of a CeleryScript sequence and
|
||||
# transforms it to a flat/homogenous intermediate representation which is better
|
||||
|
@ -11,12 +11,12 @@ module CeleryScript
|
|||
raise "Not a hash" unless node.is_a?(Hash)
|
||||
@nesting_level = 0
|
||||
@root_node = node
|
||||
heap = CSHeap.new()
|
||||
allocate(heap, node, CSHeap::NULL)
|
||||
heap = CsHeap.new()
|
||||
allocate(heap, node, CsHeap::NULL)
|
||||
@heap_values = heap.values
|
||||
@heap_values.map do |x|
|
||||
x[CSHeap::BODY] ||= CSHeap::NULL
|
||||
x[CSHeap::NEXT] ||= CSHeap::NULL
|
||||
x[CsHeap::BODY] ||= CsHeap::NULL
|
||||
x[CsHeap::NEXT] ||= CsHeap::NULL
|
||||
end
|
||||
heap.dump()
|
||||
end
|
||||
|
@ -31,8 +31,8 @@ module CeleryScript
|
|||
|
||||
def allocate(h, s, parentAddr)
|
||||
addr = h.allot(s[:kind])
|
||||
h.put(addr, CSHeap::PARENT, parentAddr)
|
||||
h.put(addr, CSHeap::COMMENT, s[:comment]) if s[:comment]
|
||||
h.put(addr, CsHeap::PARENT, parentAddr)
|
||||
h.put(addr, CsHeap::COMMENT, s[:comment]) if s[:comment]
|
||||
iterate_over_body(h, s, addr)
|
||||
iterate_over_args(h, s, addr)
|
||||
addr
|
||||
|
@ -44,7 +44,7 @@ module CeleryScript
|
|||
.map do |key|
|
||||
v = s[:args][key]
|
||||
if (is_celery_script(v))
|
||||
k = CSHeap::LINK + key.to_s
|
||||
k = CsHeap::LINK + key.to_s
|
||||
h.put(parentAddr, k, allocate(h, v, parentAddr))
|
||||
else
|
||||
h.put(parentAddr, key, v)
|
||||
|
@ -64,12 +64,12 @@ module CeleryScript
|
|||
is_head = index == 0
|
||||
# BE CAREFUL EDITING THIS LINE, YOU MIGHT BREAK `BODY` NODES:
|
||||
heap # See note above!
|
||||
.put(previous_address, CSHeap::BODY, previous_address + 1) if is_head
|
||||
.put(previous_address, CsHeap::BODY, previous_address + 1) if is_head
|
||||
|
||||
my_heap_address = allocate(heap, canonical_list[index], previous_address)
|
||||
|
||||
prev_next_key = is_head ? CSHeap::NULL : my_heap_address
|
||||
heap.put(previous_address, CSHeap::NEXT, prev_next_key)
|
||||
prev_next_key = is_head ? CsHeap::NULL : my_heap_address
|
||||
heap.put(previous_address, CsHeap::NEXT, prev_next_key)
|
||||
|
||||
recurse_into_body(heap, canonical_list, my_heap_address, index + 1)
|
||||
end
|
||||
|
|
|
@ -2,18 +2,20 @@
|
|||
# Listens to *ALL* incoming logs and stores them to the DB.
|
||||
# Also handles throttling.
|
||||
class LogService < AbstractServiceRunner
|
||||
T = ThrottlePolicy::TimePeriod
|
||||
THROTTLE_POLICY = ThrottlePolicy.new T.new(1.minute) => 0.5 * 1_000,
|
||||
T.new(1.hour) => 0.5 * 10_000,
|
||||
T.new(1.day) => 0.5 * 100_000
|
||||
THROTTLE_POLICY = ThrottlePolicy.new(name, min: 250, hour: 5_000, day: 25_000)
|
||||
|
||||
LOG_TPL = "FBOS LOG (device_%s): %s"
|
||||
LOG_TPL = Rails.env.test? ?
|
||||
"\e[32m.\e[0m" : "FBOS LOG (device_%s) [v%s]: %s\n"
|
||||
ERR_TPL = "MALFORMED LOG CAPTURE: %s"
|
||||
# Clean up excess logs in a non-deterministic manner.
|
||||
# Performs the slow DB query every nth request.
|
||||
TIDY_RATE = Rails.env.test? ? 0 : 10
|
||||
|
||||
def process(delivery_info, payload)
|
||||
params = { routing_key: delivery_info.routing_key, payload: payload }
|
||||
m = AmqpLogParser.run!(params)
|
||||
THROTTLE_POLICY.track(m.device_id)
|
||||
m.device.excess_logs.delete_all if rand(0..TIDY_RATE) == TIDY_RATE
|
||||
maybe_deliver(m)
|
||||
rescue Mutations::ValidationException => e
|
||||
msg = ERR_TPL % [params.merge({ e: e }).to_json]
|
||||
|
@ -22,19 +24,26 @@ class LogService < AbstractServiceRunner
|
|||
end
|
||||
|
||||
def maybe_deliver(data)
|
||||
violation = THROTTLE_POLICY.is_throttled(data.device_id)
|
||||
ok = data.valid? && !violation
|
||||
return unless data.valid?
|
||||
|
||||
violation = THROTTLE_POLICY.violation_for(data.device_id)
|
||||
|
||||
if violation
|
||||
return warn_user(data, violation)
|
||||
end
|
||||
|
||||
data.device.auto_sync_transaction do
|
||||
ok ? deliver(data) : warn_user(data, violation)
|
||||
deliver(data)
|
||||
end
|
||||
end
|
||||
|
||||
def deliver(data)
|
||||
dev, log = [data.device, data.payload]
|
||||
dev.maybe_unthrottle
|
||||
Log.deliver(dev, Logs::Create.run!(log, device: dev))
|
||||
puts LOG_TPL % [data.device_id, data.payload["message"] || "??"]
|
||||
Log.deliver(Logs::Create.run!(log, device: dev).id)
|
||||
print LOG_TPL % [data.device_id,
|
||||
dev.fbos_version || "?",
|
||||
data.payload["message"] || "??"]
|
||||
rescue => x
|
||||
Rollbar.error(x)
|
||||
end
|
||||
|
|
|
@ -46,5 +46,6 @@ module Resources
|
|||
Plant => Points,
|
||||
Point => Points,
|
||||
ToolSlot => Points,
|
||||
Weed => Points,
|
||||
}
|
||||
end # Resources
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# A singleton that runs on a separate process than the web server.
|
||||
# Listens to *ALL* incoming logs and stores them to the DB.
|
||||
# Also handles throttling.
|
||||
class TelemetryService < AbstractServiceRunner
|
||||
MESSAGE = "TELEMETRY MESSAGE FROM %s"
|
||||
FAILURE = "FAILED TELEMETRY MESSAGE FROM %s"
|
||||
THROTTLE_POLICY = ThrottlePolicy.new(name, min: 25,
|
||||
hour: 250,
|
||||
day: 1500)
|
||||
|
||||
def process(delivery_info, payload)
|
||||
device_key = delivery_info
|
||||
.routing_key
|
||||
.split(".")[1]
|
||||
json = JSON.parse(payload)
|
||||
other_stuff = { device: device_key,
|
||||
is_telemetry: true,
|
||||
message: MESSAGE % device_key }
|
||||
THROTTLE_POLICY.track(device_key)
|
||||
violation = THROTTLE_POLICY.violation_for(device_key)
|
||||
unless violation
|
||||
puts json.merge(other_stuff).to_json
|
||||
end
|
||||
rescue JSON::ParserError
|
||||
puts ({ device: device_key,
|
||||
is_telemetry: true,
|
||||
bad_json: payload,
|
||||
message: FAILURE % device_key }).to_json
|
||||
end
|
||||
end
|
|
@ -1,28 +1,81 @@
|
|||
# Handles devices that spin out of control and send too many logs to the server.
|
||||
# Class Hierarchy:
|
||||
# ThrottlePolicy has => Rules creates => Violation
|
||||
# Violation has => Rule has => TimePeriod
|
||||
class ThrottlePolicy
|
||||
attr_reader :rules
|
||||
TTL = { min: 60,
|
||||
hour: 60 * 60,
|
||||
day: 60 * 60 * 24 }
|
||||
ROUNDING_HELPERS = { min: :beginning_of_minute,
|
||||
hour: :beginning_of_hour,
|
||||
day: :beginning_of_day }
|
||||
|
||||
# Dictionary<TimePeriod, Integer>
|
||||
def initialize(policy_rules)
|
||||
@rules = policy_rules.map { |rule_set| Rule.new(*rule_set) }
|
||||
TIME_UNITS = TTL.keys
|
||||
VIOLATION_TPL = "more than %{limit} / %{period}"
|
||||
Violation = Struct.new(:ends_at, :explanation)
|
||||
attr_reader :rule_map, :namespace
|
||||
|
||||
def initialize(namespace, rule_map)
|
||||
@namespace = namespace
|
||||
@rule_map = rule_map
|
||||
each_rule { |unit| validate_unit!(unit) }
|
||||
end
|
||||
|
||||
def track(unique_id, now = Time.now)
|
||||
rules.each { |r| r.time_period.record_event(unique_id, now) }
|
||||
def track(id)
|
||||
each_rule { |unit| incr(id, unit) }
|
||||
end
|
||||
|
||||
# If throttled, returns the timeperiod when device will be unthrottled
|
||||
# returns nil if not throttled
|
||||
def is_throttled(unique_id)
|
||||
rules
|
||||
.map do |rule|
|
||||
is_violation = rule.time_period.usage_count_for(unique_id) > rule.limit
|
||||
is_violation ? Violation.new(rule) : nil
|
||||
end
|
||||
.compact
|
||||
.max
|
||||
def violation_for(id)
|
||||
v = all_violations(id)
|
||||
if v.empty?
|
||||
return nil
|
||||
else
|
||||
period, limit = v.max_by { |(unit)| TTL.fetch(unit) }
|
||||
message = VIOLATION_TPL % { period: period, limit: limit }
|
||||
return Violation.new(next_window(period), message)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_unit!(unit)
|
||||
raise "BAD TIME UNIT" unless TIME_UNITS.include?(unit)
|
||||
end
|
||||
|
||||
def each_rule
|
||||
rule_map.map { |(k, v)| yield(k, v) }
|
||||
end
|
||||
|
||||
def redis
|
||||
Rails.cache.redis
|
||||
end
|
||||
|
||||
def the_time_part(period, now = Time.now)
|
||||
[period, next_window(period).to_i].map(&:to_s).join()
|
||||
end
|
||||
|
||||
def cache_key(id, period)
|
||||
[namespace, the_time_part(period), id].join(":")
|
||||
end
|
||||
|
||||
def get(id, period)
|
||||
(redis.get(cache_key(id, period)) || "0").to_i
|
||||
end
|
||||
|
||||
def next_window(period, now = Time.now)
|
||||
helper = ROUNDING_HELPERS.fetch(period)
|
||||
offset = TTL.fetch(period).seconds
|
||||
(now + offset).send(helper)
|
||||
end
|
||||
|
||||
def incr(id, period)
|
||||
key = cache_key(id, period)
|
||||
redis.incr(key)
|
||||
|
||||
if (redis.ttl(key) < 0)
|
||||
ttl = (next_window(period) - Time.now).seconds.to_i
|
||||
redis.expire(key, ttl)
|
||||
end
|
||||
end
|
||||
|
||||
def all_violations(id)
|
||||
each_rule { |k, v| (get(id, k) > v) ? [k, v] : nil }.compact
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
class ThrottlePolicy
|
||||
# A time_period object paired with a max limit
|
||||
class Rule
|
||||
attr_reader :time_period, :limit
|
||||
|
||||
def initialize(time_period, limit)
|
||||
@time_period, @limit = time_period, limit
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,57 +0,0 @@
|
|||
# Track the number of occurrences of an event over time.
|
||||
#
|
||||
# Given:
|
||||
# * A fixed size duration (1 minute, 1 week etc)
|
||||
# * An event (In our case, log creation)
|
||||
# * An initiator id (eg: device_id)
|
||||
#
|
||||
# Produces:
|
||||
# * A table of event counts for the current time period, indexed by
|
||||
# the initiator ID.
|
||||
class ThrottlePolicy
|
||||
class TimePeriod
|
||||
attr_reader :time_unit,
|
||||
:current_period, # Slice time into fixed size windows
|
||||
:entries
|
||||
|
||||
def initialize(active_support_duration, now = Time.now)
|
||||
@time_unit = active_support_duration
|
||||
reset_everything now
|
||||
end
|
||||
|
||||
|
||||
def record_event(unique_id, now = Time.now)
|
||||
period = calculate_period(now)
|
||||
case period <=> current_period
|
||||
when -1 then return # Out of date- don't record.
|
||||
when 0 then increment_count_for(unique_id) # Right on schedule.
|
||||
when 1 then reset_everything(now) # Clear out old data.
|
||||
end
|
||||
end
|
||||
|
||||
def usage_count_for(unique_id)
|
||||
@entries[unique_id] || 0
|
||||
end
|
||||
|
||||
def when_does_next_period_start?
|
||||
Time.at(current_period * time_unit.to_i) + time_unit
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reset_everything(now)
|
||||
@current_period = calculate_period(now)
|
||||
@entries = {}
|
||||
end
|
||||
|
||||
def increment_count_for(unique_id)
|
||||
@entries[unique_id] ||= 0
|
||||
@entries[unique_id] += 1
|
||||
end
|
||||
|
||||
# Returns integer representation of current clock period
|
||||
def calculate_period(time)
|
||||
(time.to_i / @time_unit)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,29 +0,0 @@
|
|||
class ThrottlePolicy
|
||||
class Violation
|
||||
attr_reader :rule
|
||||
|
||||
def initialize(rule)
|
||||
@rule = rule
|
||||
end
|
||||
|
||||
def ends_at
|
||||
@rule.time_period.when_does_next_period_start?
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
self.timeframe <=> other.timeframe
|
||||
end
|
||||
|
||||
def timeframe
|
||||
rule.time_period.time_unit
|
||||
end
|
||||
|
||||
def limit
|
||||
rule.limit.to_i
|
||||
end
|
||||
|
||||
def explanation
|
||||
"more than #{limit} logs in #{timeframe.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
class InactivityMailer < ApplicationMailer
|
||||
attr_reader :user
|
||||
SUBJECT = "[ACTION REQUIRED] Your FarmBot account " \
|
||||
"will be deleted due to inactivity unless you login"
|
||||
ORDER = { 1 => "First", 2 => "Second", 3 => "Final" }
|
||||
|
||||
def send_warning(user)
|
||||
@user = user
|
||||
mail to: user.email, subject: SUBJECT
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
after_save :maybe_broadcast, on: [:create, :update]
|
||||
after_create :maybe_broadcast
|
||||
after_update :maybe_broadcast
|
||||
after_destroy :maybe_broadcast
|
||||
|
||||
class << self
|
||||
|
@ -37,16 +38,17 @@ class ApplicationRecord < ActiveRecord::Base
|
|||
def self.auto_sync_debounce
|
||||
@auto_sync_paused = true
|
||||
result = yield
|
||||
result.update_attributes!(updated_at: Time.now)
|
||||
result.update!(updated_at: Time.now)
|
||||
@auto_sync_paused = false
|
||||
result.broadcast!
|
||||
result
|
||||
end
|
||||
|
||||
def broadcast?
|
||||
!self.class.auto_sync_paused &&
|
||||
current_device &&
|
||||
(gone? || notable_changes?)
|
||||
return false if self.class.auto_sync_paused
|
||||
return false unless current_device
|
||||
return false unless (gone? || notable_changes?)
|
||||
return true
|
||||
end
|
||||
|
||||
def maybe_broadcast
|
||||
|
@ -88,12 +90,13 @@ class ApplicationRecord < ActiveRecord::Base
|
|||
current_device.id,
|
||||
chan_name,
|
||||
Time.now.utc.to_i) if current_device
|
||||
self
|
||||
end
|
||||
|
||||
def manually_sync!
|
||||
device.auto_sync_transaction do
|
||||
update_attributes!(updated_at: Time.now)
|
||||
end if device
|
||||
device && (device.auto_sync_transaction do
|
||||
update!(updated_at: Time.now)
|
||||
end)
|
||||
self
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,11 +10,15 @@ class ArgName < ApplicationRecord
|
|||
|
||||
def self.cached_by_value(v)
|
||||
key = KEY % v
|
||||
Rails.cache.fetch(key, expires_in: EXPIRY) { find_or_create_by(value: v) }
|
||||
Rails.cache.fetch(key, expires_in: EXPIRY) do
|
||||
find_or_create_by!(value: v)
|
||||
end
|
||||
end
|
||||
|
||||
def self.cached_by_id(id)
|
||||
Rails.cache.fetch(KEY % id, expires_in: EXPIRY) { find(id) }
|
||||
Rails.cache.fetch(KEY % id, expires_in: EXPIRY) do
|
||||
find(id)
|
||||
end
|
||||
end
|
||||
|
||||
def broadcast?
|
||||
|
|
|
@ -16,9 +16,9 @@ module CeleryScriptSettingsBag
|
|||
end
|
||||
|
||||
PIN_TYPE_MAP = { "Peripheral" => Peripheral,
|
||||
"Sensor" => Sensor,
|
||||
"BoxLed3" => BoxLed,
|
||||
"BoxLed4" => BoxLed }
|
||||
"Sensor" => Sensor,
|
||||
"BoxLed3" => BoxLed,
|
||||
"BoxLed4" => BoxLed }
|
||||
ALLOWED_AXIS = %w(x y z all)
|
||||
ALLOWED_ASSERTION_TYPES = %w(abort recover abort_recover continue)
|
||||
ALLOWED_CHANGES = %w(add remove update)
|
||||
|
@ -30,19 +30,19 @@ module CeleryScriptSettingsBag
|
|||
ALLOWED_PACKAGES = %w(farmbot_os arduino_firmware)
|
||||
ALLOWED_PIN_MODES = [DIGITAL = 0, ANALOG = 1]
|
||||
ALLOWED_PIN_TYPES = PIN_TYPE_MAP.keys
|
||||
ALLOWED_POINTER_TYPE = %w(GenericPointer ToolSlot Plant)
|
||||
ALLOWED_RESOURCE_TYPE = %w(Device Point Plant ToolSlot GenericPointer)
|
||||
ALLOWED_POINTER_TYPE = %w(GenericPointer ToolSlot Plant Weed)
|
||||
ALLOWED_RESOURCE_TYPE = %w(Device Point Plant ToolSlot Weed GenericPointer)
|
||||
ALLOWED_RPC_NODES = %w(assertion calibrate change_ownership
|
||||
check_updates dump_info emergency_lock
|
||||
check_updates emergency_lock
|
||||
emergency_unlock execute execute_script
|
||||
factory_reset find_home flash_firmware home
|
||||
install_farmware install_first_party_farmware _if
|
||||
move_absolute move_relative power_off read_pin
|
||||
read_status reboot remove_farmware resource_update
|
||||
read_status reboot remove_farmware update_resource
|
||||
send_message set_servo_angle set_user_env sync
|
||||
take_photo toggle_pin update_farmware wait
|
||||
write_pin zero)
|
||||
ALLOWED_SPEC_ACTION = %w(dump_info emergency_lock emergency_unlock power_off
|
||||
ALLOWED_SPEC_ACTION = %w(emergency_lock emergency_unlock power_off
|
||||
read_status reboot sync take_photo)
|
||||
ANY_VARIABLE = %i(tool coordinate point identifier)
|
||||
BAD_ALLOWED_PIN_MODES = '"%s" is not a valid pin_mode. Allowed values: %s'
|
||||
|
@ -60,6 +60,7 @@ module CeleryScriptSettingsBag
|
|||
BAD_PIN_TYPE = '"%s" is not a type of pin. Allowed values: %s'
|
||||
BAD_POINTER_ID = "Bad point ID: %s"
|
||||
BAD_POINTER_TYPE = '"%s" is not a type of point. Allowed values: %s'
|
||||
BAD_POINT_GROUP_ID = "Can't find PointGroup with id of %s"
|
||||
BAD_REGIMEN = "Regimen #%s does not exist."
|
||||
BAD_RESOURCE_ID = "Can't find %s with id of %s"
|
||||
BAD_RESOURCE_TYPE = '"%s" is not a valid resource_type. Allowed values: %s'
|
||||
|
@ -72,8 +73,7 @@ module CeleryScriptSettingsBag
|
|||
ONLY_ONE_COORD = "Move Absolute does not accept a group of locations " \
|
||||
"as input. Please change your selection to a single" \
|
||||
" location."
|
||||
PLANT_STAGES = %w(planned planted harvested sprouted)
|
||||
RESOURCE_UPDATE_ARGS = [:resource_type, :resource_id, :label, :value]
|
||||
PLANT_STAGES = %w(planned planted harvested sprouted removed)
|
||||
SCOPE_DECLARATIONS = [:variable_declaration, :parameter_declaration]
|
||||
MISC_ENUM_ERR = '"%s" is not valid. Allowed values: %s'
|
||||
MAX_WAIT_MS = 1000 * 60 * 3 # Three Minutes
|
||||
|
@ -81,6 +81,13 @@ module CeleryScriptSettingsBag
|
|||
"A single wait node cannot exceed #{MAX_WAIT_MS / 1000 / 60} minutes. " +
|
||||
"Consider lowering the wait time or using multiple WAIT blocks."
|
||||
Corpus = CeleryScript::Corpus.new
|
||||
THIS_IS_DEPRECATED = {
|
||||
args: [:resource_type, :resource_id, :label, :value],
|
||||
tags: [:function, :api_writer, :network_user],
|
||||
blk: ->(n) do
|
||||
n.invalidate!("Deprecated `mark_as` detected. Delete it and re-add")
|
||||
end,
|
||||
}
|
||||
|
||||
CORPUS_VALUES = {
|
||||
boolean: [TrueClass, FalseClass],
|
||||
|
@ -130,7 +137,7 @@ module CeleryScriptSettingsBag
|
|||
defn: [n(:execute), n(:nothing)],
|
||||
},
|
||||
data_value: {
|
||||
defn: ANY_VAR_TOKENIZED,
|
||||
defn: ANY_VAR_TOKENIZED + [n(:point_group)],
|
||||
},
|
||||
default_value: {
|
||||
defn: ANY_VAR_TOKENIZED,
|
||||
|
@ -194,6 +201,13 @@ module CeleryScriptSettingsBag
|
|||
node.invalidate!(BAD_POINTER_ID % node.value) if bad_node
|
||||
end,
|
||||
},
|
||||
point_group_id: {
|
||||
defn: [v(:integer)],
|
||||
blk: ->(node, device) do
|
||||
bad_node = !PointGroup.where(id: node.value, device_id: device.id).exists?
|
||||
node.invalidate!(BAD_POINT_GROUP_ID % node.value) if bad_node
|
||||
end,
|
||||
},
|
||||
pointer_type: {
|
||||
defn: [e(:PointType)],
|
||||
},
|
||||
|
@ -270,6 +284,9 @@ module CeleryScriptSettingsBag
|
|||
lua: {
|
||||
defn: [v(:string)],
|
||||
},
|
||||
resource: {
|
||||
defn: [n(:identifier), n(:resource)],
|
||||
},
|
||||
}.map do |(name, conf)|
|
||||
blk = conf[:blk]
|
||||
defn = conf.fetch(:defn)
|
||||
|
@ -309,10 +326,6 @@ module CeleryScriptSettingsBag
|
|||
args: [:x, :y, :z],
|
||||
tags: [:data, :location_like],
|
||||
},
|
||||
dump_info: {
|
||||
tags: [:function, :network_user, :disk_user, :api_writer],
|
||||
docs: "Sends an info dump to server administrators for troubleshooting.",
|
||||
},
|
||||
emergency_lock: {
|
||||
tags: [:function, :firmware_user, :control_flow],
|
||||
},
|
||||
|
@ -505,15 +518,30 @@ module CeleryScriptSettingsBag
|
|||
tags: [:function, :firmware_user, :rpi_user],
|
||||
blk: ->(n) { no_rpi_analog(n) },
|
||||
},
|
||||
resource_update: {
|
||||
args: RESOURCE_UPDATE_ARGS,
|
||||
tags: [:function, :api_writer, :network_user],
|
||||
# DEPRECATED- Get rid of this node ASAP -RC 15 APR 2020
|
||||
resource_update: THIS_IS_DEPRECATED,
|
||||
resource: {
|
||||
args: [:resource_type, :resource_id],
|
||||
tags: [:network_user],
|
||||
blk: ->(n) do
|
||||
resource_type = n.args.fetch(:resource_type).value
|
||||
resource_id = n.args.fetch(:resource_id).value
|
||||
check_resource_type(n, resource_type, resource_id, Device.current)
|
||||
end,
|
||||
},
|
||||
update_resource: {
|
||||
args: [:resource],
|
||||
body: [:pair],
|
||||
tags: [:function, :api_writer, :network_user],
|
||||
},
|
||||
point_group: {
|
||||
args: [:point_group_id],
|
||||
tags: [:data, :list_like],
|
||||
blk: ->(n) do
|
||||
resource_id = n.args.fetch(:point_group_id).value
|
||||
check_resource_type(n, "PointGroup", resource_id, Device.current)
|
||||
end,
|
||||
},
|
||||
}.map { |(name, list)| Corpus.node(name, **list) }
|
||||
|
||||
HASH = Corpus.as_json
|
||||
|
@ -535,6 +563,8 @@ module CeleryScriptSettingsBag
|
|||
# the current_device.
|
||||
# For convenience, we try to set it here, defaulting to 0
|
||||
node.args[:resource_id].instance_variable_set("@value", owner.id)
|
||||
when "PointGroup"
|
||||
no_resource(node, PointGroup, resource_id) unless PointGroup.exists?(resource_id)
|
||||
when *ALLOWED_RESOURCE_TYPE.without("Device")
|
||||
klass = Kernel.const_get(resource_type)
|
||||
resource_ok = klass.exists?(resource_id)
|
||||
|
|
|
@ -6,17 +6,17 @@ class Device < ApplicationRecord
|
|||
|
||||
TIMEZONES = TZInfo::Timezone.all_identifiers
|
||||
BAD_TZ = "%{value} is not a valid timezone"
|
||||
BAD_OTA_HOUR = "must be a value from 0 to 23."
|
||||
THROTTLE_ON = "Device is sending too many logs (%s). " \
|
||||
"Suspending log storage and display until %s."
|
||||
THROTTLE_OFF = "Cooldown period has ended. " \
|
||||
"Resuming log storage."
|
||||
CACHE_KEY = "devices:%s"
|
||||
|
||||
PLURAL_RESOURCES = %i(alerts farmware_envs farm_events farmware_installations
|
||||
images logs peripherals pin_bindings plant_templates
|
||||
points point_groups regimens saved_gardens
|
||||
sensor_readings sensors sequences token_issuances tools
|
||||
webcam_feeds diagnostic_dumps fragments)
|
||||
webcam_feeds fragments)
|
||||
|
||||
PLURAL_RESOURCES.map { |resources| has_many resources, dependent: :destroy }
|
||||
|
||||
|
@ -34,6 +34,7 @@ class Device < ApplicationRecord
|
|||
has_many :in_use_tools
|
||||
has_many :in_use_points
|
||||
has_many :users
|
||||
has_many :folders
|
||||
|
||||
validates_presence_of :name
|
||||
validates :timezone, inclusion: {
|
||||
|
@ -41,6 +42,8 @@ class Device < ApplicationRecord
|
|||
message: BAD_TZ,
|
||||
allow_nil: true,
|
||||
}
|
||||
validates :ota_hour,
|
||||
inclusion: { in: [*0..23], message: BAD_OTA_HOUR, allow_nil: true }
|
||||
|
||||
# Give the user back the amount of logs they are allowed to view.
|
||||
def limited_log_list
|
||||
|
@ -91,47 +94,23 @@ class Device < ApplicationRecord
|
|||
points.where(pointer_type: "GenericPointer")
|
||||
end
|
||||
|
||||
TIMEOUT = 150.seconds
|
||||
|
||||
# Like Device.find, but with 150 seconds of caching to avoid DB calls.
|
||||
def self.cached_find(id)
|
||||
Rails
|
||||
.cache
|
||||
.fetch(CACHE_KEY % id, expires_in: TIMEOUT) { Device.find(id) }
|
||||
end
|
||||
|
||||
def refresh_cache
|
||||
# Why? Device.new(self.as_json)???
|
||||
#
|
||||
# "Some objects cannot be dumped: if the objects to be dumped include
|
||||
# bindings, procedure or method objects, instances of class IO, or singleton
|
||||
# objects, a TypeError will be raised."
|
||||
# https://ruby-doc.org/core-2.3.1/Marshal.html
|
||||
# TODO: Someone plz send help! - RC
|
||||
Rails.cache.write(CACHE_KEY % self.id, Device.new(self.as_json))
|
||||
end
|
||||
|
||||
# Sets the `throttled_at` field, but only if it is unpopulated.
|
||||
# Performs no-op if `throttled_at` was already set.
|
||||
# Sets the `throttled_until` and `throttled_at` fields if unpopulated or
|
||||
# the throttle time period increases. Notifies user of cooldown period.
|
||||
def maybe_throttle(violation)
|
||||
end_t = violation.ends_at
|
||||
# Some log validation errors will result in until_time being `nil`.
|
||||
if (violation && throttled_until.nil?)
|
||||
et = violation.ends_at
|
||||
reload.update_attributes!(throttled_until: et,
|
||||
throttled_at: Time.now)
|
||||
refresh_cache
|
||||
cooldown = et.in_time_zone(self.timezone || "UTC").strftime("%I:%M%p")
|
||||
if (throttled_until.nil? || (end_t > throttled_until))
|
||||
reload.update!(throttled_until: end_t, throttled_at: Time.now)
|
||||
cooldown = end_t.in_time_zone(self.timezone || "UTC").strftime("%I:%M%p")
|
||||
info = [violation.explanation, cooldown]
|
||||
cooldown_notice(THROTTLE_ON % info, et, "warn")
|
||||
cooldown_notice(THROTTLE_ON % info, end_t, "warn")
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_unthrottle
|
||||
if throttled_until.present?
|
||||
old_time = throttled_until
|
||||
reload # <= WHY!?! TODO: Find out why it crashes without this.
|
||||
.update_attributes!(throttled_until: nil, throttled_at: nil)
|
||||
refresh_cache
|
||||
update!(throttled_until: nil, throttled_at: nil)
|
||||
cooldown_notice(THROTTLE_OFF, old_time, "info")
|
||||
end
|
||||
end
|
||||
|
@ -185,20 +164,17 @@ class Device < ApplicationRecord
|
|||
|
||||
# Helper method to create an auth token.
|
||||
# Used by sys admins to debug problems without performing a password reset.
|
||||
def create_token
|
||||
# If something manages to call this method, I'd like to be alerted of it.
|
||||
def help_customer
|
||||
Rollbar.error("Someone is creating a debug user token", { device: self.id })
|
||||
fbos_version = Api::AbstractController::EXPECTED_VER
|
||||
SessionToken
|
||||
.as_json(users.first, "SUPER", fbos_version)
|
||||
.fetch(:token)
|
||||
.encoded
|
||||
token = SessionToken.as_json(users.first, "staff", fbos_version).to_json
|
||||
return "localStorage['session'] = JSON.stringify(#{token});"
|
||||
end
|
||||
|
||||
TOO_MANY_CONNECTIONS =
|
||||
"Your device is " +
|
||||
"reconnecting to the server too often. Please " +
|
||||
"see https://developer.farm.bot/docs/connectivity-issues"
|
||||
"Your device is reconnecting to the server too often. " +
|
||||
"This may be a sign of local network issues. " +
|
||||
"Please review the documentation provided at " +
|
||||
"https://software.farm.bot/docs/connecting-farmbot-to-the-internet"
|
||||
def self.connection_warning(username)
|
||||
device_id = username.split("_").last.to_i || 0
|
||||
device = self.find_by(id: device_id)
|
||||
|
@ -206,7 +182,7 @@ class Device < ApplicationRecord
|
|||
|
||||
last_sent_at = device.mqtt_rate_limit_email_sent_at || 4.years.ago
|
||||
if last_sent_at < 1.day.ago
|
||||
device.update_attributes!(mqtt_rate_limit_email_sent_at: Time.now)
|
||||
device.update!(mqtt_rate_limit_email_sent_at: Time.now)
|
||||
device.tell(TOO_MANY_CONNECTIONS, ["fatal_email"])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
class DiagnosticDump < ApplicationRecord
|
||||
belongs_to :device
|
||||
end
|
|
@ -40,7 +40,7 @@ class FarmwareInstallation < ApplicationRecord
|
|||
known_error = KNOWN_PROBLEMS[error.class]
|
||||
description = \
|
||||
known_error || (OTHER_PROBLEM % error.class)
|
||||
update_attributes!(package_error: description,
|
||||
update!(package_error: description,
|
||||
package: nil)
|
||||
unless known_error.present?
|
||||
raise error
|
||||
|
@ -54,7 +54,7 @@ class FarmwareInstallation < ApplicationRecord
|
|||
string = string_io.read(MAX_JSON_SIZE)
|
||||
json = JSON.parse(string)
|
||||
pkg_name = json.fetch("package")
|
||||
update_attributes!(package: pkg_name, package_error: nil)
|
||||
update!(package: pkg_name, package_error: nil)
|
||||
rescue => error
|
||||
maybe_recover_from_fetch_error(error)
|
||||
end
|
||||
|
|
|
@ -3,7 +3,8 @@ class FbosConfig < ApplicationRecord
|
|||
class MissingSerial < StandardError; end
|
||||
|
||||
belongs_to :device
|
||||
after_save :maybe_sync_nerves, on: [:create, :update]
|
||||
after_create :maybe_sync_nerves
|
||||
after_update :maybe_sync_nerves
|
||||
|
||||
FIRMWARE_HARDWARE = [
|
||||
NOT_SET = nil,
|
||||
|
@ -11,6 +12,7 @@ class FbosConfig < ApplicationRecord
|
|||
ARDUINO = "arduino",
|
||||
FARMDUINO = "farmduino",
|
||||
FARMDUINO_K14 = "farmduino_k14",
|
||||
FARMDUINO_K15 = "farmduino_k15",
|
||||
EXPRESS_K10 = "express_k10",
|
||||
]
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
class Folder < ApplicationRecord
|
||||
belongs_to :device
|
||||
|
||||
has_many :sub_folders, class_name: "Folder",
|
||||
foreign_key: "parent_id"
|
||||
|
||||
belongs_to :parent, class_name: "Folder", optional: true
|
||||
end
|
|
@ -54,9 +54,7 @@ class Fragment < ApplicationRecord
|
|||
# Avoid N+1s: Fragment.includes(Fragment::EVERYTHING)
|
||||
EVERYTHING = { nodes: Node::EVERYTHING }
|
||||
belongs_to :device
|
||||
belongs_to :owner,
|
||||
polymorphic: true,
|
||||
inverse_of: :fragment
|
||||
belongs_to :owner, polymorphic: true, inverse_of: :fragment
|
||||
has_many :primitives, dependent: :destroy
|
||||
has_many :nodes
|
||||
has_many :primitive_pairs
|
||||
|
@ -80,7 +78,7 @@ class Fragment < ApplicationRecord
|
|||
|
||||
def self.from_celery(device:, kind:, args:, body:, owner:)
|
||||
p = { device: device, kind: kind, args: args, body: body }
|
||||
flat_ast = Fragments::Preprocessor.run!(p)
|
||||
flat_ast = Fragments::Preprocessor.run!(**p)
|
||||
Fragments::Create.run!(device: device,
|
||||
flat_ast: flat_ast,
|
||||
owner: owner)
|
||||
|
@ -89,4 +87,8 @@ class Fragment < ApplicationRecord
|
|||
def broadcast?
|
||||
false
|
||||
end
|
||||
|
||||
def self.remove_old_fragments_for_device(dev)
|
||||
dev.fragments.select { |x| x.owner == nil }.map { |x| x.destroy! }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,7 +28,7 @@ class GlobalConfig < ApplicationRecord
|
|||
# Remove it if the demo tour does not require it.
|
||||
"MQTT_WS" => SessionToken::MQTT_WS,
|
||||
}.map do |(key, value)|
|
||||
self.find_or_create_by(key: key).update_attributes(key: key, value: value)
|
||||
self.find_or_create_by(key: key).update(key: key, value: value)
|
||||
end
|
||||
|
||||
# Memoized version of every GlobalConfig, with key/values layed out in a hash.
|
||||
|
|
|
@ -40,17 +40,10 @@ class Image < ApplicationRecord
|
|||
# Worst case scenario for 1280x1280 BMP.
|
||||
GCS_BUCKET_NAME = ENV["GCS_BUCKET"]
|
||||
|
||||
# ========= DEPRECATED PAPERCLIP STUFF =========
|
||||
# has_attached_file :attachment, CONFIG
|
||||
# validates_attachment_content_type :attachment,
|
||||
# content_type: CONTENT_TYPES
|
||||
# ========= /DEPRECATED PAPERCLIP STUFF ========
|
||||
has_one_attached :attachment
|
||||
|
||||
def set_attachment_by_url(url)
|
||||
# File
|
||||
# URI::HTTPS
|
||||
attachment.attach(io: open(url), filename: "image_#{self.id}")
|
||||
attachment.attach(io: URI.open(url), filename: "image_#{self.id}")
|
||||
self.attachment_processed_at = Time.now
|
||||
self
|
||||
end
|
||||
|
|
|
@ -6,8 +6,9 @@ class InUsePoint < ApplicationRecord
|
|||
DEFAULT_NAME = "point"
|
||||
FANCY_NAMES = {
|
||||
GenericPointer.name => DEFAULT_NAME,
|
||||
ToolSlot.name => "tool slot",
|
||||
ToolSlot.name => "slot",
|
||||
Plant.name => "plant",
|
||||
Weed.name => "weed"
|
||||
}
|
||||
|
||||
def readonly?
|
||||
|
|
|
@ -23,8 +23,6 @@ class Log < ApplicationRecord
|
|||
|
||||
validates :device, presence: true
|
||||
validates :type, presence: true
|
||||
serialize :meta
|
||||
validates :meta, presence: true
|
||||
# http://stackoverflow.com/a/5127684/1064917
|
||||
before_validation :set_defaults
|
||||
|
||||
|
@ -32,27 +30,6 @@ class Log < ApplicationRecord
|
|||
self.channels ||= []
|
||||
end
|
||||
|
||||
# Legacy shims ===============================================================
|
||||
# TODO: Remove these once FBOS stops using the `meta` field (FBOS < v6.4.0).
|
||||
def meta
|
||||
{
|
||||
type: self.type,
|
||||
major_version: self.major_version,
|
||||
minor_version: self.minor_version,
|
||||
verbosity: self.verbosity,
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
z: self.z,
|
||||
}
|
||||
end
|
||||
|
||||
def meta=(hash)
|
||||
hash.map { |(key, value)| self.send("#{key}=", value) }
|
||||
self.meta
|
||||
end
|
||||
|
||||
# End Legacy shims ===========================================================
|
||||
|
||||
def broadcast? # Logs get their own special channel. Don't echo twice!
|
||||
false
|
||||
end
|
||||
|
|
|
@ -6,8 +6,9 @@ module LogDeliveryStuff
|
|||
|
||||
module ClassMethods
|
||||
# If this method grows, create a mutation.
|
||||
def deliver(device, log)
|
||||
send_fatal_emails(log, device)
|
||||
def deliver(log_id)
|
||||
log = Log.find_by(id: log_id)
|
||||
send_fatal_emails(log, log.device) if log
|
||||
end
|
||||
|
||||
def send_fatal_emails(log, device)
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
class PinBinding < ApplicationRecord
|
||||
OFF_LIMITS = [
|
||||
2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 19, 21, 23, 24, 25, 27
|
||||
OFF_LIMITS = [
|
||||
2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 19, 21, 23, 24, 25, 27,
|
||||
]
|
||||
BAD_PIN_NUM = \
|
||||
"The following pin numbers cannot be used: %s" % OFF_LIMITS.join(", ")
|
||||
BAD_PIN_NUM = "The following pin numbers cannot be used: %s" % OFF_LIMITS.join(", ")
|
||||
|
||||
belongs_to :device
|
||||
belongs_to :sequence
|
||||
enum special_action: { dump_info: "dump_info",
|
||||
emergency_lock: "emergency_lock",
|
||||
enum special_action: { emergency_lock: "emergency_lock",
|
||||
emergency_unlock: "emergency_unlock",
|
||||
power_off: "power_off",
|
||||
read_status: "read_status",
|
||||
reboot: "reboot",
|
||||
sync: "sync",
|
||||
take_photo: "take_photo" }
|
||||
power_off: "power_off",
|
||||
read_status: "read_status",
|
||||
reboot: "reboot",
|
||||
sync: "sync",
|
||||
take_photo: "take_photo" }
|
||||
validates :pin_num, uniqueness: { scope: :device }
|
||||
|
||||
def fancy_name
|
||||
|
|
|
@ -4,12 +4,12 @@ class Point < ApplicationRecord
|
|||
# axis value > 21k right now - RC
|
||||
# Using real constants instead of strings results
|
||||
# in circular dep. errors.
|
||||
POINTER_KINDS = ["GenericPointer", "Plant", "ToolSlot"]
|
||||
POINTER_KINDS = ["GenericPointer", "Plant", "ToolSlot", "Weed"]
|
||||
self.inheritance_column = "pointer_type"
|
||||
|
||||
belongs_to :device
|
||||
validates_presence_of :device
|
||||
has_many :point_group_items
|
||||
has_many :point_group_items, dependent: :destroy
|
||||
|
||||
after_discard :maybe_broadcast
|
||||
|
||||
|
|
|
@ -1,4 +1,19 @@
|
|||
class PointGroup < ApplicationRecord
|
||||
SORT_TYPES =
|
||||
%w(xy_ascending xy_descending yx_ascending yx_descending random).sort
|
||||
BAD_SORT = "%{value} is not valid. Valid options are: " +
|
||||
SORT_TYPES.map(&:inspect).join(", ")
|
||||
DEFAULT_CRITERIA = {
|
||||
day: { op: "<", days_ago: 0 },
|
||||
string_eq: {},
|
||||
number_eq: {},
|
||||
number_lt: {},
|
||||
number_gt: {},
|
||||
}
|
||||
|
||||
belongs_to :device
|
||||
has_many :point_group_items, dependent: :destroy
|
||||
validates_inclusion_of :sort_type, in: SORT_TYPES,
|
||||
message: BAD_SORT
|
||||
serialize :criteria
|
||||
end
|
||||
|
|
|
@ -6,23 +6,23 @@
|
|||
# CeleryScript is a tree of PrimaryNode objects in the center and primitive
|
||||
# "EdgeNode" types on the edge of the tree.
|
||||
class PrimaryNode < ApplicationRecord
|
||||
belongs_to :sequence
|
||||
belongs_to :sequence
|
||||
validates_presence_of :sequence
|
||||
has_many :edge_nodes
|
||||
has_many :edge_nodes
|
||||
BAD_KIND = "`kind` must be one of: " +
|
||||
CeleryScriptSettingsBag::ANY_NODE_NAME.join(", ")
|
||||
CeleryScriptSettingsBag::ANY_NODE_NAME.join(", ")
|
||||
validates :kind, inclusion: { in: CeleryScriptSettingsBag::ANY_NODE_NAME,
|
||||
message: BAD_KIND,
|
||||
allow_nil: false }
|
||||
validates :parent_arg_name,
|
||||
inclusion: {in: CeleryScriptSettingsBag::ANY_ARG_NAME,
|
||||
message: BAD_KIND,
|
||||
allow_nil: true}
|
||||
inclusion: { in: CeleryScriptSettingsBag::ANY_ARG_NAME,
|
||||
message: BAD_KIND,
|
||||
allow_nil: true }
|
||||
|
||||
before_save :next_must_be_body_node
|
||||
|
||||
def next_must_be_body_node
|
||||
raise "NO!" if(next_id && self.class.find(next_id).parent_arg_name)
|
||||
raise "NO!" if (next_id && self.class.find(next_id).parent_arg_name)
|
||||
end
|
||||
|
||||
def parent
|
||||
|
|
|
@ -16,6 +16,8 @@ class Sequence < ApplicationRecord
|
|||
include CeleryScriptSettingsBag
|
||||
|
||||
belongs_to :device
|
||||
belongs_to :folder
|
||||
belongs_to :fbos_config, foreign_key: :boot_sequence_id
|
||||
has_one :sequence_usage_report
|
||||
has_many :farm_events, as: :executable
|
||||
has_many :regimen_items
|
||||
|
|
|
@ -4,6 +4,7 @@ class TokenIssuance < ApplicationRecord
|
|||
belongs_to :device
|
||||
# Number of ms Rails will wait for the API.
|
||||
API_TIMEOUT = Rails.env.test? ? 0.01 : 2.5
|
||||
after_create :reset_inactivity_timer
|
||||
|
||||
def broadcast?
|
||||
false
|
||||
|
@ -40,4 +41,8 @@ class TokenIssuance < ApplicationRecord
|
|||
def self.clean_old_tokens
|
||||
expired.destroy_all
|
||||
end
|
||||
|
||||
def reset_inactivity_timer
|
||||
device.users.map(&:reset_inactivity_tracking!)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,9 @@ class ToolSlot < Point
|
|||
MIN_PULLOUT = PULLOUT_DIRECTIONS.min
|
||||
PULLOUT_ERR = "must be a value between #{MIN_PULLOUT} and #{MAX_PULLOUT}. "\
|
||||
"%{value} is not valid."
|
||||
IN_USE = "already in use by another tool slot"
|
||||
IN_USE = "already in use by another slot. "\
|
||||
"Please un-assign the tool from its current slot"\
|
||||
" before reassigning."
|
||||
|
||||
belongs_to :tool
|
||||
validates_uniqueness_of :tool,
|
||||
|
|
|
@ -42,6 +42,14 @@ class Transport
|
|||
.bind("amq.topic", routing_key: "bot.*.logs")
|
||||
end
|
||||
|
||||
def telemetry_channel
|
||||
@telemetry_channel ||= self
|
||||
.connection
|
||||
.create_channel
|
||||
.queue("api_telemetry_workers")
|
||||
.bind("amq.topic", routing_key: "bot.*.telemetry")
|
||||
end
|
||||
|
||||
def resource_channel
|
||||
@resource_channel ||= self
|
||||
.connection
|
||||
|
|
|
@ -45,15 +45,43 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
def self.refresh_everyones_ui
|
||||
Rollbar.error("Global UI refresh triggered")
|
||||
|
||||
msg = {
|
||||
"type" => "reload",
|
||||
"commit" => (ENV["HEROKU_SLUG_COMMIT"] || "NONE").first(8)
|
||||
"commit" => (ENV["HEROKU_SLUG_COMMIT"] || "NONE").first(8),
|
||||
}
|
||||
|
||||
Transport
|
||||
.current
|
||||
.raw_amqp_send(msg.to_json, Api::RmqUtilsController::PUBLIC_BROADCAST)
|
||||
end
|
||||
|
||||
# The web app deletes account that go inactive for long periods.
|
||||
# It is called when the user logs in to the app.
|
||||
def reset_inactivity_tracking!
|
||||
update!(inactivity_warning_sent_at: nil)
|
||||
end
|
||||
|
||||
def send_inactivity_warning
|
||||
User.transaction do
|
||||
update!(inactivity_warning_sent_at: Time.now)
|
||||
InactivityMailer.send_warning(self).deliver_later
|
||||
puts "INACTIVITY WARNING FOR #{email}" unless Rails.env.test?
|
||||
end
|
||||
end
|
||||
|
||||
def deactivate_account
|
||||
User.transaction do
|
||||
email = self.email
|
||||
if reload.last_sign_in_at > 3.months.ago
|
||||
puts "CANCEL DEACTIVATION FOR #{email}" unless Rails.env.test?
|
||||
update!(inactivity_warning_sent_at: nil)
|
||||
return # <== DON'T DELETE THIS LINE!!!
|
||||
else
|
||||
# Prevent double deletion / race conditions.
|
||||
update!(last_sign_in_at: Time.now, inactivity_warning_sent_at: nil)
|
||||
delay.destroy!
|
||||
puts "INACTIVITY DELETION FOR #{email}" unless Rails.env.test?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
class Weed < Point
|
||||
end
|
|
@ -26,7 +26,7 @@ module Auth
|
|||
end
|
||||
|
||||
def execute
|
||||
@user.update_attributes(agreed_to_terms_at: Time.now) if agree_to_terms
|
||||
@user.update(agreed_to_terms_at: Time.now) if agree_to_terms
|
||||
SessionToken.as_json(@user, aud, fbos_version)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Auth
|
||||
# The API supports a number of authentication strategies (Cookies, Bot token,
|
||||
# JWT). This service helps determine which auth strategy to use.
|
||||
class FromJWT < Mutations::Command
|
||||
class FromJwt < Mutations::Command
|
||||
required { string :jwt }
|
||||
|
||||
def execute
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
module Configs
|
||||
class Update < Mutations::Command
|
||||
HOTFIX = [ :encoder_scaling_x, :encoder_scaling_y, :encoder_scaling_z ]
|
||||
BAD = 56
|
||||
GOOD = 5556
|
||||
HOTFIX = [:encoder_scaling_x, :encoder_scaling_y, :encoder_scaling_z]
|
||||
BAD = 56
|
||||
GOOD = 5556
|
||||
|
||||
required do
|
||||
duck :target, methods: [:update_attributes!]
|
||||
duck :target, methods: [:update!]
|
||||
duck :update_attrs, methods: [:deep_symbolize_keys]
|
||||
end
|
||||
|
||||
def execute
|
||||
target.assign_attributes(sliced_attrs)
|
||||
# Remove HOTFIX after November 12, 2019 - RC
|
||||
HOTFIX.map do |attr|
|
||||
target.assign_attributes(attr => GOOD) if target.try(attr) == BAD
|
||||
end
|
||||
|
@ -20,7 +21,7 @@ module Configs
|
|||
|
||||
def sliced_attrs
|
||||
whitelist = target.class.column_names.map(&:to_sym)
|
||||
updates = update_attrs
|
||||
updates = update_attrs
|
||||
.deep_symbolize_keys
|
||||
.except(:device_id, :id, :created_at)
|
||||
updates.slice(*whitelist)
|
||||
|
|
|
@ -10,7 +10,7 @@ module DeviceCerts
|
|||
SendNervesHubInfoJob.perform_later(device_id: device.id,
|
||||
serial_number: serial_number,
|
||||
tags: tags)
|
||||
return device.update_attributes!(serial_number: serial_number) && device
|
||||
return device.update!(serial_number: serial_number) && device
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,7 +24,7 @@ module Devices
|
|||
# when we were using MongoDB. This can be
|
||||
# safely removed now. - RC 11-APR-19
|
||||
old_device = user.device
|
||||
user.update_attributes!(device_id: device.id) # Detach from old one.
|
||||
user.update!(device_id: device.id) # Detach from old one.
|
||||
# Remove userless devices.
|
||||
old_device.destroy! if old_device && device.users.count < 1
|
||||
end
|
||||
|
|
|
@ -7,7 +7,9 @@ module Devices
|
|||
"genesis_1.2" => Devices::Seeders::GenesisOneTwo,
|
||||
"genesis_1.3" => Devices::Seeders::GenesisOneThree,
|
||||
"genesis_1.4" => Devices::Seeders::GenesisOneFour,
|
||||
"genesis_1.5" => Devices::Seeders::GenesisOneFive,
|
||||
"genesis_xl_1.4" => Devices::Seeders::GenesisXlOneFour,
|
||||
"genesis_xl_1.5" => Devices::Seeders::GenesisXlOneFive,
|
||||
|
||||
"demo_account" => Devices::Seeders::DemoAccountSeeder,
|
||||
"none" => Devices::Seeders::None,
|
||||
|
|
|
@ -7,7 +7,7 @@ module Devices
|
|||
|
||||
def execute
|
||||
ActiveRecord::Base.transaction do
|
||||
user.update_attributes!(device: Devices::Create.run!(user: user))
|
||||
user.update!(device: Devices::Create.run!(user: user))
|
||||
device.destroy! if device.reload.users.count < 1
|
||||
end
|
||||
true
|
||||
|
|
|
@ -20,7 +20,7 @@ module Devices
|
|||
|
||||
def run_it
|
||||
ActiveRecord::Base.transaction do
|
||||
device.update_attributes!(name: "FarmBot")
|
||||
device.update!(name: "FarmBot")
|
||||
Device::SINGULAR_RESOURCES.keys.map do |resource|
|
||||
device.send(resource).destroy!
|
||||
end
|
||||
|
|
|
@ -2,14 +2,17 @@ module Devices
|
|||
module Seeders
|
||||
class AbstractExpress < AbstractGenesis
|
||||
def settings_device_name
|
||||
device.update_attributes!(name: "FarmBot Express")
|
||||
device.update!(name: "FarmBot Express")
|
||||
end
|
||||
|
||||
def peripherals_peripheral_4; end
|
||||
def peripherals_peripheral_5; end
|
||||
|
||||
def sensors_soil_sensor; end
|
||||
def sensors_tool_verification; end
|
||||
|
||||
def settings_enable_encoders
|
||||
device.firmware_config.update_attributes!(encoder_enabled_x: 0,
|
||||
device.firmware_config.update!(encoder_enabled_x: 0,
|
||||
encoder_enabled_y: 0,
|
||||
encoder_enabled_z: 0)
|
||||
end
|
||||
|
@ -17,14 +20,14 @@ module Devices
|
|||
def settings_firmware
|
||||
device
|
||||
.fbos_config
|
||||
.update_attributes!(firmware_hardware: FbosConfig::EXPRESS_K10)
|
||||
.update!(firmware_hardware: FbosConfig::EXPRESS_K10)
|
||||
end
|
||||
|
||||
def tool_slots_slot_1
|
||||
add_tool_slot(name: ToolNames::SEED_TROUGH_1,
|
||||
x: 0,
|
||||
y: 25,
|
||||
z: -200,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_1,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
|
@ -34,25 +37,18 @@ module Devices
|
|||
add_tool_slot(name: ToolNames::SEED_TROUGH_2,
|
||||
x: 0,
|
||||
y: 50,
|
||||
z: -200,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_2,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tool_slots_slot_3
|
||||
add_tool_slot(name: ToolNames::SEED_TROUGH_3,
|
||||
x: 0,
|
||||
y: 75,
|
||||
z: -200,
|
||||
tool: tools_seed_trough_3,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tool_slots_slot_3; end
|
||||
def tool_slots_slot_4; end
|
||||
def tool_slots_slot_5; end
|
||||
def tool_slots_slot_6; end
|
||||
def tool_slots_slot_7; end
|
||||
def tool_slots_slot_8; end
|
||||
def tools_seed_bin; end
|
||||
def tools_seed_tray; end
|
||||
|
||||
|
@ -66,11 +62,6 @@ module Devices
|
|||
add_tool(ToolNames::SEED_TROUGH_2)
|
||||
end
|
||||
|
||||
def tools_seed_trough_3
|
||||
@tools_seed_trough_3 ||=
|
||||
add_tool(ToolNames::SEED_TROUGH_3)
|
||||
end
|
||||
|
||||
def tools_seeder; end
|
||||
def tools_soil_sensor; end
|
||||
def tools_watering_nozzle; end
|
||||
|
@ -91,11 +82,11 @@ module Devices
|
|||
def sequences_unmount_tool; end
|
||||
|
||||
def settings_default_map_size_y
|
||||
device.web_app_config.update_attributes!(map_size_y: 1_200)
|
||||
device.web_app_config.update!(map_size_y: 1_200)
|
||||
end
|
||||
|
||||
def settings_hide_sensors
|
||||
device.web_app_config.update_attributes!(hide_sensors: true)
|
||||
device.web_app_config.update!(hide_sensors: true)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -18,11 +18,11 @@ module Devices
|
|||
end
|
||||
|
||||
def settings_device_name
|
||||
device.update_attributes!(name: "FarmBot Genesis")
|
||||
device.update!(name: "FarmBot Genesis")
|
||||
end
|
||||
|
||||
def settings_enable_encoders
|
||||
device.firmware_config.update_attributes!(encoder_enabled_x: 1,
|
||||
device.firmware_config.update!(encoder_enabled_x: 1,
|
||||
encoder_enabled_y: 1,
|
||||
encoder_enabled_z: 1)
|
||||
end
|
||||
|
@ -75,6 +75,9 @@ module Devices
|
|||
tool: tools_weeder)
|
||||
end
|
||||
|
||||
def tool_slots_slot_7; end
|
||||
def tool_slots_slot_8; end
|
||||
|
||||
def tools_seed_bin
|
||||
@tools_seed_bin ||=
|
||||
add_tool(ToolNames::SEED_BIN)
|
||||
|
@ -87,7 +90,6 @@ module Devices
|
|||
|
||||
def tools_seed_trough_1; end
|
||||
def tools_seed_trough_2; end
|
||||
def tools_seed_trough_3; end
|
||||
|
||||
def tools_seeder
|
||||
@tools_seeder ||=
|
||||
|
@ -146,15 +148,15 @@ module Devices
|
|||
def settings_firmware
|
||||
device
|
||||
.fbos_config
|
||||
.update_attributes!(firmware_hardware: FbosConfig::FARMDUINO)
|
||||
.update!(firmware_hardware: FbosConfig::FARMDUINO)
|
||||
end
|
||||
|
||||
def settings_default_map_size_x
|
||||
device.web_app_config.update_attributes!(map_size_x: 2_900)
|
||||
device.web_app_config.update!(map_size_x: 2_900)
|
||||
end
|
||||
|
||||
def settings_default_map_size_y
|
||||
device.web_app_config.update_attributes!(map_size_y: 1_400)
|
||||
device.web_app_config.update!(map_size_y: 1_400)
|
||||
end
|
||||
|
||||
def pin_bindings_button_1
|
||||
|
|
|
@ -37,7 +37,6 @@ module Devices
|
|||
:tools_seed_tray,
|
||||
:tools_seed_trough_1,
|
||||
:tools_seed_trough_2,
|
||||
:tools_seed_trough_3,
|
||||
:tools_seeder,
|
||||
:tools_soil_sensor,
|
||||
:tools_watering_nozzle,
|
||||
|
@ -50,6 +49,8 @@ module Devices
|
|||
:tool_slots_slot_4,
|
||||
:tool_slots_slot_5,
|
||||
:tool_slots_slot_6,
|
||||
:tool_slots_slot_7,
|
||||
:tool_slots_slot_8,
|
||||
|
||||
# WEBCAM FEEDS ===========================
|
||||
:webcam_feeds,
|
||||
|
@ -72,7 +73,7 @@ module Devices
|
|||
end
|
||||
|
||||
def settings_hide_sensors
|
||||
device.web_app_config.update_attributes!(hide_sensors: false)
|
||||
device.web_app_config.update!(hide_sensors: false)
|
||||
end
|
||||
|
||||
def plants
|
||||
|
@ -152,11 +153,12 @@ module Devices
|
|||
def tool_slots_slot_4; end
|
||||
def tool_slots_slot_5; end
|
||||
def tool_slots_slot_6; end
|
||||
def tool_slots_slot_7; end
|
||||
def tool_slots_slot_8; end
|
||||
def tools_seed_bin; end
|
||||
def tools_seed_tray; end
|
||||
def tools_seed_trough_1; end
|
||||
def tools_seed_trough_2; end
|
||||
def tools_seed_trough_3; end
|
||||
def tools_seeder; end
|
||||
def tools_soil_sensor; end
|
||||
def tools_watering_nozzle; end
|
||||
|
|
|
@ -31,7 +31,6 @@ module Devices
|
|||
LIGHTING = "Lighting"
|
||||
SEED_TROUGH_1 = "Seed Trough 1"
|
||||
SEED_TROUGH_2 = "Seed Trough 2"
|
||||
SEED_TROUGH_3 = "Seed Trough 3"
|
||||
end
|
||||
|
||||
# Stub plants ==============================
|
||||
|
|
|
@ -55,10 +55,10 @@ module Devices
|
|||
.map { |p| p.merge(device: device) }
|
||||
.map { |p| Alerts::Create.run!(p) }
|
||||
device
|
||||
.update_attributes!(fbos_version: READ_COMMENT_ABOVE)
|
||||
.update!(fbos_version: READ_COMMENT_ABOVE)
|
||||
device
|
||||
.web_app_config
|
||||
.update_attributes!(discard_unsaved: true)
|
||||
.update!(discard_unsaved: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,15 +2,15 @@ module Devices
|
|||
module Seeders
|
||||
class ExpressXlOneZero < AbstractExpress
|
||||
def settings_device_name
|
||||
device.update_attributes!(name: "FarmBot Express XL")
|
||||
device.update!(name: "FarmBot Express XL")
|
||||
end
|
||||
|
||||
def settings_default_map_size_x
|
||||
device.web_app_config.update_attributes!(map_size_x: 6_000)
|
||||
device.web_app_config.update!(map_size_x: 6_000)
|
||||
end
|
||||
|
||||
def settings_default_map_size_y
|
||||
device.web_app_config.update_attributes!(map_size_y: 2_400)
|
||||
device.web_app_config.update!(map_size_y: 2_400)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
module Devices
|
||||
module Seeders
|
||||
class GenesisOneFive < AbstractGenesis
|
||||
def settings_firmware
|
||||
device
|
||||
.fbos_config
|
||||
.update!(firmware_hardware: FbosConfig::FARMDUINO_K15)
|
||||
end
|
||||
|
||||
def tool_slots_slot_7
|
||||
add_tool_slot(name: ToolNames::SEED_TROUGH_1,
|
||||
x: 0,
|
||||
y: 25,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_1,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tool_slots_slot_8
|
||||
add_tool_slot(name: ToolNames::SEED_TROUGH_2,
|
||||
x: 0,
|
||||
y: 50,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_2,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tools_seed_trough_1
|
||||
@tools_seed_trough_1 ||=
|
||||
add_tool(ToolNames::SEED_TROUGH_1)
|
||||
end
|
||||
|
||||
def tools_seed_trough_2
|
||||
@tools_seed_trough_2 ||=
|
||||
add_tool(ToolNames::SEED_TROUGH_2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,7 +4,7 @@ module Devices
|
|||
def settings_firmware
|
||||
device
|
||||
.fbos_config
|
||||
.update_attributes!(firmware_hardware: FbosConfig::FARMDUINO_K14)
|
||||
.update!(firmware_hardware: FbosConfig::FARMDUINO_K14)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ module Devices
|
|||
def settings_firmware
|
||||
device
|
||||
.fbos_config
|
||||
.update_attributes!(firmware_hardware: FbosConfig::ARDUINO)
|
||||
.update!(firmware_hardware: FbosConfig::ARDUINO)
|
||||
end
|
||||
|
||||
def peripherals_lighting; end
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
module Devices
|
||||
module Seeders
|
||||
class GenesisXlOneFive < AbstractGenesis
|
||||
def settings_firmware
|
||||
device
|
||||
.fbos_config
|
||||
.update!(firmware_hardware: FbosConfig::FARMDUINO_K15)
|
||||
end
|
||||
|
||||
def settings_device_name
|
||||
device.update!(name: "FarmBot Genesis XL")
|
||||
end
|
||||
|
||||
def settings_default_map_size_x
|
||||
device.web_app_config.update!(map_size_x: 5_900)
|
||||
end
|
||||
|
||||
def settings_default_map_size_y
|
||||
device.web_app_config.update!(map_size_y: 2_900)
|
||||
end
|
||||
|
||||
def tool_slots_slot_7
|
||||
add_tool_slot(name: ToolNames::SEED_TROUGH_1,
|
||||
x: 0,
|
||||
y: 25,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_1,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tool_slots_slot_8
|
||||
add_tool_slot(name: ToolNames::SEED_TROUGH_2,
|
||||
x: 0,
|
||||
y: 50,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_2,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tools_seed_trough_1
|
||||
@tools_seed_trough_1 ||=
|
||||
add_tool(ToolNames::SEED_TROUGH_1)
|
||||
end
|
||||
|
||||
def tools_seed_trough_2
|
||||
@tools_seed_trough_2 ||=
|
||||
add_tool(ToolNames::SEED_TROUGH_2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,19 +4,19 @@ module Devices
|
|||
def settings_firmware
|
||||
device
|
||||
.fbos_config
|
||||
.update_attributes!(firmware_hardware: FbosConfig::FARMDUINO_K14)
|
||||
.update!(firmware_hardware: FbosConfig::FARMDUINO_K14)
|
||||
end
|
||||
|
||||
def settings_device_name
|
||||
device.update_attributes!(name: "FarmBot Genesis XL")
|
||||
device.update!(name: "FarmBot Genesis XL")
|
||||
end
|
||||
|
||||
def settings_default_map_size_x
|
||||
device.web_app_config.update_attributes!(map_size_x: 5_900)
|
||||
device.web_app_config.update!(map_size_x: 5_900)
|
||||
end
|
||||
|
||||
def settings_default_map_size_y
|
||||
device.web_app_config.update_attributes!(map_size_y: 2_900)
|
||||
device.web_app_config.update!(map_size_y: 2_900)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,11 +28,12 @@ module Devices
|
|||
def tool_slots_slot_4; end
|
||||
def tool_slots_slot_5; end
|
||||
def tool_slots_slot_6; end
|
||||
def tool_slots_slot_7; end
|
||||
def tool_slots_slot_8; end
|
||||
def tools_seed_bin; end
|
||||
def tools_seed_tray; end
|
||||
def tools_seed_trough_1; end
|
||||
def tools_seed_trough_2; end
|
||||
def tools_seed_trough_3; end
|
||||
def tools_seeder; end
|
||||
def tools_soil_sensor; end
|
||||
def tools_watering_nozzle; end
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
pullout_direction: 0
|
||||
radius: 25.0
|
||||
x: 400.0
|
||||
y: 200.0
|
||||
y: 100.0
|
||||
z: 0.0
|
||||
- meta: {}
|
||||
pointer_type: Plant
|
||||
|
@ -19,7 +19,7 @@
|
|||
pullout_direction: 0
|
||||
radius: 25.0
|
||||
x: 400.0
|
||||
y: 400.0
|
||||
y: 300.0
|
||||
z: 0.0
|
||||
- meta: {}
|
||||
pointer_type: Plant
|
||||
|
@ -30,7 +30,7 @@
|
|||
pullout_direction: 0
|
||||
radius: 25.0
|
||||
x: 600.0
|
||||
y: 200.0
|
||||
y: 100.0
|
||||
z: 0.0
|
||||
- meta: {}
|
||||
pointer_type: Plant
|
||||
|
@ -41,7 +41,7 @@
|
|||
pullout_direction: 0
|
||||
radius: 25.0
|
||||
x: 600.0
|
||||
y: 400.0
|
||||
y: 300.0
|
||||
z: 0.0
|
||||
- meta: {}
|
||||
pointer_type: Plant
|
||||
|
@ -52,7 +52,7 @@
|
|||
pullout_direction: 0
|
||||
radius: 25.0
|
||||
x: 800.0
|
||||
y: 400.0
|
||||
y: 300.0
|
||||
z: 0.0
|
||||
- meta: {}
|
||||
pointer_type: Plant
|
||||
|
@ -63,7 +63,7 @@
|
|||
pullout_direction: 0
|
||||
radius: 25.0
|
||||
x: 800.0
|
||||
y: 200.0
|
||||
y: 100.0
|
||||
z: 0.0
|
||||
- meta: {}
|
||||
pointer_type: Plant
|
||||
|
@ -74,49 +74,49 @@
|
|||
pullout_direction: 0
|
||||
radius: 25.0
|
||||
x: 600.0
|
||||
y: 800.0
|
||||
y: 700.0
|
||||
z: 0.0
|
||||
- meta: {}
|
||||
pointer_type: Plant
|
||||
name: Beets
|
||||
openfarm_slug: beets
|
||||
openfarm_slug: beet
|
||||
plant_stage: planned
|
||||
planted_at:
|
||||
pullout_direction: 0
|
||||
radius: 25.0
|
||||
x: 800.0
|
||||
y: 1200.0
|
||||
y: 1100.0
|
||||
z: 0.0
|
||||
- meta: {}
|
||||
pointer_type: Plant
|
||||
name: Beets
|
||||
openfarm_slug: beets
|
||||
openfarm_slug: beet
|
||||
plant_stage: planned
|
||||
planted_at:
|
||||
pullout_direction: 0
|
||||
radius: 25.0
|
||||
x: 600.0
|
||||
y: 1200.0
|
||||
y: 1100.0
|
||||
z: 0.0
|
||||
- meta: {}
|
||||
pointer_type: Plant
|
||||
name: Beets
|
||||
openfarm_slug: beets
|
||||
openfarm_slug: beet
|
||||
plant_stage: planned
|
||||
planted_at:
|
||||
pullout_direction: 0
|
||||
radius: 25.0
|
||||
x: 400.0
|
||||
y: 1200.0
|
||||
y: 1100.0
|
||||
z: 0.0
|
||||
- meta: {}
|
||||
pointer_type: Plant
|
||||
name: Beets
|
||||
openfarm_slug: beets
|
||||
openfarm_slug: beet
|
||||
plant_stage: planned
|
||||
planted_at:
|
||||
pullout_direction: 0
|
||||
radius: 25.0
|
||||
x: 200.0
|
||||
y: 1200.0
|
||||
y: 1100.0
|
||||
z: 0.0
|
||||
|
|
|
@ -2,22 +2,28 @@ module Devices
|
|||
class Sync < Mutations::Command
|
||||
SEL = "SELECT id, updated_at FROM"
|
||||
WHERE = "WHERE device_id = "
|
||||
WHERE2 = "devices WHERE id = "
|
||||
FORMAT = "%Y-%m-%d %H:%M:%S.%5N"
|
||||
|
||||
def self.basic_query(plural_resource, where = WHERE)
|
||||
[SEL, plural_resource, where].join(" ")
|
||||
end
|
||||
|
||||
QUERIES = {
|
||||
devices: [SEL, WHERE2].join(" "),
|
||||
farm_events: [SEL, "farm_events", WHERE].join(" "),
|
||||
farmware_envs: [SEL, "farmware_envs", WHERE].join(" "),
|
||||
farmware_installations: [SEL, "farmware_installations", WHERE].join(" "),
|
||||
peripherals: [SEL, "peripherals", WHERE].join(" "),
|
||||
pin_bindings: [SEL, "pin_bindings", WHERE].join(" "),
|
||||
points: [SEL, "points", WHERE].join(" "),
|
||||
regimens: [SEL, "regimens", WHERE].join(" "),
|
||||
sensor_readings: [SEL, "sensor_readings", WHERE].join(" "),
|
||||
sensors: [SEL, "sensors", WHERE].join(" "),
|
||||
sequences: [SEL, "sequences", WHERE].join(" "),
|
||||
tools: [SEL, "tools", WHERE].join(" "),
|
||||
fbos_configs: [SEL, "fbos_configs", WHERE].join(" "),
|
||||
firmware_configs: [SEL, "firmware_configs", WHERE].join(" "),
|
||||
devices: basic_query("devices", "WHERE id = "),
|
||||
farm_events: basic_query("farm_events"),
|
||||
farmware_envs: basic_query("farmware_envs"),
|
||||
farmware_installations: basic_query("farmware_installations"),
|
||||
peripherals: basic_query("peripherals"),
|
||||
pin_bindings: basic_query("pin_bindings"),
|
||||
points: basic_query("points"),
|
||||
regimens: basic_query("regimens"),
|
||||
sensor_readings: basic_query("sensor_readings"),
|
||||
sensors: basic_query("sensors"),
|
||||
sequences: basic_query("sequences"),
|
||||
tools: basic_query("tools"),
|
||||
fbos_configs: basic_query("fbos_configs"),
|
||||
firmware_configs: basic_query("firmware_configs"),
|
||||
point_groups: basic_query("point_groups"),
|
||||
}
|
||||
|
||||
STUB_FARMWARES = Api::FirstPartyFarmwaresController::STUBS.values.map do |x|
|
||||
|
|
|
@ -7,10 +7,14 @@ module Devices
|
|||
end
|
||||
|
||||
optional do
|
||||
string :name
|
||||
string :timezone#, in: Device::TIMEZONES
|
||||
time :last_saw_mq
|
||||
string :name
|
||||
string :timezone
|
||||
time :last_saw_mq
|
||||
time :last_ota
|
||||
time :last_ota_checkup
|
||||
boolean :needs_reset
|
||||
integer :mounted_tool_id, nils: true
|
||||
integer :ota_hour, nils: true
|
||||
end
|
||||
|
||||
def validate
|
||||
|
@ -19,11 +23,11 @@ module Devices
|
|||
|
||||
def execute
|
||||
p = inputs.except(:device).merge(mounted_tool_data)
|
||||
device.update_attributes!(p)
|
||||
device.update!(p)
|
||||
device
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def bad_tool_id
|
||||
add_error :mounted_tool_id, :mounted_tool_id, BAD_TOOL_ID % better_tool_id
|
||||
|
@ -38,7 +42,7 @@ module Devices
|
|||
end
|
||||
|
||||
def mounted_tool_data
|
||||
mounted_tool_id_present? ? {mounted_tool_id: better_tool_id} : {}
|
||||
mounted_tool_id_present? ? { mounted_tool_id: better_tool_id } : {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
module DiagnosticDumps
|
||||
class Create < Mutations::Command
|
||||
required do
|
||||
model :device, class: Device
|
||||
string :fbos_version
|
||||
string :fbos_commit
|
||||
string :firmware_commit
|
||||
string :network_interface
|
||||
string :fbos_dmesg_dump
|
||||
string :firmware_state
|
||||
end
|
||||
|
||||
def execute
|
||||
DiagnosticDump
|
||||
.create!(device: device,
|
||||
ticket_identifier: rand(36**5).to_s(36),
|
||||
fbos_version: fbos_version,
|
||||
fbos_commit: fbos_commit,
|
||||
firmware_commit: firmware_commit,
|
||||
network_interface: network_interface,
|
||||
fbos_dmesg_dump: fbos_dmesg_dump,
|
||||
firmware_state: firmware_state,)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
module DiagnosticDumps
|
||||
Destroy = CreateDestroyer.run!(resource: DiagnosticDump)
|
||||
end
|
|
@ -16,7 +16,7 @@ module FarmEvents
|
|||
kind: "internal_#{kind}",
|
||||
args: {},
|
||||
body: body }
|
||||
flat_ast = Fragments::Preprocessor.run!(params)
|
||||
flat_ast = Fragments::Preprocessor.run!(**params)
|
||||
Fragments::Create.run!(device: device,
|
||||
flat_ast: flat_ast,
|
||||
owner: owner)
|
||||
|
@ -56,7 +56,7 @@ module FarmEvents
|
|||
FarmEvents => ->() { farm_event },
|
||||
Regimens => ->() { regimen },
|
||||
}
|
||||
options.fetch(self.class.parent).call()
|
||||
options.fetch(self.class.module_parent).call()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,7 +33,7 @@ module FarmEvents
|
|||
FarmEvent.auto_sync_debounce do
|
||||
FarmEvent.transaction do
|
||||
handle_body_field
|
||||
farm_event.update_attributes!(p)
|
||||
farm_event.update!(p)
|
||||
farm_event
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ module FarmwareEnvs
|
|||
end
|
||||
|
||||
def execute
|
||||
farmware_env.update_attributes!(inputs.except(:farmware_env)) && farmware_env
|
||||
farmware_env.update!(inputs.except(:farmware_env)) && farmware_env
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
module Folders
|
||||
class Create < Mutations::Command
|
||||
required do
|
||||
model :device
|
||||
string :color
|
||||
string :name
|
||||
end
|
||||
|
||||
optional do
|
||||
integer :parent_id
|
||||
end
|
||||
|
||||
def execute
|
||||
Folder.create!(update_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_params
|
||||
inputs.except(:parent_id).merge({ parent: parent })
|
||||
end
|
||||
|
||||
def parent
|
||||
@parent ||= device.folders.find_by(id: parent_id)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
module Folders
|
||||
class Destroy < Mutations::Command
|
||||
IN_USE = "This folder still contains %s %s(s). " \
|
||||
"They must be removed prior to deletion"
|
||||
|
||||
required do
|
||||
model :device
|
||||
model :folder
|
||||
end
|
||||
|
||||
def validate
|
||||
check_subfolders
|
||||
check_sequences
|
||||
end
|
||||
|
||||
def execute
|
||||
folder.destroy! && ""
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sequences
|
||||
@sequences ||= Sequence.where(folder: folder)
|
||||
end
|
||||
|
||||
def subfolders
|
||||
@subfolders ||= Folder.where(parent: folder)
|
||||
end
|
||||
|
||||
def check_sequences
|
||||
count = sequences.count
|
||||
if count > 0
|
||||
add_error :in_use, :in_use, IN_USE % [count, "sequence"]
|
||||
end
|
||||
end
|
||||
|
||||
def check_subfolders
|
||||
count = subfolders.count
|
||||
if count > 0
|
||||
add_error :in_use, :in_use, IN_USE % [count, "subfolder"]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue