Compare commits
1661 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e3ee91cef | ||
|
|
74721b6d61 | ||
|
|
2190943faf | ||
|
|
eb80f07b7d | ||
|
|
83b4d8c686 | ||
|
|
0a2547d65c | ||
|
|
5ee2a3cb54 | ||
|
|
e505d0768c | ||
|
|
6d4fe84ddc | ||
|
|
c6b667f8b3 | ||
|
|
ad4cddb4f3 | ||
|
|
ddba83b993 | ||
|
|
91c33b0431 | ||
|
|
d1df40633a | ||
|
|
7f9fc484e8 | ||
|
|
ecf564648e | ||
|
|
150e3190bc | ||
|
|
63947346e9 | ||
|
|
86816ce357 | ||
|
|
0d34831df4 | ||
|
|
c35da67401 | ||
|
|
fb47022380 | ||
|
|
46c5128418 | ||
|
|
4a5bfee616 | ||
|
|
f8314e0f8e | ||
|
|
9624af4e67 | ||
|
|
5bec4768e7 | ||
|
|
3851b0943a | ||
|
|
cc1f640a50 | ||
|
|
ec0a2dc053 | ||
|
|
a6166a1ad7 | ||
|
|
41e3d1f490 | ||
|
|
2cbecaa552 | ||
|
|
8d543dcc7d | ||
|
|
18b1afe34f | ||
|
|
0f86bbfad8 | ||
|
|
0d021a800a | ||
|
|
038304384a | ||
|
|
2c09ad6b91 | ||
|
|
0bd09d03c1 | ||
|
|
faa0e6c289 | ||
|
|
c28d800d7f | ||
|
|
4fd772ecd8 | ||
|
|
5520a84062 | ||
|
|
66c7123f7c | ||
|
|
bacf4154fd | ||
|
|
61790d2261 | ||
|
|
899111a310 | ||
|
|
3bfa35e1c7 | ||
|
|
ebefcb7fc1 | ||
|
|
ce11685371 | ||
|
|
9edb848947 | ||
|
|
f326096fad | ||
|
|
46f0b23f4f | ||
|
|
1c1d3bd619 | ||
|
|
d894f92d5e | ||
|
|
6c44191fe4 | ||
|
|
0deb78a9af | ||
|
|
9c15f4ba88 | ||
|
|
4ba27ec1d6 | ||
|
|
c8dd80530a | ||
|
|
eda5ea7d1a | ||
|
|
77a916e1a8 | ||
|
|
7ba2a4b27b | ||
|
|
d33f69720a | ||
|
|
e5c355e8f9 | ||
|
|
d36fadf3ca | ||
|
|
b618cbdf7c | ||
|
|
15ec7173aa | ||
|
|
4166e92754 | ||
|
|
85166b6e8b | ||
|
|
5278599675 | ||
|
|
18cac8ba5d | ||
|
|
dfccbceea6 | ||
|
|
fc4b651e46 | ||
|
|
fb89922ecf | ||
|
|
8ab23c8cd9 | ||
|
|
787a2c5071 | ||
|
|
da76a20345 | ||
|
|
9688dbdb36 | ||
|
|
6fa16e1a5e | ||
|
|
71a2e3cfca | ||
|
|
e9c0f7e200 | ||
|
|
25154a4331 | ||
|
|
22c152f600 | ||
|
|
3eab61cbc3 | ||
|
|
a029c1d0db | ||
|
|
706757d215 | ||
|
|
9054c233f4 | ||
|
|
751b0ef716 | ||
|
|
716450b97e | ||
|
|
2c289a4d8f | ||
|
|
a4ad4c033f | ||
|
|
511bca9d66 | ||
|
|
ac3fb03b2d | ||
|
|
282087d0f3 | ||
|
|
781282599c | ||
|
|
d611ab0ee2 | ||
|
|
411cbdffee | ||
|
|
cfd19e02a7 | ||
|
|
717eeb3903 | ||
|
|
a394fb8757 | ||
|
|
2125a7ffdb | ||
|
|
00c0a6ec60 | ||
|
|
090bcf89ac | ||
|
|
c8d72ddd3b | ||
|
|
5cf618695f | ||
|
|
8a1f497265 | ||
|
|
acdf20f800 | ||
|
|
dbd1003002 | ||
|
|
41ccd14f25 | ||
|
|
60800df798 | ||
|
|
9c36f2cbc5 | ||
|
|
0b4fff907a | ||
|
|
50af28b2aa | ||
|
|
28ad74a68e | ||
|
|
13cdbae38f | ||
|
|
55c77df5ae | ||
|
|
9b1d2fd985 | ||
|
|
91b7ea0367 | ||
|
|
96d3926d09 | ||
|
|
c709b5a7eb | ||
|
|
df82914005 | ||
|
|
b1bdc38283 | ||
|
|
beb1215329 | ||
|
|
51784388b9 | ||
|
|
dbbbd53a4d | ||
|
|
f9d992c969 | ||
|
|
29a4d61e90 | ||
|
|
2667cdb26c | ||
|
|
a1669a5104 | ||
|
|
059f1bd63d | ||
|
|
82ae5e442c | ||
|
|
b10114cd7c | ||
|
|
33f730aac4 | ||
|
|
92fdfdb05c | ||
|
|
fbaf3f3623 | ||
|
|
5f400bc513 | ||
|
|
0fc59645fc | ||
|
|
e2dee272b8 | ||
|
|
364cf362f4 | ||
|
|
8394a263c4 | ||
|
|
0e9aa26cfc | ||
|
|
6a23d63266 | ||
|
|
af2fc15964 | ||
|
|
5919037a4a | ||
|
|
a761dab229 | ||
|
|
fa656e1f56 | ||
|
|
77e141e84a | ||
|
|
2439965fa8 | ||
|
|
f66afbee90 | ||
|
|
5a89d23a67 | ||
|
|
07c8dad1c3 | ||
|
|
beb8b18e98 | ||
|
|
887bb5d7cc | ||
|
|
4a9542d970 | ||
|
|
c049d9d5ff | ||
|
|
c2cc4389a0 | ||
|
|
12b5011266 | ||
|
|
6e3cad454c | ||
|
|
8251bd028c | ||
|
|
da87d452c2 | ||
|
|
9bca0dfb3c | ||
|
|
57904c4a97 | ||
|
|
4e74d851e9 | ||
|
|
e5c1f69b02 | ||
|
|
9d390d064c | ||
|
|
4994d7892c | ||
|
|
1ea06e3c42 | ||
|
|
a4b7a6dfc7 | ||
|
|
7fe1cce606 | ||
|
|
7e5abe32e0 | ||
|
|
47caf7c142 | ||
|
|
cf4d777344 | ||
|
|
255927c346 | ||
|
|
e8c5fc79a6 | ||
|
|
b309b24d0b | ||
|
|
13f4cca9d5 | ||
|
|
b3c0273e0c | ||
|
|
1df7fdf703 | ||
|
|
cbf38309e2 | ||
|
|
2ec7257dd7 | ||
|
|
531aac6923 | ||
|
|
59b4604c77 | ||
|
|
52aa269af9 | ||
|
|
8a03d9c498 | ||
|
|
a36fc7ecfd | ||
|
|
7b0c269bce | ||
|
|
c10bf9b357 | ||
|
|
0606642953 | ||
|
|
d1b2cae201 | ||
|
|
097e567122 | ||
|
|
d22e1d6a24 | ||
|
|
2827069bd9 | ||
|
|
614e3bd2a0 | ||
|
|
ff756a01d2 | ||
|
|
db14606dbe | ||
|
|
5bf5065d9a | ||
|
|
0235dadbf7 | ||
|
|
203a15b447 | ||
|
|
fe4dfe2194 | ||
|
|
c2eb93abe0 | ||
|
|
d32b834ae7 | ||
|
|
cecf45a698 | ||
|
|
69cd348cc3 | ||
|
|
868025ffa3 | ||
|
|
60126a8cc5 | ||
|
|
8cfba49559 | ||
|
|
168f053c6f | ||
|
|
897e1d4539 | ||
|
|
5ef6a0f4ea | ||
|
|
eb80e32812 | ||
|
|
620dadafe4 | ||
|
|
376b421eb9 | ||
|
|
e1643aca80 | ||
|
|
4e97c0c5c9 | ||
|
|
2d51b122af | ||
|
|
05b88a3c73 | ||
|
|
3c087d49e9 | ||
|
|
d81fcccf10 | ||
|
|
ee3a7bbbfc | ||
|
|
82d9e2fb16 | ||
|
|
4aa413e697 | ||
|
|
04b3fc54b0 | ||
|
|
e4c5a4e886 | ||
|
|
a0ee7a59eb | ||
|
|
b4a05160df | ||
|
|
1a437b3961 | ||
|
|
bda8555190 | ||
|
|
10ca38f91d | ||
|
|
a468faad20 | ||
|
|
7a20be4aff | ||
|
|
06b974c8a4 | ||
|
|
515394049a | ||
|
|
35c8b4f535 | ||
|
|
1a325a66b4 | ||
|
|
7d82116fb9 | ||
|
|
8a7bd4f21b | ||
|
|
2e5a2ef12d | ||
|
|
89aceda65a | ||
|
|
39fd83aa16 | ||
|
|
a23d811fe8 | ||
|
|
a238779724 | ||
|
|
3a848bc037 | ||
|
|
0528ecb454 | ||
|
|
141835593c | ||
|
|
3d06200368 | ||
|
|
729bef9a77 | ||
|
|
94f33bd642 | ||
|
|
7e010cdbca | ||
|
|
8887bcd941 | ||
|
|
56aeeee04c | ||
|
|
98eb3c7287 | ||
|
|
6819c1989b | ||
|
|
7e01dd3e97 | ||
|
|
ea4f2c3de8 | ||
|
|
b2f63b8761 | ||
|
|
65865101ce | ||
|
|
c3637afe69 | ||
|
|
ab543ddf0c | ||
|
|
80595e76e7 | ||
|
|
d49e68737a | ||
|
|
712e15ba80 | ||
|
|
986160e667 | ||
|
|
1ae4e23db1 | ||
|
|
bad646141c | ||
|
|
7911235b68 | ||
|
|
12dee4d14d | ||
|
|
cba841beb8 | ||
|
|
4e3ebf7078 | ||
|
|
1c34969f64 | ||
|
|
dc26cabacd | ||
|
|
a7bffcd471 | ||
|
|
6ae56ac2cc | ||
|
|
03c087020c | ||
|
|
857a1ab9c4 | ||
|
|
64d9530e13 | ||
|
|
5dac1efc30 | ||
|
|
18bc74bc96 | ||
|
|
f64efc63f8 | ||
|
|
e84b897991 | ||
|
|
519647ef93 | ||
|
|
f694fe00e4 | ||
|
|
0b951f27b6 | ||
|
|
8aa082c9df | ||
|
|
f2c5d47bd8 | ||
|
|
ac7642cc15 | ||
|
|
8f34865dab | ||
|
|
c762d12a40 | ||
|
|
fe1e71dc07 | ||
|
|
85b0350ed4 | ||
|
|
a980491455 | ||
|
|
5798c0ccaa | ||
|
|
742f49ca1f | ||
|
|
5560fc805b | ||
|
|
9d4f8a4e8c | ||
|
|
b4d25d6285 | ||
|
|
a504a376bd | ||
|
|
f61ea6e90a | ||
|
|
b2651df36f | ||
|
|
b56c086841 | ||
|
|
4343478c7b | ||
|
|
94649cbfc7 | ||
|
|
fb83f84d84 | ||
|
|
84c2632d40 | ||
|
|
3417ee25eb | ||
|
|
6ada30102c | ||
|
|
ac86ca7266 | ||
|
|
bb1d3edf71 | ||
|
|
97b9253017 | ||
|
|
971c2180c9 | ||
|
|
f96dc6991e | ||
|
|
6855493b2f | ||
|
|
ff0d1f7c42 | ||
|
|
3ae5824761 | ||
|
|
702e865715 | ||
|
|
6bcf64c83f | ||
|
|
18b270c9d0 | ||
|
|
783376acb0 | ||
|
|
81dab470d2 | ||
|
|
a12f0feb66 | ||
|
|
d3c99d9c1c | ||
|
|
3eb3586c0f | ||
|
|
fdde16cf56 | ||
|
|
b8bc5596fd | ||
|
|
47842a79c7 | ||
|
|
391d5bc386 | ||
|
|
ba8561e357 | ||
|
|
6aa1170cef | ||
|
|
6d4363e685 | ||
|
|
6b02b1e1e8 | ||
|
|
58a5550989 | ||
|
|
f225c5cf9a | ||
|
|
5c62c7992c | ||
|
|
70b8f09ccb | ||
|
|
abfeafa026 | ||
|
|
aa029b005f | ||
|
|
b753d2ca1e | ||
|
|
1e50329c9e | ||
|
|
4942811694 | ||
|
|
59e37e0ccb | ||
|
|
20aa86d8a9 | ||
|
|
64c5ab7042 | ||
|
|
d210f5171a | ||
|
|
c7eee0f14d | ||
|
|
221753b62e | ||
|
|
d213e4d37f | ||
|
|
f8695f21d3 | ||
|
|
4ac1030289 | ||
|
|
93c7117319 | ||
|
|
974afd92ce | ||
|
|
be847baaed | ||
|
|
2b819e6751 | ||
|
|
66247cc005 | ||
|
|
eafd38d3f2 | ||
|
|
c4e590e7a0 | ||
|
|
b92a594114 | ||
|
|
9dfb16f6b8 | ||
|
|
4b74866d85 | ||
|
|
f532c85247 | ||
|
|
b1cc00c1bc | ||
|
|
5696aa49d5 | ||
|
|
e12dc936fd | ||
|
|
6d39a7fb75 | ||
|
|
c87c312349 | ||
|
|
e9c1886cdd | ||
|
|
13e4b1a781 | ||
|
|
3766fb14ef | ||
|
|
29ee50e38b | ||
|
|
d1ab69dc31 | ||
|
|
e3c4a54193 | ||
|
|
2abbd2e3cf | ||
|
|
f9387a5851 | ||
|
|
7a9fb74b54 | ||
|
|
d754f3dd4c | ||
|
|
f54fc9e990 | ||
|
|
8952095da5 | ||
|
|
597240d501 | ||
|
|
7377906d02 | ||
|
|
ce6da1bce3 | ||
|
|
1bf8ff73f8 | ||
|
|
564aaaf3df | ||
|
|
64ba69b2d0 | ||
|
|
ce5ada42af | ||
|
|
1ce5973713 | ||
|
|
b035b53092 | ||
|
|
7d0e02358c | ||
|
|
374ff0aeb5 | ||
|
|
947a43111e | ||
|
|
5fed81c27b | ||
|
|
dce4f1a5ae | ||
|
|
7e1fc32a1c | ||
|
|
a69f14f504 | ||
|
|
931069458d | ||
|
|
a5259baab0 | ||
|
|
8aaa27350d | ||
|
|
ac74d2b7c2 | ||
|
|
2b316aeae9 | ||
|
|
aff96a45c6 | ||
|
|
e2f524ce7a | ||
|
|
a58b054292 | ||
|
|
ea9e5be1fc | ||
|
|
760ea4727c | ||
|
|
f57f2e53a0 | ||
|
|
136a393a17 | ||
|
|
8bbaab78b7 | ||
|
|
067cd59637 | ||
|
|
ce6ac7bf53 | ||
|
|
99271c4477 | ||
|
|
156142ed58 | ||
|
|
4b5516c0eb | ||
|
|
c3d8d2d240 | ||
|
|
c29cf70025 | ||
|
|
6ebce55be3 | ||
|
|
01c4a85bc0 | ||
|
|
12d4206d84 | ||
|
|
946de18bea | ||
|
|
904eb3538c | ||
|
|
c851ca9328 | ||
|
|
0ac415ad83 | ||
|
|
b3ba34d980 | ||
|
|
52740271d9 | ||
|
|
c2e444249a | ||
|
|
97310b091e | ||
|
|
4dda9cc3a1 | ||
|
|
a0538b57e2 | ||
|
|
d7f394eeb6 | ||
|
|
1bc4571d42 | ||
|
|
22e878502a | ||
|
|
03c1b6e30c | ||
|
|
374a434d98 | ||
|
|
f1e85ff0e9 | ||
|
|
6b010f76ea | ||
|
|
0c3e9f7824 | ||
|
|
ccca578622 | ||
|
|
56f7c18550 | ||
|
|
d438f71bbb | ||
|
|
ca5df24b6d | ||
|
|
4a6c2d106f | ||
|
|
cd25a9568b | ||
|
|
f78a787adb | ||
|
|
dc520fa77c | ||
|
|
8f06d4dd9d | ||
|
|
a7047183e1 | ||
|
|
c0b145da24 | ||
|
|
52e7fd6f72 | ||
|
|
4bbe22b1c7 | ||
|
|
4747ffc08b | ||
|
|
9d07131fd6 | ||
|
|
721126d3db | ||
|
|
2b65f5e3dc | ||
|
|
57f10cf387 | ||
|
|
f60c8a173b | ||
|
|
857cd690be | ||
|
|
a407b60152 | ||
|
|
2c3c55adc0 | ||
|
|
f586b4da17 | ||
|
|
0b7eb41049 | ||
|
|
bd19c4e2bd | ||
|
|
e8a73087d6 | ||
|
|
dde4fd82f4 | ||
|
|
0420c393f3 | ||
|
|
c88dac6437 | ||
|
|
cd450f55e2 | ||
|
|
190ee7f9fb | ||
|
|
fd057300cc | ||
|
|
56791089c1 | ||
|
|
e91cb32ca3 | ||
|
|
9ab20df8d2 | ||
|
|
050350501c | ||
|
|
d078acdf73 | ||
|
|
b786a688b5 | ||
|
|
6b7fe40dd2 | ||
|
|
6f6c422246 | ||
|
|
d371ff4f60 | ||
|
|
d1a8348912 | ||
|
|
be956d3cb6 | ||
|
|
ba5beb81b7 | ||
|
|
106bbe5244 | ||
|
|
f39d0e7ba2 | ||
|
|
de7a1fd8ff | ||
|
|
1ac2b25876 | ||
|
|
9e014d1371 | ||
|
|
93b274a113 | ||
|
|
474c7ae873 | ||
|
|
31690d4cad | ||
|
|
bbfc7e7e49 | ||
|
|
1c0aa55e7a | ||
|
|
29778ca19e | ||
|
|
9e87318cc5 | ||
|
|
c645be6b70 | ||
|
|
57fc5ac088 | ||
|
|
924774f52a | ||
|
|
446a7a0844 | ||
|
|
5cfeed76d0 | ||
|
|
de419319d8 | ||
|
|
7a3d36899b | ||
|
|
f5dbb363f4 | ||
|
|
2bbc59a212 | ||
|
|
3403d76aae | ||
|
|
58399cedb6 | ||
|
|
9bca7e9e11 | ||
|
|
3a61430e44 | ||
|
|
a2e996b550 | ||
|
|
cfc1c31050 | ||
|
|
45106bf6f9 | ||
|
|
6e3cfe491b | ||
|
|
12f2158afd | ||
|
|
6d78773c55 | ||
|
|
43a62d4eb6 | ||
|
|
cc08dfda96 | ||
|
|
622e33588e | ||
|
|
67980b58a0 | ||
|
|
027e444955 | ||
|
|
d838750389 | ||
|
|
71d8bd5266 | ||
|
|
ec4ae24bbd | ||
|
|
1128149359 | ||
|
|
bdfc6634ec | ||
|
|
ca4d19667b | ||
|
|
c71aa7baa7 | ||
|
|
9dc0b24399 | ||
|
|
747954e6fb | ||
|
|
274f4f227e | ||
|
|
92197d8d49 | ||
|
|
aee06920eb | ||
|
|
5111b17d3c | ||
|
|
2849d8f45d | ||
|
|
bac60d9bd4 | ||
|
|
9c797162f4 | ||
|
|
09d184e2f8 | ||
|
|
7bca618906 | ||
|
|
67607103e9 | ||
|
|
b42f2ffe33 | ||
|
|
30a3f185ef | ||
|
|
83b9d13ec9 | ||
|
|
cee7896c37 | ||
|
|
b472f3644e | ||
|
|
5d8ea837c8 | ||
|
|
82de6bc849 | ||
|
|
cb4bc68c48 | ||
|
|
3ce6b38247 | ||
|
|
716c0fe979 | ||
|
|
c993790b7a | ||
|
|
aa32286531 | ||
|
|
6f94abde00 | ||
|
|
fa19538c9d | ||
|
|
84c858b878 | ||
|
|
865de142d4 | ||
|
|
9118162553 | ||
|
|
f4fc6ee9b4 | ||
|
|
108c38d57b | ||
|
|
a1d73eb830 | ||
|
|
997906a610 | ||
|
|
b6e5d120d3 | ||
|
|
d469d0b435 | ||
|
|
e9f823e000 | ||
|
|
d7fb76ba74 | ||
|
|
b7dde1a0d9 | ||
|
|
15095d8c23 | ||
|
|
dfbebc7606 | ||
|
|
895309d93d | ||
|
|
bcf50e821a | ||
|
|
30195800dd | ||
|
|
6532b0f149 | ||
|
|
5e108e4057 | ||
|
|
c2b2f4d222 | ||
|
|
bc4329ad21 | ||
|
|
aec6d1b2f6 | ||
|
|
6fe4c5a2ed | ||
|
|
4abc8e41d8 | ||
|
|
af694f1ce9 | ||
|
|
7c3a5fcb83 | ||
|
|
57f64b18c6 | ||
|
|
4cccc7c2f8 | ||
|
|
903a2d6a6e | ||
|
|
34c674487a | ||
|
|
d15a8c5af3 | ||
|
|
3e0dec9383 | ||
|
|
8b810aad81 | ||
|
|
e676bcb4f4 | ||
|
|
a7aed77764 | ||
|
|
88875c0257 | ||
|
|
f711a0c91a | ||
|
|
d8a076cc6e | ||
|
|
c900831ee9 | ||
|
|
ae5d0b1d81 | ||
|
|
cd5e87be34 | ||
|
|
3e967f58d2 | ||
|
|
1ea005ba7e | ||
|
|
092772ba90 | ||
|
|
b959854a76 | ||
|
|
8ccb1ebe4f | ||
|
|
91b3be6467 | ||
|
|
d79d5feacc | ||
|
|
5cc78ef9d5 | ||
|
|
8639cd5a72 | ||
|
|
021ddc17e7 | ||
|
|
ee47b8d004 | ||
|
|
55d267c935 | ||
|
|
0fd0b9128d | ||
|
|
d9cf505b50 | ||
|
|
6079332dda | ||
|
|
929ec20365 | ||
|
|
d0cad3055f | ||
|
|
4974a13bc0 | ||
|
|
bd048df225 | ||
|
|
ed83cbd574 | ||
|
|
7230207853 | ||
|
|
1ead8a72ab | ||
|
|
36a2e9d931 | ||
|
|
0f147a5518 | ||
|
|
fce511a18b | ||
|
|
64bb61b009 | ||
|
|
4c6f829c92 | ||
|
|
8c5cdd2acb | ||
|
|
3800f19966 | ||
|
|
7336f84a4b | ||
|
|
7bf4a5b2b5 | ||
|
|
43a7b97218 | ||
|
|
9f95c57a09 | ||
|
|
8f6056ae66 | ||
|
|
9bcac6b10e | ||
|
|
86318e1b7d | ||
|
|
a8a1458833 | ||
|
|
942c1e2dfe | ||
|
|
a6b6814eae | ||
|
|
0af95aa9b1 | ||
|
|
b4b9256867 | ||
|
|
a6f1281a98 | ||
|
|
741c74e267 | ||
|
|
3061dba5ed | ||
|
|
09f5f4027e | ||
|
|
925695fd56 | ||
|
|
3c758be856 | ||
|
|
569b76a7e3 | ||
|
|
dca69eff9c | ||
|
|
6b8fedc675 | ||
|
|
c42a379e7c | ||
|
|
a40858adbf | ||
|
|
19bc720bc9 | ||
|
|
bf79ca30bb | ||
|
|
75454895e5 | ||
|
|
c81aa2d6fe | ||
|
|
376f6369b8 | ||
|
|
b1e67a1ed3 | ||
|
|
7393a30bd1 | ||
|
|
c934065f8e | ||
|
|
56124d2b50 | ||
|
|
e8a003ff8a | ||
|
|
4c789225b2 | ||
|
|
59dcdd5393 | ||
|
|
b28316a4f2 | ||
|
|
b5eed69712 | ||
|
|
b79aacb2a7 | ||
|
|
8a2eb7b058 | ||
|
|
7316d076a2 | ||
|
|
479d3bcb40 | ||
|
|
f2358f1530 | ||
|
|
47d9e1b966 | ||
|
|
c53657d693 | ||
|
|
f19ce59e00 | ||
|
|
076f3e05d6 | ||
|
|
7d017f9494 | ||
|
|
675de4e420 | ||
|
|
418a709c6c | ||
|
|
1d7dd1b754 | ||
|
|
3fa70d6d2b | ||
|
|
9c67f52161 | ||
|
|
9f2f23fa96 | ||
|
|
46d955691a | ||
|
|
3f8800187d | ||
|
|
ebbe90dfa8 | ||
|
|
a0e1783e18 | ||
|
|
fc83e11d8b | ||
|
|
f43627b170 | ||
|
|
8964441f44 | ||
|
|
cfd7a0c621 | ||
|
|
15a422873e | ||
|
|
d1f5583cd7 | ||
|
|
08f07c6f3e | ||
|
|
35a08debc3 | ||
|
|
a3424c480f | ||
|
|
118ced0a43 | ||
|
|
6d355ef0cd | ||
|
|
a8aa5ac231 | ||
|
|
df6bc0b3c9 | ||
|
|
6b965b765c | ||
|
|
d7aea6b5ba | ||
|
|
1e9a46855d | ||
|
|
91e9c18110 | ||
|
|
8ffa6088d7 | ||
|
|
52d2f8364f | ||
|
|
1f679af6fa | ||
|
|
1ba92cdcd5 | ||
|
|
45c60ba5f5 | ||
|
|
d3eef45608 | ||
|
|
1960c113d4 | ||
|
|
63d6b4a1c9 | ||
|
|
9f47bb1252 | ||
|
|
df4fea31d0 | ||
|
|
98ef1484c8 | ||
|
|
c4ef9960b9 | ||
|
|
6b6f7744aa | ||
|
|
9192fa0fe2 | ||
|
|
3c7c2dc1a5 | ||
|
|
5c176a1af0 | ||
|
|
6d03a1cc76 | ||
|
|
1cf10edef1 | ||
|
|
6a97c63bf4 | ||
|
|
15f9612bfa | ||
|
|
9a7c90b194 | ||
|
|
91f2708a87 | ||
|
|
7bf3ecd89d | ||
|
|
4768581631 | ||
|
|
aa4cd10e13 | ||
|
|
34ae57e6fe | ||
|
|
107c2b50e2 | ||
|
|
a832765203 | ||
|
|
977fee82b5 | ||
|
|
8c74cbc1c6 | ||
|
|
b38eec5039 | ||
|
|
6c20b932fa | ||
|
|
deb24c638f | ||
|
|
40fcdb4d28 | ||
|
|
f3e44cf458 | ||
|
|
483bf331fa | ||
|
|
9d62b4acdd | ||
|
|
c9deef6e76 | ||
|
|
8ba6f8b0e1 | ||
|
|
448c59ea88 | ||
|
|
91b858bf33 | ||
|
|
c12bede980 | ||
|
|
71e9fa3d16 | ||
|
|
6800b9aaae | ||
|
|
77d44f25f9 | ||
|
|
ab6227828b | ||
|
|
719ba56c59 | ||
|
|
dacedf4018 | ||
|
|
2526fa3c47 | ||
|
|
7e2295c382 | ||
|
|
6ef02004ff | ||
|
|
0e60d062e9 | ||
|
|
80a94f97c4 | ||
|
|
c18bc5fe67 | ||
|
|
02b98a2429 | ||
|
|
0383aeaa87 | ||
|
|
15a41d532e | ||
|
|
0f49725789 | ||
|
|
1db6733e66 | ||
|
|
0343ee4f6b | ||
|
|
2c37d2233a | ||
|
|
0cb8ccfddd | ||
|
|
35b1a39ed8 | ||
|
|
61a577ba70 | ||
|
|
a1e32584fa | ||
|
|
28e0ee536d | ||
|
|
9d64a9c038 | ||
|
|
702ba969c2 | ||
|
|
6dde8ee2b8 | ||
|
|
018420310c | ||
|
|
6d49d34033 | ||
|
|
1fbd403164 | ||
|
|
13f544d2be | ||
|
|
3c9e64de81 | ||
|
|
5a9bafbc32 | ||
|
|
b89d96b66f | ||
|
|
b7176191ac | ||
|
|
453c5f47c2 | ||
|
|
eea62e1263 | ||
|
|
4fb2a0f1ca | ||
|
|
bf3c65778e | ||
|
|
df7fe3e6b4 | ||
|
|
b657468b62 | ||
|
|
4edc0058d3 | ||
|
|
2c3b35293b | ||
|
|
be0c9a4d46 | ||
|
|
dd4140558e | ||
|
|
71c2519b8e | ||
|
|
badfc26aed | ||
|
|
b2bc3adb3d | ||
|
|
5ccf408fd6 | ||
|
|
af16912541 | ||
|
|
1bf9e2a5e6 | ||
|
|
5a572651ff | ||
|
|
5a191e387f | ||
|
|
18f29f5790 | ||
|
|
054a73e0f8 | ||
|
|
14824db7b0 | ||
|
|
721c48ea88 | ||
|
|
ed7bfcfb58 | ||
|
|
773a40a126 | ||
|
|
961252ef26 | ||
|
|
a2650f3c47 | ||
|
|
d71ee194e1 | ||
|
|
22e1a4cf41 | ||
|
|
a50bf901d3 | ||
|
|
c9469635b5 | ||
|
|
36df3278e5 | ||
|
|
cb2258aaa8 | ||
|
|
0391d9eb7e | ||
|
|
12698b4c20 | ||
|
|
f7b9d459ab | ||
|
|
65ab14e68b | ||
|
|
93a5dd5de4 | ||
|
|
61807bdaaa | ||
|
|
a1a5d1adba | ||
|
|
9dd4aefea5 | ||
|
|
db4540089a | ||
|
|
24c899c91a | ||
|
|
ade1a73966 | ||
|
|
fb9ec2b040 | ||
|
|
3a683812e9 | ||
|
|
6d317603c9 | ||
|
|
5a3d2d196c | ||
|
|
e740c4d980 | ||
|
|
253e4596e2 | ||
|
|
4f885c9a79 | ||
|
|
b519d2afac | ||
|
|
6b61e3b76b | ||
|
|
30b9c72c31 | ||
|
|
385bf74f6e | ||
|
|
be5615e530 | ||
|
|
d81a03c093 | ||
|
|
f8249c8267 | ||
|
|
5a1cbdcd3b | ||
|
|
e0c99d87bd | ||
|
|
548250029d | ||
|
|
66a354dbdc | ||
|
|
1f693ca4f6 | ||
|
|
97a0bc6045 | ||
|
|
8b75cdfefd | ||
|
|
917aecf1ff | ||
|
|
663dcd0396 | ||
|
|
8f2dffb1ad | ||
|
|
20228e3d19 | ||
|
|
81c6cc11b3 | ||
|
|
2ccacbe5f3 | ||
|
|
a5345e8468 | ||
|
|
8f5d62bb81 | ||
|
|
28f6838560 | ||
|
|
c86aacb31c | ||
|
|
f62f5192d6 | ||
|
|
b14ea1fe3e | ||
|
|
552633a00b | ||
|
|
7faba2a690 | ||
|
|
db910aff06 | ||
|
|
75d9f6a7e7 | ||
|
|
de677294c6 | ||
|
|
da1e6b8259 | ||
|
|
a9633b3990 | ||
|
|
7ac9af1cc1 | ||
|
|
00d8b8cd61 | ||
|
|
af7ff7f5cf | ||
|
|
2a20719130 | ||
|
|
f481940180 | ||
|
|
ca8824d1e3 | ||
|
|
f4be199b77 | ||
|
|
6bcef8334e | ||
|
|
3955eff683 | ||
|
|
aa0f6ecd75 | ||
|
|
ef4a94ed78 | ||
|
|
b5c803ce65 | ||
|
|
d4325ed82e | ||
|
|
3805fb8f26 | ||
|
|
d4d938c655 | ||
|
|
1c6911e361 | ||
|
|
a1b364f337 | ||
|
|
ece5c3da86 | ||
|
|
5d1ae6047b | ||
|
|
66bbcf0733 | ||
|
|
acc23ea7bb | ||
|
|
663bd0c9f0 | ||
|
|
39b1025dfa | ||
|
|
d2875e90b2 | ||
|
|
ff461d1d02 | ||
|
|
58164ea2d3 | ||
|
|
1bf4834004 | ||
|
|
bf58d78281 | ||
|
|
a8aedfde55 | ||
|
|
9b92d1b673 | ||
|
|
febc9aed11 | ||
|
|
de2462677e | ||
|
|
8bd94d46eb | ||
|
|
d43cefe28f | ||
|
|
b82874e261 | ||
|
|
f901614056 | ||
|
|
b555d217ab | ||
|
|
775c600234 | ||
|
|
128f2570b8 | ||
|
|
3cd53e79b4 | ||
|
|
ebba84ffda | ||
|
|
1e1a42fe98 | ||
|
|
8a744a440d | ||
|
|
f4fc3c7d55 | ||
|
|
0594d121de | ||
|
|
12c85d6234 | ||
|
|
87d05223af | ||
|
|
babf6366e8 | ||
|
|
5e37728f66 | ||
|
|
e8e19fede7 | ||
|
|
e565dbfa66 | ||
|
|
d180d6820c | ||
|
|
41db8681f8 | ||
|
|
26cd58fd6d | ||
|
|
63c7e1aa9d | ||
|
|
d5a6063e5e | ||
|
|
00affdbdec | ||
|
|
db3f0bbd4f | ||
|
|
020a59cb97 | ||
|
|
ff4fa6402d | ||
|
|
10cc187c5d | ||
|
|
25843edb48 | ||
|
|
54294141b0 | ||
|
|
f3a8886b50 | ||
|
|
268cfaf234 | ||
|
|
651ae20304 | ||
|
|
e22f69a5dc | ||
|
|
a39808f44c | ||
|
|
fcb541f734 | ||
|
|
79ca0f1684 | ||
|
|
f2ebc38044 | ||
|
|
d4335675f1 | ||
|
|
be4b05423e | ||
|
|
d9fe8db2a7 | ||
|
|
f92e780765 | ||
|
|
7aebdb7c78 | ||
|
|
abb2dd842b | ||
|
|
75713c8015 | ||
|
|
42e1717455 | ||
|
|
bfb19a9eb7 | ||
|
|
3e08585114 | ||
|
|
12e82c7a8d | ||
|
|
0fcc683903 | ||
|
|
ed7a8dc0f5 | ||
|
|
0a9d29c98d | ||
|
|
f63e801608 | ||
|
|
77f04e1a32 | ||
|
|
362819ce16 | ||
|
|
1d9165a627 | ||
|
|
7ee8aaa027 | ||
|
|
516e279fc3 | ||
|
|
880611eddb | ||
|
|
c4bf776069 | ||
|
|
097d6464c0 | ||
|
|
b86e4e017f | ||
|
|
bbec17d498 | ||
|
|
3b7b5f4ec3 | ||
|
|
0986efef29 | ||
|
|
06091cbf1c | ||
|
|
b588bab268 | ||
|
|
0736cfe959 | ||
|
|
400352254a | ||
|
|
259c3dc781 | ||
|
|
506055a815 | ||
|
|
3edf6c57ba | ||
|
|
c404ae7ac8 | ||
|
|
312774e472 | ||
|
|
c540f802b0 | ||
|
|
6a2a2761e1 | ||
|
|
2508458c80 | ||
|
|
025d9e0141 | ||
|
|
734b3b07ab | ||
|
|
e4250a857a | ||
|
|
56d1b2716c | ||
|
|
c5d7e61e6c | ||
|
|
6222a127bd | ||
|
|
f0b7e515b6 | ||
|
|
98d8c23868 | ||
|
|
978bb9afd0 | ||
|
|
058598b5f3 | ||
|
|
5b7ab3a10f | ||
|
|
e42243c78b | ||
|
|
c650ee8498 | ||
|
|
50f8968901 | ||
|
|
b0fa2e6d80 | ||
|
|
d59589425e | ||
|
|
6c810e514b | ||
|
|
efa41dbd22 | ||
|
|
f34bcfd56d | ||
|
|
8ff2e3fb29 | ||
|
|
033c04a0f2 | ||
|
|
cef1ab9512 | ||
|
|
94f02bfca3 | ||
|
|
a941bb1744 | ||
|
|
6ff591427a | ||
|
|
809e172280 | ||
|
|
17aedae0a9 | ||
|
|
ef817ccb3a | ||
|
|
0fb55b0bee | ||
|
|
a1a6eddc31 | ||
|
|
ff3d0b6b57 | ||
|
|
dd64cef4c4 | ||
|
|
9796848079 | ||
|
|
fea7eb4312 | ||
|
|
c12cd0e755 | ||
|
|
d86a72f858 | ||
|
|
50cd7f219a | ||
|
|
8252b3eccc | ||
|
|
d0c6e3a158 | ||
|
|
1505fa547e | ||
|
|
9017bad884 | ||
|
|
2ac5e316a5 | ||
|
|
29f9113062 | ||
|
|
46349672d8 | ||
|
|
4787be2db0 | ||
|
|
f0a8c5d732 | ||
|
|
9ad520bf7c | ||
|
|
bd0cc51554 | ||
|
|
12f599f974 | ||
|
|
0118d5fb40 | ||
|
|
65cadb311a | ||
|
|
dd75bd197d | ||
|
|
7e155bdb43 | ||
|
|
993b6fddf4 | ||
|
|
6ba51df6a7 | ||
|
|
1185ac58e1 | ||
|
|
f835997f49 | ||
|
|
a597dba775 | ||
|
|
3194e83a66 | ||
|
|
096c3cdd34 | ||
|
|
3a1ea42333 | ||
|
|
64877d4299 | ||
|
|
e957dc5e2c | ||
|
|
578d5c5830 | ||
|
|
96284f9508 | ||
|
|
698b38dcba | ||
|
|
6db826befe | ||
|
|
1a3d412d73 | ||
|
|
b8461c9dd8 | ||
|
|
699bd9de10 | ||
|
|
54b6866e21 | ||
|
|
afd155e9c1 | ||
|
|
910a717230 | ||
|
|
70fbd33d61 | ||
|
|
2da0d5ee21 | ||
|
|
98f64e057a | ||
|
|
3d9d936c56 | ||
|
|
2b4cb59df8 | ||
|
|
9d80da52e3 | ||
|
|
fd176d2c64 | ||
|
|
538b6de36b | ||
|
|
f7eca8aee0 | ||
|
|
a754d94c2c | ||
|
|
5e3493e6a9 | ||
|
|
619a14c26b | ||
|
|
7d9a8decf0 | ||
|
|
d11e14ad89 | ||
|
|
69189cf2af | ||
|
|
6e7d2f19d2 | ||
|
|
d99ebf5d6a | ||
|
|
ef2d19e95b | ||
|
|
9e544ad471 | ||
|
|
5f19aa527a | ||
|
|
bfd5bc5c26 | ||
|
|
2d0ec3accd | ||
|
|
d8dd3e133f | ||
|
|
528470c37f | ||
|
|
c03cd53853 | ||
|
|
b57fc8a29c | ||
|
|
a04ed5c3ca | ||
|
|
3ad1df14f6 | ||
|
|
d8caf12fdc | ||
|
|
5ca9d30d5f | ||
|
|
a7a71b4a46 | ||
|
|
638603ac6b | ||
|
|
1d70c15027 | ||
|
|
7a5f03d672 | ||
|
|
1943d8367e | ||
|
|
f91c5af9a1 | ||
|
|
2be71fc877 | ||
|
|
f5f5b4a8db | ||
|
|
ac9cfd09ea | ||
|
|
4cfc85dbfd | ||
|
|
1f3d2f47b1 | ||
|
|
653c482ff7 | ||
|
|
4b069cc2b0 | ||
|
|
c89349a43a | ||
|
|
6e92d6c62c | ||
|
|
5d3d3e9076 | ||
|
|
b440c772d6 | ||
|
|
2895560b30 | ||
|
|
bedcecb2e1 | ||
|
|
656ac829a4 | ||
|
|
4d83debc0e | ||
|
|
4ff5d19979 | ||
|
|
9acda5696e | ||
|
|
dc6255048a | ||
|
|
2acde429d7 | ||
|
|
efcac1adac | ||
|
|
81d5ecd758 | ||
|
|
d9ff004454 | ||
|
|
d57135d793 | ||
|
|
bb5a0023af | ||
|
|
e3c25a167e | ||
|
|
5be93ae17d | ||
|
|
3a2511d4a1 | ||
|
|
8ec7d98eef | ||
|
|
9421ae25f7 | ||
|
|
5b288b6fa1 | ||
|
|
d35ed2980b | ||
|
|
6d8df6d2b9 | ||
|
|
a839513f7f | ||
|
|
97b37b4742 | ||
|
|
4894031219 | ||
|
|
8985b5511c | ||
|
|
b3c2a6a0cc | ||
|
|
7291b440bb | ||
|
|
d75f134677 | ||
|
|
e60069ec1d | ||
|
|
034f49573d | ||
|
|
973d37a237 | ||
|
|
d2ec609e68 | ||
|
|
6b410399cd | ||
|
|
0c010570b9 | ||
|
|
78fc7faa13 | ||
|
|
7671cce263 | ||
|
|
a43a66a2d3 | ||
|
|
2190a2ed25 | ||
|
|
227636b705 | ||
|
|
5032170362 | ||
|
|
b94c3961eb | ||
|
|
46c7e89a94 | ||
|
|
80861fd620 | ||
|
|
44f9390790 | ||
|
|
8eca6c409a | ||
|
|
4907c01191 | ||
|
|
04bf314c61 | ||
|
|
57d92b276b | ||
|
|
6a8efddab5 | ||
|
|
fd908494ae | ||
|
|
d617b23c2f | ||
|
|
27874728bc | ||
|
|
56a0345260 | ||
|
|
c412839165 | ||
|
|
b77f927ad5 | ||
|
|
8edd7f6a56 | ||
|
|
c6915d0291 | ||
|
|
388eb94014 | ||
|
|
9ab80553e1 | ||
|
|
86d639ee6a | ||
|
|
979fd8a249 | ||
|
|
e65ab58f84 | ||
|
|
8414bdbab1 | ||
|
|
d037b09128 | ||
|
|
9a687fec9b | ||
|
|
e9d71f169c | ||
|
|
e09c307d58 | ||
|
|
d23d641b1b | ||
|
|
b1301091f9 | ||
|
|
2458eb3960 | ||
|
|
fa836d88c7 | ||
|
|
e26349f2fc | ||
|
|
daa4e4d566 | ||
|
|
8e75df686d | ||
|
|
53537e7b3a | ||
|
|
4beddc2271 | ||
|
|
a6e4a774e0 | ||
|
|
dacc1c5770 | ||
|
|
25e922bc4c | ||
|
|
56bb206f25 | ||
|
|
740a9ceaa7 | ||
|
|
64e936127a | ||
|
|
b1f7bd3ead | ||
|
|
b5e3b16e3a | ||
|
|
96a72a2cd7 | ||
|
|
c155da858e | ||
|
|
c1b2bbd152 | ||
|
|
e3b5f418d6 | ||
|
|
cddac4d0fb | ||
|
|
dd6f92e54d | ||
|
|
5d4558bddf | ||
|
|
5aa7b5a337 | ||
|
|
2fe0b5b90d | ||
|
|
aa6997990c | ||
|
|
c02ab50a0a | ||
|
|
7cb16b2259 | ||
|
|
3173dc83a5 | ||
|
|
baddc29bb8 | ||
|
|
612cbe6be4 | ||
|
|
4c1d2ab1bb | ||
|
|
6b4704b2e2 | ||
|
|
c2286cde01 | ||
|
|
24a17712e7 | ||
|
|
27d537e7bb | ||
|
|
dbd89c72a3 | ||
|
|
ff41bbd0e5 | ||
|
|
4bdb6ae84e | ||
|
|
cece7b79ad | ||
|
|
8d09d95fc3 | ||
|
|
752542a1d1 | ||
|
|
dd077383f7 | ||
|
|
6e808dbb0f | ||
|
|
4ef3441f70 | ||
|
|
82624d6657 | ||
|
|
62e2b5230c | ||
|
|
3325c30f29 | ||
|
|
18a06168f1 | ||
|
|
27e93e499f | ||
|
|
90644a21a3 | ||
|
|
7e31f43ef1 | ||
|
|
b13fc1fba4 | ||
|
|
5d9109e526 | ||
|
|
78dfa36b2a | ||
|
|
dc05d87b44 | ||
|
|
2c323a13c1 | ||
|
|
d4c5e38857 | ||
|
|
fb80e5c367 | ||
|
|
beb08a3afb | ||
|
|
7b2de8cbbd | ||
|
|
83e63bc87c | ||
|
|
4f5da33fd6 | ||
|
|
d00d003a67 | ||
|
|
002f24be10 | ||
|
|
04992a1d95 | ||
|
|
3c7cf2446e | ||
|
|
29774ac014 | ||
|
|
562d580987 | ||
|
|
d8ad6c0cb0 | ||
|
|
7897b0ebe9 | ||
|
|
e38af9fd16 | ||
|
|
6ffdf5c251 | ||
|
|
69ef7676af | ||
|
|
b0ac57040c | ||
|
|
826ac7f185 | ||
|
|
0623f53f5d | ||
|
|
b5ae875589 | ||
|
|
c152e18e1a | ||
|
|
903f0e5e19 | ||
|
|
6fefd5589c | ||
|
|
58fe14bd31 | ||
|
|
97f362ed1e | ||
|
|
b63e87ecb6 | ||
|
|
ac3550dfd7 | ||
|
|
8278a4cfd9 | ||
|
|
f161a2bbc8 | ||
|
|
6a94489df0 | ||
|
|
c3a0b9192f | ||
|
|
69ff70a9ce | ||
|
|
5284eb0af8 | ||
|
|
58384ae136 | ||
|
|
054cc78e65 | ||
|
|
8c283281d6 | ||
|
|
241fe41756 | ||
|
|
e50e0626fa | ||
|
|
c9135f1573 | ||
|
|
7567042c8a | ||
|
|
c99ceb155f | ||
|
|
f44c92f0d3 | ||
|
|
492701ec62 | ||
|
|
a6d0acaa4d | ||
|
|
f84b4e7274 | ||
|
|
b7ef5b82d8 | ||
|
|
a854d2c38c | ||
|
|
5140499bbd | ||
|
|
7183e9ee85 | ||
|
|
11885e0aca | ||
|
|
2bda4e822c | ||
|
|
154149a068 | ||
|
|
c96985af03 | ||
|
|
e282420a6a | ||
|
|
b9a207ea71 | ||
|
|
28d52b5e7a | ||
|
|
9761f1ae29 | ||
|
|
e62c8cc2e2 | ||
|
|
b5aea92791 | ||
|
|
03f35c1975 | ||
|
|
bc7dad77f4 | ||
|
|
aaa2540114 | ||
|
|
f46787839a | ||
|
|
228be95af1 | ||
|
|
a22d7e40e5 | ||
|
|
d0f87c0980 | ||
|
|
5142783db9 | ||
|
|
4aea16ca8c | ||
|
|
d91d372fc5 | ||
|
|
7405d884de | ||
|
|
a9ae63043e | ||
|
|
6b943866ef | ||
|
|
c7bb94d82a | ||
|
|
30fb855200 | ||
|
|
80f9e56e3f | ||
|
|
d301d967c7 | ||
|
|
7b7bdc4e9c | ||
|
|
796ebca74c | ||
|
|
3150bc316a | ||
|
|
0a91b12e6e | ||
|
|
918e2cc1a9 | ||
|
|
fb71f83d6d | ||
|
|
82470bf04f | ||
|
|
0ac75092e6 | ||
|
|
e898163aff | ||
|
|
418c7e1d9e | ||
|
|
24cbabeaf0 | ||
|
|
91069b989d | ||
|
|
1b7902894a | ||
|
|
47e022897e | ||
|
|
9aada993b1 | ||
|
|
cf837b6d05 | ||
|
|
09192da4fc | ||
|
|
3a792765cd | ||
|
|
a8f1b1c8bc | ||
|
|
8ffdc6bbf8 | ||
|
|
945370bc25 | ||
|
|
ed4b3b0b9c | ||
|
|
83a4268441 | ||
|
|
2938be7a70 | ||
|
|
e3b2ee44ca | ||
|
|
f0c4658c9f | ||
|
|
0a4b236293 | ||
|
|
bc7b53c3d4 | ||
|
|
5535e26eec | ||
|
|
c84c3d58db | ||
|
|
d6caac51dd | ||
|
|
979e7a5e08 | ||
|
|
40f16eb984 | ||
|
|
c17ad1b989 | ||
|
|
24bfa062da | ||
|
|
765f675da9 | ||
|
|
c0650d2ef0 | ||
|
|
168434739f | ||
|
|
337eaa46e3 | ||
|
|
94d42503b7 | ||
|
|
202edc0588 | ||
|
|
c95d11da47 | ||
|
|
4f8615398c | ||
|
|
f3b5f0128f | ||
|
|
ab5e50c29c | ||
|
|
f9236bf92f | ||
|
|
2522968b04 | ||
|
|
9c1900963d | ||
|
|
82ff41e0bb | ||
|
|
fb86c14d77 | ||
|
|
c6c0159ee4 | ||
|
|
fe5bba18a2 | ||
|
|
f61329b5de | ||
|
|
fbc04afa5b | ||
|
|
2f5bcf2263 | ||
|
|
92882c337c | ||
|
|
bd41f69a1c | ||
|
|
f801709587 | ||
|
|
1cb37d29df | ||
|
|
2d7db408fd | ||
|
|
ef1afc99c6 | ||
|
|
5682c9a5b2 | ||
|
|
c525b18a02 | ||
|
|
72159cb94d | ||
|
|
39e31a1039 | ||
|
|
734177fecc | ||
|
|
39311099df | ||
|
|
b8653e6601 | ||
|
|
cb4b1971e6 | ||
|
|
63c60ba716 | ||
|
|
50435425e5 | ||
|
|
ff192f102d | ||
|
|
99cdaa1305 | ||
|
|
7fc897dba9 | ||
|
|
3bedd65ad8 | ||
|
|
a46175ce53 | ||
|
|
dba3bf8ce9 | ||
|
|
3f32234c93 | ||
|
|
2863e64e3b | ||
|
|
68ec78e01c | ||
|
|
3a7c506a8f | ||
|
|
1ca63ed2d2 | ||
|
|
e9e98ebcfc | ||
|
|
04de7998af | ||
|
|
a5d02dc34a | ||
|
|
6181b0466e | ||
|
|
810d8f637d | ||
|
|
223b3e81d5 | ||
|
|
3a8b5bbd3f | ||
|
|
ecf3b33ca7 | ||
|
|
006b20351e | ||
|
|
4b577c9541 | ||
|
|
8db59458a8 | ||
|
|
7eed5f09aa | ||
|
|
a1bb265222 | ||
|
|
0235f33f8b | ||
|
|
3d6fca85db | ||
|
|
4c06da0646 | ||
|
|
f63603eb84 | ||
|
|
44418ef295 | ||
|
|
2a67218a34 | ||
|
|
911586ed0b | ||
|
|
9d6a6620e3 | ||
|
|
598d0acd8e | ||
|
|
f16ece6207 | ||
|
|
9b55bc9892 | ||
|
|
707e67918b | ||
|
|
faac572c30 | ||
|
|
571b37695b | ||
|
|
227adc459f | ||
|
|
2ee36f1a9c | ||
|
|
d0ce2a46ac | ||
|
|
7e5bc4e1ce | ||
|
|
d2b6d0a0ff | ||
|
|
542b0658b8 | ||
|
|
e73c7e19b5 | ||
|
|
6a32ed7d7b | ||
|
|
a63001f17c | ||
|
|
4d1ad9c832 | ||
|
|
454aa6ccda | ||
|
|
85ffebb3fa | ||
|
|
9e86020ef7 | ||
|
|
d66a41a8a3 | ||
|
|
90914bff14 | ||
|
|
62414848f4 | ||
|
|
d4ece6ecd7 | ||
|
|
d1ec60bb63 | ||
|
|
4f672c736b | ||
|
|
2e5c351d8b | ||
|
|
3562553346 | ||
|
|
4750b292a5 | ||
|
|
3eb0561e90 | ||
|
|
abb118c8ca | ||
|
|
2818a229b6 | ||
|
|
a9b8af3677 | ||
|
|
0354da00da | ||
|
|
b179587475 | ||
|
|
3021f90bc5 | ||
|
|
a14b0278c8 | ||
|
|
80070b333e | ||
|
|
3aa8dcac11 | ||
|
|
e920f05611 | ||
|
|
3594afd3aa | ||
|
|
9daaee8212 | ||
|
|
d022707349 | ||
|
|
3948605ae6 | ||
|
|
f2ded5fdd6 | ||
|
|
00b47be181 | ||
|
|
a2fac5d946 | ||
|
|
a00b5bb36b | ||
|
|
d4fbc34085 | ||
|
|
e9e3031992 | ||
|
|
c2c7553f56 | ||
|
|
4e60cb89c9 | ||
|
|
ec4523240f | ||
|
|
1655ddbcaa | ||
|
|
997c677f30 | ||
|
|
d5fc8a2d7e | ||
|
|
3bcd0302a8 | ||
|
|
de91b7e8af | ||
|
|
7efd1d7c9e | ||
|
|
b5151a2178 | ||
|
|
c8432020c6 | ||
|
|
2c9d413a1a | ||
|
|
cdf842e7ad | ||
|
|
c917007949 | ||
|
|
64278c6b3c | ||
|
|
10a01ed14a | ||
|
|
ba3bd1407b | ||
|
|
73666c9a04 | ||
|
|
eae24083c9 | ||
|
|
a644510c27 | ||
|
|
57859d0da2 | ||
|
|
057f0ff648 | ||
|
|
05d1c867f2 | ||
|
|
a2238fa435 | ||
|
|
12b7426a7c | ||
|
|
5148d613a7 | ||
|
|
f455c15882 | ||
|
|
618fdabd0e | ||
|
|
3b69e2896c | ||
|
|
7306b63ab1 | ||
|
|
7e3133caa2 | ||
|
|
560901d714 | ||
|
|
166ce9ae78 | ||
|
|
d3395a685e | ||
|
|
6d5e9a8566 | ||
|
|
69ec03feb4 | ||
|
|
f92982cd5a | ||
|
|
5570f2b464 | ||
|
|
ad19dc0240 | ||
|
|
9b1d4faff8 | ||
|
|
76756d20e9 | ||
|
|
e564500480 | ||
|
|
19c15ce58d | ||
|
|
a027785098 | ||
|
|
36a9f10aae | ||
|
|
99a11a4b53 | ||
|
|
55cac4465c | ||
|
|
ff395fd074 | ||
|
|
972b6e09c7 | ||
|
|
e793a33b15 | ||
|
|
e70d4ff3f3 | ||
|
|
cd0635d3a0 | ||
|
|
81702d8595 | ||
|
|
aaa4a65b04 | ||
|
|
430797e626 | ||
|
|
d454001f49 | ||
|
|
bd90ee1f58 | ||
|
|
196aaa5427 | ||
|
|
6e42233b33 | ||
|
|
8e44df8525 | ||
|
|
a8a1536941 | ||
|
|
99d1728c70 | ||
|
|
6bbb92cdb9 | ||
|
|
b80e7c06bf | ||
|
|
bf467b874c | ||
|
|
43c9f6be56 | ||
|
|
6811a4f4ae | ||
|
|
1f16dd9c43 | ||
|
|
63a43ce104 | ||
|
|
bd7ce5417e | ||
|
|
941ee54a97 | ||
|
|
a5d4a64f47 | ||
|
|
d96fcd4a98 | ||
|
|
de42e2f747 | ||
|
|
822a93aeb6 | ||
|
|
c31b4aaeff | ||
|
|
8c9a386054 | ||
|
|
8c90933615 | ||
|
|
6f8c242333 | ||
|
|
fe8b66873a | ||
|
|
983a5c2034 | ||
|
|
15829f04a3 | ||
|
|
934618bc1c | ||
|
|
2c5ec75b88 | ||
|
|
df11fd744f | ||
|
|
4dba0fb43d | ||
|
|
7a0d86b8dd | ||
|
|
a94cd98e0f | ||
|
|
8e95e51edc | ||
|
|
6f1b00284a | ||
|
|
58549a6cac | ||
|
|
acc9a6118f | ||
|
|
c7811e861c | ||
|
|
55cf766ff0 | ||
|
|
a1eaf38324 | ||
|
|
c6788092d3 | ||
|
|
f89f74ef3f | ||
|
|
3e40f02001 | ||
|
|
c169967c1b | ||
|
|
2830e7c569 | ||
|
|
415f08ba3a | ||
|
|
d726bcdc19 | ||
|
|
f259c25a70 | ||
|
|
4db937cf1f | ||
|
|
dad9d0660c | ||
|
|
0c450a5bb2 | ||
|
|
ef59819c01 | ||
|
|
c651e7c84b | ||
|
|
20b8debb1c | ||
|
|
dd5743f0a1 | ||
|
|
7da2b51fae | ||
|
|
0236800392 | ||
|
|
4f822878f7 | ||
|
|
c2810e5fe5 | ||
|
|
b89ba4b801 | ||
|
|
07c680b839 | ||
|
|
fd50db4eab | ||
|
|
0ee95b36a6 | ||
|
|
b8cf07149e | ||
|
|
1b699f1a87 | ||
|
|
d3bfd238d3 | ||
|
|
1f43abb3c8 | ||
|
|
287c753e4a | ||
|
|
8a5374d31a | ||
|
|
e219eaa934 | ||
|
|
fd314480ca | ||
|
|
dd45396cf3 | ||
|
|
8011773af4 | ||
|
|
ddc69c692e | ||
|
|
df925c9744 | ||
|
|
1726341aad | ||
|
|
63b1ccc7a7 | ||
|
|
ee5db31518 | ||
|
|
e80397c857 | ||
|
|
81aa7ca1a4 | ||
|
|
f0f7695890 | ||
|
|
e7e8ce2f7a | ||
|
|
ba37a3f18d | ||
|
|
60b11a7a5d | ||
|
|
29461c20a7 | ||
|
|
2ff1f34543 | ||
|
|
b75d7f970f | ||
|
|
204681f097 | ||
|
|
e239fe95a4 | ||
|
|
0a101f061a | ||
|
|
f112a17afa | ||
|
|
54658a66d2 | ||
|
|
6b8f5a76e4 | ||
|
|
623a5d338d | ||
|
|
9c5565cfd5 | ||
|
|
722f2efaee | ||
|
|
4928264204 | ||
|
|
12d62ddc2a | ||
|
|
9c0993dac8 | ||
|
|
175486b7c4 | ||
|
|
4760a287f6 | ||
|
|
0237b48c87 | ||
|
|
95c9f22e6c | ||
|
|
9b001219d5 | ||
|
|
6ff15efc7b | ||
|
|
6fe1dccc7e | ||
|
|
1c80f6f3fa | ||
|
|
54d3177fdd | ||
|
|
a24ad245d2 | ||
|
|
f38cfdcadf | ||
|
|
92e4ad8ccd | ||
|
|
3f3ab088d2 | ||
|
|
2c2cbaa175 | ||
|
|
911b6bf863 | ||
|
|
31462cab64 | ||
|
|
1ee35da62d | ||
|
|
edf4815595 | ||
|
|
06ccee5d18 | ||
|
|
d5ad85725f | ||
|
|
4d5bddb413 | ||
|
|
2f4da7c381 | ||
|
|
8b845fce03 | ||
|
|
9fd15c38a9 | ||
|
|
ec1573d01f | ||
|
|
92ec1cc9e7 | ||
|
|
8b2f9665ce | ||
|
|
cb388a5a78 | ||
|
|
7f4389ae08 | ||
|
|
76d71beaa2 | ||
|
|
31bb9c2197 | ||
|
|
6a2cd5c45a | ||
|
|
520632514b | ||
|
|
f998b28d0b | ||
|
|
1a6587e9e6 | ||
|
|
9b4b729d19 | ||
|
|
e80345295e | ||
|
|
026c259a2e | ||
|
|
63474c2269 | ||
|
|
faa1a9312f | ||
|
|
23fa0726d5 | ||
|
|
22210eaf7d | ||
|
|
dcd8bee676 | ||
|
|
06f0fa8f0e | ||
|
|
6d0f9e2cd5 | ||
|
|
732afdb65d | ||
|
|
1a9e8742f7 | ||
|
|
b8eda37339 | ||
|
|
5107db6169 | ||
|
|
2c8f207454 | ||
|
|
489bc9c3b3 | ||
|
|
514713e883 | ||
|
|
17cc0cd09c | ||
|
|
4475df1295 | ||
|
|
fdad267cfd | ||
|
|
3684fc80f0 | ||
|
|
e97a5fef94 | ||
|
|
de2972631f | ||
|
|
e5b8fd67c8 | ||
|
|
5fade89e2d | ||
|
|
e63d7a0b8a | ||
|
|
2a1b1849fa | ||
|
|
0461cb7f19 | ||
|
|
0932e0be03 | ||
|
|
4638ac9474 | ||
|
|
d8d7255029 | ||
|
|
fa05276c3f | ||
|
|
e50a5d51d8 | ||
|
|
c03ba78587 | ||
|
|
735b84b26d | ||
|
|
8dd069ad67 | ||
|
|
1857e68003 |
@@ -23,7 +23,7 @@ POSTGRES_USER=postgres
|
||||
POSTGRES_PASS=postgrespass
|
||||
|
||||
# DEV SETTINGS
|
||||
APP_PORT=80
|
||||
APP_PORT=443
|
||||
API_PORT=80
|
||||
HTTP_PROTOCOL=https
|
||||
DOCKER_NETWORK=172.21.0.0/24
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
FROM python:3.9.9-slim
|
||||
# pulls community scripts from git repo
|
||||
FROM python:3.11.8-slim AS GET_SCRIPTS_STAGE
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends git && \
|
||||
git clone https://github.com/amidaware/community-scripts.git /community-scripts
|
||||
|
||||
FROM python:3.11.8-slim
|
||||
|
||||
ENV TACTICAL_DIR /opt/tactical
|
||||
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
|
||||
@@ -10,11 +17,17 @@ ENV PYTHONUNBUFFERED=1
|
||||
|
||||
EXPOSE 8000 8383 8005
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y build-essential weasyprint
|
||||
|
||||
RUN groupadd -g 1000 tactical && \
|
||||
useradd -u 1000 -g 1000 tactical
|
||||
|
||||
# copy community scripts
|
||||
COPY --from=GET_SCRIPTS_STAGE /community-scripts /community-scripts
|
||||
|
||||
# Copy dev python reqs
|
||||
COPY .devcontainer/requirements.txt /
|
||||
COPY .devcontainer/requirements.txt /
|
||||
|
||||
# Copy docker entrypoint.sh
|
||||
COPY .devcontainer/entrypoint.sh /
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
version: '3.4'
|
||||
|
||||
services:
|
||||
api-dev:
|
||||
image: api-dev
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./api.dockerfile
|
||||
command: ["sh", "-c", "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 manage.py runserver 0.0.0.0:8000 --nothreading --noreload"]
|
||||
ports:
|
||||
- 8000:8000
|
||||
- 5678:5678
|
||||
volumes:
|
||||
- tactical-data-dev:/opt/tactical
|
||||
- ..:/workspace:cached
|
||||
networks:
|
||||
dev:
|
||||
aliases:
|
||||
- tactical-backend
|
||||
@@ -5,10 +5,11 @@ services:
|
||||
container_name: trmm-api-dev
|
||||
image: api-dev
|
||||
restart: always
|
||||
user: 1000:1000
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: .devcontainer/api.dockerfile
|
||||
command: ["tactical-api"]
|
||||
command: [ "tactical-api" ]
|
||||
environment:
|
||||
API_PORT: ${API_PORT}
|
||||
ports:
|
||||
@@ -18,29 +19,15 @@ services:
|
||||
- ..:/workspace:cached
|
||||
networks:
|
||||
dev:
|
||||
aliases:
|
||||
aliases:
|
||||
- tactical-backend
|
||||
|
||||
app-dev:
|
||||
container_name: trmm-app-dev
|
||||
image: node:14-alpine
|
||||
restart: always
|
||||
command: /bin/sh -c "npm install npm@latest -g && npm install && npm run serve -- --host 0.0.0.0 --port ${APP_PORT}"
|
||||
working_dir: /workspace/web
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
ports:
|
||||
- "8080:${APP_PORT}"
|
||||
networks:
|
||||
dev:
|
||||
aliases:
|
||||
- tactical-frontend
|
||||
|
||||
# nats
|
||||
nats-dev:
|
||||
container_name: trmm-nats-dev
|
||||
image: ${IMAGE_REPO}tactical-nats:${VERSION}
|
||||
restart: always
|
||||
user: 1000:1000
|
||||
environment:
|
||||
API_HOST: ${API_HOST}
|
||||
API_PORT: ${API_PORT}
|
||||
@@ -61,7 +48,8 @@ services:
|
||||
container_name: trmm-meshcentral-dev
|
||||
image: ${IMAGE_REPO}tactical-meshcentral:${VERSION}
|
||||
restart: always
|
||||
environment:
|
||||
user: 1000:1000
|
||||
environment:
|
||||
MESH_HOST: ${MESH_HOST}
|
||||
MESH_USER: ${MESH_USER}
|
||||
MESH_PASS: ${MESH_PASS}
|
||||
@@ -84,6 +72,7 @@ services:
|
||||
container_name: trmm-mongodb-dev
|
||||
image: mongo:4.4
|
||||
restart: always
|
||||
user: 1000:1000
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: ${MONGODB_USER}
|
||||
MONGO_INITDB_ROOT_PASSWORD: ${MONGODB_PASSWORD}
|
||||
@@ -101,7 +90,7 @@ services:
|
||||
image: postgres:13-alpine
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: tacticalrmm
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASS}
|
||||
volumes:
|
||||
@@ -115,9 +104,10 @@ services:
|
||||
redis-dev:
|
||||
container_name: trmm-redis-dev
|
||||
restart: always
|
||||
command: redis-server --appendonly yes
|
||||
user: 1000:1000
|
||||
command: redis-server
|
||||
image: redis:6.0-alpine
|
||||
volumes:
|
||||
volumes:
|
||||
- redis-data-dev:/data
|
||||
networks:
|
||||
dev:
|
||||
@@ -128,7 +118,7 @@ services:
|
||||
container_name: trmm-init-dev
|
||||
image: api-dev
|
||||
restart: on-failure
|
||||
command: ["tactical-init-dev"]
|
||||
command: [ "tactical-init-dev" ]
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASS: ${POSTGRES_PASS}
|
||||
@@ -140,6 +130,7 @@ services:
|
||||
TRMM_PASS: ${TRMM_PASS}
|
||||
HTTP_PROTOCOL: ${HTTP_PROTOCOL}
|
||||
APP_PORT: ${APP_PORT}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
depends_on:
|
||||
- postgres-dev
|
||||
- meshcentral-dev
|
||||
@@ -147,14 +138,18 @@ services:
|
||||
- dev
|
||||
volumes:
|
||||
- tactical-data-dev:/opt/tactical
|
||||
- mesh-data-dev:/meshcentral-data
|
||||
- redis-data-dev:/redis/data
|
||||
- mongo-dev-data:/mongo/data/db
|
||||
- ..:/workspace:cached
|
||||
|
||||
# container for celery worker service
|
||||
celery-dev:
|
||||
container_name: trmm-celery-dev
|
||||
image: api-dev
|
||||
command: ["tactical-celery-dev"]
|
||||
command: [ "tactical-celery-dev" ]
|
||||
restart: always
|
||||
user: 1000:1000
|
||||
networks:
|
||||
- dev
|
||||
volumes:
|
||||
@@ -168,8 +163,9 @@ services:
|
||||
celerybeat-dev:
|
||||
container_name: trmm-celerybeat-dev
|
||||
image: api-dev
|
||||
command: ["tactical-celerybeat-dev"]
|
||||
command: [ "tactical-celerybeat-dev" ]
|
||||
restart: always
|
||||
user: 1000:1000
|
||||
networks:
|
||||
- dev
|
||||
volumes:
|
||||
@@ -183,8 +179,9 @@ services:
|
||||
websockets-dev:
|
||||
container_name: trmm-websockets-dev
|
||||
image: api-dev
|
||||
command: ["tactical-websockets-dev"]
|
||||
command: [ "tactical-websockets-dev" ]
|
||||
restart: always
|
||||
user: 1000:1000
|
||||
networks:
|
||||
dev:
|
||||
aliases:
|
||||
@@ -201,6 +198,7 @@ services:
|
||||
container_name: trmm-nginx-dev
|
||||
image: ${IMAGE_REPO}tactical-nginx:${VERSION}
|
||||
restart: always
|
||||
user: 1000:1000
|
||||
environment:
|
||||
APP_HOST: ${APP_HOST}
|
||||
API_HOST: ${API_HOST}
|
||||
@@ -214,29 +212,18 @@ services:
|
||||
dev:
|
||||
ipv4_address: ${DOCKER_NGINX_IP}
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "80:8080"
|
||||
- "443:4443"
|
||||
volumes:
|
||||
- tactical-data-dev:/opt/tactical
|
||||
|
||||
mkdocs-dev:
|
||||
container_name: trmm-mkdocs-dev
|
||||
image: api-dev
|
||||
restart: always
|
||||
command: ["tactical-mkdocs-dev"]
|
||||
ports:
|
||||
- "8005:8005"
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
networks:
|
||||
- dev
|
||||
|
||||
volumes:
|
||||
tactical-data-dev:
|
||||
postgres-data-dev:
|
||||
mongo-dev-data:
|
||||
mesh-data-dev:
|
||||
redis-data-dev:
|
||||
tactical-data-dev: null
|
||||
postgres-data-dev: null
|
||||
mongo-dev-data: null
|
||||
mesh-data-dev: null
|
||||
redis-data-dev: null
|
||||
|
||||
networks:
|
||||
dev:
|
||||
|
||||
@@ -9,17 +9,18 @@ set -e
|
||||
: "${POSTGRES_USER:=tactical}"
|
||||
: "${POSTGRES_PASS:=tactical}"
|
||||
: "${POSTGRES_DB:=tacticalrmm}"
|
||||
: "${MESH_CONTAINER:=tactical-meshcentral}"
|
||||
: "${MESH_SERVICE:=tactical-meshcentral}"
|
||||
: "${MESH_WS_URL:=ws://${MESH_SERVICE}:4443}"
|
||||
: "${MESH_USER:=meshcentral}"
|
||||
: "${MESH_PASS:=meshcentralpass}"
|
||||
: "${MESH_HOST:=tactical-meshcentral}"
|
||||
: "${API_HOST:=tactical-backend}"
|
||||
: "${APP_HOST:=tactical-frontend}"
|
||||
: "${REDIS_HOST:=tactical-redis}"
|
||||
: "${HTTP_PROTOCOL:=http}"
|
||||
: "${APP_PORT:=8080}"
|
||||
: "${API_PORT:=8000}"
|
||||
|
||||
: "${CERT_PRIV_PATH:=${TACTICAL_DIR}/certs/privkey.pem}"
|
||||
: "${CERT_PUB_PATH:=${TACTICAL_DIR}/certs/fullchain.pem}"
|
||||
|
||||
# Add python venv to path
|
||||
export PATH="${VIRTUAL_ENV}/bin:$PATH"
|
||||
|
||||
@@ -32,12 +33,12 @@ function check_tactical_ready {
|
||||
}
|
||||
|
||||
function django_setup {
|
||||
until (echo > /dev/tcp/"${POSTGRES_HOST}"/"${POSTGRES_PORT}") &> /dev/null; do
|
||||
until (echo >/dev/tcp/"${POSTGRES_HOST}"/"${POSTGRES_PORT}") &>/dev/null; do
|
||||
echo "waiting for postgresql container to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
until (echo > /dev/tcp/"${MESH_CONTAINER}"/443) &> /dev/null; do
|
||||
until (echo >/dev/tcp/"${MESH_SERVICE}"/4443) &>/dev/null; do
|
||||
echo "waiting for meshcentral container to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
@@ -48,24 +49,35 @@ function django_setup {
|
||||
MESH_TOKEN="$(cat ${TACTICAL_DIR}/tmp/mesh_token)"
|
||||
|
||||
DJANGO_SEKRET=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 80 | head -n 1)
|
||||
|
||||
localvars="$(cat << EOF
|
||||
|
||||
BASE_DOMAIN=$(echo "import tldextract; no_fetch_extract = tldextract.TLDExtract(suffix_list_urls=()); extracted = no_fetch_extract('${API_HOST}'); print(f'{extracted.domain}.{extracted.suffix}')" | python)
|
||||
|
||||
localvars="$(
|
||||
cat <<EOF
|
||||
SECRET_KEY = '${DJANGO_SEKRET}'
|
||||
|
||||
DEBUG = True
|
||||
|
||||
DOCKER_BUILD = True
|
||||
|
||||
CERT_FILE = '/opt/tactical/certs/fullchain.pem'
|
||||
KEY_FILE = '/opt/tactical/certs/privkey.pem'
|
||||
SWAGGER_ENABLED = True
|
||||
|
||||
SCRIPTS_DIR = '${WORKSPACE_DIR}/scripts'
|
||||
CERT_FILE = '${CERT_PUB_PATH}'
|
||||
KEY_FILE = '${CERT_PRIV_PATH}'
|
||||
|
||||
ALLOWED_HOSTS = ['${API_HOST}', '*']
|
||||
SCRIPTS_DIR = '/community-scripts'
|
||||
|
||||
ADMIN_URL = 'admin/'
|
||||
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
ALLOWED_HOSTS = ['${API_HOST}', '${APP_HOST}', '*']
|
||||
|
||||
CORS_ORIGIN_WHITELIST = ['https://${APP_HOST}']
|
||||
|
||||
SESSION_COOKIE_DOMAIN = '${BASE_DOMAIN}'
|
||||
CSRF_COOKIE_DOMAIN = '${BASE_DOMAIN}'
|
||||
CSRF_TRUSTED_ORIGINS = ['https://${API_HOST}', 'https://${APP_HOST}']
|
||||
|
||||
HEADLESS_FRONTEND_URLS = {'socialaccount_login_error': 'https://${APP_HOST}/account/provider/callback'}
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
@@ -75,6 +87,17 @@ DATABASES = {
|
||||
'PASSWORD': '${POSTGRES_PASS}',
|
||||
'HOST': '${POSTGRES_HOST}',
|
||||
'PORT': '${POSTGRES_PORT}',
|
||||
},
|
||||
'reporting': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': '${POSTGRES_DB}',
|
||||
'USER': 'reporting_user',
|
||||
'PASSWORD': 'read_password',
|
||||
'HOST': '${POSTGRES_HOST}',
|
||||
'PORT': '${POSTGRES_PORT}',
|
||||
'OPTIONS': {
|
||||
'options': '-c default_transaction_read_only=on'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,14 +105,18 @@ MESH_USERNAME = '${MESH_USER}'
|
||||
MESH_SITE = 'https://${MESH_HOST}'
|
||||
MESH_TOKEN_KEY = '${MESH_TOKEN}'
|
||||
REDIS_HOST = '${REDIS_HOST}'
|
||||
MESH_WS_URL = '${MESH_WS_URL}'
|
||||
ADMIN_ENABLED = True
|
||||
TRMM_INSECURE = True
|
||||
EOF
|
||||
)"
|
||||
)"
|
||||
|
||||
echo "${localvars}" > ${WORKSPACE_DIR}/api/tacticalrmm/tacticalrmm/local_settings.py
|
||||
echo "${localvars}" >${WORKSPACE_DIR}/api/tacticalrmm/tacticalrmm/local_settings.py
|
||||
|
||||
# run migrations and init scripts
|
||||
"${VIRTUAL_ENV}"/bin/python manage.py pre_update_tasks
|
||||
"${VIRTUAL_ENV}"/bin/python manage.py migrate --no-input
|
||||
"${VIRTUAL_ENV}"/bin/python manage.py generate_json_schemas
|
||||
"${VIRTUAL_ENV}"/bin/python manage.py collectstatic --no-input
|
||||
"${VIRTUAL_ENV}"/bin/python manage.py initial_db_setup
|
||||
"${VIRTUAL_ENV}"/bin/python manage.py initial_mesh_setup
|
||||
@@ -98,8 +125,9 @@ EOF
|
||||
"${VIRTUAL_ENV}"/bin/python manage.py reload_nats
|
||||
"${VIRTUAL_ENV}"/bin/python manage.py create_natsapi_conf
|
||||
"${VIRTUAL_ENV}"/bin/python manage.py create_installer_user
|
||||
"${VIRTUAL_ENV}"/bin/python manage.py post_update_tasks
|
||||
|
||||
# create super user
|
||||
# create super user
|
||||
echo "from accounts.models import User; User.objects.create_superuser('${TRMM_USER}', 'admin@example.com', '${TRMM_PASS}') if not User.objects.filter(username='${TRMM_USER}').exists() else 0;" | python manage.py shell
|
||||
}
|
||||
|
||||
@@ -110,22 +138,31 @@ if [ "$1" = 'tactical-init-dev' ]; then
|
||||
|
||||
test -f "${TACTICAL_READY_FILE}" && rm "${TACTICAL_READY_FILE}"
|
||||
|
||||
mkdir -p /meshcentral-data
|
||||
mkdir -p ${TACTICAL_DIR}/tmp
|
||||
mkdir -p ${TACTICAL_DIR}/certs
|
||||
mkdir -p ${TACTICAL_DIR}/reporting
|
||||
mkdir -p ${TACTICAL_DIR}/reporting/assets
|
||||
mkdir -p /mongo/data/db
|
||||
mkdir -p /redis/data
|
||||
touch /meshcentral-data/.initialized && chown -R 1000:1000 /meshcentral-data
|
||||
touch ${TACTICAL_DIR}/tmp/.initialized && chown -R 1000:1000 ${TACTICAL_DIR}
|
||||
touch ${TACTICAL_DIR}/certs/.initialized && chown -R 1000:1000 ${TACTICAL_DIR}/certs
|
||||
touch /mongo/data/db/.initialized && chown -R 1000:1000 /mongo/data/db
|
||||
touch /redis/data/.initialized && chown -R 1000:1000 /redis/data
|
||||
touch ${TACTICAL_DIR}/reporting && chown -R 1000:1000 ${TACTICAL_DIR}/reporting
|
||||
mkdir -p ${TACTICAL_DIR}/api/tacticalrmm/private/exe
|
||||
mkdir -p ${TACTICAL_DIR}/api/tacticalrmm/private/log
|
||||
touch ${TACTICAL_DIR}/api/tacticalrmm/private/log/django_debug.log
|
||||
|
||||
# setup Python virtual env and install dependencies
|
||||
! test -e "${VIRTUAL_ENV}" && python -m venv ${VIRTUAL_ENV}
|
||||
"${VIRTUAL_ENV}"/bin/python -m pip install --upgrade pip
|
||||
"${VIRTUAL_ENV}"/bin/pip install --no-cache-dir setuptools wheel
|
||||
"${VIRTUAL_ENV}"/bin/pip install --no-cache-dir -r /requirements.txt
|
||||
|
||||
django_setup
|
||||
|
||||
# create .env file for frontend
|
||||
webenv="$(cat << EOF
|
||||
PROD_URL = "${HTTP_PROTOCOL}://${API_HOST}"
|
||||
DEV_URL = "${HTTP_PROTOCOL}://${API_HOST}"
|
||||
APP_URL = "https://${APP_HOST}"
|
||||
DOCKER_BUILD = 1
|
||||
EOF
|
||||
)"
|
||||
echo "${webenv}" | tee "${WORKSPACE_DIR}"/web/.env > /dev/null
|
||||
|
||||
# chown everything to tactical user
|
||||
chown -R "${TACTICAL_USER}":"${TACTICAL_USER}" "${WORKSPACE_DIR}"
|
||||
chown -R "${TACTICAL_USER}":"${TACTICAL_USER}" "${TACTICAL_DIR}"
|
||||
@@ -154,8 +191,3 @@ if [ "$1" = 'tactical-websockets-dev' ]; then
|
||||
check_tactical_ready
|
||||
"${VIRTUAL_ENV}"/bin/daphne tacticalrmm.asgi:application --port 8383 -b 0.0.0.0
|
||||
fi
|
||||
|
||||
if [ "$1" = 'tactical-mkdocs-dev' ]; then
|
||||
cd "${WORKSPACE_DIR}/docs"
|
||||
"${VIRTUAL_ENV}"/bin/mkdocs serve
|
||||
fi
|
||||
|
||||
@@ -1,37 +1,3 @@
|
||||
# To ensure app dependencies are ported from your virtual environment/host machine into your container, run 'pip freeze > requirements.txt' in the terminal to overwrite this file
|
||||
asyncio-nats-client
|
||||
celery
|
||||
channels
|
||||
channels_redis
|
||||
django-ipware
|
||||
Django
|
||||
django-cors-headers
|
||||
django-rest-knox
|
||||
djangorestframework
|
||||
loguru
|
||||
msgpack
|
||||
psycopg2-binary
|
||||
pycparser
|
||||
pycryptodome
|
||||
pyotp
|
||||
pyparsing
|
||||
pytz
|
||||
qrcode
|
||||
redis
|
||||
twilio
|
||||
packaging
|
||||
validators
|
||||
websockets
|
||||
black
|
||||
Werkzeug
|
||||
django-extensions
|
||||
coverage
|
||||
coveralls
|
||||
model_bakery
|
||||
mkdocs
|
||||
mkdocs-material
|
||||
pymdown-extensions
|
||||
Pygments
|
||||
mypy
|
||||
pysnooper
|
||||
isort
|
||||
-r /workspace/api/tacticalrmm/requirements.txt
|
||||
-r /workspace/api/tacticalrmm/requirements-dev.txt
|
||||
-r /workspace/api/tacticalrmm/requirements-test.txt
|
||||
|
||||
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,9 +1,9 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: wh1te909
|
||||
github: amidaware
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: tacticalrmm
|
||||
ko_fi: # tacticalrmm
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -14,11 +14,12 @@ assignees: ''
|
||||
|
||||
**Installation Method:**
|
||||
- [ ] Standard
|
||||
- [ ] Standard with `--insecure` flag at install
|
||||
- [ ] Docker
|
||||
|
||||
**Agent Info (please complete the following information):**
|
||||
- Agent version (as shown in the 'Summary' tab of the agent from web UI):
|
||||
- Agent OS: [e.g. Win 10 v2004, Server 2012 R2]
|
||||
- Agent OS: [e.g. Win 10 v2004, Server 2016]
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
82
.github/workflows/ci-tests.yml
vendored
Normal file
82
.github/workflows/ci-tests.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: Tests CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*"
|
||||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Tests
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.11.8"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: harmon758/postgresql-action@v1
|
||||
with:
|
||||
postgresql version: "15"
|
||||
postgresql db: "pipeline"
|
||||
postgresql user: "pipeline"
|
||||
postgresql password: "pipeline123456"
|
||||
|
||||
- name: Setup Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
|
||||
- name: Install redis
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y redis
|
||||
redis-server --version
|
||||
|
||||
- name: Install requirements
|
||||
working-directory: api/tacticalrmm
|
||||
run: |
|
||||
python --version
|
||||
SETTINGS_FILE="tacticalrmm/settings.py"
|
||||
SETUPTOOLS_VER=$(grep "^SETUPTOOLS_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
||||
WHEEL_VER=$(grep "^WHEEL_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
||||
pip install --upgrade pip
|
||||
pip install setuptools==${SETUPTOOLS_VER} wheel==${WHEEL_VER}
|
||||
pip install -r requirements.txt -r requirements-test.txt
|
||||
|
||||
- name: Codestyle black
|
||||
working-directory: api
|
||||
run: |
|
||||
black --exclude migrations/ --check --diff tacticalrmm
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Lint with flake8
|
||||
working-directory: api/tacticalrmm
|
||||
run: |
|
||||
flake8 --config .flake8 .
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run django tests
|
||||
env:
|
||||
GHACTIONS: "yes"
|
||||
working-directory: api/tacticalrmm
|
||||
run: |
|
||||
pytest
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: codecov/codecov-action@v3
|
||||
with:
|
||||
directory: ./api/tacticalrmm
|
||||
files: ./api/tacticalrmm/coverage.xml
|
||||
verbose: true
|
||||
22
.github/workflows/deploy-docs.yml
vendored
22
.github/workflows/deploy-docs.yml
vendored
@@ -1,22 +0,0 @@
|
||||
name: Deploy Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: pip install --upgrade pip
|
||||
- run: pip install --upgrade setuptools wheel
|
||||
- run: pip install mkdocs mkdocs-material pymdown-extensions
|
||||
- run: mkdocs gh-deploy --force
|
||||
20
.github/workflows/docker-build-push.yml
vendored
20
.github/workflows/docker-build-push.yml
vendored
@@ -9,24 +9,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get Github Tag
|
||||
id: prep
|
||||
run: |
|
||||
echo ::set-output name=version::${GITHUB_REF#refs/tags/v}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
|
||||
- name: Build and Push Tactical Image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
file: ./docker/containers/tactical/dockerfile
|
||||
platforms: linux/amd64
|
||||
tags: tacticalrmm/tactical:${{ steps.prep.outputs.version }},tacticalrmm/tactical:latest
|
||||
|
||||
|
||||
- name: Build and Push Tactical MeshCentral Image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
file: ./docker/containers/tactical-meshcentral/dockerfile
|
||||
platforms: linux/amd64
|
||||
tags: tacticalrmm/tactical-meshcentral:${{ steps.prep.outputs.version }},tacticalrmm/tactical-meshcentral:latest
|
||||
|
||||
|
||||
- name: Build and Push Tactical NATS Image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
file: ./docker/containers/tactical-nats/dockerfile
|
||||
platforms: linux/amd64
|
||||
tags: tacticalrmm/tactical-nats:${{ steps.prep.outputs.version }},tacticalrmm/tactical-nats:latest
|
||||
|
||||
|
||||
- name: Build and Push Tactical Frontend Image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
file: ./docker/containers/tactical-frontend/dockerfile
|
||||
platforms: linux/amd64
|
||||
tags: tacticalrmm/tactical-frontend:${{ steps.prep.outputs.version }},tacticalrmm/tactical-frontend:latest
|
||||
|
||||
|
||||
- name: Build and Push Tactical Nginx Image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -50,4 +50,12 @@ docs/site/
|
||||
reset_db.sh
|
||||
run_go_cmd.py
|
||||
nats-api.conf
|
||||
|
||||
ignore/
|
||||
coverage.lcov
|
||||
daphne.sock.lock
|
||||
.pytest_cache
|
||||
coverage.xml
|
||||
setup_dev.yml
|
||||
11env/
|
||||
query_schema.json
|
||||
gunicorn_config.py
|
||||
23
.vscode/extensions.json
vendored
Normal file
23
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"recommendations": [
|
||||
// frontend
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"editorconfig.editorconfig",
|
||||
"vue.volar",
|
||||
"wayou.vscode-todo-highlight",
|
||||
|
||||
// python
|
||||
"matangover.mypy",
|
||||
"ms-python.python",
|
||||
|
||||
// golang
|
||||
"golang.go"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"octref.vetur",
|
||||
"hookyqr.beautify",
|
||||
"dbaeumer.jshint",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin"
|
||||
]
|
||||
}
|
||||
123
.vscode/settings.json
vendored
123
.vscode/settings.json
vendored
@@ -1,70 +1,59 @@
|
||||
{
|
||||
"python.pythonPath": "api/tacticalrmm/env/bin/python",
|
||||
"python.languageServer": "Pylance",
|
||||
"python.analysis.extraPaths": [
|
||||
"api/tacticalrmm",
|
||||
"api/env",
|
||||
],
|
||||
"python.analysis.diagnosticSeverityOverrides": {
|
||||
"reportUnusedImport": "error",
|
||||
"reportDuplicateImport": "error",
|
||||
"python.defaultInterpreterPath": "api/env/bin/python",
|
||||
"python.languageServer": "Pylance",
|
||||
"python.analysis.extraPaths": ["api/tacticalrmm", "api/env"],
|
||||
"python.analysis.diagnosticSeverityOverrides": {
|
||||
"reportUnusedImport": "error",
|
||||
"reportDuplicateImport": "error",
|
||||
"reportGeneralTypeIssues": "none",
|
||||
"reportOptionalMemberAccess": "none",
|
||||
},
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
"editor.guides.bracketPairs": true,
|
||||
"editor.formatOnSave": true,
|
||||
"files.associations": {
|
||||
"**/ansible/**/*.yml": "ansible",
|
||||
"**/docker/**/docker-compose*.yml": "dockercompose"
|
||||
},
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/node_modules/": true,
|
||||
"/node_modules/**": true,
|
||||
"**/env/": true,
|
||||
"/env/**": true,
|
||||
"**/__pycache__": true,
|
||||
"/__pycache__/**": true,
|
||||
"**/.cache": true,
|
||||
"**/.eggs": true,
|
||||
"**/.ipynb_checkpoints": true,
|
||||
"**/.mypy_cache": true,
|
||||
"**/.pytest_cache": true,
|
||||
"**/*.egg-info": true,
|
||||
"**/*.feather": true,
|
||||
"**/*.parquet*": true,
|
||||
"**/*.pyc": true,
|
||||
"**/*.zip": true
|
||||
},
|
||||
"go.useLanguageServer": true,
|
||||
"[go]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"python.analysis.memory.keepLibraryAst": true,
|
||||
"python.linting.mypyEnabled": true,
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"python.formatting.provider": "black",
|
||||
"editor.formatOnSave": true,
|
||||
"vetur.format.defaultFormatter.js": "prettier",
|
||||
"vetur.format.defaultFormatterOptions": {
|
||||
"prettier": {
|
||||
"semi": true,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"arrowParens": "avoid",
|
||||
}
|
||||
},
|
||||
"vetur.format.options.tabSize": 2,
|
||||
"vetur.format.options.useTabs": false,
|
||||
"files.watcherExclude": {
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/node_modules/": true,
|
||||
"/node_modules/**": true,
|
||||
"**/env/": true,
|
||||
"/env/**": true,
|
||||
"**/__pycache__": true,
|
||||
"/__pycache__/**": true,
|
||||
"**/.cache": true,
|
||||
"**/.eggs": true,
|
||||
"**/.ipynb_checkpoints": true,
|
||||
"**/.mypy_cache": true,
|
||||
"**/.pytest_cache": true,
|
||||
"**/*.egg-info": true,
|
||||
"**/*.feather": true,
|
||||
"**/*.parquet*": true,
|
||||
"**/*.pyc": true,
|
||||
"**/*.zip": true
|
||||
},
|
||||
},
|
||||
"go.useLanguageServer": true,
|
||||
"[go]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": false,
|
||||
},
|
||||
"editor.snippetSuggestions": "none",
|
||||
},
|
||||
"[go.mod]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true,
|
||||
},
|
||||
},
|
||||
"gopls": {
|
||||
"usePlaceholders": true,
|
||||
"completeUnimported": true,
|
||||
"staticcheck": true,
|
||||
"editor.snippetSuggestions": "none"
|
||||
},
|
||||
"[go.mod]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gopls": {
|
||||
"usePlaceholders": true,
|
||||
"completeUnimported": true,
|
||||
"staticcheck": true
|
||||
},
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||
}
|
||||
}
|
||||
|
||||
23
.vscode/tasks.json
vendored
23
.vscode/tasks.json
vendored
@@ -1,23 +0,0 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "docker debug",
|
||||
"type": "shell",
|
||||
"command": "docker-compose",
|
||||
"args": [
|
||||
"-p",
|
||||
"trmm",
|
||||
"-f",
|
||||
".devcontainer/docker-compose.yml",
|
||||
"-f",
|
||||
".devcontainer/docker-compose.debug.yml",
|
||||
"up",
|
||||
"-d",
|
||||
"--build"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
21
LICENSE
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019-present wh1te909
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
74
LICENSE.md
Normal file
74
LICENSE.md
Normal file
@@ -0,0 +1,74 @@
|
||||
### Tactical RMM License Version 1.0
|
||||
|
||||
Text of license:   Copyright © 2022 AmidaWare LLC. All rights reserved.<br>
|
||||
          Amending the text of this license is not permitted.
|
||||
|
||||
Trade Mark:    "Tactical RMM" is a trade mark of AmidaWare LLC.
|
||||
|
||||
Licensor:      AmidaWare LLC of 1968 S Coast Hwy PMB 3847 Laguna Beach, CA, USA.
|
||||
|
||||
Licensed Software:  The software known as Tactical RMM Version v0.12.0 (and all subsequent releases and versions) and the Tactical RMM Agent v2.0.0 (and all subsequent releases and versions).
|
||||
|
||||
### 1. Preamble
|
||||
The Licensed Software is designed to facilitate the remote monitoring and management (RMM) of networks, systems, servers, computers and other devices. The Licensed Software is made available primarily for use by organisations and managed service providers for monitoring and management purposes.
|
||||
|
||||
The Tactical RMM License is not an open-source software license. This license contains certain restrictions on the use of the Licensed Software. For example the functionality of the Licensed Software may not be made available as part of a SaaS (Software-as-a-Service) service or product to provide a commercial or for-profit service without the express prior permission of the Licensor.
|
||||
|
||||
### 2. License Grant
|
||||
Permission is hereby granted, free of charge, on a non-exclusive basis, to copy, modify, create derivative works and use the Licensed Software in source and binary forms subject to the following terms and conditions. No additional rights will be implied under this license.
|
||||
|
||||
* The hosting and use of the Licensed Software to monitor and manage in-house networks/systems and/or customer networks/systems is permitted.
|
||||
|
||||
This license does not allow the functionality of the Licensed Software (whether in whole or in part) or a modified version of the Licensed Software or a derivative work to be used or otherwise made available as part of any other commercial or for-profit service, including, without limitation, any of the following:
|
||||
* a service allowing third parties to interact remotely through a computer network;
|
||||
* as part of a SaaS service or product;
|
||||
* as part of the provision of a managed hosting service or product;
|
||||
* the offering of installation and/or configuration services;
|
||||
* the offer for sale, distribution or sale of any service or product (whether or not branded as Tactical RMM).
|
||||
|
||||
The prior written approval of AmidaWare LLC must be obtained for all commercial use and/or for-profit service use of the (i) Licensed Software (whether in whole or in part), (ii) a modified version of the Licensed Software and/or (iii) a derivative work.
|
||||
|
||||
The terms of this license apply to all copies of the Licensed Software (including modified versions) and derivative works.
|
||||
|
||||
All use of the Licensed Software must immediately cease if use breaches the terms of this license.
|
||||
|
||||
### 3. Derivative Works
|
||||
If a derivative work is created which is based on or otherwise incorporates all or any part of the Licensed Software, and the derivative work is made available to any other person, the complete corresponding machine readable source code (including all changes made to the Licensed Software) must accompany the derivative work and be made publicly available online.
|
||||
|
||||
### 4. Copyright Notice
|
||||
The following copyright notice shall be included in all copies of the Licensed Software:
|
||||
|
||||
   Copyright © 2022 AmidaWare LLC.
|
||||
|
||||
   Licensed under the Tactical RMM License Version 1.0 (the “License”).<br>
|
||||
   You may only use the Licensed Software in accordance with the License.<br>
|
||||
   A copy of the License is available at: https://license.tacticalrmm.com
|
||||
|
||||
### 5. Disclaimer of Warranty
|
||||
THE LICENSED SOFTWARE IS PROVIDED "AS IS". TO THE FULLEST EXTENT PERMISSIBLE AT LAW ALL CONDITIONS, WARRANTIES OR OTHER TERMS OF ANY KIND WHICH MIGHT HAVE EFFECT OR BE IMPLIED OR INCORPORATED, WHETHER BY STATUTE, COMMON LAW OR OTHERWISE ARE HEREBY EXCLUDED, INCLUDING THE CONDITIONS, WARRANTIES OR OTHER TERMS AS TO SATISFACTORY QUALITY AND/OR MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, THE USE OF REASONABLE SKILL AND CARE AND NON-INFRINGEMENT.
|
||||
|
||||
### 6. Limits of Liability
|
||||
THE FOLLOWING EXCLUSIONS SHALL APPLY TO THE FULLEST EXTENT PERMISSIBLE AT LAW. NEITHER THE AUTHORS NOR THE COPYRIGHT HOLDERS SHALL IN ANY CIRCUMSTANCES HAVE ANY LIABILITY FOR ANY CLAIM, LOSSES, DAMAGES OR OTHER LIABILITY, WHETHER THE SAME ARE SUFFERED DIRECTLY OR INDIRECTLY OR ARE IMMEDIATE OR CONSEQUENTIAL, AND WHETHER THE SAME ARISE IN CONTRACT, TORT OR DELICT (INCLUDING NEGLIGENCE) OR OTHERWISE HOWSOEVER ARISING FROM, OUT OF OR IN CONNECTION WITH THE LICENSED SOFTWARE OR THE USE OR INABILITY TO USE THE LICENSED SOFTWARE OR OTHER DEALINGS IN THE LICENSED SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGE. THE FOREGOING EXCLUSIONS SHALL INCLUDE, WITHOUT LIMITATION, LIABILITY FOR ANY LOSSES OR DAMAGES WHICH FALL WITHIN ANY OF THE FOLLOWING CATEGORIES: SPECIAL, EXEMPLARY, OR INCIDENTAL LOSS OR DAMAGE, LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF BUSINESS OPPORTUNITY, LOSS OF GOODWILL, AND LOSS OR CORRUPTION OF DATA.
|
||||
|
||||
### 7. Termination
|
||||
This license shall terminate with immediate effect if there is a material breach of any of its terms.
|
||||
|
||||
### 8. No partnership, agency or joint venture
|
||||
Nothing in this license agreement is intended to, or shall be deemed to, establish any partnership or joint venture or any relationship of agency between AmidaWare LLC and any other person.
|
||||
|
||||
### 9. No endorsement
|
||||
The names of the authors and/or the copyright holders must not be used to promote or endorse any products or services which are in any way derived from the Licensed Software without prior written consent.
|
||||
|
||||
### 10. Trademarks
|
||||
No permission is granted to use the trademark “Tactical RMM” or any other trade name, trademark, service mark or product name of AmidaWare LLC except to the extent necessary to comply with the notice requirements in Section 4 (Copyright Notice).
|
||||
|
||||
### 11. Entire agreement
|
||||
This license contains the whole agreement relating to its subject matter.
|
||||
|
||||
|
||||
|
||||
### 12. Severance
|
||||
If any provision or part-provision of this license is or becomes invalid, illegal or unenforceable, it shall be deemed deleted, but that shall not affect the validity and enforceability of the rest of this license.
|
||||
|
||||
### 13. Acceptance of these terms
|
||||
The terms and conditions of this license are accepted by copying, downloading, installing, redistributing, or otherwise using the Licensed Software.
|
||||
39
README.md
39
README.md
@@ -1,38 +1,53 @@
|
||||
# Tactical RMM
|
||||
|
||||
[](https://dev.azure.com/dcparsi/Tactical%20RMM/_build/latest?definitionId=4&branchName=develop)
|
||||
[](https://coveralls.io/github/wh1te909/tacticalrmm?branch=develop)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||

|
||||
[](https://codecov.io/gh/amidaware/tacticalrmm)
|
||||
[](https://github.com/python/black)
|
||||
|
||||
Tactical RMM is a remote monitoring & management tool for Windows computers, built with Django and Vue.\
|
||||
It uses an [agent](https://github.com/wh1te909/rmmagent) written in golang and integrates with [MeshCentral](https://github.com/Ylianst/MeshCentral)
|
||||
Tactical RMM is a remote monitoring & management tool, built with Django and Vue.\
|
||||
It uses an [agent](https://github.com/amidaware/rmmagent) written in golang and integrates with [MeshCentral](https://github.com/Ylianst/MeshCentral)
|
||||
|
||||
# [LIVE DEMO](https://demo.tacticalrmm.com/)
|
||||
|
||||
# [LIVE DEMO](https://rmm.tacticalrmm.io/)
|
||||
Demo database resets every hour. A lot of features are disabled for obvious reasons due to the nature of this app.
|
||||
|
||||
### [Discord Chat](https://discord.gg/upGTkWp)
|
||||
|
||||
### [Documentation](https://wh1te909.github.io/tacticalrmm/)
|
||||
### [Documentation](https://docs.tacticalrmm.com)
|
||||
|
||||
## Features
|
||||
|
||||
- Teamviewer-like remote desktop control
|
||||
- Real-time remote shell
|
||||
- Remote file browser (download and upload files)
|
||||
- Remote command and script execution (batch, powershell and python scripts)
|
||||
- Remote command and script execution (batch, powershell, python, nushell and deno scripts)
|
||||
- Event log viewer
|
||||
- Services management
|
||||
- Windows patch management
|
||||
- Automated checks with email/SMS alerting (cpu, disk, memory, services, scripts, event logs)
|
||||
- Automated checks with email/SMS/Webhook alerting (cpu, disk, memory, services, scripts, event logs)
|
||||
- Automated task runner (run scripts on a schedule)
|
||||
- Remote software installation via chocolatey
|
||||
- Software and hardware inventory
|
||||
|
||||
## Windows versions supported
|
||||
## Windows agent versions supported
|
||||
|
||||
- Windows 7, 8.1, 10, Server 2008R2, 2012R2, 2016, 2019
|
||||
- Windows 7, 8.1, 10, 11, Server 2008R2, 2012R2, 2016, 2019, 2022
|
||||
|
||||
## Linux agent versions supported
|
||||
|
||||
- Any distro with systemd which includes but is not limited to: Debian (10, 11), Ubuntu x86_64 (18.04, 20.04, 22.04), Synology 7, centos, freepbx and more!
|
||||
|
||||
## Mac agent versions supported
|
||||
|
||||
- 64 bit Intel and Apple Silicon (M-Series)
|
||||
|
||||
## Sponsorship Features
|
||||
|
||||
- Mac and Linux Agents
|
||||
- Windows [Code Signed](https://docs.tacticalrmm.com/code_signing/) Agents
|
||||
- Fully Customizable [Reporting](https://docs.tacticalrmm.com/ee/reporting/reporting_overview/) Module
|
||||
- [Single Sign-On](https://docs.tacticalrmm.com/ee/sso/sso/) (SSO)
|
||||
|
||||
## Installation / Backup / Restore / Usage
|
||||
|
||||
### Refer to the [documentation](https://wh1te909.github.io/tacticalrmm/)
|
||||
### Refer to the [documentation](https://docs.tacticalrmm.com)
|
||||
|
||||
9
SECURITY.md
Normal file
9
SECURITY.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
[Latest](https://github.com/amidaware/tacticalrmm/releases/latest) release
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
https://docs.tacticalrmm.com/security
|
||||
3
ansible/README.md
Normal file
3
ansible/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### tacticalrmm ansible WIP
|
||||
|
||||
ansible role to setup a Debian 11 VM for tacticalrmm local development
|
||||
40
ansible/roles/trmm_dev/defaults/main.yml
Normal file
40
ansible/roles/trmm_dev/defaults/main.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
user: "tactical"
|
||||
python_ver: "3.11.8"
|
||||
go_ver: "1.20.7"
|
||||
backend_repo: "https://github.com/amidaware/tacticalrmm.git"
|
||||
frontend_repo: "https://github.com/amidaware/tacticalrmm-web.git"
|
||||
scripts_repo: "https://github.com/amidaware/community-scripts.git"
|
||||
backend_dir: "/opt/trmm"
|
||||
frontend_dir: "/opt/trmm-web"
|
||||
scripts_dir: "/opt/trmm-community-scripts"
|
||||
trmm_dir: "{{ backend_dir }}/api/tacticalrmm/tacticalrmm"
|
||||
mesh_dir: "/opt/meshcentral"
|
||||
settings_file: "{{ trmm_dir }}/settings.py"
|
||||
local_settings_file: "{{ trmm_dir }}/local_settings.py"
|
||||
fullchain_dest: /etc/ssl/certs/fullchain.pem
|
||||
privkey_dest: /etc/ssl/certs/privkey.pem
|
||||
|
||||
base_pkgs:
|
||||
- build-essential
|
||||
- curl
|
||||
- wget
|
||||
- dirmngr
|
||||
- gnupg
|
||||
- openssl
|
||||
- gcc
|
||||
- g++
|
||||
- make
|
||||
- ca-certificates
|
||||
- git
|
||||
|
||||
python_pkgs:
|
||||
- zlib1g-dev
|
||||
- libncurses5-dev
|
||||
- libgdbm-dev
|
||||
- libnss3-dev
|
||||
- libssl-dev
|
||||
- libreadline-dev
|
||||
- libffi-dev
|
||||
- libsqlite3-dev
|
||||
- libbz2-dev
|
||||
31
ansible/roles/trmm_dev/files/nginx-default.conf
Normal file
31
ansible/roles/trmm_dev/files/nginx-default.conf
Normal file
@@ -0,0 +1,31 @@
|
||||
worker_rlimit_nofile 1000000;
|
||||
user www-data;
|
||||
worker_processes auto;
|
||||
pid /run/nginx.pid;
|
||||
include /etc/nginx/modules-enabled/*.conf;
|
||||
|
||||
events {
|
||||
worker_connections 4096;
|
||||
}
|
||||
|
||||
http {
|
||||
sendfile on;
|
||||
server_tokens off;
|
||||
tcp_nopush on;
|
||||
types_hash_max_size 2048;
|
||||
server_names_hash_bucket_size 256;
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
|
||||
ssl_ecdh_curve secp384r1;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
gzip on;
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
include /etc/nginx/sites-enabled/*;
|
||||
}
|
||||
20
ansible/roles/trmm_dev/files/vimrc.local
Normal file
20
ansible/roles/trmm_dev/files/vimrc.local
Normal file
@@ -0,0 +1,20 @@
|
||||
" This file loads the default vim options at the beginning and prevents
|
||||
" that they are being loaded again later. All other options that will be set,
|
||||
" are added, or overwrite the default settings. Add as many options as you
|
||||
" whish at the end of this file.
|
||||
|
||||
" Load the defaults
|
||||
source $VIMRUNTIME/defaults.vim
|
||||
|
||||
" Prevent the defaults from being loaded again later, if the user doesn't
|
||||
" have a local vimrc (~/.vimrc)
|
||||
let skip_defaults_vim = 1
|
||||
|
||||
|
||||
" Set more options (overwrites settings from /usr/share/vim/vim80/defaults.vim)
|
||||
" Add as many options as you whish
|
||||
|
||||
" Set the mouse mode to 'r'
|
||||
if has('mouse')
|
||||
set mouse=r
|
||||
endif
|
||||
634
ansible/roles/trmm_dev/tasks/main.yml
Normal file
634
ansible/roles/trmm_dev/tasks/main.yml
Normal file
@@ -0,0 +1,634 @@
|
||||
---
|
||||
- name: Append subdomains to hosts
|
||||
tags: hosts
|
||||
become: yes
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/hosts
|
||||
backrefs: yes
|
||||
regexp: '^(127\.0\.1\.1 .*)$'
|
||||
line: "\\1 {{ api }} {{ mesh }} {{ rmm }}"
|
||||
|
||||
- name: set mouse mode for vim
|
||||
tags: vim
|
||||
become: yes
|
||||
ansible.builtin.copy:
|
||||
src: vimrc.local
|
||||
dest: /etc/vim/vimrc.local
|
||||
owner: "root"
|
||||
group: "root"
|
||||
mode: "0644"
|
||||
|
||||
- name: set max_user_watches
|
||||
tags: sysctl
|
||||
become: yes
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/sysctl.conf
|
||||
line: fs.inotify.max_user_watches=524288
|
||||
|
||||
- name: reload sysctl
|
||||
tags: sysctl
|
||||
become: yes
|
||||
ansible.builtin.command:
|
||||
cmd: sysctl -p
|
||||
|
||||
- name: install base packages
|
||||
tags: base
|
||||
become: yes
|
||||
ansible.builtin.apt:
|
||||
pkg: "{{ item }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
with_items:
|
||||
- "{{ base_pkgs }}"
|
||||
|
||||
- name: set arch fact
|
||||
ansible.builtin.set_fact:
|
||||
goarch: "{{ 'amd64' if ansible_architecture == 'x86_64' else 'arm64' }}"
|
||||
|
||||
- name: download and install golang
|
||||
tags: golang
|
||||
become: yes
|
||||
ansible.builtin.unarchive:
|
||||
src: "https://go.dev/dl/go{{ go_ver }}.linux-{{ goarch }}.tar.gz"
|
||||
dest: /usr/local
|
||||
remote_src: yes
|
||||
|
||||
- name: add golang to path
|
||||
become: yes
|
||||
tags: golang
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/profile.d/golang.sh
|
||||
content: "PATH=$PATH:/usr/local/go/bin"
|
||||
|
||||
- name: install python prereqs
|
||||
tags: python
|
||||
become: yes
|
||||
ansible.builtin.apt:
|
||||
pkg: "{{ item }}"
|
||||
state: present
|
||||
with_items:
|
||||
- "{{ python_pkgs }}"
|
||||
|
||||
- name: get cpu core count
|
||||
tags: python
|
||||
ansible.builtin.command: nproc
|
||||
register: numprocs
|
||||
|
||||
- name: Create python tmpdir
|
||||
tags: python
|
||||
ansible.builtin.tempfile:
|
||||
state: directory
|
||||
suffix: python
|
||||
register: python_tmp
|
||||
|
||||
- name: download and extract python
|
||||
tags: python
|
||||
ansible.builtin.unarchive:
|
||||
src: "https://www.python.org/ftp/python/{{ python_ver }}/Python-{{ python_ver }}.tgz"
|
||||
dest: "{{ python_tmp.path }}"
|
||||
remote_src: yes
|
||||
|
||||
- name: compile python
|
||||
tags: python
|
||||
ansible.builtin.shell:
|
||||
chdir: "{{ python_tmp.path }}/Python-{{ python_ver }}"
|
||||
cmd: |
|
||||
./configure --enable-optimizations
|
||||
make -j {{ numprocs.stdout }}
|
||||
|
||||
- name: alt install python
|
||||
tags: python
|
||||
become: yes
|
||||
ansible.builtin.shell:
|
||||
chdir: "{{ python_tmp.path }}/Python-{{ python_ver }}"
|
||||
cmd: |
|
||||
make altinstall
|
||||
|
||||
- name: install redis
|
||||
tags: redis
|
||||
become: yes
|
||||
ansible.builtin.apt:
|
||||
pkg: redis
|
||||
state: present
|
||||
|
||||
- name: create postgres repo
|
||||
tags: postgres
|
||||
become: yes
|
||||
ansible.builtin.copy:
|
||||
content: "deb http://apt.postgresql.org/pub/repos/apt {{ ansible_distribution_release }}-pgdg main"
|
||||
dest: /etc/apt/sources.list.d/pgdg.list
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: import postgres repo signing key
|
||||
tags: postgres
|
||||
become: yes
|
||||
ansible.builtin.apt_key:
|
||||
url: https://www.postgresql.org/media/keys/ACCC4CF8.asc
|
||||
state: present
|
||||
|
||||
- name: install postgresql
|
||||
tags: postgres
|
||||
become: yes
|
||||
ansible.builtin.apt:
|
||||
pkg: postgresql-15
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: ensure postgres enabled and started
|
||||
tags: postgres
|
||||
become: yes
|
||||
ansible.builtin.service:
|
||||
name: postgresql
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
- name: setup trmm database
|
||||
tags: postgres
|
||||
become: yes
|
||||
become_user: postgres
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
psql -c "CREATE DATABASE tacticalrmm"
|
||||
psql -c "CREATE USER {{ db_user }} WITH PASSWORD '{{ db_passwd }}'"
|
||||
psql -c "ALTER ROLE {{ db_user }} SET client_encoding TO 'utf8'"
|
||||
psql -c "ALTER ROLE {{ db_user }} SET default_transaction_isolation TO 'read committed'"
|
||||
psql -c "ALTER ROLE {{ db_user }} SET timezone TO 'UTC'"
|
||||
psql -c "ALTER ROLE {{ db_user }} CREATEDB"
|
||||
psql -c "GRANT ALL PRIVILEGES ON DATABASE tacticalrmm TO {{ db_user }}"
|
||||
psql -c "ALTER DATABASE tacticalrmm OWNER TO {{ db_user }}"
|
||||
psql -c "GRANT USAGE, CREATE ON SCHEMA PUBLIC TO {{ db_user }}"
|
||||
|
||||
- name: setup mesh database
|
||||
tags: postgres
|
||||
become: yes
|
||||
become_user: postgres
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
psql -c "CREATE DATABASE meshcentral"
|
||||
psql -c "CREATE USER {{ mesh_db_user }} WITH PASSWORD '{{ mesh_db_passwd }}'"
|
||||
psql -c "ALTER ROLE {{ mesh_db_user }} SET client_encoding TO 'utf8'"
|
||||
psql -c "ALTER ROLE {{ mesh_db_user }} SET default_transaction_isolation TO 'read committed'"
|
||||
psql -c "ALTER ROLE {{ mesh_db_user }} SET timezone TO 'UTC'"
|
||||
psql -c "GRANT ALL PRIVILEGES ON DATABASE meshcentral TO {{ mesh_db_user }}"
|
||||
psql -c "ALTER DATABASE meshcentral OWNER TO {{ mesh_db_user }}"
|
||||
psql -c "GRANT USAGE, CREATE ON SCHEMA PUBLIC TO {{ mesh_db_user }}"
|
||||
|
||||
- name: create repo dirs
|
||||
become: yes
|
||||
tags: git
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ user }}"
|
||||
group: "{{ user }}"
|
||||
mode: "0755"
|
||||
with_items:
|
||||
- "{{ backend_dir }}"
|
||||
- "{{ frontend_dir }}"
|
||||
- "{{ scripts_dir }}"
|
||||
|
||||
- name: git clone repos
|
||||
tags: git
|
||||
ansible.builtin.git:
|
||||
repo: "{{ item.repo }}"
|
||||
dest: "{{ item.dest }}"
|
||||
version: "{{ item.version }}"
|
||||
with_items:
|
||||
- {
|
||||
repo: "{{ backend_repo }}",
|
||||
dest: "{{ backend_dir }}",
|
||||
version: develop,
|
||||
}
|
||||
- {
|
||||
repo: "{{ frontend_repo }}",
|
||||
dest: "{{ frontend_dir }}",
|
||||
version: develop,
|
||||
}
|
||||
- { repo: "{{ scripts_repo }}", dest: "{{ scripts_dir }}", version: main }
|
||||
|
||||
- name: get nats_server_ver
|
||||
tags: nats
|
||||
ansible.builtin.shell: grep "^NATS_SERVER_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'
|
||||
register: nats_server_ver
|
||||
|
||||
- name: Create nats tmpdir
|
||||
tags: nats
|
||||
ansible.builtin.tempfile:
|
||||
state: directory
|
||||
suffix: nats
|
||||
register: nats_tmp
|
||||
|
||||
- name: download and extract nats
|
||||
tags: nats
|
||||
ansible.builtin.unarchive:
|
||||
src: "https://github.com/nats-io/nats-server/releases/download/v{{ nats_server_ver.stdout }}/nats-server-v{{ nats_server_ver.stdout }}-linux-{{ goarch }}.tar.gz"
|
||||
dest: "{{ nats_tmp.path }}"
|
||||
remote_src: yes
|
||||
|
||||
- name: install nats
|
||||
tags: nats
|
||||
become: yes
|
||||
ansible.builtin.copy:
|
||||
remote_src: yes
|
||||
src: "{{ nats_tmp.path }}/nats-server-v{{ nats_server_ver.stdout }}-linux-{{ goarch }}/nats-server"
|
||||
dest: /usr/local/bin/nats-server
|
||||
owner: "{{ user }}"
|
||||
group: "{{ user }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: Create nodejs tmpdir
|
||||
tags: nodejs
|
||||
ansible.builtin.tempfile:
|
||||
state: directory
|
||||
suffix: nodejs
|
||||
register: nodejs_tmp
|
||||
|
||||
- name: download nodejs setup
|
||||
tags: nodejs
|
||||
ansible.builtin.get_url:
|
||||
url: https://deb.nodesource.com/setup_18.x
|
||||
dest: "{{ nodejs_tmp.path }}/setup_node.sh"
|
||||
mode: "0755"
|
||||
|
||||
- name: run node setup script
|
||||
tags: nodejs
|
||||
become: yes
|
||||
ansible.builtin.command:
|
||||
cmd: "{{ nodejs_tmp.path }}/setup_node.sh"
|
||||
|
||||
- name: install nodejs
|
||||
tags: nodejs
|
||||
become: yes
|
||||
ansible.builtin.apt:
|
||||
pkg: nodejs
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: update npm
|
||||
tags: nodejs
|
||||
become: yes
|
||||
ansible.builtin.shell:
|
||||
cmd: npm install -g npm
|
||||
|
||||
- name: install quasar cli
|
||||
tags: quasar
|
||||
become: yes
|
||||
ansible.builtin.shell:
|
||||
cmd: npm install -g @quasar/cli
|
||||
|
||||
- name: install frontend
|
||||
tags: quasar
|
||||
ansible.builtin.shell:
|
||||
chdir: "{{ frontend_dir }}"
|
||||
cmd: npm install
|
||||
|
||||
- name: add quasar env
|
||||
tags: quasar
|
||||
ansible.builtin.template:
|
||||
src: quasar.env.j2
|
||||
dest: "{{ frontend_dir }}/.env"
|
||||
owner: "{{ user }}"
|
||||
group: "{{ user }}"
|
||||
mode: "0644"
|
||||
|
||||
- name: remove tempdirs
|
||||
tags: cleanup
|
||||
become: yes
|
||||
ignore_errors: yes
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
with_items:
|
||||
- "{{ nats_tmp.path }}"
|
||||
- "{{ python_tmp.path }}"
|
||||
- "{{ nodejs_tmp.path }}"
|
||||
|
||||
- name: deploy fullchain
|
||||
tags: certs
|
||||
become: yes
|
||||
ansible.builtin.copy:
|
||||
src: "{{ fullchain_src }}"
|
||||
dest: "{{ fullchain_dest }}"
|
||||
owner: "{{ user }}"
|
||||
group: "{{ user }}"
|
||||
mode: "0440"
|
||||
|
||||
- name: deploy privkey
|
||||
tags: certs
|
||||
become: yes
|
||||
ansible.builtin.copy:
|
||||
src: "{{ privkey_src }}"
|
||||
dest: "{{ privkey_dest }}"
|
||||
owner: "{{ user }}"
|
||||
group: "{{ user }}"
|
||||
mode: "0440"
|
||||
|
||||
- name: import nginx signing key
|
||||
tags: nginx
|
||||
become: yes
|
||||
ansible.builtin.apt_key:
|
||||
url: https://nginx.org/keys/nginx_signing.key
|
||||
state: present
|
||||
|
||||
- name: add nginx repo
|
||||
tags: nginx
|
||||
become: yes
|
||||
ansible.builtin.template:
|
||||
src: nginx.repo.j2
|
||||
dest: /etc/apt/sources.list.d/nginx.list
|
||||
owner: "root"
|
||||
group: "root"
|
||||
mode: "0644"
|
||||
|
||||
- name: install nginx
|
||||
tags: nginx
|
||||
become: yes
|
||||
ansible.builtin.apt:
|
||||
pkg: nginx
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: set nginx default conf
|
||||
tags: nginx
|
||||
become: yes
|
||||
ansible.builtin.copy:
|
||||
src: nginx-default.conf
|
||||
dest: /etc/nginx/nginx.conf
|
||||
owner: "root"
|
||||
group: "root"
|
||||
mode: "0644"
|
||||
|
||||
- name: create nginx dirs
|
||||
become: yes
|
||||
tags: nginx
|
||||
ansible.builtin.file:
|
||||
state: directory
|
||||
path: "{{ item }}"
|
||||
mode: "0755"
|
||||
with_items:
|
||||
- /etc/nginx/sites-available
|
||||
- /etc/nginx/sites-enabled
|
||||
|
||||
- name: deploy nginx sites
|
||||
become: yes
|
||||
tags: nginx
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: "0644"
|
||||
owner: root
|
||||
group: root
|
||||
with_items:
|
||||
- { src: backend.nginx.j2, dest: /etc/nginx/sites-available/backend.conf }
|
||||
- { src: mesh.nginx.j2, dest: /etc/nginx/sites-available/mesh.conf }
|
||||
|
||||
- name: enable nginx sites
|
||||
become: yes
|
||||
tags: nginx
|
||||
ansible.builtin.file:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: "0644"
|
||||
owner: root
|
||||
group: root
|
||||
state: link
|
||||
with_items:
|
||||
- {
|
||||
src: /etc/nginx/sites-available/backend.conf,
|
||||
dest: /etc/nginx/sites-enabled/backend.conf,
|
||||
}
|
||||
- {
|
||||
src: /etc/nginx/sites-available/mesh.conf,
|
||||
dest: /etc/nginx/sites-enabled/mesh.conf,
|
||||
}
|
||||
|
||||
- name: ensure nginx enabled and restarted
|
||||
tags: nginx
|
||||
become: yes
|
||||
ansible.builtin.service:
|
||||
name: nginx
|
||||
enabled: yes
|
||||
state: restarted
|
||||
|
||||
- name: set natsapi fact
|
||||
ansible.builtin.set_fact:
|
||||
natsapi: "{{ 'nats-api' if ansible_architecture == 'x86_64' else 'nats-api-arm64' }}"
|
||||
|
||||
- name: copy nats-api bin
|
||||
tags: nats-api
|
||||
become: yes
|
||||
ansible.builtin.copy:
|
||||
remote_src: yes
|
||||
src: "{{ backend_dir }}/natsapi/bin/{{ natsapi }}"
|
||||
dest: /usr/local/bin/nats-api
|
||||
owner: "{{ user }}"
|
||||
group: "{{ user }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: get setuptools_ver
|
||||
tags: pip
|
||||
ansible.builtin.shell: grep "^SETUPTOOLS_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'
|
||||
register: setuptools_ver
|
||||
|
||||
- name: get wheel_ver
|
||||
tags: pip
|
||||
ansible.builtin.shell: grep "^WHEEL_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'
|
||||
register: wheel_ver
|
||||
|
||||
- name: setup virtual env
|
||||
tags: pip
|
||||
ansible.builtin.shell:
|
||||
chdir: "{{ backend_dir }}/api"
|
||||
cmd: python3.11 -m venv env
|
||||
|
||||
- name: update pip to latest
|
||||
tags: pip
|
||||
ansible.builtin.pip:
|
||||
virtualenv: "{{ backend_dir }}/api/env"
|
||||
name: pip
|
||||
state: latest
|
||||
|
||||
- name: install setuptools and wheel
|
||||
tags: pip
|
||||
ansible.builtin.pip:
|
||||
virtualenv: "{{ backend_dir }}/api/env"
|
||||
name: "{{ item }}"
|
||||
with_items:
|
||||
- "setuptools=={{ setuptools_ver.stdout }}"
|
||||
- "wheel=={{ wheel_ver.stdout }}"
|
||||
|
||||
- name: install python packages
|
||||
tags: pip
|
||||
ansible.builtin.pip:
|
||||
virtualenv: "{{ backend_dir }}/api/env"
|
||||
chdir: "{{ backend_dir }}/api/tacticalrmm"
|
||||
requirements: "{{ item }}"
|
||||
with_items:
|
||||
- requirements.txt
|
||||
- requirements-dev.txt
|
||||
- requirements-test.txt
|
||||
|
||||
- name: deploy django local settings
|
||||
tags: django
|
||||
ansible.builtin.template:
|
||||
src: local_settings.j2
|
||||
dest: "{{ local_settings_file }}"
|
||||
mode: "0644"
|
||||
owner: "{{ user }}"
|
||||
group: "{{ user }}"
|
||||
|
||||
- name: setup django
|
||||
tags: django
|
||||
ansible.builtin.shell:
|
||||
chdir: "{{ backend_dir }}/api/tacticalrmm"
|
||||
cmd: |
|
||||
. ../env/bin/activate
|
||||
python manage.py migrate --no-input
|
||||
python manage.py collectstatic --no-input
|
||||
python manage.py create_natsapi_conf
|
||||
python manage.py load_chocos
|
||||
python manage.py load_community_scripts
|
||||
echo "from accounts.models import User; User.objects.create_superuser('{{ django_user }}', '{{ github_email }}', '{{ django_password }}') if not User.objects.filter(username='{{ django_user }}').exists() else 0;" | python manage.py shell
|
||||
python manage.py create_installer_user
|
||||
|
||||
- name: deploy services
|
||||
tags: services
|
||||
become: yes
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: "0644"
|
||||
owner: "root"
|
||||
group: "root"
|
||||
with_items:
|
||||
- { src: nats-api.systemd.j2, dest: /etc/systemd/system/nats-api.service }
|
||||
- { src: nats-server.systemd.j2, dest: /etc/systemd/system/nats.service }
|
||||
- { src: mesh.systemd.j2, dest: /etc/systemd/system/meshcentral.service }
|
||||
|
||||
- name: get mesh_ver
|
||||
tags: mesh
|
||||
ansible.builtin.shell: grep "^MESH_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'
|
||||
register: mesh_ver
|
||||
|
||||
- name: create meshcentral data directory
|
||||
tags: mesh
|
||||
become: yes
|
||||
ansible.builtin.file:
|
||||
path: "{{ mesh_dir }}/meshcentral-data"
|
||||
state: directory
|
||||
owner: "{{ user }}"
|
||||
group: "{{ user }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: install meshcentral
|
||||
tags: mesh
|
||||
ansible.builtin.command:
|
||||
chdir: "{{ mesh_dir }}"
|
||||
cmd: "npm install meshcentral@{{ mesh_ver.stdout }}"
|
||||
|
||||
- name: deploy mesh config
|
||||
tags: mesh
|
||||
ansible.builtin.template:
|
||||
src: mesh.cfg.j2
|
||||
dest: "{{ mesh_dir }}/meshcentral-data/config.json"
|
||||
mode: "0644"
|
||||
owner: "{{ user }}"
|
||||
group: "{{ user }}"
|
||||
|
||||
- name: start meshcentral
|
||||
tags: mesh
|
||||
become: yes
|
||||
ansible.builtin.systemd:
|
||||
name: meshcentral.service
|
||||
state: started
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
|
||||
- name: wait for meshcentral to be ready
|
||||
tags: mesh
|
||||
uri:
|
||||
url: "https://{{ mesh }}"
|
||||
return_content: yes
|
||||
validate_certs: yes
|
||||
status_code: 200
|
||||
register: mesh_status
|
||||
until: mesh_status.status == 200
|
||||
retries: 20
|
||||
delay: 3
|
||||
|
||||
- name: get meshcentral login token key
|
||||
tags: mesh_key
|
||||
ansible.builtin.command:
|
||||
chdir: "{{ mesh_dir }}"
|
||||
cmd: node node_modules/meshcentral --logintokenkey
|
||||
register: mesh_token_key
|
||||
|
||||
- name: add mesh key to django settings file
|
||||
tags: mesh_key
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ local_settings_file }}"
|
||||
line: 'MESH_TOKEN_KEY = "{{ mesh_token_key.stdout }}"'
|
||||
|
||||
- name: stop meshcentral service
|
||||
tags: mesh_user
|
||||
become: yes
|
||||
ansible.builtin.service:
|
||||
name: meshcentral.service
|
||||
state: stopped
|
||||
|
||||
- name: create mesh user
|
||||
tags: mesh_user
|
||||
ansible.builtin.shell:
|
||||
chdir: "{{ mesh_dir }}"
|
||||
cmd: |
|
||||
node node_modules/meshcentral --createaccount {{ mesh_user }} --pass {{ mesh_password }} --email {{ github_email }}
|
||||
node node_modules/meshcentral --adminaccount {{ mesh_user }}
|
||||
|
||||
- name: start meshcentral service
|
||||
tags: mesh_user
|
||||
become: yes
|
||||
ansible.builtin.service:
|
||||
name: meshcentral.service
|
||||
state: started
|
||||
|
||||
- name: wait for meshcentral to be ready
|
||||
tags: mesh_user
|
||||
uri:
|
||||
url: "https://{{ mesh }}"
|
||||
return_content: yes
|
||||
validate_certs: yes
|
||||
status_code: 200
|
||||
register: mesh_status
|
||||
until: mesh_status.status == 200
|
||||
retries: 20
|
||||
delay: 3
|
||||
|
||||
- name: create mesh device group
|
||||
tags: mesh_user
|
||||
ansible.builtin.shell:
|
||||
chdir: "{{ mesh_dir }}"
|
||||
cmd: |
|
||||
node node_modules/meshcentral/meshctrl.js --url wss://{{ mesh }}:443 --loginuser {{ mesh_user }} --loginpass {{ mesh_password }} AddDeviceGroup --name TacticalRMM
|
||||
|
||||
- name: finish up django
|
||||
tags: mesh_user
|
||||
ansible.builtin.shell:
|
||||
chdir: "{{ backend_dir }}/api/tacticalrmm"
|
||||
cmd: |
|
||||
. ../env/bin/activate
|
||||
python manage.py initial_db_setup
|
||||
python manage.py reload_nats
|
||||
|
||||
- name: restart services
|
||||
tags: services
|
||||
become: yes
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: yes
|
||||
enabled: yes
|
||||
state: restarted
|
||||
name: "{{ item }}.service"
|
||||
with_items:
|
||||
- nats
|
||||
- nats-api
|
||||
20
ansible/roles/trmm_dev/templates/backend.nginx.j2
Normal file
20
ansible/roles/trmm_dev/templates/backend.nginx.j2
Normal file
@@ -0,0 +1,20 @@
|
||||
server {
|
||||
listen 443 ssl reuseport;
|
||||
listen [::]:443 ssl;
|
||||
server_name {{ api }};
|
||||
client_max_body_size 300M;
|
||||
ssl_certificate {{ fullchain_dest }};
|
||||
ssl_certificate_key {{ privkey_dest }};
|
||||
|
||||
|
||||
location ~ ^/natsws {
|
||||
proxy_pass http://127.0.0.1:9235;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header X-Forwarded-Host $host:$server_port;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
20
ansible/roles/trmm_dev/templates/local_settings.j2
Normal file
20
ansible/roles/trmm_dev/templates/local_settings.j2
Normal file
@@ -0,0 +1,20 @@
|
||||
SECRET_KEY = "{{ django_secret }}"
|
||||
DEBUG = True
|
||||
ALLOWED_HOSTS = ['{{ api }}']
|
||||
ADMIN_URL = "admin/"
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'tacticalrmm',
|
||||
'USER': '{{ db_user }}',
|
||||
'PASSWORD': '{{ db_passwd }}',
|
||||
'HOST': 'localhost',
|
||||
'PORT': '5432',
|
||||
}
|
||||
}
|
||||
ADMIN_ENABLED = True
|
||||
CERT_FILE = "{{ fullchain_dest }}"
|
||||
KEY_FILE = "{{ privkey_dest }}"
|
||||
MESH_USERNAME = "{{ mesh_user }}"
|
||||
MESH_SITE = "https://{{ mesh }}"
|
||||
37
ansible/roles/trmm_dev/templates/mesh.cfg.j2
Normal file
37
ansible/roles/trmm_dev/templates/mesh.cfg.j2
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"settings": {
|
||||
"Cert": "{{ mesh }}",
|
||||
"WANonly": true,
|
||||
"Minify": 1,
|
||||
"Port": 4430,
|
||||
"AliasPort": 443,
|
||||
"RedirPort": 800,
|
||||
"AllowLoginToken": true,
|
||||
"AllowFraming": true,
|
||||
"AgentPing": 35,
|
||||
"AllowHighQualityDesktop": true,
|
||||
"TlsOffload": "127.0.0.1",
|
||||
"agentCoreDump": false,
|
||||
"Compression": true,
|
||||
"WsCompression": true,
|
||||
"AgentWsCompression": true,
|
||||
"MaxInvalidLogin": { "time": 5, "count": 5, "coolofftime": 30 },
|
||||
"postgres": {
|
||||
"user": "{{ mesh_db_user }}",
|
||||
"password": "{{ mesh_db_passwd }}",
|
||||
"port": "5432",
|
||||
"host": "localhost"
|
||||
}
|
||||
},
|
||||
"domains": {
|
||||
"": {
|
||||
"Title": "Tactical RMM Dev",
|
||||
"Title2": "Tactical RMM Dev",
|
||||
"NewAccounts": false,
|
||||
"CertUrl": "https://{{ mesh }}:443/",
|
||||
"GeoLocation": true,
|
||||
"CookieIpCheck": false,
|
||||
"mstsc": true
|
||||
}
|
||||
}
|
||||
}
|
||||
22
ansible/roles/trmm_dev/templates/mesh.nginx.j2
Normal file
22
ansible/roles/trmm_dev/templates/mesh.nginx.j2
Normal file
@@ -0,0 +1,22 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
proxy_send_timeout 330s;
|
||||
proxy_read_timeout 330s;
|
||||
server_name {{ mesh }};
|
||||
ssl_certificate {{ fullchain_dest }};
|
||||
ssl_certificate_key {{ privkey_dest }};
|
||||
|
||||
ssl_session_cache shared:WEBSSL:10m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:4430/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header X-Forwarded-Host $host:$server_port;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
17
ansible/roles/trmm_dev/templates/mesh.systemd.j2
Normal file
17
ansible/roles/trmm_dev/templates/mesh.systemd.j2
Normal file
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=MeshCentral Server
|
||||
After=network.target postgresql.service nginx.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
LimitNOFILE=1000000
|
||||
ExecStart=/usr/bin/node node_modules/meshcentral
|
||||
Environment=NODE_ENV=production
|
||||
WorkingDirectory={{ mesh_dir }}
|
||||
User={{ user }}
|
||||
Group={{ user }}
|
||||
Restart=always
|
||||
RestartSec=10s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
14
ansible/roles/trmm_dev/templates/nats-api.systemd.j2
Normal file
14
ansible/roles/trmm_dev/templates/nats-api.systemd.j2
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=TacticalRMM Nats Api
|
||||
After=nats.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/nats-api -config {{ backend_dir }}/api/tacticalrmm/nats-api.conf
|
||||
User={{ user }}
|
||||
Group={{ user }}
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
18
ansible/roles/trmm_dev/templates/nats-server.systemd.j2
Normal file
18
ansible/roles/trmm_dev/templates/nats-server.systemd.j2
Normal file
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=NATS Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
PrivateTmp=true
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/nats-server -c {{ backend_dir }}/api/tacticalrmm/nats-rmm.conf
|
||||
ExecReload=/usr/bin/kill -s HUP $MAINPID
|
||||
ExecStop=/usr/bin/kill -s SIGINT $MAINPID
|
||||
User={{ user }}
|
||||
Group={{ user }}
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
LimitNOFILE=1000000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
2
ansible/roles/trmm_dev/templates/nginx.repo.j2
Normal file
2
ansible/roles/trmm_dev/templates/nginx.repo.j2
Normal file
@@ -0,0 +1,2 @@
|
||||
deb https://nginx.org/packages/debian/ {{ ansible_distribution_release }} nginx
|
||||
deb-src https://nginx.org/packages/debian/ {{ ansible_distribution_release }} nginx
|
||||
4
ansible/roles/trmm_dev/templates/quasar.env.j2
Normal file
4
ansible/roles/trmm_dev/templates/quasar.env.j2
Normal file
@@ -0,0 +1,4 @@
|
||||
DEV_URL = "http://{{ api }}:8000"
|
||||
DEV_HOST = "0.0.0.0"
|
||||
DEV_PORT = "8080"
|
||||
USE_HTTPS = false
|
||||
22
ansible/setup_dev.yml.example
Normal file
22
ansible/setup_dev.yml.example
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
- hosts: "{{ target }}"
|
||||
vars:
|
||||
ansible_user: tactical
|
||||
fullchain_src: /path/to/fullchain.pem
|
||||
privkey_src: /path/to/privkey.pem
|
||||
api: "api.example.com"
|
||||
rmm: "rmm.example.com"
|
||||
mesh: "mesh.example.com"
|
||||
github_username: "changeme"
|
||||
github_email: "changeme@example.com"
|
||||
mesh_user: "changeme"
|
||||
mesh_password: "changeme"
|
||||
db_user: "changeme"
|
||||
db_passwd: "changeme"
|
||||
mesh_db_user: "changeme"
|
||||
mesh_db_passwd: "changeme"
|
||||
django_secret: "changeme"
|
||||
django_user: "changeme"
|
||||
django_password: "changeme"
|
||||
roles:
|
||||
- trmm_dev
|
||||
@@ -1,24 +1,15 @@
|
||||
[run]
|
||||
source = .
|
||||
[report]
|
||||
show_missing = True
|
||||
include = *.py
|
||||
omit =
|
||||
tacticalrmm/asgi.py
|
||||
tacticalrmm/wsgi.py
|
||||
manage.py
|
||||
*/__pycache__/*
|
||||
*/env/*
|
||||
*/management/*
|
||||
*/migrations/*
|
||||
*/static/*
|
||||
manage.py
|
||||
*/local_settings.py
|
||||
*/apps.py
|
||||
*/admin.py
|
||||
*/celery.py
|
||||
*/wsgi.py
|
||||
*/settings.py
|
||||
*/baker_recipes.py
|
||||
*/urls.py
|
||||
*/tests.py
|
||||
*/test.py
|
||||
checks/utils.py
|
||||
/usr/local/lib/*
|
||||
**/migrations/*
|
||||
**/test*.py
|
||||
|
||||
[report]
|
||||
show_missing = True
|
||||
|
||||
12
api/tacticalrmm/.flake8
Normal file
12
api/tacticalrmm/.flake8
Normal file
@@ -0,0 +1,12 @@
|
||||
[flake8]
|
||||
ignore = E501,W503,E722,E203
|
||||
exclude =
|
||||
.mypy*
|
||||
.pytest*
|
||||
.git
|
||||
demo_data.py
|
||||
manage.py
|
||||
*/__pycache__/*
|
||||
*/env/*
|
||||
/usr/local/lib/*
|
||||
**/migrations/*
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.contrib import admin
|
||||
from rest_framework.authtoken.admin import TokenAdmin
|
||||
|
||||
from .models import User, Role
|
||||
from .models import Role, User
|
||||
|
||||
admin.site.register(User)
|
||||
TokenAdmin.raw_id_fields = ("user",)
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import uuid
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from accounts.models import User
|
||||
from tacticalrmm.helpers import make_random_password
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Creates the installer user"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
def handle(self, *args, **kwargs): # type: ignore
|
||||
self.stdout.write("Checking if installer user has been created...")
|
||||
if User.objects.filter(is_installer_user=True).exists():
|
||||
self.stdout.write("Installer user already exists")
|
||||
return
|
||||
|
||||
User.objects.create_user( # type: ignore
|
||||
User.objects.create_user(
|
||||
username=uuid.uuid4().hex,
|
||||
is_installer_user=True,
|
||||
password=User.objects.make_random_password(60), # type: ignore
|
||||
password=make_random_password(len=60),
|
||||
block_dashboard_login=True,
|
||||
)
|
||||
self.stdout.write("Installer user has been created")
|
||||
|
||||
@@ -6,7 +6,7 @@ from knox.models import AuthToken
|
||||
class Command(BaseCommand):
|
||||
help = "Deletes all knox web tokens"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
def handle(self, *args, **kwargs): # type: ignore
|
||||
# only delete web tokens, not any generated by the installer or deployments
|
||||
dont_delete = djangotime.now() + djangotime.timedelta(hours=23)
|
||||
tokens = AuthToken.objects.exclude(deploytokens__isnull=False).filter(
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pyotp
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from accounts.models import User
|
||||
from tacticalrmm.util_settings import get_webdomain
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -21,28 +22,13 @@ class Command(BaseCommand):
|
||||
self.stdout.write(self.style.ERROR(f"User {username} doesn't exist"))
|
||||
return
|
||||
|
||||
domain = "Tactical RMM"
|
||||
nginx = "/etc/nginx/sites-available/frontend.conf"
|
||||
found = None
|
||||
if os.path.exists(nginx):
|
||||
try:
|
||||
with open(nginx, "r") as f:
|
||||
for line in f:
|
||||
if "server_name" in line:
|
||||
found = line
|
||||
break
|
||||
|
||||
if found:
|
||||
rep = found.replace("server_name", "").replace(";", "")
|
||||
domain = "".join(rep.split())
|
||||
except:
|
||||
pass
|
||||
|
||||
code = pyotp.random_base32()
|
||||
user.totp_key = code
|
||||
user.save(update_fields=["totp_key"])
|
||||
|
||||
url = pyotp.totp.TOTP(code).provisioning_uri(username, issuer_name=domain)
|
||||
url = pyotp.totp.TOTP(code).provisioning_uri(
|
||||
username, issuer_name=get_webdomain(settings.CORS_ORIGIN_WHITELIST[0])
|
||||
)
|
||||
subprocess.run(f'qr "{url}"', shell=True)
|
||||
self.stdout.write(
|
||||
self.style.WARNING("Scan the barcode above with your authenticator app")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from getpass import getpass
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from accounts.models import User
|
||||
@@ -17,7 +19,13 @@ class Command(BaseCommand):
|
||||
self.stdout.write(self.style.ERROR(f"User {username} doesn't exist"))
|
||||
return
|
||||
|
||||
passwd = input("Enter new password: ")
|
||||
user.set_password(passwd)
|
||||
pass1, pass2 = "foo", "bar"
|
||||
while pass1 != pass2:
|
||||
pass1 = getpass()
|
||||
pass2 = getpass(prompt="Confirm Password:")
|
||||
if pass1 != pass2:
|
||||
self.stdout.write(self.style.ERROR("Passwords don't match"))
|
||||
|
||||
user.set_password(pass1)
|
||||
user.save()
|
||||
self.stdout.write(self.style.SUCCESS(f"Password for {username} was reset!"))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Generated by Django 3.2.1 on 2021-05-11 02:33
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Generated by Django 3.2.6 on 2021-09-03 00:54
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Generated by Django 3.2.6 on 2021-10-10 02:49
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
18
api/tacticalrmm/accounts/migrations/0031_user_date_format.py
Normal file
18
api/tacticalrmm/accounts/migrations/0031_user_date_format.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.12 on 2022-04-02 15:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0030_auto_20211104_0221'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='date_format',
|
||||
field=models.CharField(blank=True, max_length=30, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 4.2.1 on 2023-05-17 07:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("accounts", "0031_user_date_format"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="default_agent_tbl_tab",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("server", "Servers"),
|
||||
("workstation", "Workstations"),
|
||||
("mixed", "Mixed"),
|
||||
],
|
||||
default="mixed",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,32 @@
|
||||
# Generated by Django 4.2.1 on 2023-05-23 04:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("accounts", "0032_alter_user_default_agent_tbl_tab"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="dash_info_color",
|
||||
field=models.CharField(default="info", max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="dash_negative_color",
|
||||
field=models.CharField(default="negative", max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="dash_positive_color",
|
||||
field=models.CharField(default="positive", max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="dash_warning_color",
|
||||
field=models.CharField(default="warning", max_length=255),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.1.9 on 2023-05-26 23:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("accounts", "0033_user_dash_info_color_user_dash_negative_color_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="role",
|
||||
name="can_send_wol",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 4.2.5 on 2023-10-08 22:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("accounts", "0034_role_can_send_wol"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="role",
|
||||
name="can_manage_reports",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="role",
|
||||
name="can_view_reports",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 4.2.7 on 2023-11-09 19:57
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("accounts", "0035_role_can_manage_reports_role_can_view_reports"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="role",
|
||||
name="can_ping_agents",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.2.13 on 2024-06-28 20:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("accounts", "0036_remove_role_can_ping_agents"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="role",
|
||||
name="can_run_server_scripts",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="role",
|
||||
name="can_use_webterm",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.2.16 on 2024-10-06 05:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("accounts", "0037_role_can_run_server_scripts_role_can_use_webterm"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="role",
|
||||
name="can_edit_global_keystore",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="role",
|
||||
name="can_view_global_keystore",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -1,26 +1,18 @@
|
||||
from typing import Optional
|
||||
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.db.models.fields import CharField, DateTimeField
|
||||
|
||||
from logs.models import BaseAuditModel
|
||||
|
||||
AGENT_DBLCLICK_CHOICES = [
|
||||
("editagent", "Edit Agent"),
|
||||
("takecontrol", "Take Control"),
|
||||
("remotebg", "Remote Background"),
|
||||
("urlaction", "URL Action"),
|
||||
]
|
||||
|
||||
AGENT_TBL_TAB_CHOICES = [
|
||||
("server", "Servers"),
|
||||
("workstation", "Workstations"),
|
||||
("mixed", "Mixed"),
|
||||
]
|
||||
|
||||
CLIENT_TREE_SORT_CHOICES = [
|
||||
("alphafail", "Move failing clients to the top"),
|
||||
("alpha", "Sort alphabetically"),
|
||||
]
|
||||
from tacticalrmm.constants import (
|
||||
ROLE_CACHE_PREFIX,
|
||||
AgentDblClick,
|
||||
AgentTableTabs,
|
||||
ClientTreeSort,
|
||||
)
|
||||
|
||||
|
||||
class User(AbstractUser, BaseAuditModel):
|
||||
@@ -29,8 +21,8 @@ class User(AbstractUser, BaseAuditModel):
|
||||
totp_key = models.CharField(max_length=50, null=True, blank=True)
|
||||
dark_mode = models.BooleanField(default=True)
|
||||
show_community_scripts = models.BooleanField(default=True)
|
||||
agent_dblclick_action = models.CharField(
|
||||
max_length=50, choices=AGENT_DBLCLICK_CHOICES, default="editagent"
|
||||
agent_dblclick_action: "AgentDblClick" = models.CharField(
|
||||
max_length=50, choices=AgentDblClick.choices, default=AgentDblClick.EDIT_AGENT
|
||||
)
|
||||
url_action = models.ForeignKey(
|
||||
"core.URLAction",
|
||||
@@ -40,15 +32,20 @@ class User(AbstractUser, BaseAuditModel):
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
default_agent_tbl_tab = models.CharField(
|
||||
max_length=50, choices=AGENT_TBL_TAB_CHOICES, default="server"
|
||||
max_length=50, choices=AgentTableTabs.choices, default=AgentTableTabs.MIXED
|
||||
)
|
||||
agents_per_page = models.PositiveIntegerField(default=50) # not currently used
|
||||
client_tree_sort = models.CharField(
|
||||
max_length=50, choices=CLIENT_TREE_SORT_CHOICES, default="alphafail"
|
||||
max_length=50, choices=ClientTreeSort.choices, default=ClientTreeSort.ALPHA_FAIL
|
||||
)
|
||||
client_tree_splitter = models.PositiveIntegerField(default=11)
|
||||
loading_bar_color = models.CharField(max_length=255, default="red")
|
||||
dash_info_color = models.CharField(max_length=255, default="info")
|
||||
dash_positive_color = models.CharField(max_length=255, default="positive")
|
||||
dash_negative_color = models.CharField(max_length=255, default="negative")
|
||||
dash_warning_color = models.CharField(max_length=255, default="warning")
|
||||
clear_search_when_switching = models.BooleanField(default=True)
|
||||
date_format = models.CharField(max_length=30, blank=True, null=True)
|
||||
is_installer_user = models.BooleanField(default=False)
|
||||
last_login_ip = models.GenericIPAddressField(default=None, blank=True, null=True)
|
||||
|
||||
@@ -68,6 +65,19 @@ class User(AbstractUser, BaseAuditModel):
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
|
||||
@property
|
||||
def mesh_user_id(self):
|
||||
return f"user//{self.mesh_username}"
|
||||
|
||||
@property
|
||||
def mesh_username(self):
|
||||
# lower() needed for mesh api
|
||||
return f"{self.username.replace(' ', '').lower()}___{self.pk}"
|
||||
|
||||
@property
|
||||
def is_sso_user(self):
|
||||
return SocialAccount.objects.filter(user_id=self.pk).exists()
|
||||
|
||||
@staticmethod
|
||||
def serialize(user):
|
||||
# serializes the task and returns json
|
||||
@@ -75,6 +85,23 @@ class User(AbstractUser, BaseAuditModel):
|
||||
|
||||
return UserSerializer(user).data
|
||||
|
||||
def get_and_set_role_cache(self) -> "Optional[Role]":
|
||||
role = cache.get(f"{ROLE_CACHE_PREFIX}{self.role}")
|
||||
|
||||
if role and isinstance(role, Role):
|
||||
return role
|
||||
elif not role and not self.role:
|
||||
return None
|
||||
else:
|
||||
models.prefetch_related_objects(
|
||||
[self.role],
|
||||
"can_view_clients",
|
||||
"can_view_sites",
|
||||
)
|
||||
|
||||
cache.set(f"{ROLE_CACHE_PREFIX}{self.role}", self.role, 600)
|
||||
return self.role
|
||||
|
||||
|
||||
class Role(BaseAuditModel):
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
@@ -82,7 +109,6 @@ class Role(BaseAuditModel):
|
||||
|
||||
# agents
|
||||
can_list_agents = models.BooleanField(default=False)
|
||||
can_ping_agents = models.BooleanField(default=False)
|
||||
can_use_mesh = models.BooleanField(default=False)
|
||||
can_uninstall_agents = models.BooleanField(default=False)
|
||||
can_update_agents = models.BooleanField(default=False)
|
||||
@@ -96,6 +122,7 @@ class Role(BaseAuditModel):
|
||||
can_run_bulk = models.BooleanField(default=False)
|
||||
can_recover_agents = models.BooleanField(default=False)
|
||||
can_list_agent_history = models.BooleanField(default=False)
|
||||
can_send_wol = models.BooleanField(default=False)
|
||||
|
||||
# core
|
||||
can_list_notes = models.BooleanField(default=False)
|
||||
@@ -107,6 +134,10 @@ class Role(BaseAuditModel):
|
||||
can_run_urlactions = models.BooleanField(default=False)
|
||||
can_view_customfields = models.BooleanField(default=False)
|
||||
can_manage_customfields = models.BooleanField(default=False)
|
||||
can_run_server_scripts = models.BooleanField(default=False)
|
||||
can_use_webterm = models.BooleanField(default=False)
|
||||
can_view_global_keystore = models.BooleanField(default=False)
|
||||
can_edit_global_keystore = models.BooleanField(default=False)
|
||||
|
||||
# checks
|
||||
can_list_checks = models.BooleanField(default=False)
|
||||
@@ -172,9 +203,18 @@ class Role(BaseAuditModel):
|
||||
can_list_api_keys = models.BooleanField(default=False)
|
||||
can_manage_api_keys = models.BooleanField(default=False)
|
||||
|
||||
# reporting
|
||||
can_view_reports = models.BooleanField(default=False)
|
||||
can_manage_reports = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs) -> None:
|
||||
# delete cache on save
|
||||
cache.delete(f"{ROLE_CACHE_PREFIX}{self.name}")
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def serialize(role):
|
||||
# serializes the agent and returns json
|
||||
|
||||
@@ -1,43 +1,54 @@
|
||||
from rest_framework import permissions
|
||||
|
||||
from tacticalrmm.permissions import _has_perm
|
||||
from tacticalrmm.utils import get_core_settings
|
||||
|
||||
|
||||
class AccountsPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
if r.method == "GET":
|
||||
return _has_perm(r, "can_list_accounts")
|
||||
else:
|
||||
|
||||
# allow users to reset their own password/2fa see issue #686
|
||||
base_path = "/accounts/users/"
|
||||
paths = ["reset/", "reset_totp/"]
|
||||
# allow users to reset their own password/2fa see issue #686
|
||||
base_path = "/accounts/users/"
|
||||
paths = ("reset/", "reset_totp/")
|
||||
|
||||
if r.path in [base_path + i for i in paths]:
|
||||
from accounts.models import User
|
||||
if r.path in [base_path + i for i in paths]:
|
||||
from accounts.models import User
|
||||
|
||||
try:
|
||||
user = User.objects.get(pk=r.data["id"])
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
if user == r.user:
|
||||
return True
|
||||
try:
|
||||
user = User.objects.get(pk=r.data["id"])
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
if user == r.user:
|
||||
return True
|
||||
|
||||
return _has_perm(r, "can_manage_accounts")
|
||||
return _has_perm(r, "can_manage_accounts")
|
||||
|
||||
|
||||
class RolesPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
if r.method == "GET":
|
||||
return _has_perm(r, "can_list_roles")
|
||||
else:
|
||||
return _has_perm(r, "can_manage_roles")
|
||||
|
||||
return _has_perm(r, "can_manage_roles")
|
||||
|
||||
|
||||
class APIKeyPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
if r.method == "GET":
|
||||
return _has_perm(r, "can_list_api_keys")
|
||||
|
||||
return _has_perm(r, "can_manage_api_keys")
|
||||
|
||||
|
||||
class LocalUserPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
settings = get_core_settings()
|
||||
return not settings.block_local_user_logon
|
||||
|
||||
|
||||
class SelfResetSSOPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
return not r.user.is_sso_user
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import pyotp
|
||||
from django.conf import settings
|
||||
from rest_framework.serializers import (
|
||||
ModelSerializer,
|
||||
SerializerMethodField,
|
||||
ReadOnlyField,
|
||||
SerializerMethodField,
|
||||
)
|
||||
|
||||
from .models import APIKey, User, Role
|
||||
from tacticalrmm.util_settings import get_webdomain
|
||||
|
||||
from .models import APIKey, Role, User
|
||||
|
||||
|
||||
class UserUISerializer(ModelSerializer):
|
||||
@@ -20,8 +23,13 @@ class UserUISerializer(ModelSerializer):
|
||||
"client_tree_sort",
|
||||
"client_tree_splitter",
|
||||
"loading_bar_color",
|
||||
"dash_info_color",
|
||||
"dash_positive_color",
|
||||
"dash_negative_color",
|
||||
"dash_warning_color",
|
||||
"clear_search_when_switching",
|
||||
"block_dashboard_login",
|
||||
"date_format",
|
||||
]
|
||||
|
||||
|
||||
@@ -39,11 +47,11 @@ class UserSerializer(ModelSerializer):
|
||||
"last_login_ip",
|
||||
"role",
|
||||
"block_dashboard_login",
|
||||
"date_format",
|
||||
]
|
||||
|
||||
|
||||
class TOTPSetupSerializer(ModelSerializer):
|
||||
|
||||
qr_url = SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
@@ -56,7 +64,7 @@ class TOTPSetupSerializer(ModelSerializer):
|
||||
|
||||
def get_qr_url(self, obj):
|
||||
return pyotp.totp.TOTP(obj.totp_key).provisioning_uri(
|
||||
obj.username, issuer_name="Tactical RMM"
|
||||
obj.username, issuer_name=get_webdomain(settings.CORS_ORIGIN_WHITELIST[0])
|
||||
)
|
||||
|
||||
|
||||
@@ -78,7 +86,6 @@ class RoleAuditSerializer(ModelSerializer):
|
||||
|
||||
|
||||
class APIKeySerializer(ModelSerializer):
|
||||
|
||||
username = ReadOnlyField(source="user.username")
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -2,27 +2,29 @@ from unittest.mock import patch
|
||||
|
||||
from django.test import override_settings
|
||||
from model_bakery import baker, seq
|
||||
from accounts.models import User, APIKey
|
||||
from tacticalrmm.test import TacticalTestCase
|
||||
|
||||
from accounts.models import APIKey, User
|
||||
from accounts.serializers import APIKeySerializer
|
||||
from tacticalrmm.constants import AgentDblClick, AgentTableTabs, ClientTreeSort
|
||||
from tacticalrmm.test import TacticalTestCase
|
||||
|
||||
|
||||
class TestAccounts(TacticalTestCase):
|
||||
def setUp(self):
|
||||
self.client_setup()
|
||||
self.setup_coresettings()
|
||||
self.setup_client()
|
||||
self.bob = User(username="bob")
|
||||
self.bob.set_password("hunter2")
|
||||
self.bob.save()
|
||||
|
||||
def test_check_creds(self):
|
||||
url = "/checkcreds/"
|
||||
url = "/v2/checkcreds/"
|
||||
|
||||
data = {"username": "bob", "password": "hunter2"}
|
||||
r = self.client.post(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertIn("totp", r.data.keys())
|
||||
self.assertEqual(r.data["totp"], "totp not set")
|
||||
self.assertEqual(r.data["totp"], False)
|
||||
|
||||
data = {"username": "bob", "password": "a3asdsa2314"}
|
||||
r = self.client.post(url, data, format="json")
|
||||
@@ -39,7 +41,7 @@ class TestAccounts(TacticalTestCase):
|
||||
data = {"username": "bob", "password": "hunter2"}
|
||||
r = self.client.post(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.data, "ok")
|
||||
self.assertEqual(r.data["totp"], True)
|
||||
|
||||
# test user set to block dashboard logins
|
||||
self.bob.block_dashboard_login = True
|
||||
@@ -49,7 +51,7 @@ class TestAccounts(TacticalTestCase):
|
||||
|
||||
@patch("pyotp.TOTP.verify")
|
||||
def test_login_view(self, mock_verify):
|
||||
url = "/login/"
|
||||
url = "/v2/login/"
|
||||
|
||||
mock_verify.return_value = True
|
||||
data = {"username": "bob", "password": "hunter2", "twofactor": "123456"}
|
||||
@@ -69,17 +71,17 @@ class TestAccounts(TacticalTestCase):
|
||||
self.assertEqual(r.status_code, 400)
|
||||
self.assertIn("non_field_errors", r.data.keys())
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("pyotp.TOTP.verify")
|
||||
def test_debug_login_view(self, mock_verify):
|
||||
url = "/login/"
|
||||
mock_verify.return_value = True
|
||||
# @override_settings(DEBUG=True)
|
||||
# @patch("pyotp.TOTP.verify")
|
||||
# def test_debug_login_view(self, mock_verify):
|
||||
# url = "/login/"
|
||||
# mock_verify.return_value = True
|
||||
|
||||
data = {"username": "bob", "password": "hunter2", "twofactor": "sekret"}
|
||||
r = self.client.post(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertIn("expiry", r.data.keys())
|
||||
self.assertIn("token", r.data.keys())
|
||||
# data = {"username": "bob", "password": "hunter2", "twofactor": "sekret"}
|
||||
# r = self.client.post(url, data, format="json")
|
||||
# self.assertEqual(r.status_code, 200)
|
||||
# self.assertIn("expiry", r.data.keys())
|
||||
# self.assertIn("token", r.data.keys())
|
||||
|
||||
|
||||
class TestGetAddUsers(TacticalTestCase):
|
||||
@@ -196,7 +198,7 @@ class GetUpdateDeleteUser(TacticalTestCase):
|
||||
r = self.client.delete(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
url = f"/accounts/893452/users/"
|
||||
url = "/accounts/893452/users/"
|
||||
r = self.client.delete(url)
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
@@ -283,9 +285,9 @@ class TestUserAction(TacticalTestCase):
|
||||
data = {
|
||||
"dark_mode": True,
|
||||
"show_community_scripts": True,
|
||||
"agent_dblclick_action": "editagent",
|
||||
"default_agent_tbl_tab": "mixed",
|
||||
"client_tree_sort": "alpha",
|
||||
"agent_dblclick_action": AgentDblClick.EDIT_AGENT,
|
||||
"default_agent_tbl_tab": AgentTableTabs.MIXED,
|
||||
"client_tree_sort": ClientTreeSort.ALPHA,
|
||||
"client_tree_splitter": 14,
|
||||
"loading_bar_color": "green",
|
||||
"clear_search_when_switching": False,
|
||||
@@ -296,6 +298,27 @@ class TestUserAction(TacticalTestCase):
|
||||
self.check_not_authenticated("patch", url)
|
||||
|
||||
|
||||
class TestUserReset(TacticalTestCase):
|
||||
def setUp(self):
|
||||
self.authenticate()
|
||||
self.setup_coresettings()
|
||||
|
||||
def test_reset_pw(self):
|
||||
url = "/accounts/resetpw/"
|
||||
data = {"password": "superSekret123456"}
|
||||
r = self.client.put(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.check_not_authenticated("put", url)
|
||||
|
||||
def test_reset_2fa(self):
|
||||
url = "/accounts/reset2fa/"
|
||||
r = self.client.put(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.check_not_authenticated("put", url)
|
||||
|
||||
|
||||
class TestAPIKeyViews(TacticalTestCase):
|
||||
def setUp(self):
|
||||
self.setup_coresettings()
|
||||
@@ -308,7 +331,7 @@ class TestAPIKeyViews(TacticalTestCase):
|
||||
serializer = APIKeySerializer(apikeys, many=True)
|
||||
resp = self.client.get(url, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(serializer.data, resp.data) # type: ignore
|
||||
self.assertEqual(serializer.data, resp.data)
|
||||
|
||||
self.check_not_authenticated("get", url)
|
||||
|
||||
@@ -331,14 +354,14 @@ class TestAPIKeyViews(TacticalTestCase):
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
|
||||
apikey = baker.make("accounts.APIKey", name="Test")
|
||||
url = f"/accounts/apikeys/{apikey.pk}/" # type: ignore
|
||||
url = f"/accounts/apikeys/{apikey.pk}/"
|
||||
|
||||
data = {"name": "New Name"} # type: ignore
|
||||
data = {"name": "New Name"}
|
||||
|
||||
resp = self.client.put(url, data, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
apikey = APIKey.objects.get(pk=apikey.pk) # type: ignore
|
||||
self.assertEquals(apikey.name, "New Name")
|
||||
apikey = APIKey.objects.get(pk=apikey.pk)
|
||||
self.assertEqual(apikey.name, "New Name")
|
||||
|
||||
self.check_not_authenticated("put", url)
|
||||
|
||||
@@ -349,11 +372,11 @@ class TestAPIKeyViews(TacticalTestCase):
|
||||
|
||||
# test delete api key
|
||||
apikey = baker.make("accounts.APIKey")
|
||||
url = f"/accounts/apikeys/{apikey.pk}/" # type: ignore
|
||||
url = f"/accounts/apikeys/{apikey.pk}/"
|
||||
resp = self.client.delete(url, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
self.assertFalse(APIKey.objects.filter(pk=apikey.pk).exists()) # type: ignore
|
||||
self.assertFalse(APIKey.objects.filter(pk=apikey.pk).exists())
|
||||
|
||||
self.check_not_authenticated("delete", url)
|
||||
|
||||
@@ -382,7 +405,7 @@ class TestTOTPSetup(TacticalTestCase):
|
||||
|
||||
r = self.client.post(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.data, "totp token already set")
|
||||
self.assertEqual(r.data, False)
|
||||
|
||||
|
||||
class TestAPIAuthentication(TacticalTestCase):
|
||||
@@ -393,7 +416,7 @@ class TestAPIAuthentication(TacticalTestCase):
|
||||
name="Test Token", key="123456", user=self.user
|
||||
)
|
||||
|
||||
self.client_setup()
|
||||
self.setup_client()
|
||||
|
||||
def test_api_auth(self):
|
||||
url = "/clients/"
|
||||
|
||||
@@ -5,6 +5,10 @@ from . import views
|
||||
urlpatterns = [
|
||||
path("users/", views.GetAddUsers.as_view()),
|
||||
path("<int:pk>/users/", views.GetUpdateDeleteUser.as_view()),
|
||||
path("sessions/<str:pk>/", views.DeleteActiveLoginSession.as_view()),
|
||||
path(
|
||||
"users/<int:pk>/sessions/", views.GetDeleteActiveLoginSessionsPerUser.as_view()
|
||||
),
|
||||
path("users/reset/", views.UserActions.as_view()),
|
||||
path("users/reset_totp/", views.UserActions.as_view()),
|
||||
path("users/setup_totp/", views.TOTPSetup.as_view()),
|
||||
@@ -13,4 +17,6 @@ urlpatterns = [
|
||||
path("roles/<int:pk>/", views.GetUpdateDeleteRole.as_view()),
|
||||
path("apikeys/", views.GetAddAPIKeys.as_view()),
|
||||
path("apikeys/<int:pk>/", views.GetUpdateDeleteAPIKey.as_view()),
|
||||
path("resetpw/", views.ResetPass.as_view()),
|
||||
path("reset2fa/", views.Reset2FA.as_view()),
|
||||
]
|
||||
|
||||
24
api/tacticalrmm/accounts/utils.py
Normal file
24
api/tacticalrmm/accounts/utils.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.http import HttpRequest
|
||||
|
||||
from accounts.models import User
|
||||
|
||||
|
||||
def is_root_user(*, request: "HttpRequest", user: "User") -> bool:
|
||||
root = (
|
||||
hasattr(settings, "ROOT_USER")
|
||||
and request.user != user
|
||||
and user.username == settings.ROOT_USER
|
||||
)
|
||||
demo = (
|
||||
getattr(settings, "DEMO", False) and request.user.username == settings.ROOT_USER
|
||||
)
|
||||
return root or demo
|
||||
|
||||
|
||||
def is_superuser(user: "User") -> bool:
|
||||
return user.role and getattr(user.role, "is_superuser")
|
||||
@@ -1,20 +1,39 @@
|
||||
import datetime
|
||||
|
||||
import pyotp
|
||||
from allauth.socialaccount.models import SocialAccount, SocialApp
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import login
|
||||
from django.db import IntegrityError
|
||||
from django.shortcuts import get_object_or_404
|
||||
from ipware import get_client_ip
|
||||
from django.utils import timezone as djangotime
|
||||
from knox.models import AuthToken
|
||||
from knox.views import LoginView as KnoxLoginView
|
||||
from logs.models import AuditLog
|
||||
from rest_framework import status
|
||||
from python_ipware import IpWare
|
||||
from rest_framework.authtoken.serializers import AuthTokenSerializer
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import (
|
||||
ModelSerializer,
|
||||
ReadOnlyField,
|
||||
SerializerMethodField,
|
||||
)
|
||||
from rest_framework.views import APIView
|
||||
from tacticalrmm.utils import notify_error
|
||||
|
||||
from accounts.utils import is_root_user
|
||||
from core.tasks import sync_mesh_perms_task
|
||||
from logs.models import AuditLog
|
||||
from tacticalrmm.helpers import notify_error
|
||||
from tacticalrmm.utils import get_core_settings
|
||||
|
||||
from .models import APIKey, Role, User
|
||||
from .permissions import APIKeyPerms, AccountsPerms, RolesPerms
|
||||
from .permissions import (
|
||||
AccountsPerms,
|
||||
APIKeyPerms,
|
||||
LocalUserPerms,
|
||||
RolesPerms,
|
||||
SelfResetSSOPerms,
|
||||
)
|
||||
from .serializers import (
|
||||
APIKeySerializer,
|
||||
RoleSerializer,
|
||||
@@ -24,20 +43,14 @@ from .serializers import (
|
||||
)
|
||||
|
||||
|
||||
def _is_root_user(request, user) -> bool:
|
||||
return (
|
||||
hasattr(settings, "ROOT_USER")
|
||||
and request.user != user
|
||||
and user.username == settings.ROOT_USER
|
||||
)
|
||||
|
||||
|
||||
class CheckCreds(KnoxLoginView):
|
||||
|
||||
class CheckCredsV2(KnoxLoginView):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def post(self, request, format=None):
|
||||
# restrict time on tokens issued by this view to 3 min
|
||||
def get_token_ttl(self):
|
||||
return datetime.timedelta(seconds=180)
|
||||
|
||||
def post(self, request, format=None):
|
||||
# check credentials
|
||||
serializer = AuthTokenSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
@@ -48,21 +61,25 @@ class CheckCreds(KnoxLoginView):
|
||||
|
||||
user = serializer.validated_data["user"]
|
||||
|
||||
if user.block_dashboard_login:
|
||||
if user.block_dashboard_login or user.is_sso_user:
|
||||
return notify_error("Bad credentials")
|
||||
|
||||
# block local logon if configured
|
||||
core_settings = get_core_settings()
|
||||
if not user.is_superuser and core_settings.block_local_user_logon:
|
||||
return notify_error("Bad credentials")
|
||||
|
||||
# if totp token not set modify response to notify frontend
|
||||
if not user.totp_key:
|
||||
login(request, user)
|
||||
response = super(CheckCreds, self).post(request, format=None)
|
||||
response.data["totp"] = "totp not set"
|
||||
response = super().post(request, format=None)
|
||||
response.data["totp"] = False
|
||||
return response
|
||||
|
||||
return Response("ok")
|
||||
return Response({"totp": True})
|
||||
|
||||
|
||||
class LoginView(KnoxLoginView):
|
||||
|
||||
class LoginViewV2(KnoxLoginView):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def post(self, request, format=None):
|
||||
@@ -75,11 +92,21 @@ class LoginView(KnoxLoginView):
|
||||
if user.block_dashboard_login:
|
||||
return notify_error("Bad credentials")
|
||||
|
||||
# block local logon if configured
|
||||
core_settings = get_core_settings()
|
||||
if not user.is_superuser and core_settings.block_local_user_logon:
|
||||
return notify_error("Bad credentials")
|
||||
|
||||
if user.is_sso_user:
|
||||
return notify_error("Bad credentials")
|
||||
|
||||
token = request.data["twofactor"]
|
||||
totp = pyotp.TOTP(user.totp_key)
|
||||
|
||||
if settings.DEBUG and token == "sekret":
|
||||
valid = True
|
||||
elif getattr(settings, "DEMO", False):
|
||||
valid = True
|
||||
elif totp.verify(token, valid_window=10):
|
||||
valid = True
|
||||
|
||||
@@ -87,14 +114,20 @@ class LoginView(KnoxLoginView):
|
||||
login(request, user)
|
||||
|
||||
# save ip information
|
||||
client_ip, is_routable = get_client_ip(request)
|
||||
user.last_login_ip = client_ip
|
||||
user.save()
|
||||
ipw = IpWare()
|
||||
client_ip, _ = ipw.get_client_ip(request.META)
|
||||
if client_ip:
|
||||
user.last_login_ip = str(client_ip)
|
||||
user.save()
|
||||
|
||||
AuditLog.audit_user_login_successful(
|
||||
request.data["username"], debug_info={"ip": request._client_ip}
|
||||
)
|
||||
return super(LoginView, self).post(request, format=None)
|
||||
response = super().post(request, format=None)
|
||||
response.data["username"] = request.user.username
|
||||
response.data["name"] = None
|
||||
|
||||
return Response(response.data)
|
||||
else:
|
||||
AuditLog.audit_user_failed_twofactor(
|
||||
request.data["username"], debug_info={"ip": request._client_ip}
|
||||
@@ -102,9 +135,100 @@ class LoginView(KnoxLoginView):
|
||||
return notify_error("Bad credentials")
|
||||
|
||||
|
||||
class GetDeleteActiveLoginSessionsPerUser(APIView):
|
||||
permission_classes = [IsAuthenticated, AccountsPerms]
|
||||
|
||||
class TokenSerializer(ModelSerializer):
|
||||
user = ReadOnlyField(source="user.username")
|
||||
|
||||
class Meta:
|
||||
model = AuthToken
|
||||
fields = (
|
||||
"digest",
|
||||
"user",
|
||||
"created",
|
||||
"expiry",
|
||||
)
|
||||
|
||||
def get(self, request, pk):
|
||||
tokens = get_object_or_404(User, pk=pk).auth_token_set.filter(
|
||||
expiry__gt=djangotime.now()
|
||||
)
|
||||
|
||||
return Response(self.TokenSerializer(tokens, many=True).data)
|
||||
|
||||
def delete(self, request, pk):
|
||||
tokens = get_object_or_404(User, pk=pk).auth_token_set.filter(
|
||||
expiry__gt=djangotime.now()
|
||||
)
|
||||
|
||||
tokens.delete()
|
||||
return Response("ok")
|
||||
|
||||
|
||||
class DeleteActiveLoginSession(APIView):
|
||||
permission_classes = [IsAuthenticated, AccountsPerms]
|
||||
|
||||
def delete(self, request, pk):
|
||||
token = get_object_or_404(AuthToken, digest=pk)
|
||||
|
||||
token.delete()
|
||||
|
||||
return Response("ok")
|
||||
|
||||
|
||||
class GetAddUsers(APIView):
|
||||
permission_classes = [IsAuthenticated, AccountsPerms]
|
||||
|
||||
class UserSerializerSSO(ModelSerializer):
|
||||
social_accounts = SerializerMethodField()
|
||||
|
||||
def get_social_accounts(self, obj):
|
||||
accounts = SocialAccount.objects.filter(user_id=obj.pk)
|
||||
|
||||
if accounts:
|
||||
social_accounts = []
|
||||
for account in accounts:
|
||||
try:
|
||||
provider_account = account.get_provider_account()
|
||||
display = provider_account.to_str()
|
||||
except SocialApp.DoesNotExist:
|
||||
display = "Orphaned Provider"
|
||||
except Exception:
|
||||
display = "Unknown"
|
||||
|
||||
social_accounts.append(
|
||||
{
|
||||
"uid": account.uid,
|
||||
"provider": account.provider,
|
||||
"display": display,
|
||||
"last_login": account.last_login,
|
||||
"date_joined": account.date_joined,
|
||||
"extra_data": account.extra_data,
|
||||
}
|
||||
)
|
||||
|
||||
return social_accounts
|
||||
|
||||
return []
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
"id",
|
||||
"username",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"is_active",
|
||||
"last_login",
|
||||
"last_login_ip",
|
||||
"role",
|
||||
"block_dashboard_login",
|
||||
"date_format",
|
||||
"social_accounts",
|
||||
]
|
||||
|
||||
def get(self, request):
|
||||
search = request.GET.get("search", None)
|
||||
|
||||
@@ -115,7 +239,7 @@ class GetAddUsers(APIView):
|
||||
else:
|
||||
users = User.objects.filter(agent=None, is_installer_user=False)
|
||||
|
||||
return Response(UserSerializer(users, many=True).data)
|
||||
return Response(self.UserSerializerSSO(users, many=True).data)
|
||||
|
||||
def post(self, request):
|
||||
# add new user
|
||||
@@ -139,6 +263,7 @@ class GetAddUsers(APIView):
|
||||
user.role = role
|
||||
|
||||
user.save()
|
||||
sync_mesh_perms_task.delay()
|
||||
return Response(user.username)
|
||||
|
||||
|
||||
@@ -153,31 +278,33 @@ class GetUpdateDeleteUser(APIView):
|
||||
def put(self, request, pk):
|
||||
user = get_object_or_404(User, pk=pk)
|
||||
|
||||
if _is_root_user(request, user):
|
||||
if is_root_user(request=request, user=user):
|
||||
return notify_error("The root user cannot be modified from the UI")
|
||||
|
||||
serializer = UserSerializer(instance=user, data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
sync_mesh_perms_task.delay()
|
||||
|
||||
return Response("ok")
|
||||
|
||||
def delete(self, request, pk):
|
||||
user = get_object_or_404(User, pk=pk)
|
||||
if _is_root_user(request, user):
|
||||
if is_root_user(request=request, user=user):
|
||||
return notify_error("The root user cannot be deleted from the UI")
|
||||
|
||||
user.delete()
|
||||
|
||||
sync_mesh_perms_task.delay()
|
||||
return Response("ok")
|
||||
|
||||
|
||||
class UserActions(APIView):
|
||||
permission_classes = [IsAuthenticated, AccountsPerms]
|
||||
permission_classes = [IsAuthenticated, AccountsPerms, LocalUserPerms]
|
||||
|
||||
# reset password
|
||||
def post(self, request):
|
||||
user = get_object_or_404(User, pk=request.data["id"])
|
||||
if _is_root_user(request, user):
|
||||
if is_root_user(request=request, user=user):
|
||||
return notify_error("The root user cannot be modified from the UI")
|
||||
|
||||
user.set_password(request.data["password"])
|
||||
@@ -188,7 +315,7 @@ class UserActions(APIView):
|
||||
# reset two factor token
|
||||
def put(self, request):
|
||||
user = get_object_or_404(User, pk=request.data["id"])
|
||||
if _is_root_user(request, user):
|
||||
if is_root_user(request=request, user=user):
|
||||
return notify_error("The root user cannot be modified from the UI")
|
||||
|
||||
user.totp_key = ""
|
||||
@@ -200,10 +327,8 @@ class UserActions(APIView):
|
||||
|
||||
|
||||
class TOTPSetup(APIView):
|
||||
|
||||
# totp setup
|
||||
def post(self, request):
|
||||
|
||||
user = request.user
|
||||
if not user.totp_key:
|
||||
code = pyotp.random_base32()
|
||||
@@ -211,7 +336,7 @@ class TOTPSetup(APIView):
|
||||
user.save(update_fields=["totp_key"])
|
||||
return Response(TOTPSetupSerializer(user).data)
|
||||
|
||||
return Response("totp token already set")
|
||||
return Response(False)
|
||||
|
||||
|
||||
class UserUI(APIView):
|
||||
@@ -250,11 +375,13 @@ class GetUpdateDeleteRole(APIView):
|
||||
serializer = RoleSerializer(instance=role, data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
sync_mesh_perms_task.delay()
|
||||
return Response("Role was edited")
|
||||
|
||||
def delete(self, request, pk):
|
||||
role = get_object_or_404(Role, pk=pk)
|
||||
role.delete()
|
||||
sync_mesh_perms_task.delay()
|
||||
return Response("Role was removed")
|
||||
|
||||
|
||||
@@ -272,7 +399,7 @@ class GetAddAPIKeys(APIView):
|
||||
request.data["key"] = get_random_string(length=32).upper()
|
||||
serializer = APIKeySerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
obj = serializer.save()
|
||||
serializer.save()
|
||||
return Response("The API Key was added")
|
||||
|
||||
|
||||
@@ -295,3 +422,23 @@ class GetUpdateDeleteAPIKey(APIView):
|
||||
apikey = get_object_or_404(APIKey, pk=pk)
|
||||
apikey.delete()
|
||||
return Response("The API Key was deleted")
|
||||
|
||||
|
||||
class ResetPass(APIView):
|
||||
permission_classes = [IsAuthenticated, SelfResetSSOPerms]
|
||||
|
||||
def put(self, request):
|
||||
user = request.user
|
||||
user.set_password(request.data["password"])
|
||||
user.save()
|
||||
return Response("Password was reset.")
|
||||
|
||||
|
||||
class Reset2FA(APIView):
|
||||
permission_classes = [IsAuthenticated, SelfResetSSOPerms]
|
||||
|
||||
def put(self, request):
|
||||
user = request.user
|
||||
user.totp_key = ""
|
||||
user.save()
|
||||
return Response("2FA was reset. Log out and back in to setup.")
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Agent, AgentCustomField, Note, RecoveryAction, AgentHistory
|
||||
from .models import Agent, AgentCustomField, AgentHistory, Note
|
||||
|
||||
admin.site.register(Agent)
|
||||
admin.site.register(RecoveryAction)
|
||||
admin.site.register(Note)
|
||||
admin.site.register(AgentCustomField)
|
||||
admin.site.register(AgentHistory)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import secrets
|
||||
import string
|
||||
from itertools import cycle
|
||||
|
||||
@@ -8,10 +8,11 @@ from django.conf import settings
|
||||
from django.utils import timezone as djangotime
|
||||
from model_bakery.recipe import Recipe, foreign_key, seq
|
||||
|
||||
from tacticalrmm.constants import AgentMonType, AgentPlat
|
||||
|
||||
def generate_agent_id(hostname):
|
||||
rand = "".join(random.choice(string.ascii_letters) for _ in range(35))
|
||||
return f"{rand}-{hostname}"
|
||||
|
||||
def generate_agent_id() -> str:
|
||||
return "".join(secrets.choice(string.ascii_letters) for i in range(39))
|
||||
|
||||
|
||||
site = Recipe("clients.Site")
|
||||
@@ -24,25 +25,34 @@ def get_wmi_data():
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def get_win_svcs():
|
||||
svcs = settings.BASE_DIR.joinpath("tacticalrmm/test_data/winsvcs.json")
|
||||
with open(svcs) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
agent = Recipe(
|
||||
"agents.Agent",
|
||||
site=foreign_key(site),
|
||||
hostname="DESKTOP-TEST123",
|
||||
version="1.3.0",
|
||||
monitoring_type=cycle(["workstation", "server"]),
|
||||
agent_id=seq(generate_agent_id("DESKTOP-TEST123")),
|
||||
monitoring_type=cycle(AgentMonType.values),
|
||||
agent_id=seq(generate_agent_id()),
|
||||
last_seen=djangotime.now() - djangotime.timedelta(days=5),
|
||||
plat=AgentPlat.WINDOWS,
|
||||
)
|
||||
|
||||
server_agent = agent.extend(
|
||||
monitoring_type="server",
|
||||
monitoring_type=AgentMonType.SERVER,
|
||||
)
|
||||
|
||||
workstation_agent = agent.extend(
|
||||
monitoring_type="workstation",
|
||||
monitoring_type=AgentMonType.WORKSTATION,
|
||||
)
|
||||
|
||||
online_agent = agent.extend(last_seen=djangotime.now())
|
||||
online_agent = agent.extend(
|
||||
last_seen=djangotime.now(), services=get_win_svcs(), wmi_detail=get_wmi_data()
|
||||
)
|
||||
|
||||
offline_agent = agent.extend(
|
||||
last_seen=djangotime.now() - djangotime.timedelta(minutes=7)
|
||||
@@ -77,4 +87,4 @@ agent_with_services = agent.extend(
|
||||
],
|
||||
)
|
||||
|
||||
agent_with_wmi = agent.extend(wmi=get_wmi_data())
|
||||
agent_with_wmi = agent.extend(wmi_detail=get_wmi_data())
|
||||
|
||||
82
api/tacticalrmm/agents/consumers.py
Normal file
82
api/tacticalrmm/agents/consumers.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from agents.models import Agent, AgentHistory
|
||||
from channels.db import database_sync_to_async
|
||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.shortcuts import get_object_or_404
|
||||
from tacticalrmm.constants import AGENT_DEFER, AgentHistoryType
|
||||
from tacticalrmm.permissions import _has_perm_on_agent
|
||||
|
||||
|
||||
class SendCMD(AsyncJsonWebsocketConsumer):
|
||||
async def connect(self):
|
||||
self.user = self.scope["user"]
|
||||
|
||||
if isinstance(self.user, AnonymousUser):
|
||||
await self.close()
|
||||
|
||||
await self.accept()
|
||||
|
||||
async def receive_json(self, payload, **kwargs):
|
||||
auth = await self.has_perm(payload["agent_id"])
|
||||
if not auth:
|
||||
await self.send_json(
|
||||
{"ret": "You do not have permission to perform this action."}
|
||||
)
|
||||
return
|
||||
|
||||
agent = await self.get_agent(payload["agent_id"])
|
||||
timeout = int(payload["timeout"])
|
||||
if payload["shell"] == "custom" and payload["custom_shell"]:
|
||||
shell = payload["custom_shell"]
|
||||
else:
|
||||
shell = payload["shell"]
|
||||
|
||||
hist_pk = await self.get_history_id(agent, payload["cmd"])
|
||||
|
||||
data = {
|
||||
"func": "rawcmd",
|
||||
"timeout": timeout,
|
||||
"payload": {
|
||||
"command": payload["cmd"],
|
||||
"shell": shell,
|
||||
},
|
||||
"id": hist_pk,
|
||||
}
|
||||
|
||||
ret = await agent.nats_cmd(data, timeout=timeout + 2)
|
||||
await self.send_json({"ret": ret})
|
||||
|
||||
async def disconnect(self, _):
|
||||
pass
|
||||
|
||||
def _has_perm(self, perm: str) -> bool:
|
||||
if self.user.is_superuser or (
|
||||
self.user.role and getattr(self.user.role, "is_superuser")
|
||||
):
|
||||
return True
|
||||
|
||||
# make sure non-superusers with empty roles aren't permitted
|
||||
elif not self.user.role:
|
||||
return False
|
||||
|
||||
return self.user.role and getattr(self.user.role, perm)
|
||||
|
||||
@database_sync_to_async # type: ignore
|
||||
def get_agent(self, agent_id: str) -> "Agent":
|
||||
return get_object_or_404(Agent.objects.defer(*AGENT_DEFER), agent_id=agent_id)
|
||||
|
||||
@database_sync_to_async # type: ignore
|
||||
def get_history_id(self, agent: "Agent", cmd: str) -> int:
|
||||
hist = AgentHistory.objects.create(
|
||||
agent=agent,
|
||||
type=AgentHistoryType.CMD_RUN,
|
||||
command=cmd,
|
||||
username=self.user.username[:50],
|
||||
)
|
||||
return hist.pk
|
||||
|
||||
@database_sync_to_async # type: ignore
|
||||
def has_perm(self, agent_id: str) -> bool:
|
||||
return self._has_perm("can_send_cmd") and _has_perm_on_agent(
|
||||
self.user, agent_id
|
||||
)
|
||||
112
api/tacticalrmm/agents/management/commands/bulk_delete_agents.py
Normal file
112
api/tacticalrmm/agents/management/commands/bulk_delete_agents.py
Normal file
@@ -0,0 +1,112 @@
|
||||
import asyncio
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone as djangotime
|
||||
from packaging import version as pyver
|
||||
|
||||
from agents.models import Agent
|
||||
from tacticalrmm.constants import AGENT_DEFER
|
||||
from tacticalrmm.utils import reload_nats
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Delete multiple agents based on criteria"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--days",
|
||||
type=int,
|
||||
help="Delete agents that have not checked in for this many days",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--agentver",
|
||||
type=str,
|
||||
help="Delete agents that equal to or less than this version",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--site",
|
||||
type=str,
|
||||
help="Delete agents that belong to the specified site",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--client",
|
||||
type=str,
|
||||
help="Delete agents that belong to the specified client",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--hostname",
|
||||
type=str,
|
||||
help="Delete agents with hostname starting with argument",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--delete",
|
||||
action="store_true",
|
||||
help="This will delete agents",
|
||||
)
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
days = kwargs["days"]
|
||||
agentver = kwargs["agentver"]
|
||||
site = kwargs["site"]
|
||||
client = kwargs["client"]
|
||||
hostname = kwargs["hostname"]
|
||||
delete = kwargs["delete"]
|
||||
|
||||
if not days and not agentver and not site and not client and not hostname:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
"Must have at least one parameter: days, agentver, site, client or hostname"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
agents = Agent.objects.select_related("site__client").defer(*AGENT_DEFER)
|
||||
|
||||
if days:
|
||||
overdue = djangotime.now() - djangotime.timedelta(days=days)
|
||||
agents = agents.filter(last_seen__lt=overdue)
|
||||
|
||||
if site:
|
||||
agents = agents.filter(site__name=site)
|
||||
|
||||
if client:
|
||||
agents = agents.filter(site__client__name=client)
|
||||
|
||||
if hostname:
|
||||
agents = agents.filter(hostname__istartswith=hostname)
|
||||
|
||||
if agentver:
|
||||
agents = [
|
||||
i for i in agents if pyver.parse(i.version) <= pyver.parse(agentver)
|
||||
]
|
||||
|
||||
if len(agents) == 0:
|
||||
self.stdout.write(self.style.ERROR("No agents matched"))
|
||||
return
|
||||
|
||||
deleted_count = 0
|
||||
for agent in agents:
|
||||
s = f"{agent.hostname} | Version {agent.version} | Last Seen {agent.last_seen} | {agent.client} > {agent.site}"
|
||||
if delete:
|
||||
s = "Deleting " + s
|
||||
self.stdout.write(self.style.SUCCESS(s))
|
||||
asyncio.run(agent.nats_cmd({"func": "uninstall"}, wait=False))
|
||||
try:
|
||||
agent.delete()
|
||||
except Exception as e:
|
||||
err = f"Failed to delete agent {agent.hostname}: {e}"
|
||||
self.stdout.write(self.style.ERROR(err))
|
||||
else:
|
||||
deleted_count += 1
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING(s))
|
||||
|
||||
if delete:
|
||||
reload_nats()
|
||||
self.stdout.write(self.style.SUCCESS(f"Deleted {deleted_count} agents"))
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
"The above agents would be deleted. Run again with --delete to actually delete them."
|
||||
)
|
||||
)
|
||||
31
api/tacticalrmm/agents/management/commands/demo_cron.py
Normal file
31
api/tacticalrmm/agents/management/commands/demo_cron.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# import datetime as dt
|
||||
import random
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone as djangotime
|
||||
|
||||
from agents.models import Agent
|
||||
from core.tasks import cache_db_fields_task
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "stuff for demo site in cron"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
random_dates = []
|
||||
now = djangotime.now()
|
||||
|
||||
for _ in range(20):
|
||||
rand = now - djangotime.timedelta(minutes=random.randint(1, 2))
|
||||
random_dates.append(rand)
|
||||
|
||||
for _ in range(5):
|
||||
rand = now - djangotime.timedelta(minutes=random.randint(10, 20))
|
||||
random_dates.append(rand)
|
||||
|
||||
agents = Agent.objects.only("last_seen")
|
||||
for agent in agents:
|
||||
agent.last_seen = random.choice(random_dates)
|
||||
agent.save(update_fields=["last_seen"])
|
||||
|
||||
cache_db_fields_task()
|
||||
846
api/tacticalrmm/agents/management/commands/fake_agents.py
Normal file
846
api/tacticalrmm/agents/management/commands/fake_agents.py
Normal file
@@ -0,0 +1,846 @@
|
||||
import datetime as dt
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone as djangotime
|
||||
|
||||
from accounts.models import User
|
||||
from agents.models import Agent, AgentHistory
|
||||
from automation.models import Policy
|
||||
from autotasks.models import AutomatedTask, TaskResult
|
||||
from checks.models import Check, CheckHistory, CheckResult
|
||||
from clients.models import Client, Site
|
||||
from logs.models import AuditLog, PendingAction
|
||||
from scripts.models import Script
|
||||
from software.models import InstalledSoftware
|
||||
from tacticalrmm.constants import (
|
||||
AgentHistoryType,
|
||||
AgentMonType,
|
||||
AgentPlat,
|
||||
AlertSeverity,
|
||||
CheckStatus,
|
||||
CheckType,
|
||||
EvtLogFailWhen,
|
||||
EvtLogNames,
|
||||
EvtLogTypes,
|
||||
GoArch,
|
||||
PAAction,
|
||||
ScriptShell,
|
||||
TaskSyncStatus,
|
||||
TaskType,
|
||||
)
|
||||
from tacticalrmm.demo_data import (
|
||||
check_network_loc_aware_ps1,
|
||||
check_storage_pool_health_ps1,
|
||||
clear_print_spool_bat,
|
||||
disks,
|
||||
disks_linux_deb,
|
||||
disks_linux_pi,
|
||||
ping_fail_output,
|
||||
ping_success_output,
|
||||
restart_nla_ps1,
|
||||
show_temp_dir_py,
|
||||
spooler_stdout,
|
||||
temp_dir_stdout,
|
||||
wmi_deb,
|
||||
wmi_pi,
|
||||
wmi_mac,
|
||||
disks_mac,
|
||||
)
|
||||
from winupdate.models import WinUpdate, WinUpdatePolicy
|
||||
|
||||
AGENTS_TO_GENERATE = 250
|
||||
|
||||
SVCS = settings.BASE_DIR.joinpath("tacticalrmm/test_data/winsvcs.json")
|
||||
WMI_1 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/wmi1.json")
|
||||
WMI_2 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/wmi2.json")
|
||||
WMI_3 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/wmi3.json")
|
||||
SW_1 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/software1.json")
|
||||
SW_2 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/software2.json")
|
||||
WIN_UPDATES = settings.BASE_DIR.joinpath("tacticalrmm/test_data/winupdates.json")
|
||||
EVT_LOG_FAIL = settings.BASE_DIR.joinpath(
|
||||
"tacticalrmm/test_data/eventlog_check_fail.json"
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "populate database with fake agents"
|
||||
|
||||
def rand_string(self, length: int) -> str:
|
||||
chars = string.ascii_letters
|
||||
return "".join(random.choice(chars) for _ in range(length))
|
||||
|
||||
def handle(self, *args, **kwargs) -> None:
|
||||
user = User.objects.first()
|
||||
if user:
|
||||
user.totp_key = "ABSA234234"
|
||||
user.save(update_fields=["totp_key"])
|
||||
|
||||
Agent.objects.all().delete()
|
||||
Client.objects.all().delete()
|
||||
Check.objects.all().delete()
|
||||
Script.objects.all().delete()
|
||||
AutomatedTask.objects.all().delete()
|
||||
CheckHistory.objects.all().delete()
|
||||
Policy.objects.all().delete()
|
||||
AuditLog.objects.all().delete()
|
||||
PendingAction.objects.all().delete()
|
||||
|
||||
call_command("load_community_scripts")
|
||||
call_command("initial_db_setup")
|
||||
call_command("load_chocos")
|
||||
call_command("create_installer_user")
|
||||
|
||||
# policies
|
||||
check_policy = Policy()
|
||||
check_policy.name = "Demo Checks Policy"
|
||||
check_policy.desc = "Demo Checks Policy"
|
||||
check_policy.active = True
|
||||
check_policy.enforced = True
|
||||
check_policy.save()
|
||||
|
||||
patch_policy = Policy()
|
||||
patch_policy.name = "Demo Patch Policy"
|
||||
patch_policy.desc = "Demo Patch Policy"
|
||||
patch_policy.active = True
|
||||
patch_policy.enforced = True
|
||||
patch_policy.save()
|
||||
|
||||
update_policy = WinUpdatePolicy()
|
||||
update_policy.policy = patch_policy
|
||||
update_policy.critical = "approve"
|
||||
update_policy.important = "approve"
|
||||
update_policy.moderate = "approve"
|
||||
update_policy.low = "ignore"
|
||||
update_policy.other = "ignore"
|
||||
update_policy.run_time_days = [6, 0, 2]
|
||||
update_policy.run_time_day = 1
|
||||
update_policy.reboot_after_install = "required"
|
||||
update_policy.reprocess_failed = True
|
||||
update_policy.email_if_fail = True
|
||||
update_policy.save()
|
||||
|
||||
clients = (
|
||||
"Company 1",
|
||||
"Company 2",
|
||||
"Company 3",
|
||||
"Company 4",
|
||||
"Company 5",
|
||||
"Company 6",
|
||||
)
|
||||
sites1 = ("HQ1", "LA Office 1", "NY Office 1")
|
||||
sites2 = ("HQ2", "LA Office 2", "NY Office 2")
|
||||
sites3 = ("HQ3", "LA Office 3", "NY Office 3")
|
||||
sites4 = ("HQ4", "LA Office 4", "NY Office 4")
|
||||
sites5 = ("HQ5", "LA Office 5", "NY Office 5")
|
||||
sites6 = ("HQ6", "LA Office 6", "NY Office 6")
|
||||
|
||||
client1 = Client(name=clients[0])
|
||||
client2 = Client(name=clients[1])
|
||||
client3 = Client(name=clients[2])
|
||||
client4 = Client(name=clients[3])
|
||||
client5 = Client(name=clients[4])
|
||||
client6 = Client(name=clients[5])
|
||||
|
||||
client1.save()
|
||||
client2.save()
|
||||
client3.save()
|
||||
client4.save()
|
||||
client5.save()
|
||||
client6.save()
|
||||
|
||||
for site in sites1:
|
||||
Site(client=client1, name=site).save()
|
||||
|
||||
for site in sites2:
|
||||
Site(client=client2, name=site).save()
|
||||
|
||||
for site in sites3:
|
||||
Site(client=client3, name=site).save()
|
||||
|
||||
for site in sites4:
|
||||
Site(client=client4, name=site).save()
|
||||
|
||||
for site in sites5:
|
||||
Site(client=client5, name=site).save()
|
||||
|
||||
for site in sites6:
|
||||
Site(client=client6, name=site).save()
|
||||
|
||||
hostnames = (
|
||||
"DC-1",
|
||||
"DC-2",
|
||||
"FSV-1",
|
||||
"FSV-2",
|
||||
"WSUS",
|
||||
"DESKTOP-12345",
|
||||
"LAPTOP-55443",
|
||||
"db-aws-01",
|
||||
"Karens-MacBook-Air.local",
|
||||
)
|
||||
descriptions = ("Bob's computer", "Primary DC", "File Server", "Karen's Laptop")
|
||||
modes = AgentMonType.values
|
||||
op_systems_servers = (
|
||||
"Microsoft Windows Server 2016 Standard, 64bit (build 14393)",
|
||||
"Microsoft Windows Server 2012 R2 Standard, 64bit (build 9600)",
|
||||
"Microsoft Windows Server 2019 Standard, 64bit (build 17763)",
|
||||
)
|
||||
|
||||
op_systems_workstations = (
|
||||
"Microsoft Windows 8.1 Pro, 64bit (build 9600)",
|
||||
"Microsoft Windows 10 Pro for Workstations, 64bit (build 18363)",
|
||||
"Microsoft Windows 10 Pro, 64bit (build 18363)",
|
||||
)
|
||||
|
||||
linux_deb_os = "Debian 11.2 x86_64 5.10.0-11-amd64"
|
||||
linux_pi_os = "Raspbian 11.2 armv7l 5.10.92-v7+"
|
||||
mac_os = "Darwin 12.5.1 arm64 21.6.0"
|
||||
|
||||
public_ips = ("65.234.22.4", "74.123.43.5", "44.21.134.45")
|
||||
|
||||
total_rams = (4, 8, 16, 32, 64, 128)
|
||||
|
||||
now = dt.datetime.now()
|
||||
django_now = djangotime.now()
|
||||
|
||||
boot_times = []
|
||||
|
||||
for _ in range(15):
|
||||
rand_hour = now - dt.timedelta(hours=random.randint(1, 22))
|
||||
boot_times.append(str(rand_hour.timestamp()))
|
||||
|
||||
for _ in range(5):
|
||||
rand_days = now - dt.timedelta(days=random.randint(2, 50))
|
||||
boot_times.append(str(rand_days.timestamp()))
|
||||
|
||||
user_names = ("None", "Karen", "Steve", "jsmith", "jdoe")
|
||||
|
||||
with open(SVCS) as f:
|
||||
services = json.load(f)
|
||||
|
||||
# WMI
|
||||
with open(WMI_1) as f:
|
||||
wmi1 = json.load(f)
|
||||
|
||||
with open(WMI_2) as f:
|
||||
wmi2 = json.load(f)
|
||||
|
||||
with open(WMI_3) as f:
|
||||
wmi3 = json.load(f)
|
||||
|
||||
wmi_details = [i for i in (wmi1, wmi2, wmi3)]
|
||||
|
||||
# software
|
||||
with open(SW_1) as f:
|
||||
software1 = json.load(f)
|
||||
|
||||
with open(SW_2) as f:
|
||||
software2 = json.load(f)
|
||||
|
||||
softwares = [i for i in (software1, software2)]
|
||||
|
||||
# windows updates
|
||||
with open(WIN_UPDATES) as f:
|
||||
windows_updates = json.load(f)["samplecomputer"]
|
||||
|
||||
# event log check fail data
|
||||
with open(EVT_LOG_FAIL) as f:
|
||||
eventlog_check_fail_data = json.load(f)
|
||||
|
||||
# create scripts
|
||||
|
||||
clear_spool = Script()
|
||||
clear_spool.name = "Clear Print Spooler"
|
||||
clear_spool.description = "clears the print spooler. Fuck printers"
|
||||
clear_spool.filename = "clear_print_spool.bat"
|
||||
clear_spool.shell = ScriptShell.CMD
|
||||
clear_spool.script_body = clear_print_spool_bat
|
||||
clear_spool.save()
|
||||
|
||||
check_net_aware = Script()
|
||||
check_net_aware.name = "Check Network Location Awareness"
|
||||
check_net_aware.description = "Check's network location awareness on domain computers, should always be domain profile and not public or private. Sometimes happens when computer restarts before domain available. This script will return 0 if check passes or 1 if it fails."
|
||||
check_net_aware.filename = "check_network_loc_aware.ps1"
|
||||
check_net_aware.shell = ScriptShell.POWERSHELL
|
||||
check_net_aware.script_body = check_network_loc_aware_ps1
|
||||
check_net_aware.save()
|
||||
|
||||
check_pool_health = Script()
|
||||
check_pool_health.name = "Check storage spool health"
|
||||
check_pool_health.description = "loops through all storage pools and will fail if any of them are not healthy"
|
||||
check_pool_health.filename = "check_storage_pool_health.ps1"
|
||||
check_pool_health.shell = ScriptShell.POWERSHELL
|
||||
check_pool_health.script_body = check_storage_pool_health_ps1
|
||||
check_pool_health.save()
|
||||
|
||||
restart_nla = Script()
|
||||
restart_nla.name = "Restart NLA Service"
|
||||
restart_nla.description = "restarts the Network Location Awareness windows service to fix the nic profile. Run this after the check network service fails"
|
||||
restart_nla.filename = "restart_nla.ps1"
|
||||
restart_nla.shell = ScriptShell.POWERSHELL
|
||||
restart_nla.script_body = restart_nla_ps1
|
||||
restart_nla.save()
|
||||
|
||||
show_tmp_dir_script = Script()
|
||||
show_tmp_dir_script.name = "Check temp dir"
|
||||
show_tmp_dir_script.description = "shows files in temp dir using python"
|
||||
show_tmp_dir_script.filename = "show_temp_dir.py"
|
||||
show_tmp_dir_script.shell = ScriptShell.PYTHON
|
||||
show_tmp_dir_script.script_body = show_temp_dir_py
|
||||
show_tmp_dir_script.save()
|
||||
|
||||
for count_agents in range(AGENTS_TO_GENERATE):
|
||||
client = random.choice(clients)
|
||||
|
||||
if client == clients[0]:
|
||||
site = random.choice(sites1)
|
||||
elif client == clients[1]:
|
||||
site = random.choice(sites2)
|
||||
elif client == clients[2]:
|
||||
site = random.choice(sites3)
|
||||
elif client == clients[3]:
|
||||
site = random.choice(sites4)
|
||||
elif client == clients[4]:
|
||||
site = random.choice(sites5)
|
||||
elif client == clients[5]:
|
||||
site = random.choice(sites6)
|
||||
|
||||
agent = Agent()
|
||||
|
||||
plat_pick = random.randint(1, 15)
|
||||
if plat_pick in (7, 11):
|
||||
agent.plat = AgentPlat.LINUX
|
||||
mode = AgentMonType.SERVER
|
||||
# pi arm
|
||||
if plat_pick == 7:
|
||||
agent.goarch = GoArch.ARM32
|
||||
agent.wmi_detail = wmi_pi
|
||||
agent.disks = disks_linux_pi
|
||||
agent.operating_system = linux_pi_os
|
||||
else:
|
||||
agent.goarch = GoArch.AMD64
|
||||
agent.wmi_detail = wmi_deb
|
||||
agent.disks = disks_linux_deb
|
||||
agent.operating_system = linux_deb_os
|
||||
elif plat_pick in (4, 14):
|
||||
agent.plat = AgentPlat.DARWIN
|
||||
mode = random.choice([AgentMonType.SERVER, AgentMonType.WORKSTATION])
|
||||
agent.goarch = GoArch.ARM64
|
||||
agent.wmi_detail = wmi_mac
|
||||
agent.disks = disks_mac
|
||||
agent.operating_system = mac_os
|
||||
else:
|
||||
agent.plat = AgentPlat.WINDOWS
|
||||
agent.goarch = GoArch.AMD64
|
||||
mode = random.choice(modes)
|
||||
agent.wmi_detail = random.choice(wmi_details)
|
||||
agent.services = services
|
||||
agent.disks = random.choice(disks)
|
||||
if mode == AgentMonType.SERVER:
|
||||
agent.operating_system = random.choice(op_systems_servers)
|
||||
else:
|
||||
agent.operating_system = random.choice(op_systems_workstations)
|
||||
|
||||
agent.version = settings.LATEST_AGENT_VER
|
||||
agent.hostname = random.choice(hostnames)
|
||||
agent.site = Site.objects.get(name=site)
|
||||
agent.agent_id = self.rand_string(40)
|
||||
agent.description = random.choice(descriptions)
|
||||
agent.monitoring_type = mode
|
||||
agent.public_ip = random.choice(public_ips)
|
||||
agent.last_seen = django_now
|
||||
|
||||
agent.total_ram = random.choice(total_rams)
|
||||
agent.boot_time = random.choice(boot_times)
|
||||
agent.logged_in_username = random.choice(user_names)
|
||||
agent.mesh_node_id = (
|
||||
"3UiLhe420@kaVQ0rswzBeonW$WY0xrFFUDBQlcYdXoriLXzvPmBpMrV99vRHXFlb"
|
||||
)
|
||||
agent.overdue_email_alert = random.choice([True, False])
|
||||
agent.overdue_text_alert = random.choice([True, False])
|
||||
agent.needs_reboot = random.choice([True, False])
|
||||
|
||||
agent.save()
|
||||
|
||||
if agent.plat == AgentPlat.WINDOWS:
|
||||
InstalledSoftware(agent=agent, software=random.choice(softwares)).save()
|
||||
|
||||
if mode == AgentMonType.WORKSTATION:
|
||||
WinUpdatePolicy(agent=agent, run_time_days=[5, 6]).save()
|
||||
else:
|
||||
WinUpdatePolicy(agent=agent).save()
|
||||
|
||||
if agent.plat == AgentPlat.WINDOWS:
|
||||
# windows updates load
|
||||
guids = [i for i in windows_updates.keys()]
|
||||
for i in guids:
|
||||
WinUpdate(
|
||||
agent=agent,
|
||||
guid=i,
|
||||
kb=windows_updates[i]["KBs"][0],
|
||||
title=windows_updates[i]["Title"],
|
||||
installed=windows_updates[i]["Installed"],
|
||||
downloaded=windows_updates[i]["Downloaded"],
|
||||
description=windows_updates[i]["Description"],
|
||||
severity=windows_updates[i]["Severity"],
|
||||
).save()
|
||||
|
||||
# agent histories
|
||||
hist = AgentHistory()
|
||||
hist.agent = agent
|
||||
hist.type = AgentHistoryType.CMD_RUN
|
||||
hist.command = "ping google.com"
|
||||
hist.username = "demo"
|
||||
hist.results = ping_success_output
|
||||
hist.save()
|
||||
|
||||
hist1 = AgentHistory()
|
||||
hist1.agent = agent
|
||||
hist1.type = AgentHistoryType.SCRIPT_RUN
|
||||
hist1.script = clear_spool
|
||||
hist1.script_results = {
|
||||
"id": 1,
|
||||
"stderr": "",
|
||||
"stdout": spooler_stdout,
|
||||
"execution_time": 3.5554593,
|
||||
"retcode": 0,
|
||||
}
|
||||
hist1.save()
|
||||
|
||||
if agent.plat == AgentPlat.WINDOWS:
|
||||
# disk space check
|
||||
check1 = Check()
|
||||
check1.agent = agent
|
||||
check1.check_type = CheckType.DISK_SPACE
|
||||
check1.warning_threshold = 25
|
||||
check1.error_threshold = 10
|
||||
check1.disk = "C:"
|
||||
check1.email_alert = random.choice([True, False])
|
||||
check1.text_alert = random.choice([True, False])
|
||||
check1.save()
|
||||
|
||||
check_result1 = CheckResult()
|
||||
check_result1.agent = agent
|
||||
check_result1.assigned_check = check1
|
||||
check_result1.status = CheckStatus.PASSING
|
||||
check_result1.last_run = django_now
|
||||
check_result1.more_info = "Total: 498.7GB, Free: 287.4GB"
|
||||
check_result1.save()
|
||||
|
||||
for i in range(30):
|
||||
check1_history = CheckHistory()
|
||||
check1_history.check_id = check1.pk
|
||||
check1_history.agent_id = agent.agent_id
|
||||
check1_history.x = django_now - djangotime.timedelta(minutes=i * 2)
|
||||
check1_history.y = random.randint(13, 40)
|
||||
check1_history.save()
|
||||
|
||||
# ping check
|
||||
check2 = Check()
|
||||
check_result2 = CheckResult()
|
||||
|
||||
check2.agent = agent
|
||||
check2.check_type = CheckType.PING
|
||||
|
||||
check2.email_alert = random.choice([True, False])
|
||||
check2.text_alert = random.choice([True, False])
|
||||
|
||||
check_result2.agent = agent
|
||||
check_result2.assigned_check = check2
|
||||
check_result2.last_run = django_now
|
||||
|
||||
if site in sites5:
|
||||
check2.name = "Synology NAS"
|
||||
check2.alert_severity = AlertSeverity.ERROR
|
||||
check_result2.status = CheckStatus.FAILING
|
||||
check2.ip = "172.17.14.26"
|
||||
check_result2.more_info = ping_fail_output
|
||||
else:
|
||||
check2.name = "Google"
|
||||
check_result2.status = CheckStatus.PASSING
|
||||
check2.ip = "8.8.8.8"
|
||||
check_result2.more_info = ping_success_output
|
||||
|
||||
check2.save()
|
||||
check_result2.save()
|
||||
|
||||
for i in range(30):
|
||||
check2_history = CheckHistory()
|
||||
check2_history.check_id = check2.pk
|
||||
check2_history.agent_id = agent.agent_id
|
||||
check2_history.x = django_now - djangotime.timedelta(minutes=i * 2)
|
||||
if site in sites5:
|
||||
check2_history.y = 1
|
||||
check2_history.results = ping_fail_output
|
||||
else:
|
||||
check2_history.y = 0
|
||||
check2_history.results = ping_success_output
|
||||
check2_history.save()
|
||||
|
||||
# cpu load check
|
||||
check3 = Check()
|
||||
check3.agent = agent
|
||||
check3.check_type = CheckType.CPU_LOAD
|
||||
check3.warning_threshold = 70
|
||||
check3.error_threshold = 90
|
||||
check3.email_alert = random.choice([True, False])
|
||||
check3.text_alert = random.choice([True, False])
|
||||
check3.save()
|
||||
|
||||
check_result3 = CheckResult()
|
||||
check_result3.agent = agent
|
||||
check_result3.assigned_check = check3
|
||||
check_result3.status = CheckStatus.PASSING
|
||||
check_result3.last_run = django_now
|
||||
check_result3.history = [
|
||||
15,
|
||||
23,
|
||||
16,
|
||||
22,
|
||||
22,
|
||||
27,
|
||||
15,
|
||||
23,
|
||||
23,
|
||||
20,
|
||||
10,
|
||||
10,
|
||||
13,
|
||||
34,
|
||||
]
|
||||
check_result3.save()
|
||||
|
||||
for i in range(30):
|
||||
check3_history = CheckHistory()
|
||||
check3_history.check_id = check3.pk
|
||||
check3_history.agent_id = agent.agent_id
|
||||
check3_history.x = django_now - djangotime.timedelta(minutes=i * 2)
|
||||
check3_history.y = random.randint(2, 79)
|
||||
check3_history.save()
|
||||
|
||||
# memory check
|
||||
check4 = Check()
|
||||
check4.agent = agent
|
||||
check4.check_type = CheckType.MEMORY
|
||||
check4.warning_threshold = 70
|
||||
check4.error_threshold = 85
|
||||
check4.email_alert = random.choice([True, False])
|
||||
check4.text_alert = random.choice([True, False])
|
||||
check4.save()
|
||||
|
||||
check_result4 = CheckResult()
|
||||
check_result4.agent = agent
|
||||
check_result4.assigned_check = check4
|
||||
check_result4.status = CheckStatus.PASSING
|
||||
check_result4.last_run = django_now
|
||||
check_result4.history = [34, 34, 35, 36, 34, 34, 34, 34, 34, 34]
|
||||
check_result4.save()
|
||||
|
||||
for i in range(30):
|
||||
check4_history = CheckHistory()
|
||||
check4_history.check_id = check4.pk
|
||||
check4_history.agent_id = agent.agent_id
|
||||
check4_history.x = django_now - djangotime.timedelta(minutes=i * 2)
|
||||
check4_history.y = random.randint(2, 79)
|
||||
check4_history.save()
|
||||
|
||||
# script check storage pool
|
||||
check5 = Check()
|
||||
|
||||
check5.agent = agent
|
||||
check5.check_type = CheckType.SCRIPT
|
||||
|
||||
check5.email_alert = random.choice([True, False])
|
||||
check5.text_alert = random.choice([True, False])
|
||||
check5.timeout = 120
|
||||
|
||||
check5.script = check_pool_health
|
||||
check5.save()
|
||||
|
||||
check_result5 = CheckResult()
|
||||
check_result5.agent = agent
|
||||
check_result5.assigned_check = check5
|
||||
check_result5.status = CheckStatus.PASSING
|
||||
check_result5.last_run = django_now
|
||||
check_result5.retcode = 0
|
||||
check_result5.execution_time = "4.0000"
|
||||
check_result5.save()
|
||||
|
||||
for i in range(30):
|
||||
check5_history = CheckHistory()
|
||||
check5_history.check_id = check5.pk
|
||||
check5_history.agent_id = agent.agent_id
|
||||
check5_history.x = django_now - djangotime.timedelta(minutes=i * 2)
|
||||
if i == 10 or i == 18:
|
||||
check5_history.y = 1
|
||||
else:
|
||||
check5_history.y = 0
|
||||
check5_history.results = {
|
||||
"retcode": 0,
|
||||
"stdout": None,
|
||||
"stderr": None,
|
||||
"execution_time": "4.0000",
|
||||
}
|
||||
check5_history.save()
|
||||
|
||||
check6 = Check()
|
||||
|
||||
check6.agent = agent
|
||||
check6.check_type = CheckType.SCRIPT
|
||||
check6.email_alert = random.choice([True, False])
|
||||
check6.text_alert = random.choice([True, False])
|
||||
check6.timeout = 120
|
||||
check6.script = check_net_aware
|
||||
check6.save()
|
||||
|
||||
check_result6 = CheckResult()
|
||||
check_result6.agent = agent
|
||||
check_result6.assigned_check = check6
|
||||
check_result6.status = CheckStatus.PASSING
|
||||
check_result6.last_run = django_now
|
||||
check_result6.retcode = 0
|
||||
check_result6.execution_time = "4.0000"
|
||||
check_result6.save()
|
||||
|
||||
for i in range(30):
|
||||
check6_history = CheckHistory()
|
||||
check6_history.check_id = check6.pk
|
||||
check6_history.agent_id = agent.agent_id
|
||||
check6_history.x = django_now - djangotime.timedelta(minutes=i * 2)
|
||||
check6_history.y = 0
|
||||
check6_history.results = {
|
||||
"retcode": 0,
|
||||
"stdout": None,
|
||||
"stderr": None,
|
||||
"execution_time": "4.0000",
|
||||
}
|
||||
check6_history.save()
|
||||
|
||||
nla_task = AutomatedTask()
|
||||
|
||||
nla_task.agent = agent
|
||||
actions = [
|
||||
{
|
||||
"name": restart_nla.name,
|
||||
"type": "script",
|
||||
"script": restart_nla.pk,
|
||||
"timeout": 90,
|
||||
"script_args": [],
|
||||
}
|
||||
]
|
||||
nla_task.actions = actions
|
||||
nla_task.assigned_check = check6
|
||||
nla_task.name = "Restart NLA"
|
||||
nla_task.task_type = TaskType.CHECK_FAILURE
|
||||
nla_task.save()
|
||||
|
||||
nla_task_result = TaskResult()
|
||||
nla_task_result.task = nla_task
|
||||
nla_task_result.agent = agent
|
||||
nla_task_result.execution_time = "1.8443"
|
||||
nla_task_result.last_run = django_now
|
||||
nla_task_result.stdout = "no stdout"
|
||||
nla_task_result.retcode = 0
|
||||
nla_task_result.sync_status = TaskSyncStatus.SYNCED
|
||||
nla_task_result.save()
|
||||
|
||||
spool_task = AutomatedTask()
|
||||
|
||||
spool_task.agent = agent
|
||||
actions = [
|
||||
{
|
||||
"name": clear_spool.name,
|
||||
"type": "script",
|
||||
"script": clear_spool.pk,
|
||||
"timeout": 90,
|
||||
"script_args": [],
|
||||
}
|
||||
]
|
||||
spool_task.actions = actions
|
||||
spool_task.name = "Clear the print spooler"
|
||||
spool_task.task_type = TaskType.DAILY
|
||||
spool_task.run_time_date = django_now + djangotime.timedelta(minutes=10)
|
||||
spool_task.expire_date = django_now + djangotime.timedelta(days=753)
|
||||
spool_task.daily_interval = 1
|
||||
spool_task.weekly_interval = 1
|
||||
spool_task.task_repetition_duration = "2h"
|
||||
spool_task.task_repetition_interval = "25m"
|
||||
spool_task.random_task_delay = "3m"
|
||||
spool_task.save()
|
||||
|
||||
spool_task_result = TaskResult()
|
||||
spool_task_result.task = spool_task
|
||||
spool_task_result.agent = agent
|
||||
spool_task_result.last_run = django_now
|
||||
spool_task_result.retcode = 0
|
||||
spool_task_result.stdout = spooler_stdout
|
||||
spool_task_result.sync_status = TaskSyncStatus.SYNCED
|
||||
spool_task_result.save()
|
||||
|
||||
tmp_dir_task = AutomatedTask()
|
||||
tmp_dir_task.agent = agent
|
||||
tmp_dir_task.name = "show temp dir files"
|
||||
actions = [
|
||||
{
|
||||
"name": show_tmp_dir_script.name,
|
||||
"type": "script",
|
||||
"script": show_tmp_dir_script.pk,
|
||||
"timeout": 90,
|
||||
"script_args": [],
|
||||
}
|
||||
]
|
||||
tmp_dir_task.actions = actions
|
||||
tmp_dir_task.task_type = TaskType.MANUAL
|
||||
tmp_dir_task.save()
|
||||
|
||||
tmp_dir_task_result = TaskResult()
|
||||
tmp_dir_task_result.task = tmp_dir_task
|
||||
tmp_dir_task_result.agent = agent
|
||||
tmp_dir_task_result.last_run = django_now
|
||||
tmp_dir_task_result.stdout = temp_dir_stdout
|
||||
tmp_dir_task_result.retcode = 0
|
||||
tmp_dir_task_result.sync_status = TaskSyncStatus.SYNCED
|
||||
tmp_dir_task_result.save()
|
||||
|
||||
check7 = Check()
|
||||
|
||||
check7.agent = agent
|
||||
check7.check_type = CheckType.SCRIPT
|
||||
|
||||
check7.email_alert = random.choice([True, False])
|
||||
check7.text_alert = random.choice([True, False])
|
||||
check7.timeout = 120
|
||||
|
||||
check7.script = clear_spool
|
||||
|
||||
check7.save()
|
||||
|
||||
check_result7 = CheckResult()
|
||||
check_result7.assigned_check = check7
|
||||
check_result7.agent = agent
|
||||
check_result7.status = CheckStatus.PASSING
|
||||
check_result7.last_run = django_now
|
||||
check_result7.retcode = 0
|
||||
check_result7.execution_time = "3.1337"
|
||||
check_result7.stdout = spooler_stdout
|
||||
check_result7.save()
|
||||
|
||||
for i in range(30):
|
||||
check7_history = CheckHistory()
|
||||
check7_history.check_id = check7.pk
|
||||
check7_history.agent_id = agent.agent_id
|
||||
check7_history.x = django_now - djangotime.timedelta(minutes=i * 2)
|
||||
check7_history.y = 0
|
||||
check7_history.results = {
|
||||
"retcode": 0,
|
||||
"stdout": spooler_stdout,
|
||||
"stderr": None,
|
||||
"execution_time": "3.1337",
|
||||
}
|
||||
check7_history.save()
|
||||
|
||||
if agent.plat == AgentPlat.WINDOWS:
|
||||
check8 = Check()
|
||||
check8.agent = agent
|
||||
check8.check_type = CheckType.WINSVC
|
||||
check8.email_alert = random.choice([True, False])
|
||||
check8.text_alert = random.choice([True, False])
|
||||
check8.fails_b4_alert = 4
|
||||
check8.svc_name = "Spooler"
|
||||
check8.svc_display_name = "Print Spooler"
|
||||
check8.pass_if_start_pending = False
|
||||
check8.restart_if_stopped = True
|
||||
check8.save()
|
||||
|
||||
check_result8 = CheckResult()
|
||||
check_result8.assigned_check = check8
|
||||
check_result8.agent = agent
|
||||
check_result8.status = CheckStatus.PASSING
|
||||
check_result8.last_run = django_now
|
||||
check_result8.more_info = "Status RUNNING"
|
||||
check_result8.save()
|
||||
|
||||
for i in range(30):
|
||||
check8_history = CheckHistory()
|
||||
check8_history.check_id = check8.pk
|
||||
check8_history.agent_id = agent.agent_id
|
||||
check8_history.x = django_now - djangotime.timedelta(minutes=i * 2)
|
||||
if i == 10 or i == 18:
|
||||
check8_history.y = 1
|
||||
check8_history.results = "Status STOPPED"
|
||||
else:
|
||||
check8_history.y = 0
|
||||
check8_history.results = "Status RUNNING"
|
||||
check8_history.save()
|
||||
|
||||
check9 = Check()
|
||||
check9.agent = agent
|
||||
check9.check_type = CheckType.EVENT_LOG
|
||||
check9.name = "unexpected shutdown"
|
||||
check9.email_alert = random.choice([True, False])
|
||||
check9.text_alert = random.choice([True, False])
|
||||
check9.fails_b4_alert = 2
|
||||
check9.log_name = EvtLogNames.APPLICATION
|
||||
check9.event_id = 1001
|
||||
check9.event_type = EvtLogTypes.INFO
|
||||
check9.fail_when = EvtLogFailWhen.CONTAINS
|
||||
check9.search_last_days = 30
|
||||
|
||||
check_result9 = CheckResult()
|
||||
check_result9.agent = agent
|
||||
check_result9.assigned_check = check9
|
||||
|
||||
check_result9.last_run = django_now
|
||||
if site in sites5:
|
||||
check_result9.extra_details = eventlog_check_fail_data
|
||||
check_result9.status = CheckStatus.FAILING
|
||||
else:
|
||||
check_result9.extra_details = {"log": []}
|
||||
check_result9.status = CheckStatus.PASSING
|
||||
|
||||
check9.save()
|
||||
check_result9.save()
|
||||
|
||||
for i in range(30):
|
||||
check9_history = CheckHistory()
|
||||
check9_history.check_id = check9.pk
|
||||
check9_history.agent_id = agent.agent_id
|
||||
check9_history.x = django_now - djangotime.timedelta(minutes=i * 2)
|
||||
if i == 10 or i == 18:
|
||||
check9_history.y = 1
|
||||
check9_history.results = "Events Found: 16"
|
||||
else:
|
||||
check9_history.y = 0
|
||||
check9_history.results = "Events Found: 0"
|
||||
check9_history.save()
|
||||
|
||||
pick = random.randint(1, 10)
|
||||
|
||||
if pick == 5 or pick == 3:
|
||||
reboot_time = django_now + djangotime.timedelta(
|
||||
minutes=random.randint(1000, 500000)
|
||||
)
|
||||
date_obj = dt.datetime.strftime(reboot_time, "%Y-%m-%d %H:%M")
|
||||
|
||||
obj = dt.datetime.strptime(date_obj, "%Y-%m-%d %H:%M")
|
||||
|
||||
task_name = "TacticalRMM_SchedReboot_" + "".join(
|
||||
random.choice(string.ascii_letters) for _ in range(10)
|
||||
)
|
||||
|
||||
sched_reboot = PendingAction()
|
||||
sched_reboot.agent = agent
|
||||
sched_reboot.action_type = PAAction.SCHED_REBOOT
|
||||
sched_reboot.details = {
|
||||
"time": str(obj),
|
||||
"taskname": task_name,
|
||||
}
|
||||
sched_reboot.save()
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Added agent # {count_agents + 1}"))
|
||||
|
||||
self.stdout.write("done")
|
||||
30
api/tacticalrmm/agents/management/commands/find_services.py
Normal file
30
api/tacticalrmm/agents/management/commands/find_services.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from agents.models import Agent
|
||||
from tacticalrmm.constants import AGENT_DEFER
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Find all agents that have a certain service installed"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("name", type=str)
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
search = kwargs["name"].lower()
|
||||
|
||||
agents = Agent.objects.defer(*AGENT_DEFER)
|
||||
for agent in agents:
|
||||
try:
|
||||
for svc in agent.services:
|
||||
if (
|
||||
search in svc["name"].lower()
|
||||
or search in svc["display_name"].lower()
|
||||
):
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"{agent.hostname} - {svc['name']} ({svc['display_name']}) - {svc['status']}"
|
||||
)
|
||||
)
|
||||
except:
|
||||
continue
|
||||
@@ -0,0 +1,24 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from agents.models import Agent
|
||||
from tacticalrmm.constants import AGENT_DEFER
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def find_duplicates(self, lst):
|
||||
return list(set([item for item in lst if lst.count(item) > 1]))
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
for agent in Agent.objects.defer(*AGENT_DEFER).prefetch_related(
|
||||
"custom_fields__field"
|
||||
):
|
||||
if dupes := self.find_duplicates(
|
||||
[i.field.name for i in agent.custom_fields.all()]
|
||||
):
|
||||
for dupe in dupes:
|
||||
cf = list(
|
||||
agent.custom_fields.filter(field__name=dupe).order_by("id")
|
||||
)
|
||||
to_delete = cf[:-1]
|
||||
for i in to_delete:
|
||||
i.delete()
|
||||
@@ -1,16 +0,0 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from agents.models import Agent
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Changes existing agents salt_id from a property to a model field"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
agents = Agent.objects.filter(salt_id=None)
|
||||
for agent in agents:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f"Setting salt_id on {agent.hostname}")
|
||||
)
|
||||
agent.salt_id = f"{agent.hostname}-{agent.pk}"
|
||||
agent.save(update_fields=["salt_id"])
|
||||
@@ -2,16 +2,16 @@ from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from agents.models import Agent
|
||||
from tacticalrmm.constants import AGENT_STATUS_ONLINE, ONLINE_AGENTS
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Shows online agents that are not on the latest version"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
q = Agent.objects.exclude(version=settings.LATEST_AGENT_VER).only(
|
||||
"pk", "version", "last_seen", "overdue_time", "offline_time"
|
||||
)
|
||||
agents = [i for i in q if i.status == "online"]
|
||||
only = ONLINE_AGENTS + ("hostname",)
|
||||
q = Agent.objects.exclude(version=settings.LATEST_AGENT_VER).only(*only)
|
||||
agents = [i for i in q if i.status == AGENT_STATUS_ONLINE]
|
||||
for agent in agents:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f"{agent.hostname} - v{agent.version}")
|
||||
|
||||
@@ -4,17 +4,23 @@ from packaging import version as pyver
|
||||
|
||||
from agents.models import Agent
|
||||
from agents.tasks import send_agent_update_task
|
||||
from tacticalrmm.utils import AGENT_DEFER
|
||||
from core.utils import get_core_settings, token_is_valid
|
||||
from tacticalrmm.constants import AGENT_DEFER
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Triggers an agent update task to run"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
core = get_core_settings()
|
||||
if not core.agent_auto_update:
|
||||
return
|
||||
|
||||
q = Agent.objects.defer(*AGENT_DEFER).exclude(version=settings.LATEST_AGENT_VER)
|
||||
agent_ids: list[str] = [
|
||||
i.agent_id
|
||||
for i in q
|
||||
if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
|
||||
]
|
||||
send_agent_update_task.delay(agent_ids=agent_ids)
|
||||
token, _ = token_is_valid()
|
||||
send_agent_update_task.delay(agent_ids=agent_ids, token=token, force=False)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Generated by Django 3.2.1 on 2021-07-06 02:01
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Generated by Django 3.2.5 on 2021-07-14 07:38
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
25
api/tacticalrmm/agents/migrations/0043_auto_20220227_0554.py
Normal file
25
api/tacticalrmm/agents/migrations/0043_auto_20220227_0554.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 3.2.12 on 2022-02-27 05:54
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0042_alter_agent_time_zone'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='agent',
|
||||
name='antivirus',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='agent',
|
||||
name='local_ip',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='agent',
|
||||
name='used_ram',
|
||||
),
|
||||
]
|
||||
22
api/tacticalrmm/agents/migrations/0044_auto_20220227_0717.py
Normal file
22
api/tacticalrmm/agents/migrations/0044_auto_20220227_0717.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 3.2.12 on 2022-02-27 07:17
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0043_auto_20220227_0554'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='agent',
|
||||
old_name='salt_id',
|
||||
new_name='goarch',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='agent',
|
||||
name='salt_ver',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 3.2.12 on 2022-03-12 02:30
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0044_auto_20220227_0717'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='RecoveryAction',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.12 on 2022-03-17 17:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0045_delete_recoveryaction'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='agenthistory',
|
||||
name='command',
|
||||
field=models.TextField(blank=True, default='', null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 4.0.3 on 2022-04-07 17:28
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('clients', '0020_auto_20211226_0547'),
|
||||
('agents', '0046_alter_agenthistory_command'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='agent',
|
||||
name='plat',
|
||||
field=models.CharField(default='windows', max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='agent',
|
||||
name='site',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.RESTRICT, related_name='agents', to='clients.site'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.0.3 on 2022-04-16 17:39
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0047_alter_agent_plat_alter_agent_site'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='agent',
|
||||
name='has_patches_pending',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='agent',
|
||||
name='pending_actions_count',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.0.3 on 2022-04-18 14:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0048_remove_agent_has_patches_pending_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='agent',
|
||||
index=models.Index(fields=['monitoring_type'], name='agents_agen_monitor_df8816_idx'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.0.4 on 2022-04-25 06:51
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0049_agent_agents_agen_monitor_df8816_idx'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='agent',
|
||||
name='plat_release',
|
||||
),
|
||||
]
|
||||
18
api/tacticalrmm/agents/migrations/0051_alter_agent_plat.py
Normal file
18
api/tacticalrmm/agents/migrations/0051_alter_agent_plat.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.0.4 on 2022-05-18 03:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0050_remove_agent_plat_release'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='agent',
|
||||
name='plat',
|
||||
field=models.CharField(choices=[('windows', 'Windows'), ('linux', 'Linux'), ('darwin', 'macOS')], default='windows', max_length=255),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.0.4 on 2022-05-18 05:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0051_alter_agent_plat'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='agent',
|
||||
name='monitoring_type',
|
||||
field=models.CharField(choices=[('server', 'Server'), ('workstation', 'Workstation')], default='server', max_length=30),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.0.4 on 2022-05-18 06:10
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0052_alter_agent_monitoring_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='agenthistory',
|
||||
name='status',
|
||||
),
|
||||
]
|
||||
18
api/tacticalrmm/agents/migrations/0054_alter_agent_goarch.py
Normal file
18
api/tacticalrmm/agents/migrations/0054_alter_agent_goarch.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.0.4 on 2022-06-06 04:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0053_remove_agenthistory_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='agent',
|
||||
name='goarch',
|
||||
field=models.CharField(blank=True, choices=[('amd64', 'amd64'), ('386', '386'), ('arm64', 'arm64'), ('arm', 'arm')], max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
631
api/tacticalrmm/agents/migrations/0055_alter_agent_time_zone.py
Normal file
631
api/tacticalrmm/agents/migrations/0055_alter_agent_time_zone.py
Normal file
@@ -0,0 +1,631 @@
|
||||
# Generated by Django 4.1 on 2022-08-24 07:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("agents", "0054_alter_agent_goarch"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="agent",
|
||||
name="time_zone",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("Africa/Abidjan", "Africa/Abidjan"),
|
||||
("Africa/Accra", "Africa/Accra"),
|
||||
("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
|
||||
("Africa/Algiers", "Africa/Algiers"),
|
||||
("Africa/Asmara", "Africa/Asmara"),
|
||||
("Africa/Asmera", "Africa/Asmera"),
|
||||
("Africa/Bamako", "Africa/Bamako"),
|
||||
("Africa/Bangui", "Africa/Bangui"),
|
||||
("Africa/Banjul", "Africa/Banjul"),
|
||||
("Africa/Bissau", "Africa/Bissau"),
|
||||
("Africa/Blantyre", "Africa/Blantyre"),
|
||||
("Africa/Brazzaville", "Africa/Brazzaville"),
|
||||
("Africa/Bujumbura", "Africa/Bujumbura"),
|
||||
("Africa/Cairo", "Africa/Cairo"),
|
||||
("Africa/Casablanca", "Africa/Casablanca"),
|
||||
("Africa/Ceuta", "Africa/Ceuta"),
|
||||
("Africa/Conakry", "Africa/Conakry"),
|
||||
("Africa/Dakar", "Africa/Dakar"),
|
||||
("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
|
||||
("Africa/Djibouti", "Africa/Djibouti"),
|
||||
("Africa/Douala", "Africa/Douala"),
|
||||
("Africa/El_Aaiun", "Africa/El_Aaiun"),
|
||||
("Africa/Freetown", "Africa/Freetown"),
|
||||
("Africa/Gaborone", "Africa/Gaborone"),
|
||||
("Africa/Harare", "Africa/Harare"),
|
||||
("Africa/Johannesburg", "Africa/Johannesburg"),
|
||||
("Africa/Juba", "Africa/Juba"),
|
||||
("Africa/Kampala", "Africa/Kampala"),
|
||||
("Africa/Khartoum", "Africa/Khartoum"),
|
||||
("Africa/Kigali", "Africa/Kigali"),
|
||||
("Africa/Kinshasa", "Africa/Kinshasa"),
|
||||
("Africa/Lagos", "Africa/Lagos"),
|
||||
("Africa/Libreville", "Africa/Libreville"),
|
||||
("Africa/Lome", "Africa/Lome"),
|
||||
("Africa/Luanda", "Africa/Luanda"),
|
||||
("Africa/Lubumbashi", "Africa/Lubumbashi"),
|
||||
("Africa/Lusaka", "Africa/Lusaka"),
|
||||
("Africa/Malabo", "Africa/Malabo"),
|
||||
("Africa/Maputo", "Africa/Maputo"),
|
||||
("Africa/Maseru", "Africa/Maseru"),
|
||||
("Africa/Mbabane", "Africa/Mbabane"),
|
||||
("Africa/Mogadishu", "Africa/Mogadishu"),
|
||||
("Africa/Monrovia", "Africa/Monrovia"),
|
||||
("Africa/Nairobi", "Africa/Nairobi"),
|
||||
("Africa/Ndjamena", "Africa/Ndjamena"),
|
||||
("Africa/Niamey", "Africa/Niamey"),
|
||||
("Africa/Nouakchott", "Africa/Nouakchott"),
|
||||
("Africa/Ouagadougou", "Africa/Ouagadougou"),
|
||||
("Africa/Porto-Novo", "Africa/Porto-Novo"),
|
||||
("Africa/Sao_Tome", "Africa/Sao_Tome"),
|
||||
("Africa/Timbuktu", "Africa/Timbuktu"),
|
||||
("Africa/Tripoli", "Africa/Tripoli"),
|
||||
("Africa/Tunis", "Africa/Tunis"),
|
||||
("Africa/Windhoek", "Africa/Windhoek"),
|
||||
("America/Adak", "America/Adak"),
|
||||
("America/Anchorage", "America/Anchorage"),
|
||||
("America/Anguilla", "America/Anguilla"),
|
||||
("America/Antigua", "America/Antigua"),
|
||||
("America/Araguaina", "America/Araguaina"),
|
||||
(
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"America/Argentina/Buenos_Aires",
|
||||
),
|
||||
("America/Argentina/Catamarca", "America/Argentina/Catamarca"),
|
||||
(
|
||||
"America/Argentina/ComodRivadavia",
|
||||
"America/Argentina/ComodRivadavia",
|
||||
),
|
||||
("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
|
||||
("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
|
||||
("America/Argentina/La_Rioja", "America/Argentina/La_Rioja"),
|
||||
("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
|
||||
(
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
),
|
||||
("America/Argentina/Salta", "America/Argentina/Salta"),
|
||||
("America/Argentina/San_Juan", "America/Argentina/San_Juan"),
|
||||
("America/Argentina/San_Luis", "America/Argentina/San_Luis"),
|
||||
("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
|
||||
("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
|
||||
("America/Aruba", "America/Aruba"),
|
||||
("America/Asuncion", "America/Asuncion"),
|
||||
("America/Atikokan", "America/Atikokan"),
|
||||
("America/Atka", "America/Atka"),
|
||||
("America/Bahia", "America/Bahia"),
|
||||
("America/Bahia_Banderas", "America/Bahia_Banderas"),
|
||||
("America/Barbados", "America/Barbados"),
|
||||
("America/Belem", "America/Belem"),
|
||||
("America/Belize", "America/Belize"),
|
||||
("America/Blanc-Sablon", "America/Blanc-Sablon"),
|
||||
("America/Boa_Vista", "America/Boa_Vista"),
|
||||
("America/Bogota", "America/Bogota"),
|
||||
("America/Boise", "America/Boise"),
|
||||
("America/Buenos_Aires", "America/Buenos_Aires"),
|
||||
("America/Cambridge_Bay", "America/Cambridge_Bay"),
|
||||
("America/Campo_Grande", "America/Campo_Grande"),
|
||||
("America/Cancun", "America/Cancun"),
|
||||
("America/Caracas", "America/Caracas"),
|
||||
("America/Catamarca", "America/Catamarca"),
|
||||
("America/Cayenne", "America/Cayenne"),
|
||||
("America/Cayman", "America/Cayman"),
|
||||
("America/Chicago", "America/Chicago"),
|
||||
("America/Chihuahua", "America/Chihuahua"),
|
||||
("America/Coral_Harbour", "America/Coral_Harbour"),
|
||||
("America/Cordoba", "America/Cordoba"),
|
||||
("America/Costa_Rica", "America/Costa_Rica"),
|
||||
("America/Creston", "America/Creston"),
|
||||
("America/Cuiaba", "America/Cuiaba"),
|
||||
("America/Curacao", "America/Curacao"),
|
||||
("America/Danmarkshavn", "America/Danmarkshavn"),
|
||||
("America/Dawson", "America/Dawson"),
|
||||
("America/Dawson_Creek", "America/Dawson_Creek"),
|
||||
("America/Denver", "America/Denver"),
|
||||
("America/Detroit", "America/Detroit"),
|
||||
("America/Dominica", "America/Dominica"),
|
||||
("America/Edmonton", "America/Edmonton"),
|
||||
("America/Eirunepe", "America/Eirunepe"),
|
||||
("America/El_Salvador", "America/El_Salvador"),
|
||||
("America/Ensenada", "America/Ensenada"),
|
||||
("America/Fort_Nelson", "America/Fort_Nelson"),
|
||||
("America/Fort_Wayne", "America/Fort_Wayne"),
|
||||
("America/Fortaleza", "America/Fortaleza"),
|
||||
("America/Glace_Bay", "America/Glace_Bay"),
|
||||
("America/Godthab", "America/Godthab"),
|
||||
("America/Goose_Bay", "America/Goose_Bay"),
|
||||
("America/Grand_Turk", "America/Grand_Turk"),
|
||||
("America/Grenada", "America/Grenada"),
|
||||
("America/Guadeloupe", "America/Guadeloupe"),
|
||||
("America/Guatemala", "America/Guatemala"),
|
||||
("America/Guayaquil", "America/Guayaquil"),
|
||||
("America/Guyana", "America/Guyana"),
|
||||
("America/Halifax", "America/Halifax"),
|
||||
("America/Havana", "America/Havana"),
|
||||
("America/Hermosillo", "America/Hermosillo"),
|
||||
("America/Indiana/Indianapolis", "America/Indiana/Indianapolis"),
|
||||
("America/Indiana/Knox", "America/Indiana/Knox"),
|
||||
("America/Indiana/Marengo", "America/Indiana/Marengo"),
|
||||
("America/Indiana/Petersburg", "America/Indiana/Petersburg"),
|
||||
("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
|
||||
("America/Indiana/Vevay", "America/Indiana/Vevay"),
|
||||
("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
|
||||
("America/Indiana/Winamac", "America/Indiana/Winamac"),
|
||||
("America/Indianapolis", "America/Indianapolis"),
|
||||
("America/Inuvik", "America/Inuvik"),
|
||||
("America/Iqaluit", "America/Iqaluit"),
|
||||
("America/Jamaica", "America/Jamaica"),
|
||||
("America/Jujuy", "America/Jujuy"),
|
||||
("America/Juneau", "America/Juneau"),
|
||||
("America/Kentucky/Louisville", "America/Kentucky/Louisville"),
|
||||
("America/Kentucky/Monticello", "America/Kentucky/Monticello"),
|
||||
("America/Knox_IN", "America/Knox_IN"),
|
||||
("America/Kralendijk", "America/Kralendijk"),
|
||||
("America/La_Paz", "America/La_Paz"),
|
||||
("America/Lima", "America/Lima"),
|
||||
("America/Los_Angeles", "America/Los_Angeles"),
|
||||
("America/Louisville", "America/Louisville"),
|
||||
("America/Lower_Princes", "America/Lower_Princes"),
|
||||
("America/Maceio", "America/Maceio"),
|
||||
("America/Managua", "America/Managua"),
|
||||
("America/Manaus", "America/Manaus"),
|
||||
("America/Marigot", "America/Marigot"),
|
||||
("America/Martinique", "America/Martinique"),
|
||||
("America/Matamoros", "America/Matamoros"),
|
||||
("America/Mazatlan", "America/Mazatlan"),
|
||||
("America/Mendoza", "America/Mendoza"),
|
||||
("America/Menominee", "America/Menominee"),
|
||||
("America/Merida", "America/Merida"),
|
||||
("America/Metlakatla", "America/Metlakatla"),
|
||||
("America/Mexico_City", "America/Mexico_City"),
|
||||
("America/Miquelon", "America/Miquelon"),
|
||||
("America/Moncton", "America/Moncton"),
|
||||
("America/Monterrey", "America/Monterrey"),
|
||||
("America/Montevideo", "America/Montevideo"),
|
||||
("America/Montreal", "America/Montreal"),
|
||||
("America/Montserrat", "America/Montserrat"),
|
||||
("America/Nassau", "America/Nassau"),
|
||||
("America/New_York", "America/New_York"),
|
||||
("America/Nipigon", "America/Nipigon"),
|
||||
("America/Nome", "America/Nome"),
|
||||
("America/Noronha", "America/Noronha"),
|
||||
("America/North_Dakota/Beulah", "America/North_Dakota/Beulah"),
|
||||
("America/North_Dakota/Center", "America/North_Dakota/Center"),
|
||||
(
|
||||
"America/North_Dakota/New_Salem",
|
||||
"America/North_Dakota/New_Salem",
|
||||
),
|
||||
("America/Nuuk", "America/Nuuk"),
|
||||
("America/Ojinaga", "America/Ojinaga"),
|
||||
("America/Panama", "America/Panama"),
|
||||
("America/Pangnirtung", "America/Pangnirtung"),
|
||||
("America/Paramaribo", "America/Paramaribo"),
|
||||
("America/Phoenix", "America/Phoenix"),
|
||||
("America/Port-au-Prince", "America/Port-au-Prince"),
|
||||
("America/Port_of_Spain", "America/Port_of_Spain"),
|
||||
("America/Porto_Acre", "America/Porto_Acre"),
|
||||
("America/Porto_Velho", "America/Porto_Velho"),
|
||||
("America/Puerto_Rico", "America/Puerto_Rico"),
|
||||
("America/Punta_Arenas", "America/Punta_Arenas"),
|
||||
("America/Rainy_River", "America/Rainy_River"),
|
||||
("America/Rankin_Inlet", "America/Rankin_Inlet"),
|
||||
("America/Recife", "America/Recife"),
|
||||
("America/Regina", "America/Regina"),
|
||||
("America/Resolute", "America/Resolute"),
|
||||
("America/Rio_Branco", "America/Rio_Branco"),
|
||||
("America/Rosario", "America/Rosario"),
|
||||
("America/Santa_Isabel", "America/Santa_Isabel"),
|
||||
("America/Santarem", "America/Santarem"),
|
||||
("America/Santiago", "America/Santiago"),
|
||||
("America/Santo_Domingo", "America/Santo_Domingo"),
|
||||
("America/Sao_Paulo", "America/Sao_Paulo"),
|
||||
("America/Scoresbysund", "America/Scoresbysund"),
|
||||
("America/Shiprock", "America/Shiprock"),
|
||||
("America/Sitka", "America/Sitka"),
|
||||
("America/St_Barthelemy", "America/St_Barthelemy"),
|
||||
("America/St_Johns", "America/St_Johns"),
|
||||
("America/St_Kitts", "America/St_Kitts"),
|
||||
("America/St_Lucia", "America/St_Lucia"),
|
||||
("America/St_Thomas", "America/St_Thomas"),
|
||||
("America/St_Vincent", "America/St_Vincent"),
|
||||
("America/Swift_Current", "America/Swift_Current"),
|
||||
("America/Tegucigalpa", "America/Tegucigalpa"),
|
||||
("America/Thule", "America/Thule"),
|
||||
("America/Thunder_Bay", "America/Thunder_Bay"),
|
||||
("America/Tijuana", "America/Tijuana"),
|
||||
("America/Toronto", "America/Toronto"),
|
||||
("America/Tortola", "America/Tortola"),
|
||||
("America/Vancouver", "America/Vancouver"),
|
||||
("America/Virgin", "America/Virgin"),
|
||||
("America/Whitehorse", "America/Whitehorse"),
|
||||
("America/Winnipeg", "America/Winnipeg"),
|
||||
("America/Yakutat", "America/Yakutat"),
|
||||
("America/Yellowknife", "America/Yellowknife"),
|
||||
("Antarctica/Casey", "Antarctica/Casey"),
|
||||
("Antarctica/Davis", "Antarctica/Davis"),
|
||||
("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
|
||||
("Antarctica/Macquarie", "Antarctica/Macquarie"),
|
||||
("Antarctica/Mawson", "Antarctica/Mawson"),
|
||||
("Antarctica/McMurdo", "Antarctica/McMurdo"),
|
||||
("Antarctica/Palmer", "Antarctica/Palmer"),
|
||||
("Antarctica/Rothera", "Antarctica/Rothera"),
|
||||
("Antarctica/South_Pole", "Antarctica/South_Pole"),
|
||||
("Antarctica/Syowa", "Antarctica/Syowa"),
|
||||
("Antarctica/Troll", "Antarctica/Troll"),
|
||||
("Antarctica/Vostok", "Antarctica/Vostok"),
|
||||
("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
|
||||
("Asia/Aden", "Asia/Aden"),
|
||||
("Asia/Almaty", "Asia/Almaty"),
|
||||
("Asia/Amman", "Asia/Amman"),
|
||||
("Asia/Anadyr", "Asia/Anadyr"),
|
||||
("Asia/Aqtau", "Asia/Aqtau"),
|
||||
("Asia/Aqtobe", "Asia/Aqtobe"),
|
||||
("Asia/Ashgabat", "Asia/Ashgabat"),
|
||||
("Asia/Ashkhabad", "Asia/Ashkhabad"),
|
||||
("Asia/Atyrau", "Asia/Atyrau"),
|
||||
("Asia/Baghdad", "Asia/Baghdad"),
|
||||
("Asia/Bahrain", "Asia/Bahrain"),
|
||||
("Asia/Baku", "Asia/Baku"),
|
||||
("Asia/Bangkok", "Asia/Bangkok"),
|
||||
("Asia/Barnaul", "Asia/Barnaul"),
|
||||
("Asia/Beirut", "Asia/Beirut"),
|
||||
("Asia/Bishkek", "Asia/Bishkek"),
|
||||
("Asia/Brunei", "Asia/Brunei"),
|
||||
("Asia/Calcutta", "Asia/Calcutta"),
|
||||
("Asia/Chita", "Asia/Chita"),
|
||||
("Asia/Choibalsan", "Asia/Choibalsan"),
|
||||
("Asia/Chongqing", "Asia/Chongqing"),
|
||||
("Asia/Chungking", "Asia/Chungking"),
|
||||
("Asia/Colombo", "Asia/Colombo"),
|
||||
("Asia/Dacca", "Asia/Dacca"),
|
||||
("Asia/Damascus", "Asia/Damascus"),
|
||||
("Asia/Dhaka", "Asia/Dhaka"),
|
||||
("Asia/Dili", "Asia/Dili"),
|
||||
("Asia/Dubai", "Asia/Dubai"),
|
||||
("Asia/Dushanbe", "Asia/Dushanbe"),
|
||||
("Asia/Famagusta", "Asia/Famagusta"),
|
||||
("Asia/Gaza", "Asia/Gaza"),
|
||||
("Asia/Harbin", "Asia/Harbin"),
|
||||
("Asia/Hebron", "Asia/Hebron"),
|
||||
("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
|
||||
("Asia/Hong_Kong", "Asia/Hong_Kong"),
|
||||
("Asia/Hovd", "Asia/Hovd"),
|
||||
("Asia/Irkutsk", "Asia/Irkutsk"),
|
||||
("Asia/Istanbul", "Asia/Istanbul"),
|
||||
("Asia/Jakarta", "Asia/Jakarta"),
|
||||
("Asia/Jayapura", "Asia/Jayapura"),
|
||||
("Asia/Jerusalem", "Asia/Jerusalem"),
|
||||
("Asia/Kabul", "Asia/Kabul"),
|
||||
("Asia/Kamchatka", "Asia/Kamchatka"),
|
||||
("Asia/Karachi", "Asia/Karachi"),
|
||||
("Asia/Kashgar", "Asia/Kashgar"),
|
||||
("Asia/Kathmandu", "Asia/Kathmandu"),
|
||||
("Asia/Katmandu", "Asia/Katmandu"),
|
||||
("Asia/Khandyga", "Asia/Khandyga"),
|
||||
("Asia/Kolkata", "Asia/Kolkata"),
|
||||
("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
|
||||
("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
|
||||
("Asia/Kuching", "Asia/Kuching"),
|
||||
("Asia/Kuwait", "Asia/Kuwait"),
|
||||
("Asia/Macao", "Asia/Macao"),
|
||||
("Asia/Macau", "Asia/Macau"),
|
||||
("Asia/Magadan", "Asia/Magadan"),
|
||||
("Asia/Makassar", "Asia/Makassar"),
|
||||
("Asia/Manila", "Asia/Manila"),
|
||||
("Asia/Muscat", "Asia/Muscat"),
|
||||
("Asia/Nicosia", "Asia/Nicosia"),
|
||||
("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
|
||||
("Asia/Novosibirsk", "Asia/Novosibirsk"),
|
||||
("Asia/Omsk", "Asia/Omsk"),
|
||||
("Asia/Oral", "Asia/Oral"),
|
||||
("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
|
||||
("Asia/Pontianak", "Asia/Pontianak"),
|
||||
("Asia/Pyongyang", "Asia/Pyongyang"),
|
||||
("Asia/Qatar", "Asia/Qatar"),
|
||||
("Asia/Qostanay", "Asia/Qostanay"),
|
||||
("Asia/Qyzylorda", "Asia/Qyzylorda"),
|
||||
("Asia/Rangoon", "Asia/Rangoon"),
|
||||
("Asia/Riyadh", "Asia/Riyadh"),
|
||||
("Asia/Saigon", "Asia/Saigon"),
|
||||
("Asia/Sakhalin", "Asia/Sakhalin"),
|
||||
("Asia/Samarkand", "Asia/Samarkand"),
|
||||
("Asia/Seoul", "Asia/Seoul"),
|
||||
("Asia/Shanghai", "Asia/Shanghai"),
|
||||
("Asia/Singapore", "Asia/Singapore"),
|
||||
("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
|
||||
("Asia/Taipei", "Asia/Taipei"),
|
||||
("Asia/Tashkent", "Asia/Tashkent"),
|
||||
("Asia/Tbilisi", "Asia/Tbilisi"),
|
||||
("Asia/Tehran", "Asia/Tehran"),
|
||||
("Asia/Tel_Aviv", "Asia/Tel_Aviv"),
|
||||
("Asia/Thimbu", "Asia/Thimbu"),
|
||||
("Asia/Thimphu", "Asia/Thimphu"),
|
||||
("Asia/Tokyo", "Asia/Tokyo"),
|
||||
("Asia/Tomsk", "Asia/Tomsk"),
|
||||
("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"),
|
||||
("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
|
||||
("Asia/Ulan_Bator", "Asia/Ulan_Bator"),
|
||||
("Asia/Urumqi", "Asia/Urumqi"),
|
||||
("Asia/Ust-Nera", "Asia/Ust-Nera"),
|
||||
("Asia/Vientiane", "Asia/Vientiane"),
|
||||
("Asia/Vladivostok", "Asia/Vladivostok"),
|
||||
("Asia/Yakutsk", "Asia/Yakutsk"),
|
||||
("Asia/Yangon", "Asia/Yangon"),
|
||||
("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
|
||||
("Asia/Yerevan", "Asia/Yerevan"),
|
||||
("Atlantic/Azores", "Atlantic/Azores"),
|
||||
("Atlantic/Bermuda", "Atlantic/Bermuda"),
|
||||
("Atlantic/Canary", "Atlantic/Canary"),
|
||||
("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
|
||||
("Atlantic/Faeroe", "Atlantic/Faeroe"),
|
||||
("Atlantic/Faroe", "Atlantic/Faroe"),
|
||||
("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"),
|
||||
("Atlantic/Madeira", "Atlantic/Madeira"),
|
||||
("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
|
||||
("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
|
||||
("Atlantic/St_Helena", "Atlantic/St_Helena"),
|
||||
("Atlantic/Stanley", "Atlantic/Stanley"),
|
||||
("Australia/ACT", "Australia/ACT"),
|
||||
("Australia/Adelaide", "Australia/Adelaide"),
|
||||
("Australia/Brisbane", "Australia/Brisbane"),
|
||||
("Australia/Broken_Hill", "Australia/Broken_Hill"),
|
||||
("Australia/Canberra", "Australia/Canberra"),
|
||||
("Australia/Currie", "Australia/Currie"),
|
||||
("Australia/Darwin", "Australia/Darwin"),
|
||||
("Australia/Eucla", "Australia/Eucla"),
|
||||
("Australia/Hobart", "Australia/Hobart"),
|
||||
("Australia/LHI", "Australia/LHI"),
|
||||
("Australia/Lindeman", "Australia/Lindeman"),
|
||||
("Australia/Lord_Howe", "Australia/Lord_Howe"),
|
||||
("Australia/Melbourne", "Australia/Melbourne"),
|
||||
("Australia/NSW", "Australia/NSW"),
|
||||
("Australia/North", "Australia/North"),
|
||||
("Australia/Perth", "Australia/Perth"),
|
||||
("Australia/Queensland", "Australia/Queensland"),
|
||||
("Australia/South", "Australia/South"),
|
||||
("Australia/Sydney", "Australia/Sydney"),
|
||||
("Australia/Tasmania", "Australia/Tasmania"),
|
||||
("Australia/Victoria", "Australia/Victoria"),
|
||||
("Australia/West", "Australia/West"),
|
||||
("Australia/Yancowinna", "Australia/Yancowinna"),
|
||||
("Brazil/Acre", "Brazil/Acre"),
|
||||
("Brazil/DeNoronha", "Brazil/DeNoronha"),
|
||||
("Brazil/East", "Brazil/East"),
|
||||
("Brazil/West", "Brazil/West"),
|
||||
("CET", "CET"),
|
||||
("CST6CDT", "CST6CDT"),
|
||||
("Canada/Atlantic", "Canada/Atlantic"),
|
||||
("Canada/Central", "Canada/Central"),
|
||||
("Canada/Eastern", "Canada/Eastern"),
|
||||
("Canada/Mountain", "Canada/Mountain"),
|
||||
("Canada/Newfoundland", "Canada/Newfoundland"),
|
||||
("Canada/Pacific", "Canada/Pacific"),
|
||||
("Canada/Saskatchewan", "Canada/Saskatchewan"),
|
||||
("Canada/Yukon", "Canada/Yukon"),
|
||||
("Chile/Continental", "Chile/Continental"),
|
||||
("Chile/EasterIsland", "Chile/EasterIsland"),
|
||||
("Cuba", "Cuba"),
|
||||
("EET", "EET"),
|
||||
("EST", "EST"),
|
||||
("EST5EDT", "EST5EDT"),
|
||||
("Egypt", "Egypt"),
|
||||
("Eire", "Eire"),
|
||||
("Etc/GMT", "Etc/GMT"),
|
||||
("Etc/GMT+0", "Etc/GMT+0"),
|
||||
("Etc/GMT+1", "Etc/GMT+1"),
|
||||
("Etc/GMT+10", "Etc/GMT+10"),
|
||||
("Etc/GMT+11", "Etc/GMT+11"),
|
||||
("Etc/GMT+12", "Etc/GMT+12"),
|
||||
("Etc/GMT+2", "Etc/GMT+2"),
|
||||
("Etc/GMT+3", "Etc/GMT+3"),
|
||||
("Etc/GMT+4", "Etc/GMT+4"),
|
||||
("Etc/GMT+5", "Etc/GMT+5"),
|
||||
("Etc/GMT+6", "Etc/GMT+6"),
|
||||
("Etc/GMT+7", "Etc/GMT+7"),
|
||||
("Etc/GMT+8", "Etc/GMT+8"),
|
||||
("Etc/GMT+9", "Etc/GMT+9"),
|
||||
("Etc/GMT-0", "Etc/GMT-0"),
|
||||
("Etc/GMT-1", "Etc/GMT-1"),
|
||||
("Etc/GMT-10", "Etc/GMT-10"),
|
||||
("Etc/GMT-11", "Etc/GMT-11"),
|
||||
("Etc/GMT-12", "Etc/GMT-12"),
|
||||
("Etc/GMT-13", "Etc/GMT-13"),
|
||||
("Etc/GMT-14", "Etc/GMT-14"),
|
||||
("Etc/GMT-2", "Etc/GMT-2"),
|
||||
("Etc/GMT-3", "Etc/GMT-3"),
|
||||
("Etc/GMT-4", "Etc/GMT-4"),
|
||||
("Etc/GMT-5", "Etc/GMT-5"),
|
||||
("Etc/GMT-6", "Etc/GMT-6"),
|
||||
("Etc/GMT-7", "Etc/GMT-7"),
|
||||
("Etc/GMT-8", "Etc/GMT-8"),
|
||||
("Etc/GMT-9", "Etc/GMT-9"),
|
||||
("Etc/GMT0", "Etc/GMT0"),
|
||||
("Etc/Greenwich", "Etc/Greenwich"),
|
||||
("Etc/UCT", "Etc/UCT"),
|
||||
("Etc/UTC", "Etc/UTC"),
|
||||
("Etc/Universal", "Etc/Universal"),
|
||||
("Etc/Zulu", "Etc/Zulu"),
|
||||
("Europe/Amsterdam", "Europe/Amsterdam"),
|
||||
("Europe/Andorra", "Europe/Andorra"),
|
||||
("Europe/Astrakhan", "Europe/Astrakhan"),
|
||||
("Europe/Athens", "Europe/Athens"),
|
||||
("Europe/Belfast", "Europe/Belfast"),
|
||||
("Europe/Belgrade", "Europe/Belgrade"),
|
||||
("Europe/Berlin", "Europe/Berlin"),
|
||||
("Europe/Bratislava", "Europe/Bratislava"),
|
||||
("Europe/Brussels", "Europe/Brussels"),
|
||||
("Europe/Bucharest", "Europe/Bucharest"),
|
||||
("Europe/Budapest", "Europe/Budapest"),
|
||||
("Europe/Busingen", "Europe/Busingen"),
|
||||
("Europe/Chisinau", "Europe/Chisinau"),
|
||||
("Europe/Copenhagen", "Europe/Copenhagen"),
|
||||
("Europe/Dublin", "Europe/Dublin"),
|
||||
("Europe/Gibraltar", "Europe/Gibraltar"),
|
||||
("Europe/Guernsey", "Europe/Guernsey"),
|
||||
("Europe/Helsinki", "Europe/Helsinki"),
|
||||
("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
|
||||
("Europe/Istanbul", "Europe/Istanbul"),
|
||||
("Europe/Jersey", "Europe/Jersey"),
|
||||
("Europe/Kaliningrad", "Europe/Kaliningrad"),
|
||||
("Europe/Kiev", "Europe/Kiev"),
|
||||
("Europe/Kirov", "Europe/Kirov"),
|
||||
("Europe/Kyiv", "Europe/Kyiv"),
|
||||
("Europe/Lisbon", "Europe/Lisbon"),
|
||||
("Europe/Ljubljana", "Europe/Ljubljana"),
|
||||
("Europe/London", "Europe/London"),
|
||||
("Europe/Luxembourg", "Europe/Luxembourg"),
|
||||
("Europe/Madrid", "Europe/Madrid"),
|
||||
("Europe/Malta", "Europe/Malta"),
|
||||
("Europe/Mariehamn", "Europe/Mariehamn"),
|
||||
("Europe/Minsk", "Europe/Minsk"),
|
||||
("Europe/Monaco", "Europe/Monaco"),
|
||||
("Europe/Moscow", "Europe/Moscow"),
|
||||
("Europe/Nicosia", "Europe/Nicosia"),
|
||||
("Europe/Oslo", "Europe/Oslo"),
|
||||
("Europe/Paris", "Europe/Paris"),
|
||||
("Europe/Podgorica", "Europe/Podgorica"),
|
||||
("Europe/Prague", "Europe/Prague"),
|
||||
("Europe/Riga", "Europe/Riga"),
|
||||
("Europe/Rome", "Europe/Rome"),
|
||||
("Europe/Samara", "Europe/Samara"),
|
||||
("Europe/San_Marino", "Europe/San_Marino"),
|
||||
("Europe/Sarajevo", "Europe/Sarajevo"),
|
||||
("Europe/Saratov", "Europe/Saratov"),
|
||||
("Europe/Simferopol", "Europe/Simferopol"),
|
||||
("Europe/Skopje", "Europe/Skopje"),
|
||||
("Europe/Sofia", "Europe/Sofia"),
|
||||
("Europe/Stockholm", "Europe/Stockholm"),
|
||||
("Europe/Tallinn", "Europe/Tallinn"),
|
||||
("Europe/Tirane", "Europe/Tirane"),
|
||||
("Europe/Tiraspol", "Europe/Tiraspol"),
|
||||
("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
|
||||
("Europe/Uzhgorod", "Europe/Uzhgorod"),
|
||||
("Europe/Vaduz", "Europe/Vaduz"),
|
||||
("Europe/Vatican", "Europe/Vatican"),
|
||||
("Europe/Vienna", "Europe/Vienna"),
|
||||
("Europe/Vilnius", "Europe/Vilnius"),
|
||||
("Europe/Volgograd", "Europe/Volgograd"),
|
||||
("Europe/Warsaw", "Europe/Warsaw"),
|
||||
("Europe/Zagreb", "Europe/Zagreb"),
|
||||
("Europe/Zaporozhye", "Europe/Zaporozhye"),
|
||||
("Europe/Zurich", "Europe/Zurich"),
|
||||
("GB", "GB"),
|
||||
("GB-Eire", "GB-Eire"),
|
||||
("GMT", "GMT"),
|
||||
("GMT+0", "GMT+0"),
|
||||
("GMT-0", "GMT-0"),
|
||||
("GMT0", "GMT0"),
|
||||
("Greenwich", "Greenwich"),
|
||||
("HST", "HST"),
|
||||
("Hongkong", "Hongkong"),
|
||||
("Iceland", "Iceland"),
|
||||
("Indian/Antananarivo", "Indian/Antananarivo"),
|
||||
("Indian/Chagos", "Indian/Chagos"),
|
||||
("Indian/Christmas", "Indian/Christmas"),
|
||||
("Indian/Cocos", "Indian/Cocos"),
|
||||
("Indian/Comoro", "Indian/Comoro"),
|
||||
("Indian/Kerguelen", "Indian/Kerguelen"),
|
||||
("Indian/Mahe", "Indian/Mahe"),
|
||||
("Indian/Maldives", "Indian/Maldives"),
|
||||
("Indian/Mauritius", "Indian/Mauritius"),
|
||||
("Indian/Mayotte", "Indian/Mayotte"),
|
||||
("Indian/Reunion", "Indian/Reunion"),
|
||||
("Iran", "Iran"),
|
||||
("Israel", "Israel"),
|
||||
("Jamaica", "Jamaica"),
|
||||
("Japan", "Japan"),
|
||||
("Kwajalein", "Kwajalein"),
|
||||
("Libya", "Libya"),
|
||||
("MET", "MET"),
|
||||
("MST", "MST"),
|
||||
("MST7MDT", "MST7MDT"),
|
||||
("Mexico/BajaNorte", "Mexico/BajaNorte"),
|
||||
("Mexico/BajaSur", "Mexico/BajaSur"),
|
||||
("Mexico/General", "Mexico/General"),
|
||||
("NZ", "NZ"),
|
||||
("NZ-CHAT", "NZ-CHAT"),
|
||||
("Navajo", "Navajo"),
|
||||
("PRC", "PRC"),
|
||||
("PST8PDT", "PST8PDT"),
|
||||
("Pacific/Apia", "Pacific/Apia"),
|
||||
("Pacific/Auckland", "Pacific/Auckland"),
|
||||
("Pacific/Bougainville", "Pacific/Bougainville"),
|
||||
("Pacific/Chatham", "Pacific/Chatham"),
|
||||
("Pacific/Chuuk", "Pacific/Chuuk"),
|
||||
("Pacific/Easter", "Pacific/Easter"),
|
||||
("Pacific/Efate", "Pacific/Efate"),
|
||||
("Pacific/Enderbury", "Pacific/Enderbury"),
|
||||
("Pacific/Fakaofo", "Pacific/Fakaofo"),
|
||||
("Pacific/Fiji", "Pacific/Fiji"),
|
||||
("Pacific/Funafuti", "Pacific/Funafuti"),
|
||||
("Pacific/Galapagos", "Pacific/Galapagos"),
|
||||
("Pacific/Gambier", "Pacific/Gambier"),
|
||||
("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
|
||||
("Pacific/Guam", "Pacific/Guam"),
|
||||
("Pacific/Honolulu", "Pacific/Honolulu"),
|
||||
("Pacific/Johnston", "Pacific/Johnston"),
|
||||
("Pacific/Kanton", "Pacific/Kanton"),
|
||||
("Pacific/Kiritimati", "Pacific/Kiritimati"),
|
||||
("Pacific/Kosrae", "Pacific/Kosrae"),
|
||||
("Pacific/Kwajalein", "Pacific/Kwajalein"),
|
||||
("Pacific/Majuro", "Pacific/Majuro"),
|
||||
("Pacific/Marquesas", "Pacific/Marquesas"),
|
||||
("Pacific/Midway", "Pacific/Midway"),
|
||||
("Pacific/Nauru", "Pacific/Nauru"),
|
||||
("Pacific/Niue", "Pacific/Niue"),
|
||||
("Pacific/Norfolk", "Pacific/Norfolk"),
|
||||
("Pacific/Noumea", "Pacific/Noumea"),
|
||||
("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
|
||||
("Pacific/Palau", "Pacific/Palau"),
|
||||
("Pacific/Pitcairn", "Pacific/Pitcairn"),
|
||||
("Pacific/Pohnpei", "Pacific/Pohnpei"),
|
||||
("Pacific/Ponape", "Pacific/Ponape"),
|
||||
("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
|
||||
("Pacific/Rarotonga", "Pacific/Rarotonga"),
|
||||
("Pacific/Saipan", "Pacific/Saipan"),
|
||||
("Pacific/Samoa", "Pacific/Samoa"),
|
||||
("Pacific/Tahiti", "Pacific/Tahiti"),
|
||||
("Pacific/Tarawa", "Pacific/Tarawa"),
|
||||
("Pacific/Tongatapu", "Pacific/Tongatapu"),
|
||||
("Pacific/Truk", "Pacific/Truk"),
|
||||
("Pacific/Wake", "Pacific/Wake"),
|
||||
("Pacific/Wallis", "Pacific/Wallis"),
|
||||
("Pacific/Yap", "Pacific/Yap"),
|
||||
("Poland", "Poland"),
|
||||
("Portugal", "Portugal"),
|
||||
("ROC", "ROC"),
|
||||
("ROK", "ROK"),
|
||||
("Singapore", "Singapore"),
|
||||
("Turkey", "Turkey"),
|
||||
("UCT", "UCT"),
|
||||
("US/Alaska", "US/Alaska"),
|
||||
("US/Aleutian", "US/Aleutian"),
|
||||
("US/Arizona", "US/Arizona"),
|
||||
("US/Central", "US/Central"),
|
||||
("US/East-Indiana", "US/East-Indiana"),
|
||||
("US/Eastern", "US/Eastern"),
|
||||
("US/Hawaii", "US/Hawaii"),
|
||||
("US/Indiana-Starke", "US/Indiana-Starke"),
|
||||
("US/Michigan", "US/Michigan"),
|
||||
("US/Mountain", "US/Mountain"),
|
||||
("US/Pacific", "US/Pacific"),
|
||||
("US/Samoa", "US/Samoa"),
|
||||
("UTC", "UTC"),
|
||||
("Universal", "Universal"),
|
||||
("W-SU", "W-SU"),
|
||||
("WET", "WET"),
|
||||
("Zulu", "Zulu"),
|
||||
],
|
||||
max_length=255,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
||||
631
api/tacticalrmm/agents/migrations/0056_alter_agent_time_zone.py
Normal file
631
api/tacticalrmm/agents/migrations/0056_alter_agent_time_zone.py
Normal file
@@ -0,0 +1,631 @@
|
||||
# Generated by Django 4.1.7 on 2023-02-28 22:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("agents", "0055_alter_agent_time_zone"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="agent",
|
||||
name="time_zone",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("Africa/Abidjan", "Africa/Abidjan"),
|
||||
("Africa/Accra", "Africa/Accra"),
|
||||
("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
|
||||
("Africa/Algiers", "Africa/Algiers"),
|
||||
("Africa/Asmara", "Africa/Asmara"),
|
||||
("Africa/Asmera", "Africa/Asmera"),
|
||||
("Africa/Bamako", "Africa/Bamako"),
|
||||
("Africa/Bangui", "Africa/Bangui"),
|
||||
("Africa/Banjul", "Africa/Banjul"),
|
||||
("Africa/Bissau", "Africa/Bissau"),
|
||||
("Africa/Blantyre", "Africa/Blantyre"),
|
||||
("Africa/Brazzaville", "Africa/Brazzaville"),
|
||||
("Africa/Bujumbura", "Africa/Bujumbura"),
|
||||
("Africa/Cairo", "Africa/Cairo"),
|
||||
("Africa/Casablanca", "Africa/Casablanca"),
|
||||
("Africa/Ceuta", "Africa/Ceuta"),
|
||||
("Africa/Conakry", "Africa/Conakry"),
|
||||
("Africa/Dakar", "Africa/Dakar"),
|
||||
("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
|
||||
("Africa/Djibouti", "Africa/Djibouti"),
|
||||
("Africa/Douala", "Africa/Douala"),
|
||||
("Africa/El_Aaiun", "Africa/El_Aaiun"),
|
||||
("Africa/Freetown", "Africa/Freetown"),
|
||||
("Africa/Gaborone", "Africa/Gaborone"),
|
||||
("Africa/Harare", "Africa/Harare"),
|
||||
("Africa/Johannesburg", "Africa/Johannesburg"),
|
||||
("Africa/Juba", "Africa/Juba"),
|
||||
("Africa/Kampala", "Africa/Kampala"),
|
||||
("Africa/Khartoum", "Africa/Khartoum"),
|
||||
("Africa/Kigali", "Africa/Kigali"),
|
||||
("Africa/Kinshasa", "Africa/Kinshasa"),
|
||||
("Africa/Lagos", "Africa/Lagos"),
|
||||
("Africa/Libreville", "Africa/Libreville"),
|
||||
("Africa/Lome", "Africa/Lome"),
|
||||
("Africa/Luanda", "Africa/Luanda"),
|
||||
("Africa/Lubumbashi", "Africa/Lubumbashi"),
|
||||
("Africa/Lusaka", "Africa/Lusaka"),
|
||||
("Africa/Malabo", "Africa/Malabo"),
|
||||
("Africa/Maputo", "Africa/Maputo"),
|
||||
("Africa/Maseru", "Africa/Maseru"),
|
||||
("Africa/Mbabane", "Africa/Mbabane"),
|
||||
("Africa/Mogadishu", "Africa/Mogadishu"),
|
||||
("Africa/Monrovia", "Africa/Monrovia"),
|
||||
("Africa/Nairobi", "Africa/Nairobi"),
|
||||
("Africa/Ndjamena", "Africa/Ndjamena"),
|
||||
("Africa/Niamey", "Africa/Niamey"),
|
||||
("Africa/Nouakchott", "Africa/Nouakchott"),
|
||||
("Africa/Ouagadougou", "Africa/Ouagadougou"),
|
||||
("Africa/Porto-Novo", "Africa/Porto-Novo"),
|
||||
("Africa/Sao_Tome", "Africa/Sao_Tome"),
|
||||
("Africa/Timbuktu", "Africa/Timbuktu"),
|
||||
("Africa/Tripoli", "Africa/Tripoli"),
|
||||
("Africa/Tunis", "Africa/Tunis"),
|
||||
("Africa/Windhoek", "Africa/Windhoek"),
|
||||
("America/Adak", "America/Adak"),
|
||||
("America/Anchorage", "America/Anchorage"),
|
||||
("America/Anguilla", "America/Anguilla"),
|
||||
("America/Antigua", "America/Antigua"),
|
||||
("America/Araguaina", "America/Araguaina"),
|
||||
(
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"America/Argentina/Buenos_Aires",
|
||||
),
|
||||
("America/Argentina/Catamarca", "America/Argentina/Catamarca"),
|
||||
(
|
||||
"America/Argentina/ComodRivadavia",
|
||||
"America/Argentina/ComodRivadavia",
|
||||
),
|
||||
("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
|
||||
("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
|
||||
("America/Argentina/La_Rioja", "America/Argentina/La_Rioja"),
|
||||
("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
|
||||
(
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
),
|
||||
("America/Argentina/Salta", "America/Argentina/Salta"),
|
||||
("America/Argentina/San_Juan", "America/Argentina/San_Juan"),
|
||||
("America/Argentina/San_Luis", "America/Argentina/San_Luis"),
|
||||
("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
|
||||
("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
|
||||
("America/Aruba", "America/Aruba"),
|
||||
("America/Asuncion", "America/Asuncion"),
|
||||
("America/Atikokan", "America/Atikokan"),
|
||||
("America/Atka", "America/Atka"),
|
||||
("America/Bahia", "America/Bahia"),
|
||||
("America/Bahia_Banderas", "America/Bahia_Banderas"),
|
||||
("America/Barbados", "America/Barbados"),
|
||||
("America/Belem", "America/Belem"),
|
||||
("America/Belize", "America/Belize"),
|
||||
("America/Blanc-Sablon", "America/Blanc-Sablon"),
|
||||
("America/Boa_Vista", "America/Boa_Vista"),
|
||||
("America/Bogota", "America/Bogota"),
|
||||
("America/Boise", "America/Boise"),
|
||||
("America/Buenos_Aires", "America/Buenos_Aires"),
|
||||
("America/Cambridge_Bay", "America/Cambridge_Bay"),
|
||||
("America/Campo_Grande", "America/Campo_Grande"),
|
||||
("America/Cancun", "America/Cancun"),
|
||||
("America/Caracas", "America/Caracas"),
|
||||
("America/Catamarca", "America/Catamarca"),
|
||||
("America/Cayenne", "America/Cayenne"),
|
||||
("America/Cayman", "America/Cayman"),
|
||||
("America/Chicago", "America/Chicago"),
|
||||
("America/Chihuahua", "America/Chihuahua"),
|
||||
("America/Ciudad_Juarez", "America/Ciudad_Juarez"),
|
||||
("America/Coral_Harbour", "America/Coral_Harbour"),
|
||||
("America/Cordoba", "America/Cordoba"),
|
||||
("America/Costa_Rica", "America/Costa_Rica"),
|
||||
("America/Creston", "America/Creston"),
|
||||
("America/Cuiaba", "America/Cuiaba"),
|
||||
("America/Curacao", "America/Curacao"),
|
||||
("America/Danmarkshavn", "America/Danmarkshavn"),
|
||||
("America/Dawson", "America/Dawson"),
|
||||
("America/Dawson_Creek", "America/Dawson_Creek"),
|
||||
("America/Denver", "America/Denver"),
|
||||
("America/Detroit", "America/Detroit"),
|
||||
("America/Dominica", "America/Dominica"),
|
||||
("America/Edmonton", "America/Edmonton"),
|
||||
("America/Eirunepe", "America/Eirunepe"),
|
||||
("America/El_Salvador", "America/El_Salvador"),
|
||||
("America/Ensenada", "America/Ensenada"),
|
||||
("America/Fort_Nelson", "America/Fort_Nelson"),
|
||||
("America/Fort_Wayne", "America/Fort_Wayne"),
|
||||
("America/Fortaleza", "America/Fortaleza"),
|
||||
("America/Glace_Bay", "America/Glace_Bay"),
|
||||
("America/Godthab", "America/Godthab"),
|
||||
("America/Goose_Bay", "America/Goose_Bay"),
|
||||
("America/Grand_Turk", "America/Grand_Turk"),
|
||||
("America/Grenada", "America/Grenada"),
|
||||
("America/Guadeloupe", "America/Guadeloupe"),
|
||||
("America/Guatemala", "America/Guatemala"),
|
||||
("America/Guayaquil", "America/Guayaquil"),
|
||||
("America/Guyana", "America/Guyana"),
|
||||
("America/Halifax", "America/Halifax"),
|
||||
("America/Havana", "America/Havana"),
|
||||
("America/Hermosillo", "America/Hermosillo"),
|
||||
("America/Indiana/Indianapolis", "America/Indiana/Indianapolis"),
|
||||
("America/Indiana/Knox", "America/Indiana/Knox"),
|
||||
("America/Indiana/Marengo", "America/Indiana/Marengo"),
|
||||
("America/Indiana/Petersburg", "America/Indiana/Petersburg"),
|
||||
("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
|
||||
("America/Indiana/Vevay", "America/Indiana/Vevay"),
|
||||
("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
|
||||
("America/Indiana/Winamac", "America/Indiana/Winamac"),
|
||||
("America/Indianapolis", "America/Indianapolis"),
|
||||
("America/Inuvik", "America/Inuvik"),
|
||||
("America/Iqaluit", "America/Iqaluit"),
|
||||
("America/Jamaica", "America/Jamaica"),
|
||||
("America/Jujuy", "America/Jujuy"),
|
||||
("America/Juneau", "America/Juneau"),
|
||||
("America/Kentucky/Louisville", "America/Kentucky/Louisville"),
|
||||
("America/Kentucky/Monticello", "America/Kentucky/Monticello"),
|
||||
("America/Knox_IN", "America/Knox_IN"),
|
||||
("America/Kralendijk", "America/Kralendijk"),
|
||||
("America/La_Paz", "America/La_Paz"),
|
||||
("America/Lima", "America/Lima"),
|
||||
("America/Los_Angeles", "America/Los_Angeles"),
|
||||
("America/Louisville", "America/Louisville"),
|
||||
("America/Lower_Princes", "America/Lower_Princes"),
|
||||
("America/Maceio", "America/Maceio"),
|
||||
("America/Managua", "America/Managua"),
|
||||
("America/Manaus", "America/Manaus"),
|
||||
("America/Marigot", "America/Marigot"),
|
||||
("America/Martinique", "America/Martinique"),
|
||||
("America/Matamoros", "America/Matamoros"),
|
||||
("America/Mazatlan", "America/Mazatlan"),
|
||||
("America/Mendoza", "America/Mendoza"),
|
||||
("America/Menominee", "America/Menominee"),
|
||||
("America/Merida", "America/Merida"),
|
||||
("America/Metlakatla", "America/Metlakatla"),
|
||||
("America/Mexico_City", "America/Mexico_City"),
|
||||
("America/Miquelon", "America/Miquelon"),
|
||||
("America/Moncton", "America/Moncton"),
|
||||
("America/Monterrey", "America/Monterrey"),
|
||||
("America/Montevideo", "America/Montevideo"),
|
||||
("America/Montreal", "America/Montreal"),
|
||||
("America/Montserrat", "America/Montserrat"),
|
||||
("America/Nassau", "America/Nassau"),
|
||||
("America/New_York", "America/New_York"),
|
||||
("America/Nipigon", "America/Nipigon"),
|
||||
("America/Nome", "America/Nome"),
|
||||
("America/Noronha", "America/Noronha"),
|
||||
("America/North_Dakota/Beulah", "America/North_Dakota/Beulah"),
|
||||
("America/North_Dakota/Center", "America/North_Dakota/Center"),
|
||||
(
|
||||
"America/North_Dakota/New_Salem",
|
||||
"America/North_Dakota/New_Salem",
|
||||
),
|
||||
("America/Nuuk", "America/Nuuk"),
|
||||
("America/Ojinaga", "America/Ojinaga"),
|
||||
("America/Panama", "America/Panama"),
|
||||
("America/Pangnirtung", "America/Pangnirtung"),
|
||||
("America/Paramaribo", "America/Paramaribo"),
|
||||
("America/Phoenix", "America/Phoenix"),
|
||||
("America/Port-au-Prince", "America/Port-au-Prince"),
|
||||
("America/Port_of_Spain", "America/Port_of_Spain"),
|
||||
("America/Porto_Acre", "America/Porto_Acre"),
|
||||
("America/Porto_Velho", "America/Porto_Velho"),
|
||||
("America/Puerto_Rico", "America/Puerto_Rico"),
|
||||
("America/Punta_Arenas", "America/Punta_Arenas"),
|
||||
("America/Rainy_River", "America/Rainy_River"),
|
||||
("America/Rankin_Inlet", "America/Rankin_Inlet"),
|
||||
("America/Recife", "America/Recife"),
|
||||
("America/Regina", "America/Regina"),
|
||||
("America/Resolute", "America/Resolute"),
|
||||
("America/Rio_Branco", "America/Rio_Branco"),
|
||||
("America/Rosario", "America/Rosario"),
|
||||
("America/Santa_Isabel", "America/Santa_Isabel"),
|
||||
("America/Santarem", "America/Santarem"),
|
||||
("America/Santiago", "America/Santiago"),
|
||||
("America/Santo_Domingo", "America/Santo_Domingo"),
|
||||
("America/Sao_Paulo", "America/Sao_Paulo"),
|
||||
("America/Scoresbysund", "America/Scoresbysund"),
|
||||
("America/Shiprock", "America/Shiprock"),
|
||||
("America/Sitka", "America/Sitka"),
|
||||
("America/St_Barthelemy", "America/St_Barthelemy"),
|
||||
("America/St_Johns", "America/St_Johns"),
|
||||
("America/St_Kitts", "America/St_Kitts"),
|
||||
("America/St_Lucia", "America/St_Lucia"),
|
||||
("America/St_Thomas", "America/St_Thomas"),
|
||||
("America/St_Vincent", "America/St_Vincent"),
|
||||
("America/Swift_Current", "America/Swift_Current"),
|
||||
("America/Tegucigalpa", "America/Tegucigalpa"),
|
||||
("America/Thule", "America/Thule"),
|
||||
("America/Thunder_Bay", "America/Thunder_Bay"),
|
||||
("America/Tijuana", "America/Tijuana"),
|
||||
("America/Toronto", "America/Toronto"),
|
||||
("America/Tortola", "America/Tortola"),
|
||||
("America/Vancouver", "America/Vancouver"),
|
||||
("America/Virgin", "America/Virgin"),
|
||||
("America/Whitehorse", "America/Whitehorse"),
|
||||
("America/Winnipeg", "America/Winnipeg"),
|
||||
("America/Yakutat", "America/Yakutat"),
|
||||
("America/Yellowknife", "America/Yellowknife"),
|
||||
("Antarctica/Casey", "Antarctica/Casey"),
|
||||
("Antarctica/Davis", "Antarctica/Davis"),
|
||||
("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
|
||||
("Antarctica/Macquarie", "Antarctica/Macquarie"),
|
||||
("Antarctica/Mawson", "Antarctica/Mawson"),
|
||||
("Antarctica/McMurdo", "Antarctica/McMurdo"),
|
||||
("Antarctica/Palmer", "Antarctica/Palmer"),
|
||||
("Antarctica/Rothera", "Antarctica/Rothera"),
|
||||
("Antarctica/South_Pole", "Antarctica/South_Pole"),
|
||||
("Antarctica/Syowa", "Antarctica/Syowa"),
|
||||
("Antarctica/Troll", "Antarctica/Troll"),
|
||||
("Antarctica/Vostok", "Antarctica/Vostok"),
|
||||
("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
|
||||
("Asia/Aden", "Asia/Aden"),
|
||||
("Asia/Almaty", "Asia/Almaty"),
|
||||
("Asia/Amman", "Asia/Amman"),
|
||||
("Asia/Anadyr", "Asia/Anadyr"),
|
||||
("Asia/Aqtau", "Asia/Aqtau"),
|
||||
("Asia/Aqtobe", "Asia/Aqtobe"),
|
||||
("Asia/Ashgabat", "Asia/Ashgabat"),
|
||||
("Asia/Ashkhabad", "Asia/Ashkhabad"),
|
||||
("Asia/Atyrau", "Asia/Atyrau"),
|
||||
("Asia/Baghdad", "Asia/Baghdad"),
|
||||
("Asia/Bahrain", "Asia/Bahrain"),
|
||||
("Asia/Baku", "Asia/Baku"),
|
||||
("Asia/Bangkok", "Asia/Bangkok"),
|
||||
("Asia/Barnaul", "Asia/Barnaul"),
|
||||
("Asia/Beirut", "Asia/Beirut"),
|
||||
("Asia/Bishkek", "Asia/Bishkek"),
|
||||
("Asia/Brunei", "Asia/Brunei"),
|
||||
("Asia/Calcutta", "Asia/Calcutta"),
|
||||
("Asia/Chita", "Asia/Chita"),
|
||||
("Asia/Choibalsan", "Asia/Choibalsan"),
|
||||
("Asia/Chongqing", "Asia/Chongqing"),
|
||||
("Asia/Chungking", "Asia/Chungking"),
|
||||
("Asia/Colombo", "Asia/Colombo"),
|
||||
("Asia/Dacca", "Asia/Dacca"),
|
||||
("Asia/Damascus", "Asia/Damascus"),
|
||||
("Asia/Dhaka", "Asia/Dhaka"),
|
||||
("Asia/Dili", "Asia/Dili"),
|
||||
("Asia/Dubai", "Asia/Dubai"),
|
||||
("Asia/Dushanbe", "Asia/Dushanbe"),
|
||||
("Asia/Famagusta", "Asia/Famagusta"),
|
||||
("Asia/Gaza", "Asia/Gaza"),
|
||||
("Asia/Harbin", "Asia/Harbin"),
|
||||
("Asia/Hebron", "Asia/Hebron"),
|
||||
("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
|
||||
("Asia/Hong_Kong", "Asia/Hong_Kong"),
|
||||
("Asia/Hovd", "Asia/Hovd"),
|
||||
("Asia/Irkutsk", "Asia/Irkutsk"),
|
||||
("Asia/Istanbul", "Asia/Istanbul"),
|
||||
("Asia/Jakarta", "Asia/Jakarta"),
|
||||
("Asia/Jayapura", "Asia/Jayapura"),
|
||||
("Asia/Jerusalem", "Asia/Jerusalem"),
|
||||
("Asia/Kabul", "Asia/Kabul"),
|
||||
("Asia/Kamchatka", "Asia/Kamchatka"),
|
||||
("Asia/Karachi", "Asia/Karachi"),
|
||||
("Asia/Kashgar", "Asia/Kashgar"),
|
||||
("Asia/Kathmandu", "Asia/Kathmandu"),
|
||||
("Asia/Katmandu", "Asia/Katmandu"),
|
||||
("Asia/Khandyga", "Asia/Khandyga"),
|
||||
("Asia/Kolkata", "Asia/Kolkata"),
|
||||
("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
|
||||
("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
|
||||
("Asia/Kuching", "Asia/Kuching"),
|
||||
("Asia/Kuwait", "Asia/Kuwait"),
|
||||
("Asia/Macao", "Asia/Macao"),
|
||||
("Asia/Macau", "Asia/Macau"),
|
||||
("Asia/Magadan", "Asia/Magadan"),
|
||||
("Asia/Makassar", "Asia/Makassar"),
|
||||
("Asia/Manila", "Asia/Manila"),
|
||||
("Asia/Muscat", "Asia/Muscat"),
|
||||
("Asia/Nicosia", "Asia/Nicosia"),
|
||||
("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
|
||||
("Asia/Novosibirsk", "Asia/Novosibirsk"),
|
||||
("Asia/Omsk", "Asia/Omsk"),
|
||||
("Asia/Oral", "Asia/Oral"),
|
||||
("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
|
||||
("Asia/Pontianak", "Asia/Pontianak"),
|
||||
("Asia/Pyongyang", "Asia/Pyongyang"),
|
||||
("Asia/Qatar", "Asia/Qatar"),
|
||||
("Asia/Qostanay", "Asia/Qostanay"),
|
||||
("Asia/Qyzylorda", "Asia/Qyzylorda"),
|
||||
("Asia/Rangoon", "Asia/Rangoon"),
|
||||
("Asia/Riyadh", "Asia/Riyadh"),
|
||||
("Asia/Saigon", "Asia/Saigon"),
|
||||
("Asia/Sakhalin", "Asia/Sakhalin"),
|
||||
("Asia/Samarkand", "Asia/Samarkand"),
|
||||
("Asia/Seoul", "Asia/Seoul"),
|
||||
("Asia/Shanghai", "Asia/Shanghai"),
|
||||
("Asia/Singapore", "Asia/Singapore"),
|
||||
("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
|
||||
("Asia/Taipei", "Asia/Taipei"),
|
||||
("Asia/Tashkent", "Asia/Tashkent"),
|
||||
("Asia/Tbilisi", "Asia/Tbilisi"),
|
||||
("Asia/Tehran", "Asia/Tehran"),
|
||||
("Asia/Tel_Aviv", "Asia/Tel_Aviv"),
|
||||
("Asia/Thimbu", "Asia/Thimbu"),
|
||||
("Asia/Thimphu", "Asia/Thimphu"),
|
||||
("Asia/Tokyo", "Asia/Tokyo"),
|
||||
("Asia/Tomsk", "Asia/Tomsk"),
|
||||
("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"),
|
||||
("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
|
||||
("Asia/Ulan_Bator", "Asia/Ulan_Bator"),
|
||||
("Asia/Urumqi", "Asia/Urumqi"),
|
||||
("Asia/Ust-Nera", "Asia/Ust-Nera"),
|
||||
("Asia/Vientiane", "Asia/Vientiane"),
|
||||
("Asia/Vladivostok", "Asia/Vladivostok"),
|
||||
("Asia/Yakutsk", "Asia/Yakutsk"),
|
||||
("Asia/Yangon", "Asia/Yangon"),
|
||||
("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
|
||||
("Asia/Yerevan", "Asia/Yerevan"),
|
||||
("Atlantic/Azores", "Atlantic/Azores"),
|
||||
("Atlantic/Bermuda", "Atlantic/Bermuda"),
|
||||
("Atlantic/Canary", "Atlantic/Canary"),
|
||||
("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
|
||||
("Atlantic/Faeroe", "Atlantic/Faeroe"),
|
||||
("Atlantic/Faroe", "Atlantic/Faroe"),
|
||||
("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"),
|
||||
("Atlantic/Madeira", "Atlantic/Madeira"),
|
||||
("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
|
||||
("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
|
||||
("Atlantic/St_Helena", "Atlantic/St_Helena"),
|
||||
("Atlantic/Stanley", "Atlantic/Stanley"),
|
||||
("Australia/ACT", "Australia/ACT"),
|
||||
("Australia/Adelaide", "Australia/Adelaide"),
|
||||
("Australia/Brisbane", "Australia/Brisbane"),
|
||||
("Australia/Broken_Hill", "Australia/Broken_Hill"),
|
||||
("Australia/Canberra", "Australia/Canberra"),
|
||||
("Australia/Currie", "Australia/Currie"),
|
||||
("Australia/Darwin", "Australia/Darwin"),
|
||||
("Australia/Eucla", "Australia/Eucla"),
|
||||
("Australia/Hobart", "Australia/Hobart"),
|
||||
("Australia/LHI", "Australia/LHI"),
|
||||
("Australia/Lindeman", "Australia/Lindeman"),
|
||||
("Australia/Lord_Howe", "Australia/Lord_Howe"),
|
||||
("Australia/Melbourne", "Australia/Melbourne"),
|
||||
("Australia/NSW", "Australia/NSW"),
|
||||
("Australia/North", "Australia/North"),
|
||||
("Australia/Perth", "Australia/Perth"),
|
||||
("Australia/Queensland", "Australia/Queensland"),
|
||||
("Australia/South", "Australia/South"),
|
||||
("Australia/Sydney", "Australia/Sydney"),
|
||||
("Australia/Tasmania", "Australia/Tasmania"),
|
||||
("Australia/Victoria", "Australia/Victoria"),
|
||||
("Australia/West", "Australia/West"),
|
||||
("Australia/Yancowinna", "Australia/Yancowinna"),
|
||||
("Brazil/Acre", "Brazil/Acre"),
|
||||
("Brazil/DeNoronha", "Brazil/DeNoronha"),
|
||||
("Brazil/East", "Brazil/East"),
|
||||
("Brazil/West", "Brazil/West"),
|
||||
("CET", "CET"),
|
||||
("CST6CDT", "CST6CDT"),
|
||||
("Canada/Atlantic", "Canada/Atlantic"),
|
||||
("Canada/Central", "Canada/Central"),
|
||||
("Canada/Eastern", "Canada/Eastern"),
|
||||
("Canada/Mountain", "Canada/Mountain"),
|
||||
("Canada/Newfoundland", "Canada/Newfoundland"),
|
||||
("Canada/Pacific", "Canada/Pacific"),
|
||||
("Canada/Saskatchewan", "Canada/Saskatchewan"),
|
||||
("Canada/Yukon", "Canada/Yukon"),
|
||||
("Chile/Continental", "Chile/Continental"),
|
||||
("Chile/EasterIsland", "Chile/EasterIsland"),
|
||||
("Cuba", "Cuba"),
|
||||
("EET", "EET"),
|
||||
("EST", "EST"),
|
||||
("EST5EDT", "EST5EDT"),
|
||||
("Egypt", "Egypt"),
|
||||
("Eire", "Eire"),
|
||||
("Etc/GMT", "Etc/GMT"),
|
||||
("Etc/GMT+0", "Etc/GMT+0"),
|
||||
("Etc/GMT+1", "Etc/GMT+1"),
|
||||
("Etc/GMT+10", "Etc/GMT+10"),
|
||||
("Etc/GMT+11", "Etc/GMT+11"),
|
||||
("Etc/GMT+12", "Etc/GMT+12"),
|
||||
("Etc/GMT+2", "Etc/GMT+2"),
|
||||
("Etc/GMT+3", "Etc/GMT+3"),
|
||||
("Etc/GMT+4", "Etc/GMT+4"),
|
||||
("Etc/GMT+5", "Etc/GMT+5"),
|
||||
("Etc/GMT+6", "Etc/GMT+6"),
|
||||
("Etc/GMT+7", "Etc/GMT+7"),
|
||||
("Etc/GMT+8", "Etc/GMT+8"),
|
||||
("Etc/GMT+9", "Etc/GMT+9"),
|
||||
("Etc/GMT-0", "Etc/GMT-0"),
|
||||
("Etc/GMT-1", "Etc/GMT-1"),
|
||||
("Etc/GMT-10", "Etc/GMT-10"),
|
||||
("Etc/GMT-11", "Etc/GMT-11"),
|
||||
("Etc/GMT-12", "Etc/GMT-12"),
|
||||
("Etc/GMT-13", "Etc/GMT-13"),
|
||||
("Etc/GMT-14", "Etc/GMT-14"),
|
||||
("Etc/GMT-2", "Etc/GMT-2"),
|
||||
("Etc/GMT-3", "Etc/GMT-3"),
|
||||
("Etc/GMT-4", "Etc/GMT-4"),
|
||||
("Etc/GMT-5", "Etc/GMT-5"),
|
||||
("Etc/GMT-6", "Etc/GMT-6"),
|
||||
("Etc/GMT-7", "Etc/GMT-7"),
|
||||
("Etc/GMT-8", "Etc/GMT-8"),
|
||||
("Etc/GMT-9", "Etc/GMT-9"),
|
||||
("Etc/GMT0", "Etc/GMT0"),
|
||||
("Etc/Greenwich", "Etc/Greenwich"),
|
||||
("Etc/UCT", "Etc/UCT"),
|
||||
("Etc/UTC", "Etc/UTC"),
|
||||
("Etc/Universal", "Etc/Universal"),
|
||||
("Etc/Zulu", "Etc/Zulu"),
|
||||
("Europe/Amsterdam", "Europe/Amsterdam"),
|
||||
("Europe/Andorra", "Europe/Andorra"),
|
||||
("Europe/Astrakhan", "Europe/Astrakhan"),
|
||||
("Europe/Athens", "Europe/Athens"),
|
||||
("Europe/Belfast", "Europe/Belfast"),
|
||||
("Europe/Belgrade", "Europe/Belgrade"),
|
||||
("Europe/Berlin", "Europe/Berlin"),
|
||||
("Europe/Bratislava", "Europe/Bratislava"),
|
||||
("Europe/Brussels", "Europe/Brussels"),
|
||||
("Europe/Bucharest", "Europe/Bucharest"),
|
||||
("Europe/Budapest", "Europe/Budapest"),
|
||||
("Europe/Busingen", "Europe/Busingen"),
|
||||
("Europe/Chisinau", "Europe/Chisinau"),
|
||||
("Europe/Copenhagen", "Europe/Copenhagen"),
|
||||
("Europe/Dublin", "Europe/Dublin"),
|
||||
("Europe/Gibraltar", "Europe/Gibraltar"),
|
||||
("Europe/Guernsey", "Europe/Guernsey"),
|
||||
("Europe/Helsinki", "Europe/Helsinki"),
|
||||
("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
|
||||
("Europe/Istanbul", "Europe/Istanbul"),
|
||||
("Europe/Jersey", "Europe/Jersey"),
|
||||
("Europe/Kaliningrad", "Europe/Kaliningrad"),
|
||||
("Europe/Kiev", "Europe/Kiev"),
|
||||
("Europe/Kirov", "Europe/Kirov"),
|
||||
("Europe/Kyiv", "Europe/Kyiv"),
|
||||
("Europe/Lisbon", "Europe/Lisbon"),
|
||||
("Europe/Ljubljana", "Europe/Ljubljana"),
|
||||
("Europe/London", "Europe/London"),
|
||||
("Europe/Luxembourg", "Europe/Luxembourg"),
|
||||
("Europe/Madrid", "Europe/Madrid"),
|
||||
("Europe/Malta", "Europe/Malta"),
|
||||
("Europe/Mariehamn", "Europe/Mariehamn"),
|
||||
("Europe/Minsk", "Europe/Minsk"),
|
||||
("Europe/Monaco", "Europe/Monaco"),
|
||||
("Europe/Moscow", "Europe/Moscow"),
|
||||
("Europe/Nicosia", "Europe/Nicosia"),
|
||||
("Europe/Oslo", "Europe/Oslo"),
|
||||
("Europe/Paris", "Europe/Paris"),
|
||||
("Europe/Podgorica", "Europe/Podgorica"),
|
||||
("Europe/Prague", "Europe/Prague"),
|
||||
("Europe/Riga", "Europe/Riga"),
|
||||
("Europe/Rome", "Europe/Rome"),
|
||||
("Europe/Samara", "Europe/Samara"),
|
||||
("Europe/San_Marino", "Europe/San_Marino"),
|
||||
("Europe/Sarajevo", "Europe/Sarajevo"),
|
||||
("Europe/Saratov", "Europe/Saratov"),
|
||||
("Europe/Simferopol", "Europe/Simferopol"),
|
||||
("Europe/Skopje", "Europe/Skopje"),
|
||||
("Europe/Sofia", "Europe/Sofia"),
|
||||
("Europe/Stockholm", "Europe/Stockholm"),
|
||||
("Europe/Tallinn", "Europe/Tallinn"),
|
||||
("Europe/Tirane", "Europe/Tirane"),
|
||||
("Europe/Tiraspol", "Europe/Tiraspol"),
|
||||
("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
|
||||
("Europe/Uzhgorod", "Europe/Uzhgorod"),
|
||||
("Europe/Vaduz", "Europe/Vaduz"),
|
||||
("Europe/Vatican", "Europe/Vatican"),
|
||||
("Europe/Vienna", "Europe/Vienna"),
|
||||
("Europe/Vilnius", "Europe/Vilnius"),
|
||||
("Europe/Volgograd", "Europe/Volgograd"),
|
||||
("Europe/Warsaw", "Europe/Warsaw"),
|
||||
("Europe/Zagreb", "Europe/Zagreb"),
|
||||
("Europe/Zaporozhye", "Europe/Zaporozhye"),
|
||||
("Europe/Zurich", "Europe/Zurich"),
|
||||
("GB", "GB"),
|
||||
("GB-Eire", "GB-Eire"),
|
||||
("GMT", "GMT"),
|
||||
("GMT+0", "GMT+0"),
|
||||
("GMT-0", "GMT-0"),
|
||||
("GMT0", "GMT0"),
|
||||
("Greenwich", "Greenwich"),
|
||||
("HST", "HST"),
|
||||
("Hongkong", "Hongkong"),
|
||||
("Iceland", "Iceland"),
|
||||
("Indian/Antananarivo", "Indian/Antananarivo"),
|
||||
("Indian/Chagos", "Indian/Chagos"),
|
||||
("Indian/Christmas", "Indian/Christmas"),
|
||||
("Indian/Cocos", "Indian/Cocos"),
|
||||
("Indian/Comoro", "Indian/Comoro"),
|
||||
("Indian/Kerguelen", "Indian/Kerguelen"),
|
||||
("Indian/Mahe", "Indian/Mahe"),
|
||||
("Indian/Maldives", "Indian/Maldives"),
|
||||
("Indian/Mauritius", "Indian/Mauritius"),
|
||||
("Indian/Mayotte", "Indian/Mayotte"),
|
||||
("Indian/Reunion", "Indian/Reunion"),
|
||||
("Iran", "Iran"),
|
||||
("Israel", "Israel"),
|
||||
("Jamaica", "Jamaica"),
|
||||
("Japan", "Japan"),
|
||||
("Kwajalein", "Kwajalein"),
|
||||
("Libya", "Libya"),
|
||||
("MET", "MET"),
|
||||
("MST", "MST"),
|
||||
("MST7MDT", "MST7MDT"),
|
||||
("Mexico/BajaNorte", "Mexico/BajaNorte"),
|
||||
("Mexico/BajaSur", "Mexico/BajaSur"),
|
||||
("Mexico/General", "Mexico/General"),
|
||||
("NZ", "NZ"),
|
||||
("NZ-CHAT", "NZ-CHAT"),
|
||||
("Navajo", "Navajo"),
|
||||
("PRC", "PRC"),
|
||||
("PST8PDT", "PST8PDT"),
|
||||
("Pacific/Apia", "Pacific/Apia"),
|
||||
("Pacific/Auckland", "Pacific/Auckland"),
|
||||
("Pacific/Bougainville", "Pacific/Bougainville"),
|
||||
("Pacific/Chatham", "Pacific/Chatham"),
|
||||
("Pacific/Chuuk", "Pacific/Chuuk"),
|
||||
("Pacific/Easter", "Pacific/Easter"),
|
||||
("Pacific/Efate", "Pacific/Efate"),
|
||||
("Pacific/Enderbury", "Pacific/Enderbury"),
|
||||
("Pacific/Fakaofo", "Pacific/Fakaofo"),
|
||||
("Pacific/Fiji", "Pacific/Fiji"),
|
||||
("Pacific/Funafuti", "Pacific/Funafuti"),
|
||||
("Pacific/Galapagos", "Pacific/Galapagos"),
|
||||
("Pacific/Gambier", "Pacific/Gambier"),
|
||||
("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
|
||||
("Pacific/Guam", "Pacific/Guam"),
|
||||
("Pacific/Honolulu", "Pacific/Honolulu"),
|
||||
("Pacific/Johnston", "Pacific/Johnston"),
|
||||
("Pacific/Kanton", "Pacific/Kanton"),
|
||||
("Pacific/Kiritimati", "Pacific/Kiritimati"),
|
||||
("Pacific/Kosrae", "Pacific/Kosrae"),
|
||||
("Pacific/Kwajalein", "Pacific/Kwajalein"),
|
||||
("Pacific/Majuro", "Pacific/Majuro"),
|
||||
("Pacific/Marquesas", "Pacific/Marquesas"),
|
||||
("Pacific/Midway", "Pacific/Midway"),
|
||||
("Pacific/Nauru", "Pacific/Nauru"),
|
||||
("Pacific/Niue", "Pacific/Niue"),
|
||||
("Pacific/Norfolk", "Pacific/Norfolk"),
|
||||
("Pacific/Noumea", "Pacific/Noumea"),
|
||||
("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
|
||||
("Pacific/Palau", "Pacific/Palau"),
|
||||
("Pacific/Pitcairn", "Pacific/Pitcairn"),
|
||||
("Pacific/Pohnpei", "Pacific/Pohnpei"),
|
||||
("Pacific/Ponape", "Pacific/Ponape"),
|
||||
("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
|
||||
("Pacific/Rarotonga", "Pacific/Rarotonga"),
|
||||
("Pacific/Saipan", "Pacific/Saipan"),
|
||||
("Pacific/Samoa", "Pacific/Samoa"),
|
||||
("Pacific/Tahiti", "Pacific/Tahiti"),
|
||||
("Pacific/Tarawa", "Pacific/Tarawa"),
|
||||
("Pacific/Tongatapu", "Pacific/Tongatapu"),
|
||||
("Pacific/Truk", "Pacific/Truk"),
|
||||
("Pacific/Wake", "Pacific/Wake"),
|
||||
("Pacific/Wallis", "Pacific/Wallis"),
|
||||
("Pacific/Yap", "Pacific/Yap"),
|
||||
("Poland", "Poland"),
|
||||
("Portugal", "Portugal"),
|
||||
("ROC", "ROC"),
|
||||
("ROK", "ROK"),
|
||||
("Singapore", "Singapore"),
|
||||
("Turkey", "Turkey"),
|
||||
("UCT", "UCT"),
|
||||
("US/Alaska", "US/Alaska"),
|
||||
("US/Aleutian", "US/Aleutian"),
|
||||
("US/Arizona", "US/Arizona"),
|
||||
("US/Central", "US/Central"),
|
||||
("US/East-Indiana", "US/East-Indiana"),
|
||||
("US/Eastern", "US/Eastern"),
|
||||
("US/Hawaii", "US/Hawaii"),
|
||||
("US/Indiana-Starke", "US/Indiana-Starke"),
|
||||
("US/Michigan", "US/Michigan"),
|
||||
("US/Mountain", "US/Mountain"),
|
||||
("US/Pacific", "US/Pacific"),
|
||||
("US/Samoa", "US/Samoa"),
|
||||
("UTC", "UTC"),
|
||||
("Universal", "Universal"),
|
||||
("W-SU", "W-SU"),
|
||||
("WET", "WET"),
|
||||
("Zulu", "Zulu"),
|
||||
],
|
||||
max_length=255,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.2.3 on 2023-07-18 01:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("core", "0037_coresettings_open_ai_model_and_more"),
|
||||
("agents", "0056_alter_agent_time_zone"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name="agentcustomfield",
|
||||
unique_together={("agent", "field")},
|
||||
),
|
||||
]
|
||||
633
api/tacticalrmm/agents/migrations/0058_alter_agent_time_zone.py
Normal file
633
api/tacticalrmm/agents/migrations/0058_alter_agent_time_zone.py
Normal file
@@ -0,0 +1,633 @@
|
||||
# Generated by Django 4.2.7 on 2023-11-09 19:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("agents", "0057_alter_agentcustomfield_unique_together"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="agent",
|
||||
name="time_zone",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("Africa/Abidjan", "Africa/Abidjan"),
|
||||
("Africa/Accra", "Africa/Accra"),
|
||||
("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
|
||||
("Africa/Algiers", "Africa/Algiers"),
|
||||
("Africa/Asmara", "Africa/Asmara"),
|
||||
("Africa/Asmera", "Africa/Asmera"),
|
||||
("Africa/Bamako", "Africa/Bamako"),
|
||||
("Africa/Bangui", "Africa/Bangui"),
|
||||
("Africa/Banjul", "Africa/Banjul"),
|
||||
("Africa/Bissau", "Africa/Bissau"),
|
||||
("Africa/Blantyre", "Africa/Blantyre"),
|
||||
("Africa/Brazzaville", "Africa/Brazzaville"),
|
||||
("Africa/Bujumbura", "Africa/Bujumbura"),
|
||||
("Africa/Cairo", "Africa/Cairo"),
|
||||
("Africa/Casablanca", "Africa/Casablanca"),
|
||||
("Africa/Ceuta", "Africa/Ceuta"),
|
||||
("Africa/Conakry", "Africa/Conakry"),
|
||||
("Africa/Dakar", "Africa/Dakar"),
|
||||
("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
|
||||
("Africa/Djibouti", "Africa/Djibouti"),
|
||||
("Africa/Douala", "Africa/Douala"),
|
||||
("Africa/El_Aaiun", "Africa/El_Aaiun"),
|
||||
("Africa/Freetown", "Africa/Freetown"),
|
||||
("Africa/Gaborone", "Africa/Gaborone"),
|
||||
("Africa/Harare", "Africa/Harare"),
|
||||
("Africa/Johannesburg", "Africa/Johannesburg"),
|
||||
("Africa/Juba", "Africa/Juba"),
|
||||
("Africa/Kampala", "Africa/Kampala"),
|
||||
("Africa/Khartoum", "Africa/Khartoum"),
|
||||
("Africa/Kigali", "Africa/Kigali"),
|
||||
("Africa/Kinshasa", "Africa/Kinshasa"),
|
||||
("Africa/Lagos", "Africa/Lagos"),
|
||||
("Africa/Libreville", "Africa/Libreville"),
|
||||
("Africa/Lome", "Africa/Lome"),
|
||||
("Africa/Luanda", "Africa/Luanda"),
|
||||
("Africa/Lubumbashi", "Africa/Lubumbashi"),
|
||||
("Africa/Lusaka", "Africa/Lusaka"),
|
||||
("Africa/Malabo", "Africa/Malabo"),
|
||||
("Africa/Maputo", "Africa/Maputo"),
|
||||
("Africa/Maseru", "Africa/Maseru"),
|
||||
("Africa/Mbabane", "Africa/Mbabane"),
|
||||
("Africa/Mogadishu", "Africa/Mogadishu"),
|
||||
("Africa/Monrovia", "Africa/Monrovia"),
|
||||
("Africa/Nairobi", "Africa/Nairobi"),
|
||||
("Africa/Ndjamena", "Africa/Ndjamena"),
|
||||
("Africa/Niamey", "Africa/Niamey"),
|
||||
("Africa/Nouakchott", "Africa/Nouakchott"),
|
||||
("Africa/Ouagadougou", "Africa/Ouagadougou"),
|
||||
("Africa/Porto-Novo", "Africa/Porto-Novo"),
|
||||
("Africa/Sao_Tome", "Africa/Sao_Tome"),
|
||||
("Africa/Timbuktu", "Africa/Timbuktu"),
|
||||
("Africa/Tripoli", "Africa/Tripoli"),
|
||||
("Africa/Tunis", "Africa/Tunis"),
|
||||
("Africa/Windhoek", "Africa/Windhoek"),
|
||||
("America/Adak", "America/Adak"),
|
||||
("America/Anchorage", "America/Anchorage"),
|
||||
("America/Anguilla", "America/Anguilla"),
|
||||
("America/Antigua", "America/Antigua"),
|
||||
("America/Araguaina", "America/Araguaina"),
|
||||
(
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"America/Argentina/Buenos_Aires",
|
||||
),
|
||||
("America/Argentina/Catamarca", "America/Argentina/Catamarca"),
|
||||
(
|
||||
"America/Argentina/ComodRivadavia",
|
||||
"America/Argentina/ComodRivadavia",
|
||||
),
|
||||
("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
|
||||
("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
|
||||
("America/Argentina/La_Rioja", "America/Argentina/La_Rioja"),
|
||||
("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
|
||||
(
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
),
|
||||
("America/Argentina/Salta", "America/Argentina/Salta"),
|
||||
("America/Argentina/San_Juan", "America/Argentina/San_Juan"),
|
||||
("America/Argentina/San_Luis", "America/Argentina/San_Luis"),
|
||||
("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
|
||||
("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
|
||||
("America/Aruba", "America/Aruba"),
|
||||
("America/Asuncion", "America/Asuncion"),
|
||||
("America/Atikokan", "America/Atikokan"),
|
||||
("America/Atka", "America/Atka"),
|
||||
("America/Bahia", "America/Bahia"),
|
||||
("America/Bahia_Banderas", "America/Bahia_Banderas"),
|
||||
("America/Barbados", "America/Barbados"),
|
||||
("America/Belem", "America/Belem"),
|
||||
("America/Belize", "America/Belize"),
|
||||
("America/Blanc-Sablon", "America/Blanc-Sablon"),
|
||||
("America/Boa_Vista", "America/Boa_Vista"),
|
||||
("America/Bogota", "America/Bogota"),
|
||||
("America/Boise", "America/Boise"),
|
||||
("America/Buenos_Aires", "America/Buenos_Aires"),
|
||||
("America/Cambridge_Bay", "America/Cambridge_Bay"),
|
||||
("America/Campo_Grande", "America/Campo_Grande"),
|
||||
("America/Cancun", "America/Cancun"),
|
||||
("America/Caracas", "America/Caracas"),
|
||||
("America/Catamarca", "America/Catamarca"),
|
||||
("America/Cayenne", "America/Cayenne"),
|
||||
("America/Cayman", "America/Cayman"),
|
||||
("America/Chicago", "America/Chicago"),
|
||||
("America/Chihuahua", "America/Chihuahua"),
|
||||
("America/Ciudad_Juarez", "America/Ciudad_Juarez"),
|
||||
("America/Coral_Harbour", "America/Coral_Harbour"),
|
||||
("America/Cordoba", "America/Cordoba"),
|
||||
("America/Costa_Rica", "America/Costa_Rica"),
|
||||
("America/Creston", "America/Creston"),
|
||||
("America/Cuiaba", "America/Cuiaba"),
|
||||
("America/Curacao", "America/Curacao"),
|
||||
("America/Danmarkshavn", "America/Danmarkshavn"),
|
||||
("America/Dawson", "America/Dawson"),
|
||||
("America/Dawson_Creek", "America/Dawson_Creek"),
|
||||
("America/Denver", "America/Denver"),
|
||||
("America/Detroit", "America/Detroit"),
|
||||
("America/Dominica", "America/Dominica"),
|
||||
("America/Edmonton", "America/Edmonton"),
|
||||
("America/Eirunepe", "America/Eirunepe"),
|
||||
("America/El_Salvador", "America/El_Salvador"),
|
||||
("America/Ensenada", "America/Ensenada"),
|
||||
("America/Fort_Nelson", "America/Fort_Nelson"),
|
||||
("America/Fort_Wayne", "America/Fort_Wayne"),
|
||||
("America/Fortaleza", "America/Fortaleza"),
|
||||
("America/Glace_Bay", "America/Glace_Bay"),
|
||||
("America/Godthab", "America/Godthab"),
|
||||
("America/Goose_Bay", "America/Goose_Bay"),
|
||||
("America/Grand_Turk", "America/Grand_Turk"),
|
||||
("America/Grenada", "America/Grenada"),
|
||||
("America/Guadeloupe", "America/Guadeloupe"),
|
||||
("America/Guatemala", "America/Guatemala"),
|
||||
("America/Guayaquil", "America/Guayaquil"),
|
||||
("America/Guyana", "America/Guyana"),
|
||||
("America/Halifax", "America/Halifax"),
|
||||
("America/Havana", "America/Havana"),
|
||||
("America/Hermosillo", "America/Hermosillo"),
|
||||
("America/Indiana/Indianapolis", "America/Indiana/Indianapolis"),
|
||||
("America/Indiana/Knox", "America/Indiana/Knox"),
|
||||
("America/Indiana/Marengo", "America/Indiana/Marengo"),
|
||||
("America/Indiana/Petersburg", "America/Indiana/Petersburg"),
|
||||
("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
|
||||
("America/Indiana/Vevay", "America/Indiana/Vevay"),
|
||||
("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
|
||||
("America/Indiana/Winamac", "America/Indiana/Winamac"),
|
||||
("America/Indianapolis", "America/Indianapolis"),
|
||||
("America/Inuvik", "America/Inuvik"),
|
||||
("America/Iqaluit", "America/Iqaluit"),
|
||||
("America/Jamaica", "America/Jamaica"),
|
||||
("America/Jujuy", "America/Jujuy"),
|
||||
("America/Juneau", "America/Juneau"),
|
||||
("America/Kentucky/Louisville", "America/Kentucky/Louisville"),
|
||||
("America/Kentucky/Monticello", "America/Kentucky/Monticello"),
|
||||
("America/Knox_IN", "America/Knox_IN"),
|
||||
("America/Kralendijk", "America/Kralendijk"),
|
||||
("America/La_Paz", "America/La_Paz"),
|
||||
("America/Lima", "America/Lima"),
|
||||
("America/Los_Angeles", "America/Los_Angeles"),
|
||||
("America/Louisville", "America/Louisville"),
|
||||
("America/Lower_Princes", "America/Lower_Princes"),
|
||||
("America/Maceio", "America/Maceio"),
|
||||
("America/Managua", "America/Managua"),
|
||||
("America/Manaus", "America/Manaus"),
|
||||
("America/Marigot", "America/Marigot"),
|
||||
("America/Martinique", "America/Martinique"),
|
||||
("America/Matamoros", "America/Matamoros"),
|
||||
("America/Mazatlan", "America/Mazatlan"),
|
||||
("America/Mendoza", "America/Mendoza"),
|
||||
("America/Menominee", "America/Menominee"),
|
||||
("America/Merida", "America/Merida"),
|
||||
("America/Metlakatla", "America/Metlakatla"),
|
||||
("America/Mexico_City", "America/Mexico_City"),
|
||||
("America/Miquelon", "America/Miquelon"),
|
||||
("America/Moncton", "America/Moncton"),
|
||||
("America/Monterrey", "America/Monterrey"),
|
||||
("America/Montevideo", "America/Montevideo"),
|
||||
("America/Montreal", "America/Montreal"),
|
||||
("America/Montserrat", "America/Montserrat"),
|
||||
("America/Nassau", "America/Nassau"),
|
||||
("America/New_York", "America/New_York"),
|
||||
("America/Nipigon", "America/Nipigon"),
|
||||
("America/Nome", "America/Nome"),
|
||||
("America/Noronha", "America/Noronha"),
|
||||
("America/North_Dakota/Beulah", "America/North_Dakota/Beulah"),
|
||||
("America/North_Dakota/Center", "America/North_Dakota/Center"),
|
||||
(
|
||||
"America/North_Dakota/New_Salem",
|
||||
"America/North_Dakota/New_Salem",
|
||||
),
|
||||
("America/Nuuk", "America/Nuuk"),
|
||||
("America/Ojinaga", "America/Ojinaga"),
|
||||
("America/Panama", "America/Panama"),
|
||||
("America/Pangnirtung", "America/Pangnirtung"),
|
||||
("America/Paramaribo", "America/Paramaribo"),
|
||||
("America/Phoenix", "America/Phoenix"),
|
||||
("America/Port-au-Prince", "America/Port-au-Prince"),
|
||||
("America/Port_of_Spain", "America/Port_of_Spain"),
|
||||
("America/Porto_Acre", "America/Porto_Acre"),
|
||||
("America/Porto_Velho", "America/Porto_Velho"),
|
||||
("America/Puerto_Rico", "America/Puerto_Rico"),
|
||||
("America/Punta_Arenas", "America/Punta_Arenas"),
|
||||
("America/Rainy_River", "America/Rainy_River"),
|
||||
("America/Rankin_Inlet", "America/Rankin_Inlet"),
|
||||
("America/Recife", "America/Recife"),
|
||||
("America/Regina", "America/Regina"),
|
||||
("America/Resolute", "America/Resolute"),
|
||||
("America/Rio_Branco", "America/Rio_Branco"),
|
||||
("America/Rosario", "America/Rosario"),
|
||||
("America/Santa_Isabel", "America/Santa_Isabel"),
|
||||
("America/Santarem", "America/Santarem"),
|
||||
("America/Santiago", "America/Santiago"),
|
||||
("America/Santo_Domingo", "America/Santo_Domingo"),
|
||||
("America/Sao_Paulo", "America/Sao_Paulo"),
|
||||
("America/Scoresbysund", "America/Scoresbysund"),
|
||||
("America/Shiprock", "America/Shiprock"),
|
||||
("America/Sitka", "America/Sitka"),
|
||||
("America/St_Barthelemy", "America/St_Barthelemy"),
|
||||
("America/St_Johns", "America/St_Johns"),
|
||||
("America/St_Kitts", "America/St_Kitts"),
|
||||
("America/St_Lucia", "America/St_Lucia"),
|
||||
("America/St_Thomas", "America/St_Thomas"),
|
||||
("America/St_Vincent", "America/St_Vincent"),
|
||||
("America/Swift_Current", "America/Swift_Current"),
|
||||
("America/Tegucigalpa", "America/Tegucigalpa"),
|
||||
("America/Thule", "America/Thule"),
|
||||
("America/Thunder_Bay", "America/Thunder_Bay"),
|
||||
("America/Tijuana", "America/Tijuana"),
|
||||
("America/Toronto", "America/Toronto"),
|
||||
("America/Tortola", "America/Tortola"),
|
||||
("America/Vancouver", "America/Vancouver"),
|
||||
("America/Virgin", "America/Virgin"),
|
||||
("America/Whitehorse", "America/Whitehorse"),
|
||||
("America/Winnipeg", "America/Winnipeg"),
|
||||
("America/Yakutat", "America/Yakutat"),
|
||||
("America/Yellowknife", "America/Yellowknife"),
|
||||
("Antarctica/Casey", "Antarctica/Casey"),
|
||||
("Antarctica/Davis", "Antarctica/Davis"),
|
||||
("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
|
||||
("Antarctica/Macquarie", "Antarctica/Macquarie"),
|
||||
("Antarctica/Mawson", "Antarctica/Mawson"),
|
||||
("Antarctica/McMurdo", "Antarctica/McMurdo"),
|
||||
("Antarctica/Palmer", "Antarctica/Palmer"),
|
||||
("Antarctica/Rothera", "Antarctica/Rothera"),
|
||||
("Antarctica/South_Pole", "Antarctica/South_Pole"),
|
||||
("Antarctica/Syowa", "Antarctica/Syowa"),
|
||||
("Antarctica/Troll", "Antarctica/Troll"),
|
||||
("Antarctica/Vostok", "Antarctica/Vostok"),
|
||||
("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
|
||||
("Asia/Aden", "Asia/Aden"),
|
||||
("Asia/Almaty", "Asia/Almaty"),
|
||||
("Asia/Amman", "Asia/Amman"),
|
||||
("Asia/Anadyr", "Asia/Anadyr"),
|
||||
("Asia/Aqtau", "Asia/Aqtau"),
|
||||
("Asia/Aqtobe", "Asia/Aqtobe"),
|
||||
("Asia/Ashgabat", "Asia/Ashgabat"),
|
||||
("Asia/Ashkhabad", "Asia/Ashkhabad"),
|
||||
("Asia/Atyrau", "Asia/Atyrau"),
|
||||
("Asia/Baghdad", "Asia/Baghdad"),
|
||||
("Asia/Bahrain", "Asia/Bahrain"),
|
||||
("Asia/Baku", "Asia/Baku"),
|
||||
("Asia/Bangkok", "Asia/Bangkok"),
|
||||
("Asia/Barnaul", "Asia/Barnaul"),
|
||||
("Asia/Beirut", "Asia/Beirut"),
|
||||
("Asia/Bishkek", "Asia/Bishkek"),
|
||||
("Asia/Brunei", "Asia/Brunei"),
|
||||
("Asia/Calcutta", "Asia/Calcutta"),
|
||||
("Asia/Chita", "Asia/Chita"),
|
||||
("Asia/Choibalsan", "Asia/Choibalsan"),
|
||||
("Asia/Chongqing", "Asia/Chongqing"),
|
||||
("Asia/Chungking", "Asia/Chungking"),
|
||||
("Asia/Colombo", "Asia/Colombo"),
|
||||
("Asia/Dacca", "Asia/Dacca"),
|
||||
("Asia/Damascus", "Asia/Damascus"),
|
||||
("Asia/Dhaka", "Asia/Dhaka"),
|
||||
("Asia/Dili", "Asia/Dili"),
|
||||
("Asia/Dubai", "Asia/Dubai"),
|
||||
("Asia/Dushanbe", "Asia/Dushanbe"),
|
||||
("Asia/Famagusta", "Asia/Famagusta"),
|
||||
("Asia/Gaza", "Asia/Gaza"),
|
||||
("Asia/Harbin", "Asia/Harbin"),
|
||||
("Asia/Hebron", "Asia/Hebron"),
|
||||
("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
|
||||
("Asia/Hong_Kong", "Asia/Hong_Kong"),
|
||||
("Asia/Hovd", "Asia/Hovd"),
|
||||
("Asia/Irkutsk", "Asia/Irkutsk"),
|
||||
("Asia/Istanbul", "Asia/Istanbul"),
|
||||
("Asia/Jakarta", "Asia/Jakarta"),
|
||||
("Asia/Jayapura", "Asia/Jayapura"),
|
||||
("Asia/Jerusalem", "Asia/Jerusalem"),
|
||||
("Asia/Kabul", "Asia/Kabul"),
|
||||
("Asia/Kamchatka", "Asia/Kamchatka"),
|
||||
("Asia/Karachi", "Asia/Karachi"),
|
||||
("Asia/Kashgar", "Asia/Kashgar"),
|
||||
("Asia/Kathmandu", "Asia/Kathmandu"),
|
||||
("Asia/Katmandu", "Asia/Katmandu"),
|
||||
("Asia/Khandyga", "Asia/Khandyga"),
|
||||
("Asia/Kolkata", "Asia/Kolkata"),
|
||||
("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
|
||||
("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
|
||||
("Asia/Kuching", "Asia/Kuching"),
|
||||
("Asia/Kuwait", "Asia/Kuwait"),
|
||||
("Asia/Macao", "Asia/Macao"),
|
||||
("Asia/Macau", "Asia/Macau"),
|
||||
("Asia/Magadan", "Asia/Magadan"),
|
||||
("Asia/Makassar", "Asia/Makassar"),
|
||||
("Asia/Manila", "Asia/Manila"),
|
||||
("Asia/Muscat", "Asia/Muscat"),
|
||||
("Asia/Nicosia", "Asia/Nicosia"),
|
||||
("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
|
||||
("Asia/Novosibirsk", "Asia/Novosibirsk"),
|
||||
("Asia/Omsk", "Asia/Omsk"),
|
||||
("Asia/Oral", "Asia/Oral"),
|
||||
("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
|
||||
("Asia/Pontianak", "Asia/Pontianak"),
|
||||
("Asia/Pyongyang", "Asia/Pyongyang"),
|
||||
("Asia/Qatar", "Asia/Qatar"),
|
||||
("Asia/Qostanay", "Asia/Qostanay"),
|
||||
("Asia/Qyzylorda", "Asia/Qyzylorda"),
|
||||
("Asia/Rangoon", "Asia/Rangoon"),
|
||||
("Asia/Riyadh", "Asia/Riyadh"),
|
||||
("Asia/Saigon", "Asia/Saigon"),
|
||||
("Asia/Sakhalin", "Asia/Sakhalin"),
|
||||
("Asia/Samarkand", "Asia/Samarkand"),
|
||||
("Asia/Seoul", "Asia/Seoul"),
|
||||
("Asia/Shanghai", "Asia/Shanghai"),
|
||||
("Asia/Singapore", "Asia/Singapore"),
|
||||
("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
|
||||
("Asia/Taipei", "Asia/Taipei"),
|
||||
("Asia/Tashkent", "Asia/Tashkent"),
|
||||
("Asia/Tbilisi", "Asia/Tbilisi"),
|
||||
("Asia/Tehran", "Asia/Tehran"),
|
||||
("Asia/Tel_Aviv", "Asia/Tel_Aviv"),
|
||||
("Asia/Thimbu", "Asia/Thimbu"),
|
||||
("Asia/Thimphu", "Asia/Thimphu"),
|
||||
("Asia/Tokyo", "Asia/Tokyo"),
|
||||
("Asia/Tomsk", "Asia/Tomsk"),
|
||||
("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"),
|
||||
("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
|
||||
("Asia/Ulan_Bator", "Asia/Ulan_Bator"),
|
||||
("Asia/Urumqi", "Asia/Urumqi"),
|
||||
("Asia/Ust-Nera", "Asia/Ust-Nera"),
|
||||
("Asia/Vientiane", "Asia/Vientiane"),
|
||||
("Asia/Vladivostok", "Asia/Vladivostok"),
|
||||
("Asia/Yakutsk", "Asia/Yakutsk"),
|
||||
("Asia/Yangon", "Asia/Yangon"),
|
||||
("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
|
||||
("Asia/Yerevan", "Asia/Yerevan"),
|
||||
("Atlantic/Azores", "Atlantic/Azores"),
|
||||
("Atlantic/Bermuda", "Atlantic/Bermuda"),
|
||||
("Atlantic/Canary", "Atlantic/Canary"),
|
||||
("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
|
||||
("Atlantic/Faeroe", "Atlantic/Faeroe"),
|
||||
("Atlantic/Faroe", "Atlantic/Faroe"),
|
||||
("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"),
|
||||
("Atlantic/Madeira", "Atlantic/Madeira"),
|
||||
("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
|
||||
("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
|
||||
("Atlantic/St_Helena", "Atlantic/St_Helena"),
|
||||
("Atlantic/Stanley", "Atlantic/Stanley"),
|
||||
("Australia/ACT", "Australia/ACT"),
|
||||
("Australia/Adelaide", "Australia/Adelaide"),
|
||||
("Australia/Brisbane", "Australia/Brisbane"),
|
||||
("Australia/Broken_Hill", "Australia/Broken_Hill"),
|
||||
("Australia/Canberra", "Australia/Canberra"),
|
||||
("Australia/Currie", "Australia/Currie"),
|
||||
("Australia/Darwin", "Australia/Darwin"),
|
||||
("Australia/Eucla", "Australia/Eucla"),
|
||||
("Australia/Hobart", "Australia/Hobart"),
|
||||
("Australia/LHI", "Australia/LHI"),
|
||||
("Australia/Lindeman", "Australia/Lindeman"),
|
||||
("Australia/Lord_Howe", "Australia/Lord_Howe"),
|
||||
("Australia/Melbourne", "Australia/Melbourne"),
|
||||
("Australia/NSW", "Australia/NSW"),
|
||||
("Australia/North", "Australia/North"),
|
||||
("Australia/Perth", "Australia/Perth"),
|
||||
("Australia/Queensland", "Australia/Queensland"),
|
||||
("Australia/South", "Australia/South"),
|
||||
("Australia/Sydney", "Australia/Sydney"),
|
||||
("Australia/Tasmania", "Australia/Tasmania"),
|
||||
("Australia/Victoria", "Australia/Victoria"),
|
||||
("Australia/West", "Australia/West"),
|
||||
("Australia/Yancowinna", "Australia/Yancowinna"),
|
||||
("Brazil/Acre", "Brazil/Acre"),
|
||||
("Brazil/DeNoronha", "Brazil/DeNoronha"),
|
||||
("Brazil/East", "Brazil/East"),
|
||||
("Brazil/West", "Brazil/West"),
|
||||
("CET", "CET"),
|
||||
("CST6CDT", "CST6CDT"),
|
||||
("Canada/Atlantic", "Canada/Atlantic"),
|
||||
("Canada/Central", "Canada/Central"),
|
||||
("Canada/Eastern", "Canada/Eastern"),
|
||||
("Canada/Mountain", "Canada/Mountain"),
|
||||
("Canada/Newfoundland", "Canada/Newfoundland"),
|
||||
("Canada/Pacific", "Canada/Pacific"),
|
||||
("Canada/Saskatchewan", "Canada/Saskatchewan"),
|
||||
("Canada/Yukon", "Canada/Yukon"),
|
||||
("Chile/Continental", "Chile/Continental"),
|
||||
("Chile/EasterIsland", "Chile/EasterIsland"),
|
||||
("Cuba", "Cuba"),
|
||||
("EET", "EET"),
|
||||
("EST", "EST"),
|
||||
("EST5EDT", "EST5EDT"),
|
||||
("Egypt", "Egypt"),
|
||||
("Eire", "Eire"),
|
||||
("Etc/GMT", "Etc/GMT"),
|
||||
("Etc/GMT+0", "Etc/GMT+0"),
|
||||
("Etc/GMT+1", "Etc/GMT+1"),
|
||||
("Etc/GMT+10", "Etc/GMT+10"),
|
||||
("Etc/GMT+11", "Etc/GMT+11"),
|
||||
("Etc/GMT+12", "Etc/GMT+12"),
|
||||
("Etc/GMT+2", "Etc/GMT+2"),
|
||||
("Etc/GMT+3", "Etc/GMT+3"),
|
||||
("Etc/GMT+4", "Etc/GMT+4"),
|
||||
("Etc/GMT+5", "Etc/GMT+5"),
|
||||
("Etc/GMT+6", "Etc/GMT+6"),
|
||||
("Etc/GMT+7", "Etc/GMT+7"),
|
||||
("Etc/GMT+8", "Etc/GMT+8"),
|
||||
("Etc/GMT+9", "Etc/GMT+9"),
|
||||
("Etc/GMT-0", "Etc/GMT-0"),
|
||||
("Etc/GMT-1", "Etc/GMT-1"),
|
||||
("Etc/GMT-10", "Etc/GMT-10"),
|
||||
("Etc/GMT-11", "Etc/GMT-11"),
|
||||
("Etc/GMT-12", "Etc/GMT-12"),
|
||||
("Etc/GMT-13", "Etc/GMT-13"),
|
||||
("Etc/GMT-14", "Etc/GMT-14"),
|
||||
("Etc/GMT-2", "Etc/GMT-2"),
|
||||
("Etc/GMT-3", "Etc/GMT-3"),
|
||||
("Etc/GMT-4", "Etc/GMT-4"),
|
||||
("Etc/GMT-5", "Etc/GMT-5"),
|
||||
("Etc/GMT-6", "Etc/GMT-6"),
|
||||
("Etc/GMT-7", "Etc/GMT-7"),
|
||||
("Etc/GMT-8", "Etc/GMT-8"),
|
||||
("Etc/GMT-9", "Etc/GMT-9"),
|
||||
("Etc/GMT0", "Etc/GMT0"),
|
||||
("Etc/Greenwich", "Etc/Greenwich"),
|
||||
("Etc/UCT", "Etc/UCT"),
|
||||
("Etc/UTC", "Etc/UTC"),
|
||||
("Etc/Universal", "Etc/Universal"),
|
||||
("Etc/Zulu", "Etc/Zulu"),
|
||||
("Europe/Amsterdam", "Europe/Amsterdam"),
|
||||
("Europe/Andorra", "Europe/Andorra"),
|
||||
("Europe/Astrakhan", "Europe/Astrakhan"),
|
||||
("Europe/Athens", "Europe/Athens"),
|
||||
("Europe/Belfast", "Europe/Belfast"),
|
||||
("Europe/Belgrade", "Europe/Belgrade"),
|
||||
("Europe/Berlin", "Europe/Berlin"),
|
||||
("Europe/Bratislava", "Europe/Bratislava"),
|
||||
("Europe/Brussels", "Europe/Brussels"),
|
||||
("Europe/Bucharest", "Europe/Bucharest"),
|
||||
("Europe/Budapest", "Europe/Budapest"),
|
||||
("Europe/Busingen", "Europe/Busingen"),
|
||||
("Europe/Chisinau", "Europe/Chisinau"),
|
||||
("Europe/Copenhagen", "Europe/Copenhagen"),
|
||||
("Europe/Dublin", "Europe/Dublin"),
|
||||
("Europe/Gibraltar", "Europe/Gibraltar"),
|
||||
("Europe/Guernsey", "Europe/Guernsey"),
|
||||
("Europe/Helsinki", "Europe/Helsinki"),
|
||||
("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
|
||||
("Europe/Istanbul", "Europe/Istanbul"),
|
||||
("Europe/Jersey", "Europe/Jersey"),
|
||||
("Europe/Kaliningrad", "Europe/Kaliningrad"),
|
||||
("Europe/Kiev", "Europe/Kiev"),
|
||||
("Europe/Kirov", "Europe/Kirov"),
|
||||
("Europe/Kyiv", "Europe/Kyiv"),
|
||||
("Europe/Lisbon", "Europe/Lisbon"),
|
||||
("Europe/Ljubljana", "Europe/Ljubljana"),
|
||||
("Europe/London", "Europe/London"),
|
||||
("Europe/Luxembourg", "Europe/Luxembourg"),
|
||||
("Europe/Madrid", "Europe/Madrid"),
|
||||
("Europe/Malta", "Europe/Malta"),
|
||||
("Europe/Mariehamn", "Europe/Mariehamn"),
|
||||
("Europe/Minsk", "Europe/Minsk"),
|
||||
("Europe/Monaco", "Europe/Monaco"),
|
||||
("Europe/Moscow", "Europe/Moscow"),
|
||||
("Europe/Nicosia", "Europe/Nicosia"),
|
||||
("Europe/Oslo", "Europe/Oslo"),
|
||||
("Europe/Paris", "Europe/Paris"),
|
||||
("Europe/Podgorica", "Europe/Podgorica"),
|
||||
("Europe/Prague", "Europe/Prague"),
|
||||
("Europe/Riga", "Europe/Riga"),
|
||||
("Europe/Rome", "Europe/Rome"),
|
||||
("Europe/Samara", "Europe/Samara"),
|
||||
("Europe/San_Marino", "Europe/San_Marino"),
|
||||
("Europe/Sarajevo", "Europe/Sarajevo"),
|
||||
("Europe/Saratov", "Europe/Saratov"),
|
||||
("Europe/Simferopol", "Europe/Simferopol"),
|
||||
("Europe/Skopje", "Europe/Skopje"),
|
||||
("Europe/Sofia", "Europe/Sofia"),
|
||||
("Europe/Stockholm", "Europe/Stockholm"),
|
||||
("Europe/Tallinn", "Europe/Tallinn"),
|
||||
("Europe/Tirane", "Europe/Tirane"),
|
||||
("Europe/Tiraspol", "Europe/Tiraspol"),
|
||||
("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
|
||||
("Europe/Uzhgorod", "Europe/Uzhgorod"),
|
||||
("Europe/Vaduz", "Europe/Vaduz"),
|
||||
("Europe/Vatican", "Europe/Vatican"),
|
||||
("Europe/Vienna", "Europe/Vienna"),
|
||||
("Europe/Vilnius", "Europe/Vilnius"),
|
||||
("Europe/Volgograd", "Europe/Volgograd"),
|
||||
("Europe/Warsaw", "Europe/Warsaw"),
|
||||
("Europe/Zagreb", "Europe/Zagreb"),
|
||||
("Europe/Zaporozhye", "Europe/Zaporozhye"),
|
||||
("Europe/Zurich", "Europe/Zurich"),
|
||||
("Factory", "Factory"),
|
||||
("GB", "GB"),
|
||||
("GB-Eire", "GB-Eire"),
|
||||
("GMT", "GMT"),
|
||||
("GMT+0", "GMT+0"),
|
||||
("GMT-0", "GMT-0"),
|
||||
("GMT0", "GMT0"),
|
||||
("Greenwich", "Greenwich"),
|
||||
("HST", "HST"),
|
||||
("Hongkong", "Hongkong"),
|
||||
("Iceland", "Iceland"),
|
||||
("Indian/Antananarivo", "Indian/Antananarivo"),
|
||||
("Indian/Chagos", "Indian/Chagos"),
|
||||
("Indian/Christmas", "Indian/Christmas"),
|
||||
("Indian/Cocos", "Indian/Cocos"),
|
||||
("Indian/Comoro", "Indian/Comoro"),
|
||||
("Indian/Kerguelen", "Indian/Kerguelen"),
|
||||
("Indian/Mahe", "Indian/Mahe"),
|
||||
("Indian/Maldives", "Indian/Maldives"),
|
||||
("Indian/Mauritius", "Indian/Mauritius"),
|
||||
("Indian/Mayotte", "Indian/Mayotte"),
|
||||
("Indian/Reunion", "Indian/Reunion"),
|
||||
("Iran", "Iran"),
|
||||
("Israel", "Israel"),
|
||||
("Jamaica", "Jamaica"),
|
||||
("Japan", "Japan"),
|
||||
("Kwajalein", "Kwajalein"),
|
||||
("Libya", "Libya"),
|
||||
("MET", "MET"),
|
||||
("MST", "MST"),
|
||||
("MST7MDT", "MST7MDT"),
|
||||
("Mexico/BajaNorte", "Mexico/BajaNorte"),
|
||||
("Mexico/BajaSur", "Mexico/BajaSur"),
|
||||
("Mexico/General", "Mexico/General"),
|
||||
("NZ", "NZ"),
|
||||
("NZ-CHAT", "NZ-CHAT"),
|
||||
("Navajo", "Navajo"),
|
||||
("PRC", "PRC"),
|
||||
("PST8PDT", "PST8PDT"),
|
||||
("Pacific/Apia", "Pacific/Apia"),
|
||||
("Pacific/Auckland", "Pacific/Auckland"),
|
||||
("Pacific/Bougainville", "Pacific/Bougainville"),
|
||||
("Pacific/Chatham", "Pacific/Chatham"),
|
||||
("Pacific/Chuuk", "Pacific/Chuuk"),
|
||||
("Pacific/Easter", "Pacific/Easter"),
|
||||
("Pacific/Efate", "Pacific/Efate"),
|
||||
("Pacific/Enderbury", "Pacific/Enderbury"),
|
||||
("Pacific/Fakaofo", "Pacific/Fakaofo"),
|
||||
("Pacific/Fiji", "Pacific/Fiji"),
|
||||
("Pacific/Funafuti", "Pacific/Funafuti"),
|
||||
("Pacific/Galapagos", "Pacific/Galapagos"),
|
||||
("Pacific/Gambier", "Pacific/Gambier"),
|
||||
("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
|
||||
("Pacific/Guam", "Pacific/Guam"),
|
||||
("Pacific/Honolulu", "Pacific/Honolulu"),
|
||||
("Pacific/Johnston", "Pacific/Johnston"),
|
||||
("Pacific/Kanton", "Pacific/Kanton"),
|
||||
("Pacific/Kiritimati", "Pacific/Kiritimati"),
|
||||
("Pacific/Kosrae", "Pacific/Kosrae"),
|
||||
("Pacific/Kwajalein", "Pacific/Kwajalein"),
|
||||
("Pacific/Majuro", "Pacific/Majuro"),
|
||||
("Pacific/Marquesas", "Pacific/Marquesas"),
|
||||
("Pacific/Midway", "Pacific/Midway"),
|
||||
("Pacific/Nauru", "Pacific/Nauru"),
|
||||
("Pacific/Niue", "Pacific/Niue"),
|
||||
("Pacific/Norfolk", "Pacific/Norfolk"),
|
||||
("Pacific/Noumea", "Pacific/Noumea"),
|
||||
("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
|
||||
("Pacific/Palau", "Pacific/Palau"),
|
||||
("Pacific/Pitcairn", "Pacific/Pitcairn"),
|
||||
("Pacific/Pohnpei", "Pacific/Pohnpei"),
|
||||
("Pacific/Ponape", "Pacific/Ponape"),
|
||||
("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
|
||||
("Pacific/Rarotonga", "Pacific/Rarotonga"),
|
||||
("Pacific/Saipan", "Pacific/Saipan"),
|
||||
("Pacific/Samoa", "Pacific/Samoa"),
|
||||
("Pacific/Tahiti", "Pacific/Tahiti"),
|
||||
("Pacific/Tarawa", "Pacific/Tarawa"),
|
||||
("Pacific/Tongatapu", "Pacific/Tongatapu"),
|
||||
("Pacific/Truk", "Pacific/Truk"),
|
||||
("Pacific/Wake", "Pacific/Wake"),
|
||||
("Pacific/Wallis", "Pacific/Wallis"),
|
||||
("Pacific/Yap", "Pacific/Yap"),
|
||||
("Poland", "Poland"),
|
||||
("Portugal", "Portugal"),
|
||||
("ROC", "ROC"),
|
||||
("ROK", "ROK"),
|
||||
("Singapore", "Singapore"),
|
||||
("Turkey", "Turkey"),
|
||||
("UCT", "UCT"),
|
||||
("US/Alaska", "US/Alaska"),
|
||||
("US/Aleutian", "US/Aleutian"),
|
||||
("US/Arizona", "US/Arizona"),
|
||||
("US/Central", "US/Central"),
|
||||
("US/East-Indiana", "US/East-Indiana"),
|
||||
("US/Eastern", "US/Eastern"),
|
||||
("US/Hawaii", "US/Hawaii"),
|
||||
("US/Indiana-Starke", "US/Indiana-Starke"),
|
||||
("US/Michigan", "US/Michigan"),
|
||||
("US/Mountain", "US/Mountain"),
|
||||
("US/Pacific", "US/Pacific"),
|
||||
("US/Samoa", "US/Samoa"),
|
||||
("UTC", "UTC"),
|
||||
("Universal", "Universal"),
|
||||
("W-SU", "W-SU"),
|
||||
("WET", "WET"),
|
||||
("Zulu", "Zulu"),
|
||||
("localtime", "localtime"),
|
||||
],
|
||||
max_length=255,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.10 on 2024-02-19 05:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("agents", "0058_alter_agent_time_zone"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="agenthistory",
|
||||
name="id",
|
||||
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
# Generated by Django 4.2.16 on 2024-10-05 20:39
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("core", "0047_alter_coresettings_notify_on_warning_alerts"),
|
||||
("agents", "0059_alter_agenthistory_id"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="agenthistory",
|
||||
name="collector_all_output",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="agenthistory",
|
||||
name="custom_field",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="history",
|
||||
to="core.customfield",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="agenthistory",
|
||||
name="save_to_agent_note",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ from tacticalrmm.permissions import _has_perm, _has_perm_on_agent
|
||||
|
||||
|
||||
class AgentPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
if r.method == "GET":
|
||||
if "agent_id" in view.kwargs.keys():
|
||||
return _has_perm(r, "can_list_agents") and _has_perm_on_agent(
|
||||
@@ -26,77 +26,71 @@ class AgentPerms(permissions.BasePermission):
|
||||
|
||||
|
||||
class RecoverAgentPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
if "agent_id" not in view.kwargs.keys():
|
||||
return _has_perm(r, "can_recover_agents")
|
||||
|
||||
return _has_perm(r, "can_recover_agents") and _has_perm_on_agent(
|
||||
r.user, view.kwargs["agent_id"]
|
||||
)
|
||||
|
||||
|
||||
class MeshPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
return _has_perm(r, "can_use_mesh") and _has_perm_on_agent(
|
||||
r.user, view.kwargs["agent_id"]
|
||||
)
|
||||
|
||||
|
||||
class UpdateAgentPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
return _has_perm(r, "can_update_agents")
|
||||
|
||||
|
||||
class PingAgentPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
return _has_perm(r, "can_ping_agents") and _has_perm_on_agent(
|
||||
r.user, view.kwargs["agent_id"]
|
||||
)
|
||||
|
||||
|
||||
class ManageProcPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
return _has_perm(r, "can_manage_procs") and _has_perm_on_agent(
|
||||
r.user, view.kwargs["agent_id"]
|
||||
)
|
||||
|
||||
|
||||
class EvtLogPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
return _has_perm(r, "can_view_eventlogs") and _has_perm_on_agent(
|
||||
r.user, view.kwargs["agent_id"]
|
||||
)
|
||||
|
||||
|
||||
class SendCMDPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
return _has_perm(r, "can_send_cmd") and _has_perm_on_agent(
|
||||
r.user, view.kwargs["agent_id"]
|
||||
)
|
||||
|
||||
|
||||
class RebootAgentPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
return _has_perm(r, "can_reboot_agents") and _has_perm_on_agent(
|
||||
r.user, view.kwargs["agent_id"]
|
||||
)
|
||||
|
||||
|
||||
class InstallAgentPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
return _has_perm(r, "can_install_agents")
|
||||
|
||||
|
||||
class RunScriptPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
return _has_perm(r, "can_run_scripts") and _has_perm_on_agent(
|
||||
r.user, view.kwargs["agent_id"]
|
||||
)
|
||||
|
||||
|
||||
class AgentNotesPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
|
||||
def has_permission(self, r, view) -> bool:
|
||||
# permissions for GET /agents/notes/ endpoint
|
||||
if r.method == "GET":
|
||||
|
||||
# permissions for /agents/<agent_id>/notes endpoint
|
||||
if "agent_id" in view.kwargs.keys():
|
||||
return _has_perm(r, "can_list_notes") and _has_perm_on_agent(
|
||||
@@ -109,15 +103,25 @@ class AgentNotesPerms(permissions.BasePermission):
|
||||
|
||||
|
||||
class RunBulkPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
return _has_perm(r, "can_run_bulk")
|
||||
|
||||
|
||||
class AgentHistoryPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
if "agent_id" in view.kwargs.keys():
|
||||
return _has_perm(r, "can_list_agent_history") and _has_perm_on_agent(
|
||||
r.user, view.kwargs["agent_id"]
|
||||
)
|
||||
else:
|
||||
return _has_perm(r, "can_list_agent_history")
|
||||
|
||||
return _has_perm(r, "can_list_agent_history")
|
||||
|
||||
|
||||
class AgentWOLPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
if "agent_id" in view.kwargs.keys():
|
||||
return _has_perm(r, "can_send_wol") and _has_perm_on_agent(
|
||||
r.user, view.kwargs["agent_id"]
|
||||
)
|
||||
|
||||
return _has_perm(r, "can_send_wol")
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import pytz
|
||||
from rest_framework import serializers
|
||||
|
||||
from tacticalrmm.constants import AGENT_STATUS_ONLINE, ALL_TIMEZONES
|
||||
from winupdate.serializers import WinUpdatePolicySerializer
|
||||
|
||||
from .models import Agent, AgentCustomField, Note, AgentHistory
|
||||
from .models import Agent, AgentCustomField, AgentHistory, Note
|
||||
|
||||
|
||||
class AgentCustomFieldSerializer(serializers.ModelSerializer):
|
||||
@@ -40,9 +41,36 @@ class AgentSerializer(serializers.ModelSerializer):
|
||||
custom_fields = AgentCustomFieldSerializer(many=True, read_only=True)
|
||||
patches_last_installed = serializers.ReadOnlyField()
|
||||
last_seen = serializers.ReadOnlyField()
|
||||
applied_policies = serializers.SerializerMethodField()
|
||||
effective_patch_policy = serializers.SerializerMethodField()
|
||||
alert_template = serializers.SerializerMethodField()
|
||||
|
||||
def get_alert_template(self, obj):
|
||||
from alerts.serializers import AlertTemplateSerializer
|
||||
|
||||
return (
|
||||
AlertTemplateSerializer(obj.alert_template).data
|
||||
if obj.alert_template
|
||||
else None
|
||||
)
|
||||
|
||||
def get_effective_patch_policy(self, obj):
|
||||
return WinUpdatePolicySerializer(obj.get_patch_policy()).data
|
||||
|
||||
def get_applied_policies(self, obj):
|
||||
from automation.serializers import PolicySerializer
|
||||
|
||||
policies = obj.get_agent_policies()
|
||||
|
||||
# need to serialize model objects manually
|
||||
for key, policy in policies.items():
|
||||
if policy:
|
||||
policies[key] = PolicySerializer(policy).data
|
||||
|
||||
return policies
|
||||
|
||||
def get_all_timezones(self, obj):
|
||||
return pytz.all_timezones
|
||||
return ALL_TIMEZONES
|
||||
|
||||
class Meta:
|
||||
model = Agent
|
||||
@@ -52,44 +80,44 @@ class AgentSerializer(serializers.ModelSerializer):
|
||||
class AgentTableSerializer(serializers.ModelSerializer):
|
||||
status = serializers.ReadOnlyField()
|
||||
checks = serializers.ReadOnlyField()
|
||||
last_seen = serializers.SerializerMethodField()
|
||||
client_name = serializers.ReadOnlyField(source="client.name")
|
||||
site_name = serializers.ReadOnlyField(source="site.name")
|
||||
logged_username = serializers.SerializerMethodField()
|
||||
italic = serializers.SerializerMethodField()
|
||||
policy = serializers.ReadOnlyField(source="policy.id")
|
||||
alert_template = serializers.SerializerMethodField()
|
||||
last_seen = serializers.ReadOnlyField()
|
||||
pending_actions_count = serializers.ReadOnlyField()
|
||||
has_patches_pending = serializers.ReadOnlyField()
|
||||
cpu_model = serializers.ReadOnlyField()
|
||||
graphics = serializers.ReadOnlyField()
|
||||
local_ips = serializers.ReadOnlyField()
|
||||
make_model = serializers.ReadOnlyField()
|
||||
physical_disks = serializers.ReadOnlyField()
|
||||
serial_number = serializers.ReadOnlyField()
|
||||
custom_fields = AgentCustomFieldSerializer(many=True, read_only=True)
|
||||
|
||||
def get_alert_template(self, obj):
|
||||
|
||||
if not obj.alert_template:
|
||||
return None
|
||||
else:
|
||||
return {
|
||||
"name": obj.alert_template.name,
|
||||
"always_email": obj.alert_template.agent_always_email,
|
||||
"always_text": obj.alert_template.agent_always_text,
|
||||
"always_alert": obj.alert_template.agent_always_alert,
|
||||
}
|
||||
|
||||
def get_last_seen(self, obj) -> str:
|
||||
if obj.time_zone is not None:
|
||||
agent_tz = pytz.timezone(obj.time_zone)
|
||||
else:
|
||||
agent_tz = self.context["default_tz"]
|
||||
|
||||
return obj.last_seen.astimezone(agent_tz).strftime("%m %d %Y %H:%M")
|
||||
return {
|
||||
"name": obj.alert_template.name,
|
||||
"always_email": obj.alert_template.agent_always_email,
|
||||
"always_text": obj.alert_template.agent_always_text,
|
||||
"always_alert": obj.alert_template.agent_always_alert,
|
||||
}
|
||||
|
||||
def get_logged_username(self, obj) -> str:
|
||||
if obj.logged_in_username == "None" and obj.status == "online":
|
||||
if obj.logged_in_username == "None" and obj.status == AGENT_STATUS_ONLINE:
|
||||
return obj.last_logged_in_user
|
||||
elif obj.logged_in_username != "None":
|
||||
return obj.logged_in_username
|
||||
else:
|
||||
return "-"
|
||||
|
||||
return "-"
|
||||
|
||||
def get_italic(self, obj) -> bool:
|
||||
return obj.logged_in_username == "None" and obj.status == "online"
|
||||
return obj.logged_in_username == "None" and obj.status == AGENT_STATUS_ONLINE
|
||||
|
||||
class Meta:
|
||||
model = Agent
|
||||
@@ -102,7 +130,6 @@ class AgentTableSerializer(serializers.ModelSerializer):
|
||||
"monitoring_type",
|
||||
"description",
|
||||
"needs_reboot",
|
||||
"has_patches_pending",
|
||||
"pending_actions_count",
|
||||
"status",
|
||||
"overdue_text_alert",
|
||||
@@ -116,16 +143,23 @@ class AgentTableSerializer(serializers.ModelSerializer):
|
||||
"italic",
|
||||
"policy",
|
||||
"block_policy_inheritance",
|
||||
"plat",
|
||||
"goarch",
|
||||
"has_patches_pending",
|
||||
"version",
|
||||
"operating_system",
|
||||
"public_ip",
|
||||
"cpu_model",
|
||||
"graphics",
|
||||
"local_ips",
|
||||
"make_model",
|
||||
"physical_disks",
|
||||
"custom_fields",
|
||||
"serial_number",
|
||||
]
|
||||
depth = 2
|
||||
|
||||
|
||||
class WinAgentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Agent
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class AgentHostnameSerializer(serializers.ModelSerializer):
|
||||
client = serializers.ReadOnlyField(source="client.name")
|
||||
site = serializers.ReadOnlyField(source="site.name")
|
||||
@@ -152,17 +186,12 @@ class AgentNoteSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class AgentHistorySerializer(serializers.ModelSerializer):
|
||||
time = serializers.SerializerMethodField(read_only=True)
|
||||
script_name = serializers.ReadOnlyField(source="script.name")
|
||||
|
||||
class Meta:
|
||||
model = AgentHistory
|
||||
fields = "__all__"
|
||||
|
||||
def get_time(self, history):
|
||||
tz = self.context["default_tz"]
|
||||
return history.time.astimezone(tz).strftime("%m %d %Y %H:%M:%S")
|
||||
|
||||
|
||||
class AgentAuditSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
|
||||
@@ -1,121 +1,54 @@
|
||||
import asyncio
|
||||
import datetime as dt
|
||||
import random
|
||||
from time import sleep
|
||||
from typing import Union
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from alerts.models import Alert
|
||||
from core.models import CoreSettings
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.utils import timezone as djangotime
|
||||
from logs.models import DebugLog, PendingAction
|
||||
from packaging import version as pyver
|
||||
from scripts.models import Script
|
||||
from tacticalrmm.celery import app
|
||||
|
||||
from agents.models import Agent
|
||||
from agents.utils import get_winagent_url
|
||||
from core.utils import get_core_settings
|
||||
from logs.models import DebugLog
|
||||
from scripts.models import Script
|
||||
from tacticalrmm.celery import app
|
||||
from tacticalrmm.constants import (
|
||||
AGENT_DEFER,
|
||||
AGENT_OUTAGES_LOCK,
|
||||
AGENT_STATUS_OVERDUE,
|
||||
CheckStatus,
|
||||
DebugLogType,
|
||||
)
|
||||
from tacticalrmm.helpers import rand_range
|
||||
from tacticalrmm.utils import redis_lock
|
||||
|
||||
|
||||
def agent_update(agent_id: str, force: bool = False) -> str:
|
||||
|
||||
agent = Agent.objects.get(agent_id=agent_id)
|
||||
|
||||
if pyver.parse(agent.version) <= pyver.parse("1.3.0"):
|
||||
return "not supported"
|
||||
|
||||
# skip if we can't determine the arch
|
||||
if agent.arch is None:
|
||||
DebugLog.warning(
|
||||
agent=agent,
|
||||
log_type="agent_issues",
|
||||
message=f"Unable to determine arch on {agent.hostname}({agent.agent_id}). Skipping agent update.",
|
||||
)
|
||||
return "noarch"
|
||||
|
||||
version = settings.LATEST_AGENT_VER
|
||||
inno = agent.win_inno_exe
|
||||
url = get_winagent_url(agent.arch)
|
||||
|
||||
if not force:
|
||||
if agent.pendingactions.filter(
|
||||
action_type="agentupdate", status="pending"
|
||||
).exists():
|
||||
agent.pendingactions.filter(
|
||||
action_type="agentupdate", status="pending"
|
||||
).delete()
|
||||
|
||||
PendingAction.objects.create(
|
||||
agent=agent,
|
||||
action_type="agentupdate",
|
||||
details={
|
||||
"url": url,
|
||||
"version": version,
|
||||
"inno": inno,
|
||||
},
|
||||
)
|
||||
|
||||
nats_data = {
|
||||
"func": "agentupdate",
|
||||
"payload": {
|
||||
"url": url,
|
||||
"version": version,
|
||||
"inno": inno,
|
||||
},
|
||||
}
|
||||
asyncio.run(agent.nats_cmd(nats_data, wait=False))
|
||||
return "created"
|
||||
if TYPE_CHECKING:
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
|
||||
@app.task
|
||||
def force_code_sign(agent_ids: list[str]) -> None:
|
||||
chunks = (agent_ids[i : i + 50] for i in range(0, len(agent_ids), 50))
|
||||
for chunk in chunks:
|
||||
for agent_id in chunk:
|
||||
agent_update(agent_id=agent_id, force=True)
|
||||
sleep(0.05)
|
||||
sleep(4)
|
||||
|
||||
|
||||
@app.task
|
||||
def send_agent_update_task(agent_ids: list[str]) -> None:
|
||||
chunks = (agent_ids[i : i + 50] for i in range(0, len(agent_ids), 50))
|
||||
for chunk in chunks:
|
||||
for agent_id in chunk:
|
||||
agent_update(agent_id)
|
||||
sleep(0.05)
|
||||
sleep(4)
|
||||
def send_agent_update_task(*, agent_ids: list[str], token: str, force: bool) -> None:
|
||||
agents: "QuerySet[Agent]" = Agent.objects.defer(*AGENT_DEFER).filter(
|
||||
agent_id__in=agent_ids
|
||||
)
|
||||
for agent in agents:
|
||||
agent.do_update(token=token, force=force)
|
||||
|
||||
|
||||
@app.task
|
||||
def auto_self_agent_update_task() -> None:
|
||||
core = CoreSettings.objects.first()
|
||||
if not core.agent_auto_update: # type:ignore
|
||||
return
|
||||
|
||||
q = Agent.objects.only("agent_id", "version")
|
||||
agent_ids: list[str] = [
|
||||
i.agent_id
|
||||
for i in q
|
||||
if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
|
||||
]
|
||||
|
||||
chunks = (agent_ids[i : i + 30] for i in range(0, len(agent_ids), 30))
|
||||
for chunk in chunks:
|
||||
for agent_id in chunk:
|
||||
agent_update(agent_id)
|
||||
sleep(0.05)
|
||||
sleep(4)
|
||||
call_command("update_agents")
|
||||
|
||||
|
||||
@app.task
|
||||
def agent_outage_email_task(pk: int, alert_interval: Union[float, None] = None) -> str:
|
||||
def agent_outage_email_task(pk: int, alert_interval: Optional[float] = None) -> str:
|
||||
from alerts.models import Alert
|
||||
|
||||
alert = Alert.objects.get(pk=pk)
|
||||
try:
|
||||
alert = Alert.objects.get(pk=pk)
|
||||
except Alert.DoesNotExist:
|
||||
return "alert not found"
|
||||
|
||||
if not alert.email_sent:
|
||||
sleep(random.randint(1, 15))
|
||||
sleep(rand_range(100, 1500))
|
||||
alert.agent.send_outage_email()
|
||||
alert.email_sent = djangotime.now()
|
||||
alert.save(update_fields=["email_sent"])
|
||||
@@ -124,7 +57,7 @@ def agent_outage_email_task(pk: int, alert_interval: Union[float, None] = None)
|
||||
# send an email only if the last email sent is older than alert interval
|
||||
delta = djangotime.now() - dt.timedelta(days=alert_interval)
|
||||
if alert.email_sent < delta:
|
||||
sleep(random.randint(1, 10))
|
||||
sleep(rand_range(100, 1500))
|
||||
alert.agent.send_outage_email()
|
||||
alert.email_sent = djangotime.now()
|
||||
alert.save(update_fields=["email_sent"])
|
||||
@@ -136,8 +69,13 @@ def agent_outage_email_task(pk: int, alert_interval: Union[float, None] = None)
|
||||
def agent_recovery_email_task(pk: int) -> str:
|
||||
from alerts.models import Alert
|
||||
|
||||
sleep(random.randint(1, 15))
|
||||
alert = Alert.objects.get(pk=pk)
|
||||
sleep(rand_range(100, 1500))
|
||||
|
||||
try:
|
||||
alert = Alert.objects.get(pk=pk)
|
||||
except Alert.DoesNotExist:
|
||||
return "alert not found"
|
||||
|
||||
alert.agent.send_recovery_email()
|
||||
alert.resolved_email_sent = djangotime.now()
|
||||
alert.save(update_fields=["resolved_email_sent"])
|
||||
@@ -146,13 +84,16 @@ def agent_recovery_email_task(pk: int) -> str:
|
||||
|
||||
|
||||
@app.task
|
||||
def agent_outage_sms_task(pk: int, alert_interval: Union[float, None] = None) -> str:
|
||||
def agent_outage_sms_task(pk: int, alert_interval: Optional[float] = None) -> str:
|
||||
from alerts.models import Alert
|
||||
|
||||
alert = Alert.objects.get(pk=pk)
|
||||
try:
|
||||
alert = Alert.objects.get(pk=pk)
|
||||
except Alert.DoesNotExist:
|
||||
return "alert not found"
|
||||
|
||||
if not alert.sms_sent:
|
||||
sleep(random.randint(1, 15))
|
||||
sleep(rand_range(100, 1500))
|
||||
alert.agent.send_outage_sms()
|
||||
alert.sms_sent = djangotime.now()
|
||||
alert.save(update_fields=["sms_sent"])
|
||||
@@ -161,7 +102,7 @@ def agent_outage_sms_task(pk: int, alert_interval: Union[float, None] = None) ->
|
||||
# send an sms only if the last sms sent is older than alert interval
|
||||
delta = djangotime.now() - dt.timedelta(days=alert_interval)
|
||||
if alert.sms_sent < delta:
|
||||
sleep(random.randint(1, 10))
|
||||
sleep(rand_range(100, 1500))
|
||||
alert.agent.send_outage_sms()
|
||||
alert.sms_sent = djangotime.now()
|
||||
alert.save(update_fields=["sms_sent"])
|
||||
@@ -173,8 +114,12 @@ def agent_outage_sms_task(pk: int, alert_interval: Union[float, None] = None) ->
|
||||
def agent_recovery_sms_task(pk: int) -> str:
|
||||
from alerts.models import Alert
|
||||
|
||||
sleep(random.randint(1, 3))
|
||||
alert = Alert.objects.get(pk=pk)
|
||||
sleep(rand_range(100, 1500))
|
||||
try:
|
||||
alert = Alert.objects.get(pk=pk)
|
||||
except Alert.DoesNotExist:
|
||||
return "alert not found"
|
||||
|
||||
alert.agent.send_recovery_sms()
|
||||
alert.resolved_sms_sent = djangotime.now()
|
||||
alert.save(update_fields=["resolved_sms_sent"])
|
||||
@@ -182,24 +127,20 @@ def agent_recovery_sms_task(pk: int) -> str:
|
||||
return "ok"
|
||||
|
||||
|
||||
@app.task
|
||||
def agent_outages_task() -> None:
|
||||
from alerts.models import Alert
|
||||
@app.task(bind=True)
|
||||
def agent_outages_task(self) -> str:
|
||||
with redis_lock(AGENT_OUTAGES_LOCK, self.app.oid) as acquired:
|
||||
if not acquired:
|
||||
return f"{self.app.oid} still running"
|
||||
|
||||
agents = Agent.objects.only(
|
||||
"pk",
|
||||
"agent_id",
|
||||
"last_seen",
|
||||
"offline_time",
|
||||
"overdue_time",
|
||||
"overdue_email_alert",
|
||||
"overdue_text_alert",
|
||||
"overdue_dashboard_alert",
|
||||
)
|
||||
from alerts.models import Alert
|
||||
from core.tasks import _get_agent_qs
|
||||
|
||||
for agent in agents:
|
||||
if agent.status == "overdue":
|
||||
Alert.handle_alert_failure(agent)
|
||||
for agent in _get_agent_qs():
|
||||
if agent.status == AGENT_STATUS_OVERDUE:
|
||||
Alert.handle_alert_failure(agent)
|
||||
|
||||
return "completed"
|
||||
|
||||
|
||||
@app.task
|
||||
@@ -210,6 +151,8 @@ def run_script_email_results_task(
|
||||
emails: list[str],
|
||||
args: list[str] = [],
|
||||
history_pk: int = 0,
|
||||
run_as_user: bool = False,
|
||||
env_vars: list[str] = [],
|
||||
):
|
||||
agent = Agent.objects.get(pk=agentpk)
|
||||
script = Script.objects.get(pk=scriptpk)
|
||||
@@ -220,17 +163,19 @@ def run_script_email_results_task(
|
||||
timeout=nats_timeout,
|
||||
wait=True,
|
||||
history_pk=history_pk,
|
||||
run_as_user=run_as_user,
|
||||
env_vars=env_vars,
|
||||
)
|
||||
if r == "timeout":
|
||||
DebugLog.error(
|
||||
agent=agent,
|
||||
log_type="scripting",
|
||||
log_type=DebugLogType.SCRIPTING,
|
||||
message=f"{agent.hostname}({agent.pk}) timed out running script.",
|
||||
)
|
||||
return
|
||||
|
||||
CORE = CoreSettings.objects.first()
|
||||
subject = f"{agent.hostname} {script.name} Results"
|
||||
CORE = get_core_settings()
|
||||
subject = f"{agent.client.name}, {agent.site.name}, {agent.hostname} {script.name} Results"
|
||||
exec_time = "{:.4f}".format(r["execution_time"])
|
||||
body = (
|
||||
subject
|
||||
@@ -242,25 +187,21 @@ def run_script_email_results_task(
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = CORE.smtp_from_email # type:ignore
|
||||
msg["From"] = CORE.smtp_from_email
|
||||
|
||||
if emails:
|
||||
msg["To"] = ", ".join(emails)
|
||||
else:
|
||||
msg["To"] = ", ".join(CORE.email_alert_recipients) # type:ignore
|
||||
msg["To"] = ", ".join(CORE.email_alert_recipients)
|
||||
|
||||
msg.set_content(body)
|
||||
|
||||
try:
|
||||
with smtplib.SMTP(
|
||||
CORE.smtp_host, CORE.smtp_port, timeout=20 # type:ignore
|
||||
) as server: # type:ignore
|
||||
if CORE.smtp_requires_auth: # type:ignore
|
||||
with smtplib.SMTP(CORE.smtp_host, CORE.smtp_port, timeout=20) as server:
|
||||
if CORE.smtp_requires_auth:
|
||||
server.ehlo()
|
||||
server.starttls()
|
||||
server.login(
|
||||
CORE.smtp_host_user, CORE.smtp_host_password # type:ignore
|
||||
) # type:ignore
|
||||
server.login(CORE.smtp_host_user, CORE.smtp_host_password)
|
||||
server.send_message(msg)
|
||||
server.quit()
|
||||
else:
|
||||
@@ -272,18 +213,22 @@ def run_script_email_results_task(
|
||||
|
||||
@app.task
|
||||
def clear_faults_task(older_than_days: int) -> None:
|
||||
# https://github.com/wh1te909/tacticalrmm/issues/484
|
||||
from alerts.models import Alert
|
||||
|
||||
# https://github.com/amidaware/tacticalrmm/issues/484
|
||||
agents = Agent.objects.exclude(last_seen__isnull=True).filter(
|
||||
last_seen__lt=djangotime.now() - djangotime.timedelta(days=older_than_days)
|
||||
)
|
||||
for agent in agents:
|
||||
if agent.agentchecks.exists():
|
||||
for check in agent.agentchecks.all():
|
||||
# reset check status
|
||||
check.status = "passing"
|
||||
check.save(update_fields=["status"])
|
||||
if check.alert.filter(resolved=False).exists():
|
||||
check.alert.get(resolved=False).resolve()
|
||||
for check in agent.get_checks_with_policies():
|
||||
# reset check status
|
||||
if check.check_result:
|
||||
check.check_result.status = CheckStatus.PASSING
|
||||
check.check_result.save(update_fields=["status"])
|
||||
if check.alert.filter(agent=agent, resolved=False).exists():
|
||||
alert = Alert.create_or_return_check_alert(check, agent=agent)
|
||||
if alert:
|
||||
alert.resolve()
|
||||
|
||||
# reset overdue alerts
|
||||
agent.overdue_email_alert = False
|
||||
@@ -310,42 +255,5 @@ def prune_agent_history(older_than_days: int) -> str:
|
||||
|
||||
|
||||
@app.task
|
||||
def handle_agents_task() -> None:
|
||||
q = Agent.objects.prefetch_related("pendingactions", "autotasks").only(
|
||||
"pk", "agent_id", "version", "last_seen", "overdue_time", "offline_time"
|
||||
)
|
||||
agents = [
|
||||
i
|
||||
for i in q
|
||||
if pyver.parse(i.version) >= pyver.parse("1.6.0") and i.status == "online"
|
||||
]
|
||||
for agent in agents:
|
||||
# change agent update pending status to completed if agent has just updated
|
||||
if (
|
||||
pyver.parse(agent.version) == pyver.parse(settings.LATEST_AGENT_VER)
|
||||
and agent.pendingactions.filter(
|
||||
action_type="agentupdate", status="pending"
|
||||
).exists()
|
||||
):
|
||||
agent.pendingactions.filter(
|
||||
action_type="agentupdate", status="pending"
|
||||
).update(status="completed")
|
||||
|
||||
# sync scheduled tasks
|
||||
if agent.autotasks.exclude(sync_status="synced").exists(): # type: ignore
|
||||
tasks = agent.autotasks.exclude(sync_status="synced") # type: ignore
|
||||
|
||||
for task in tasks:
|
||||
if task.sync_status == "pendingdeletion":
|
||||
task.delete_task_on_agent()
|
||||
elif task.sync_status == "initial":
|
||||
task.modify_task_on_agent()
|
||||
elif task.sync_status == "notsynced":
|
||||
task.create_task_on_agent()
|
||||
|
||||
# handles any alerting actions
|
||||
if Alert.objects.filter(agent=agent, resolved=False).exists():
|
||||
try:
|
||||
Alert.handle_alert_resolve(agent)
|
||||
except:
|
||||
continue
|
||||
def bulk_recover_agents_task() -> None:
|
||||
call_command("bulk_restart_agents")
|
||||
|
||||
106
api/tacticalrmm/agents/tests/test_agent_installs.py
Normal file
106
api/tacticalrmm/agents/tests/test_agent_installs.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from rest_framework.response import Response
|
||||
|
||||
from tacticalrmm.test import TacticalTestCase
|
||||
|
||||
|
||||
class TestAgentInstalls(TacticalTestCase):
|
||||
def setUp(self) -> None:
|
||||
self.authenticate()
|
||||
self.setup_coresettings()
|
||||
self.setup_base_instance()
|
||||
|
||||
@patch("agents.utils.generate_linux_install")
|
||||
@patch("knox.models.AuthToken.objects.create")
|
||||
@patch("tacticalrmm.utils.generate_winagent_exe")
|
||||
@patch("core.utils.token_is_valid")
|
||||
@patch("agents.utils.get_agent_url")
|
||||
def test_install_agent(
|
||||
self,
|
||||
mock_agent_url,
|
||||
mock_token_valid,
|
||||
mock_gen_win_exe,
|
||||
mock_auth,
|
||||
mock_linux_install,
|
||||
):
|
||||
mock_agent_url.return_value = "https://example.com"
|
||||
mock_token_valid.return_value = "", False
|
||||
mock_gen_win_exe.return_value = Response("ok")
|
||||
mock_auth.return_value = "", "token"
|
||||
mock_linux_install.return_value = Response("ok")
|
||||
|
||||
url = "/agents/installer/"
|
||||
|
||||
# test windows dynamic exe
|
||||
data = {
|
||||
"installMethod": "exe",
|
||||
"client": self.site2.client.pk,
|
||||
"site": self.site2.pk,
|
||||
"expires": 24,
|
||||
"agenttype": "server",
|
||||
"power": 0,
|
||||
"rdp": 1,
|
||||
"ping": 0,
|
||||
"goarch": "amd64",
|
||||
"api": "https://api.example.com",
|
||||
"fileName": "rmm-client-site-server.exe",
|
||||
"plat": "windows",
|
||||
}
|
||||
|
||||
r = self.client.post(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
mock_gen_win_exe.assert_called_with(
|
||||
client=self.site2.client.pk,
|
||||
site=self.site2.pk,
|
||||
agent_type="server",
|
||||
rdp=1,
|
||||
ping=0,
|
||||
power=0,
|
||||
goarch="amd64",
|
||||
token="token",
|
||||
api="https://api.example.com",
|
||||
file_name="rmm-client-site-server.exe",
|
||||
)
|
||||
|
||||
# test linux no code sign
|
||||
data["plat"] = "linux"
|
||||
data["installMethod"] = "bash"
|
||||
data["rdp"] = 0
|
||||
data["agenttype"] = "workstation"
|
||||
r = self.client.post(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 400)
|
||||
|
||||
# test linux
|
||||
mock_token_valid.return_value = "token123", True
|
||||
r = self.client.post(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
mock_linux_install.assert_called_with(
|
||||
client=str(self.site2.client.pk),
|
||||
site=str(self.site2.pk),
|
||||
agent_type="workstation",
|
||||
arch="amd64",
|
||||
token="token",
|
||||
api="https://api.example.com",
|
||||
download_url="https://example.com",
|
||||
)
|
||||
|
||||
# test manual
|
||||
data["rdp"] = 1
|
||||
data["installMethod"] = "manual"
|
||||
r = self.client.post(url, data, format="json")
|
||||
self.assertIn("rdp", r.json()["cmd"])
|
||||
self.assertNotIn("power", r.json()["cmd"])
|
||||
|
||||
data.update({"ping": 1, "power": 1})
|
||||
r = self.client.post(url, data, format="json")
|
||||
self.assertIn("power", r.json()["cmd"])
|
||||
self.assertIn("ping", r.json()["cmd"])
|
||||
|
||||
# test powershell
|
||||
data["installMethod"] = "powershell"
|
||||
r = self.client.post(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.check_not_authenticated("post", url)
|
||||
61
api/tacticalrmm/agents/tests/test_agent_save.py
Normal file
61
api/tacticalrmm/agents/tests/test_agent_save.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from model_bakery import baker
|
||||
|
||||
from agents.models import Agent
|
||||
from tacticalrmm.constants import AgentMonType
|
||||
from tacticalrmm.test import TacticalTestCase
|
||||
|
||||
|
||||
class AgentSaveTestCase(TacticalTestCase):
|
||||
def setUp(self):
|
||||
self.client1 = baker.make("clients.Client")
|
||||
self.client2 = baker.make("clients.Client")
|
||||
self.site1 = baker.make("clients.Site", client=self.client1)
|
||||
self.site2 = baker.make("clients.Site", client=self.client2)
|
||||
self.site3 = baker.make("clients.Site", client=self.client2)
|
||||
self.agent = baker.make(
|
||||
"agents.Agent",
|
||||
site=self.site1,
|
||||
monitoring_type=AgentMonType.SERVER,
|
||||
)
|
||||
|
||||
@patch.object(Agent, "set_alert_template")
|
||||
def test_set_alert_template_called_on_mon_type_change(
|
||||
self, mock_set_alert_template
|
||||
):
|
||||
self.agent.monitoring_type = AgentMonType.WORKSTATION
|
||||
self.agent.save()
|
||||
mock_set_alert_template.assert_called_once()
|
||||
|
||||
@patch.object(Agent, "set_alert_template")
|
||||
def test_set_alert_template_called_on_site_change(self, mock_set_alert_template):
|
||||
self.agent.site = self.site2
|
||||
self.agent.save()
|
||||
mock_set_alert_template.assert_called_once()
|
||||
|
||||
@patch.object(Agent, "set_alert_template")
|
||||
def test_set_alert_template_called_on_site_and_montype_change(
|
||||
self, mock_set_alert_template
|
||||
):
|
||||
print(f"before: {self.agent.monitoring_type} site: {self.agent.site_id}")
|
||||
self.agent.site = self.site3
|
||||
self.agent.monitoring_type = AgentMonType.WORKSTATION
|
||||
self.agent.save()
|
||||
mock_set_alert_template.assert_called_once()
|
||||
print(f"after: {self.agent.monitoring_type} site: {self.agent.site_id}")
|
||||
|
||||
@patch.object(Agent, "set_alert_template")
|
||||
def test_set_alert_template_not_called_without_changes(
|
||||
self, mock_set_alert_template
|
||||
):
|
||||
self.agent.save()
|
||||
mock_set_alert_template.assert_not_called()
|
||||
|
||||
@patch.object(Agent, "set_alert_template")
|
||||
def test_set_alert_template_not_called_on_non_relevant_field_change(
|
||||
self, mock_set_alert_template
|
||||
):
|
||||
self.agent.hostname = "abc123"
|
||||
self.agent.save()
|
||||
mock_set_alert_template.assert_not_called()
|
||||
313
api/tacticalrmm/agents/tests/test_agent_update.py
Normal file
313
api/tacticalrmm/agents/tests/test_agent_update.py
Normal file
@@ -0,0 +1,313 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from model_bakery import baker
|
||||
from packaging import version as pyver
|
||||
|
||||
from agents.models import Agent
|
||||
from agents.tasks import auto_self_agent_update_task, send_agent_update_task
|
||||
from logs.models import PendingAction
|
||||
from tacticalrmm.constants import (
|
||||
AGENT_DEFER,
|
||||
AgentMonType,
|
||||
AgentPlat,
|
||||
GoArch,
|
||||
PAAction,
|
||||
PAStatus,
|
||||
)
|
||||
from tacticalrmm.test import TacticalTestCase
|
||||
|
||||
|
||||
class TestAgentUpdate(TacticalTestCase):
|
||||
def setUp(self) -> None:
|
||||
self.authenticate()
|
||||
self.setup_coresettings()
|
||||
self.setup_base_instance()
|
||||
|
||||
@patch("agents.management.commands.update_agents.send_agent_update_task.delay")
|
||||
@patch("agents.management.commands.update_agents.token_is_valid")
|
||||
@patch("agents.management.commands.update_agents.get_core_settings")
|
||||
def test_update_agents_mgmt_command(self, mock_core, mock_token, mock_update):
|
||||
mock_token.return_value = ("token123", True)
|
||||
|
||||
baker.make_recipe(
|
||||
"agents.online_agent",
|
||||
site=self.site1,
|
||||
monitoring_type=AgentMonType.SERVER,
|
||||
plat=AgentPlat.WINDOWS,
|
||||
version="2.0.3",
|
||||
_quantity=6,
|
||||
)
|
||||
|
||||
baker.make_recipe(
|
||||
"agents.online_agent",
|
||||
site=self.site3,
|
||||
monitoring_type=AgentMonType.WORKSTATION,
|
||||
plat=AgentPlat.LINUX,
|
||||
version="2.0.3",
|
||||
_quantity=5,
|
||||
)
|
||||
|
||||
baker.make_recipe(
|
||||
"agents.online_agent",
|
||||
site=self.site2,
|
||||
monitoring_type=AgentMonType.SERVER,
|
||||
plat=AgentPlat.WINDOWS,
|
||||
version=settings.LATEST_AGENT_VER,
|
||||
_quantity=8,
|
||||
)
|
||||
|
||||
mock_core.return_value.agent_auto_update = False
|
||||
call_command("update_agents")
|
||||
mock_update.assert_not_called()
|
||||
|
||||
mock_core.return_value.agent_auto_update = True
|
||||
call_command("update_agents")
|
||||
|
||||
ids = list(
|
||||
Agent.objects.defer(*AGENT_DEFER)
|
||||
.exclude(version=settings.LATEST_AGENT_VER)
|
||||
.values_list("agent_id", flat=True)
|
||||
)
|
||||
|
||||
mock_update.assert_called_with(agent_ids=ids, token="token123", force=False)
|
||||
|
||||
@patch("agents.models.Agent.nats_cmd")
|
||||
@patch("agents.models.get_agent_url")
|
||||
def test_do_update(self, mock_agent_url, mock_nats_cmd):
|
||||
mock_agent_url.return_value = "https://example.com/123"
|
||||
|
||||
# test noarch
|
||||
agent_noarch = baker.make_recipe(
|
||||
"agents.online_agent",
|
||||
site=self.site1,
|
||||
monitoring_type=AgentMonType.SERVER,
|
||||
plat=AgentPlat.WINDOWS,
|
||||
version="2.1.1",
|
||||
)
|
||||
r = agent_noarch.do_update(token="", force=True)
|
||||
self.assertEqual(r, "noarch")
|
||||
|
||||
# test too old
|
||||
agent_old = baker.make_recipe(
|
||||
"agents.online_agent",
|
||||
site=self.site2,
|
||||
monitoring_type=AgentMonType.SERVER,
|
||||
plat=AgentPlat.WINDOWS,
|
||||
version="1.3.0",
|
||||
goarch=GoArch.AMD64,
|
||||
)
|
||||
r = agent_old.do_update(token="", force=True)
|
||||
self.assertEqual(r, "not supported")
|
||||
|
||||
win = baker.make_recipe(
|
||||
"agents.online_agent",
|
||||
site=self.site1,
|
||||
monitoring_type=AgentMonType.SERVER,
|
||||
plat=AgentPlat.WINDOWS,
|
||||
version="2.1.1",
|
||||
goarch=GoArch.AMD64,
|
||||
)
|
||||
|
||||
lin = baker.make_recipe(
|
||||
"agents.online_agent",
|
||||
site=self.site3,
|
||||
monitoring_type=AgentMonType.WORKSTATION,
|
||||
plat=AgentPlat.LINUX,
|
||||
version="2.1.1",
|
||||
goarch=GoArch.ARM32,
|
||||
)
|
||||
|
||||
# test windows agent update
|
||||
r = win.do_update(token="", force=False)
|
||||
self.assertEqual(r, "created")
|
||||
mock_nats_cmd.assert_called_with(
|
||||
{
|
||||
"func": "agentupdate",
|
||||
"payload": {
|
||||
"url": "https://example.com/123",
|
||||
"version": settings.LATEST_AGENT_VER,
|
||||
"inno": f"tacticalagent-v{settings.LATEST_AGENT_VER}-windows-amd64.exe",
|
||||
},
|
||||
},
|
||||
wait=False,
|
||||
)
|
||||
action1 = PendingAction.objects.get(agent__agent_id=win.agent_id)
|
||||
self.assertEqual(action1.action_type, PAAction.AGENT_UPDATE)
|
||||
self.assertEqual(action1.status, PAStatus.PENDING)
|
||||
self.assertEqual(action1.details["url"], "https://example.com/123")
|
||||
self.assertEqual(
|
||||
action1.details["inno"],
|
||||
f"tacticalagent-v{settings.LATEST_AGENT_VER}-windows-amd64.exe",
|
||||
)
|
||||
self.assertEqual(action1.details["version"], settings.LATEST_AGENT_VER)
|
||||
|
||||
mock_nats_cmd.reset_mock()
|
||||
|
||||
# test linux agent update
|
||||
r = lin.do_update(token="", force=False)
|
||||
mock_nats_cmd.assert_called_with(
|
||||
{
|
||||
"func": "agentupdate",
|
||||
"payload": {
|
||||
"url": "https://example.com/123",
|
||||
"version": settings.LATEST_AGENT_VER,
|
||||
"inno": f"tacticalagent-v{settings.LATEST_AGENT_VER}-linux-arm.exe",
|
||||
},
|
||||
},
|
||||
wait=False,
|
||||
)
|
||||
action2 = PendingAction.objects.get(agent__agent_id=lin.agent_id)
|
||||
self.assertEqual(action2.action_type, PAAction.AGENT_UPDATE)
|
||||
self.assertEqual(action2.status, PAStatus.PENDING)
|
||||
self.assertEqual(action2.details["url"], "https://example.com/123")
|
||||
self.assertEqual(
|
||||
action2.details["inno"],
|
||||
f"tacticalagent-v{settings.LATEST_AGENT_VER}-linux-arm.exe",
|
||||
)
|
||||
self.assertEqual(action2.details["version"], settings.LATEST_AGENT_VER)
|
||||
|
||||
# check if old agent update pending actions are being deleted
|
||||
# should only be 1 pending action at all times
|
||||
pa_count = win.pendingactions.filter(
|
||||
action_type=PAAction.AGENT_UPDATE, status=PAStatus.PENDING
|
||||
).count()
|
||||
self.assertEqual(pa_count, 1)
|
||||
|
||||
for _ in range(4):
|
||||
win.do_update(token="", force=False)
|
||||
|
||||
pa_count = win.pendingactions.filter(
|
||||
action_type=PAAction.AGENT_UPDATE, status=PAStatus.PENDING
|
||||
).count()
|
||||
self.assertEqual(pa_count, 1)
|
||||
|
||||
def test_auto_self_agent_update_task(self):
|
||||
auto_self_agent_update_task()
|
||||
|
||||
@patch("agents.models.Agent.do_update")
|
||||
def test_send_agent_update_task(self, mock_update):
|
||||
baker.make_recipe(
|
||||
"agents.online_agent",
|
||||
site=self.site2,
|
||||
monitoring_type=AgentMonType.SERVER,
|
||||
plat=AgentPlat.WINDOWS,
|
||||
version="2.1.1",
|
||||
goarch=GoArch.AMD64,
|
||||
_quantity=6,
|
||||
)
|
||||
ids = list(
|
||||
Agent.objects.defer(*AGENT_DEFER)
|
||||
.exclude(version=settings.LATEST_AGENT_VER)
|
||||
.values_list("agent_id", flat=True)
|
||||
)
|
||||
send_agent_update_task(agent_ids=ids, token="", force=False)
|
||||
self.assertEqual(mock_update.call_count, 6)
|
||||
|
||||
@patch("agents.views.token_is_valid")
|
||||
@patch("agents.tasks.send_agent_update_task.delay")
|
||||
def test_update_agents(self, mock_update, mock_token):
|
||||
mock_token.return_value = ("", False)
|
||||
url = "/agents/update/"
|
||||
baker.make_recipe(
|
||||
"agents.online_agent",
|
||||
site=self.site2,
|
||||
monitoring_type=AgentMonType.SERVER,
|
||||
plat=AgentPlat.WINDOWS,
|
||||
version="2.1.1",
|
||||
goarch=GoArch.AMD64,
|
||||
_quantity=7,
|
||||
)
|
||||
baker.make_recipe(
|
||||
"agents.online_agent",
|
||||
site=self.site2,
|
||||
monitoring_type=AgentMonType.SERVER,
|
||||
plat=AgentPlat.WINDOWS,
|
||||
version=settings.LATEST_AGENT_VER,
|
||||
goarch=GoArch.AMD64,
|
||||
_quantity=3,
|
||||
)
|
||||
baker.make_recipe(
|
||||
"agents.online_agent",
|
||||
site=self.site2,
|
||||
monitoring_type=AgentMonType.WORKSTATION,
|
||||
plat=AgentPlat.LINUX,
|
||||
version="2.0.1",
|
||||
goarch=GoArch.ARM32,
|
||||
_quantity=9,
|
||||
)
|
||||
|
||||
agent_ids: list[str] = list(
|
||||
Agent.objects.only("agent_id").values_list("agent_id", flat=True)
|
||||
)
|
||||
|
||||
data = {"agent_ids": agent_ids}
|
||||
expected: list[str] = [
|
||||
i.agent_id
|
||||
for i in Agent.objects.only("agent_id", "version")
|
||||
if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
|
||||
]
|
||||
|
||||
r = self.client.post(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
mock_update.assert_called_with(agent_ids=expected, token="", force=False)
|
||||
|
||||
self.check_not_authenticated("post", url)
|
||||
|
||||
@patch("agents.views.token_is_valid")
|
||||
@patch("agents.tasks.send_agent_update_task.delay")
|
||||
def test_agent_update_permissions(self, update_task, mock_token):
|
||||
mock_token.return_value = ("", False)
|
||||
|
||||
agents = baker.make_recipe("agents.agent", _quantity=5)
|
||||
other_agents = baker.make_recipe("agents.agent", _quantity=7)
|
||||
|
||||
url = "/agents/update/"
|
||||
|
||||
data = {
|
||||
"agent_ids": [agent.agent_id for agent in agents]
|
||||
+ [agent.agent_id for agent in other_agents]
|
||||
}
|
||||
|
||||
# test superuser access
|
||||
self.check_authorized_superuser("post", url, data)
|
||||
update_task.assert_called_with(
|
||||
agent_ids=data["agent_ids"], token="", force=False
|
||||
)
|
||||
update_task.reset_mock()
|
||||
|
||||
user = self.create_user_with_roles([])
|
||||
self.client.force_authenticate(user=user)
|
||||
|
||||
self.check_not_authorized("post", url, data)
|
||||
update_task.assert_not_called()
|
||||
|
||||
user.role.can_update_agents = True
|
||||
user.role.save()
|
||||
|
||||
self.check_authorized("post", url, data)
|
||||
update_task.assert_called_with(
|
||||
agent_ids=data["agent_ids"], token="", force=False
|
||||
)
|
||||
update_task.reset_mock()
|
||||
|
||||
# limit to client
|
||||
# user.role.can_view_clients.set([agents[0].client])
|
||||
# self.check_authorized("post", url, data)
|
||||
# update_task.assert_called_with(agent_ids=[agent.agent_id for agent in agents])
|
||||
# update_task.reset_mock()
|
||||
|
||||
# add site
|
||||
# user.role.can_view_sites.set([other_agents[0].site])
|
||||
# self.check_authorized("post", url, data)
|
||||
# update_task.assert_called_with(agent_ids=data["agent_ids"])
|
||||
# update_task.reset_mock()
|
||||
|
||||
# remove client permissions
|
||||
# user.role.can_view_clients.clear()
|
||||
# self.check_authorized("post", url, data)
|
||||
# update_task.assert_called_with(
|
||||
# agent_ids=[agent.agent_id for agent in other_agents]
|
||||
# )
|
||||
59
api/tacticalrmm/agents/tests/test_agent_utils.py
Normal file
59
api/tacticalrmm/agents/tests/test_agent_utils.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from agents.utils import generate_linux_install, get_agent_url
|
||||
from tacticalrmm.test import TacticalTestCase
|
||||
|
||||
|
||||
class TestAgentUtils(TacticalTestCase):
|
||||
def setUp(self) -> None:
|
||||
self.authenticate()
|
||||
self.setup_coresettings()
|
||||
self.setup_base_instance()
|
||||
|
||||
def test_get_agent_url(self):
|
||||
ver = settings.LATEST_AGENT_VER
|
||||
|
||||
# test without token
|
||||
r = get_agent_url(goarch="amd64", plat="windows", token="")
|
||||
expected = f"https://github.com/amidaware/rmmagent/releases/download/v{ver}/tacticalagent-v{ver}-windows-amd64.exe"
|
||||
self.assertEqual(r, expected)
|
||||
|
||||
# test with token
|
||||
r = get_agent_url(goarch="386", plat="linux", token="token123")
|
||||
expected = f"https://{settings.AGENTS_URL}version={ver}&arch=386&token=token123&plat=linux&api=api.example.com"
|
||||
|
||||
@patch("agents.utils.get_mesh_device_id")
|
||||
@patch("agents.utils.asyncio.run")
|
||||
@patch("agents.utils.get_mesh_ws_url")
|
||||
@patch("agents.utils.get_core_settings")
|
||||
def test_generate_linux_install(
|
||||
self, mock_core, mock_mesh, mock_async_run, mock_mesh_device_id
|
||||
):
|
||||
mock_mesh_device_id.return_value = "meshdeviceid"
|
||||
mock_core.return_value.mesh_site = "meshsite"
|
||||
mock_async_run.return_value = "meshid"
|
||||
mock_mesh.return_value = "meshws"
|
||||
r = generate_linux_install(
|
||||
client="1",
|
||||
site="1",
|
||||
agent_type="server",
|
||||
arch="amd64",
|
||||
token="token123",
|
||||
api="api.example.com",
|
||||
download_url="asdasd3423",
|
||||
)
|
||||
|
||||
ret = r.getvalue().decode("utf-8")
|
||||
|
||||
self.assertIn(r"agentDL='asdasd3423'", ret)
|
||||
self.assertIn(
|
||||
r"meshDL='meshsite/meshagents?id=meshid&installflags=2&meshinstall=6'", ret
|
||||
)
|
||||
self.assertIn(r"apiURL='api.example.com'", ret)
|
||||
self.assertIn(r"agentDL='asdasd3423'", ret)
|
||||
self.assertIn(r"token='token123'", ret)
|
||||
self.assertIn(r"clientID='1'", ret)
|
||||
self.assertIn(r"siteID='1'", ret)
|
||||
self.assertIn(r"agentType='server'", ret)
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user