Compare commits
	
		
			2275 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 | ||
|  | 9cb952b116 | ||
|  | 105e8089bb | ||
|  | 730f37f247 | ||
|  | 284716751f | ||
|  | 8d0db699bf | ||
|  | 53cf1cae58 | ||
|  | 307e4719e0 | ||
|  | 5effae787a | ||
|  | 6532be0b52 | ||
|  | fb225a5347 | ||
|  | b83830a45e | ||
|  | ca28288c33 | ||
|  | b6f8d9cb25 | ||
|  | 9cad0f11e5 | ||
|  | 807be08566 | ||
|  | 67f6a985f8 | ||
|  | f87d54ae8d | ||
|  | d894bf7271 | ||
|  | 56e0e5cace | ||
|  | 685084e784 | ||
|  | cbeec5a973 | ||
|  | 3fff56bcd7 | ||
|  | c504c23eec | ||
|  | 16dae5a655 | ||
|  | e512c5ae7d | ||
|  | 094078b928 | ||
|  | 34fc3ff919 | ||
|  | 4391f48e78 | ||
|  | 775608a3c0 | ||
|  | b326228901 | ||
|  | b2e98173a8 | ||
|  | 65c9b7952c | ||
|  | b9dc9e7d62 | ||
|  | ce178d0354 | ||
|  | a3ff6efebc | ||
|  | 6a9bc56723 | ||
|  | c9ac158d25 | ||
|  | 4b937a0fe8 | ||
|  | 405bf26ac5 | ||
|  | 5dcda0e0a0 | ||
|  | 83e9b60308 | ||
|  | 10b40b4730 | ||
|  | 79d6d804ef | ||
|  | e9c7b6d8f8 | ||
|  | 4fcfbfb3f4 | ||
|  | 30cde14ed3 | ||
|  | cf76e6f538 | ||
|  | d0f600ec8d | ||
|  | 675f9e956f | ||
|  | 381605a6bb | ||
|  | 0fce66062b | ||
|  | 747cc9e5da | ||
|  | 25a1b464da | ||
|  | 3b6738b547 | ||
|  | fc93e3e97f | ||
|  | 0edbb13d48 | ||
|  | 673687341c | ||
|  | 3fa89b58df | ||
|  | a43a9c8543 | ||
|  | 45deda4dea | ||
|  | 6ec46f02a9 | ||
|  | d643c17ff1 | ||
|  | e5de89c6b4 | ||
|  | c21e7c632d | ||
|  | 6ae771682a | ||
|  | bf2075b902 | ||
|  | b84d4a99b8 | ||
|  | cce9dfe585 | ||
|  | 166be395b9 | ||
|  | fa3f5f8d68 | ||
|  | 2926b68c32 | ||
|  | a55f187958 | ||
|  | c76d263375 | ||
|  | 6740d97f8f | ||
|  | b079eebe79 | ||
|  | 363e48a1e8 | ||
|  | f60e4e3e4f | ||
|  | 1b02974efa | ||
|  | 496abdd230 | ||
|  | bc495d77d1 | ||
|  | 0786163dc3 | ||
|  | ed85611e75 | ||
|  | 86ebfce44a | ||
|  | dae51cff51 | ||
|  | 358a2e7220 | ||
|  | d45353e8c8 | ||
|  | 2f56e4e3a1 | ||
|  | 0e503f8273 | ||
|  | 876fe803f5 | ||
|  | 6adb9678b6 | ||
|  | 39bf7ba4a9 | ||
|  | 5da6e2ff99 | ||
|  | 44603c41a2 | ||
|  | 0feb982a73 | ||
|  | d93cb32f2e | ||
|  | 40c47eace2 | ||
|  | 509bdd879c | ||
|  | b98ebb6e9f | ||
|  | 924ddecff0 | ||
|  | ca64fd218d | ||
|  | 9b12b55acd | ||
|  | 450239564a | ||
|  | bb1cc62d2a | ||
|  | b4875c1e2d | ||
|  | a21440d663 | ||
|  | eb6836b63c | ||
|  | b39a2690c1 | ||
|  | 706902da1c | ||
|  | d5104b5d27 | ||
|  | a13ae5c4b1 | ||
|  | a92d1d9958 | ||
|  | 10852a9427 | ||
|  | b757ce1e38 | ||
|  | 91e75f3fa2 | ||
|  | 6c8e55eb2f | ||
|  | f821f700fa | ||
|  | d76d24408f | ||
|  | 7ad85dfe1c | ||
|  | 7d8be0a719 | ||
|  | bac15c18e4 | ||
|  | 2f266d39e6 | ||
|  | 5726d1fc52 | ||
|  | 69aee1823e | ||
|  | e6a0ae5f57 | ||
|  | e5df566c7a | ||
|  | 81e173b609 | ||
|  | d0ebcc6606 | ||
|  | 99c3fcf42a | ||
|  | 794666e7cc | ||
|  | 45abe4955d | ||
|  | 7eed421c70 | ||
|  | 69f7c397c2 | ||
|  | d2d136e922 | ||
|  | 396e435ae0 | ||
|  | 45d8e9102a | ||
|  | 12a51deffa | ||
|  | f2f69abec2 | ||
|  | 02b7f962e9 | ||
|  | eb813e6b22 | ||
|  | 5ddc604341 | ||
|  | 313e672e93 | ||
|  | ce77ad6de4 | ||
|  | bea22690b1 | ||
|  | c9a52bd7d0 | ||
|  | a244a341ec | ||
|  | 2b47870032 | ||
|  | de9e35ae6a | ||
|  | 1a6fec8ca9 | ||
|  | 094054cd99 | ||
|  | f85b8a81f1 | ||
|  | a44eaebf7c | ||
|  | f37b3c063e | ||
|  | 6e5d5a3b82 | ||
|  | bf0562d619 | ||
|  | ecaa81be3c | ||
|  | d98ae48935 | ||
|  | f52a76b16c | ||
|  | d421c27602 | ||
|  | 70e4cd4de1 | ||
|  | 29767e9265 | ||
|  | 46d4c7f96d | ||
|  | 161a6f3923 | ||
|  | 53e912341b | ||
|  | 19396ea11a | ||
|  | 1d9a5e742b | ||
|  | e8dfdd03f7 | ||
|  | 2f5b15dac7 | ||
|  | 525e1f5136 | ||
|  | 7d63d188af | ||
|  | 87889c12ea | ||
|  | 53d023f5ee | ||
|  | 1877ab8c67 | ||
|  | 72a5a8cab7 | ||
|  | 221e49a978 | ||
|  | 1a4c67d173 | ||
|  | 42fd23ece3 | ||
|  | 3035c0712a | ||
|  | 61315f8bfd | ||
|  | 52683124d8 | ||
|  | 1f77390366 | ||
|  | 322d492540 | ||
|  | f977d8cca9 | ||
|  | a9aedea2bd | ||
|  | 5560bbeecb | ||
|  | f226206703 | ||
|  | 170687226d | ||
|  | d56d3dc271 | ||
|  | 32a202aff4 | ||
|  | 6ee75e6e60 | ||
|  | 13d74cae3b | ||
|  | 88651916b0 | ||
|  | be12505d2f | ||
|  | 23fcf3b045 | ||
|  | 9e7459b204 | ||
|  | 4f0eb1d566 | ||
|  | ce00481f47 | ||
|  | f596af90ba | ||
|  | aff659b6b6 | ||
|  | 58724d95fa | ||
|  | 8d61fcd5c9 | ||
|  | 3e1be53c36 | ||
|  | f3754588bd | ||
|  | c4ffffeec8 | ||
|  | 5b69f6a358 | ||
|  | 1af89a7447 | ||
|  | 90abd81035 | ||
|  | 898824b13f | ||
|  | 9d093aa7f8 | ||
|  | 1770549f6c | ||
|  | d21be77fd2 | ||
|  | 41a1c19877 | ||
|  | 9b6571ce68 | ||
|  | 88e98e4e35 | ||
|  | 10c56ffbfa | ||
|  | cb2c8d6f3c | ||
|  | ca62b850ce | ||
|  | 5a75d4e140 | ||
|  | e0972b7c24 | ||
|  | 0db497916d | ||
|  | 23a0ad3c4e | ||
|  | 2b4e1c4b67 | ||
|  | 9b1b9244cf | ||
|  | ad570e9b16 | ||
|  | 812ba6de62 | ||
|  | 8f97124adb | ||
|  | 28289838f9 | ||
|  | cca8a010c3 | ||
|  | 91ab296692 | ||
|  | ee6c9c4272 | ||
|  | 21cd36fa92 | ||
|  | b1aafe3dbc | ||
|  | 5cd832de89 | ||
|  | 24dd9d0518 | ||
|  | aab6ab810a | ||
|  | d1d6d5e71e | ||
|  | e67dd68522 | ||
|  | e25eae846d | ||
|  | 995eeaa455 | ||
|  | 240c61b967 | ||
|  | 2d8b0753b4 | ||
|  | 44eab3de7f | ||
|  | 007be5bf95 | ||
|  | ee19c7c51f | ||
|  | ce56afbdf9 | ||
|  | 51012695a1 | ||
|  | 0eef2d2cc5 | ||
|  | 487f9f2815 | ||
|  | d065adcd8e | ||
|  | 0d9a1dc5eb | ||
|  | 8f9ad15108 | ||
|  | e538e9b843 | ||
|  | 4a702b6813 | ||
|  | 1e6fd2c57a | ||
|  | 600b959d89 | ||
|  | b96de9eb13 | ||
|  | 93be19b647 | ||
|  | 74f45f6f1d | ||
|  | 54ba3d2888 | ||
|  | 65d5149f60 | ||
|  | 917ebb3771 | ||
|  | 7e66b1f545 | ||
|  | 05837dca35 | ||
|  | 53be2ebe59 | ||
|  | 0341efcaea | ||
|  | ec75210fd3 | ||
|  | e6afe3e806 | ||
|  | 5aa46f068e | ||
|  | a11a5b28bc | ||
|  | 907aa566ca | ||
|  | 5c21f099a8 | ||
|  | b91201ae3e | ||
|  | 56d7e19968 | ||
|  | cf91c6c90e | ||
|  | 897d0590d2 | ||
|  | 33b33e8458 | ||
|  | 7758f5c187 | ||
|  | 83d7a03ba4 | ||
|  | a9a0df9699 | ||
|  | df44f8f5f8 | ||
|  | 216a9ed035 | ||
|  | 35d61b6a6c | ||
|  | 5fb72cea53 | ||
|  | d54d021e9f | ||
|  | 06e78311df | ||
|  | df720f95ca | ||
|  | 00faff34d3 | ||
|  | 2b5b3ea4f3 | ||
|  | 95e608d0b4 | ||
|  | 1d55bf87dd | ||
|  | 1220ce53eb | ||
|  | 2006218f87 | ||
|  | 40f427a387 | ||
|  | 445e95baed | ||
|  | 67fbc9ad33 | ||
|  | 1253e9e465 | ||
|  | 21069432e8 | ||
|  | 6facf6a324 | ||
|  | 7556197485 | ||
|  | f319c95c2b | ||
|  | 8e972b0907 | ||
|  | 395e400215 | ||
|  | 3685e3111f | ||
|  | 7bb1c75dc6 | ||
|  | b20834929c | ||
|  | 181891757e | ||
|  | b16feeae44 | ||
|  | 684e049f27 | ||
|  | 8cebd901b2 | ||
|  | 3c96beb8fb | ||
|  | 8a46459cf9 | ||
|  | be5c3e9daa | ||
|  | e44453877c | ||
|  | f772a4ec56 | ||
|  | 44182ec683 | ||
|  | b9ab13fa53 | ||
|  | 2ad6721c95 | ||
|  | b7d0604e62 | ||
|  | a7518b4b26 | ||
|  | 50613f5d3e | ||
|  | f814767703 | ||
|  | 4af86d6456 | ||
|  | f0a4f00c2d | ||
|  | 4321affddb | ||
|  | 926ed55b9b | ||
|  | 2ebf308565 | ||
|  | 1c5e736dce | ||
|  | b591f9f5b7 | ||
|  | 9724882578 | ||
|  | ddef2df101 | ||
|  | 8af69c4284 | ||
|  | 6ebe1ab467 | ||
|  | 24e4d9cf6d | ||
|  | f35fa0aa58 | ||
|  | a20b1a973e | ||
|  | eae5e00706 | ||
|  | 403762d862 | ||
|  | 5c92d4b454 | ||
|  | 8f510dde5a | ||
|  | be42d56e37 | ||
|  | 6294530fa3 | ||
|  | c5c8f5fab1 | ||
|  | 3d41d79078 | ||
|  | 3005061a11 | ||
|  | 65ea46f457 | ||
|  | 8d1ef19c61 | ||
|  | 71d87d866b | ||
|  | c4f88bdce7 | ||
|  | f722a115b1 | ||
|  | 1583beea7b | ||
|  | 5b388c587b | ||
|  | e254923167 | ||
|  | b0dbdd7803 | ||
|  | aa6ebe0122 | ||
|  | c5f179bab8 | ||
|  | e65cb86638 | ||
|  | a349998640 | ||
|  | 43f60610b8 | ||
|  | 46d042087a | ||
|  | ee214727f6 | ||
|  | b4c1ec55ec | ||
|  | 0fdd54f710 | ||
|  | 4f0cdeaec0 | ||
|  | e5cc38857c | ||
|  | fe4b9d71c0 | ||
|  | 5c1181e40e | ||
|  | 8b71832bc2 | ||
|  | 8412ed6065 | ||
|  | 207f6cdc7c | ||
|  | b0b51f5730 | ||
|  | def6833ef0 | ||
|  | c528dd3de1 | ||
|  | 544270e35d | ||
|  | 657e029fee | ||
|  | 49469d7689 | ||
|  | 4f0dd452c8 | ||
|  | 3f741eab11 | ||
|  | 190368788f | ||
|  | 8306a3f566 | ||
|  | 988c134c09 | ||
|  | af0a4d578b | ||
|  | 9bc0abc831 | ||
|  | 41410e99e7 | ||
|  | deae04d5ff | ||
|  | 7d6eeffd66 | ||
|  | 629858e095 | ||
|  | dfdb628347 | ||
|  | 6e48b28fc9 | ||
|  | 3ba450e837 | ||
|  | 688ed93500 | ||
|  | 7268ba20a2 | ||
|  | 63d9e73098 | ||
|  | 564c048f90 | ||
|  | 5f801c74d5 | ||
|  | b405fbc09a | ||
|  | 7a64c2eb49 | ||
|  | c93cbac3b1 | ||
|  | 8b0f67b8a6 | ||
|  | 0d96129f2d | ||
|  | 54ee12d2b3 | ||
|  | 92fc042103 | ||
|  | 9bb7016fa7 | ||
|  | 3ad56feafb | ||
|  | 14d59c3dec | ||
|  | 443f419770 | ||
|  | ddbb58755e | ||
|  | 524283b9ff | ||
|  | fb178d2944 | ||
|  | 52f4ad9403 | ||
|  | ba0c08ef1f | ||
|  | 9e19b1e04c | ||
|  | b2118201b1 | ||
|  | b4346aa056 | ||
|  | b599f05aab | ||
|  | 93d78a0200 | ||
|  | 449957b2eb | ||
|  | 0a6d44bad3 | ||
|  | 17ceaaa503 | ||
|  | d70803b416 | ||
|  | aa414d4702 | ||
|  | f24e1b91ea | ||
|  | 1df8163090 | ||
|  | 659ddf6a45 | ||
|  | e110068da4 | ||
|  | c943f6f936 | ||
|  | cb1fe7fe54 | ||
|  | 593f1f63cc | ||
|  | 66aa70cf75 | ||
|  | 304be99067 | ||
|  | 9a01ec35f4 | ||
|  | bfa5b4fba5 | ||
|  | d2f63ef353 | ||
|  | 50f334425e | ||
|  | f78212073c | ||
|  | 5c655f5a82 | ||
|  | 6a6446bfcb | ||
|  | b60a3a5e50 | ||
|  | 02ccbab8e5 | ||
|  | 023ff3f964 | ||
|  | 7c5e8df3b8 | ||
|  | 56fdab260b | ||
|  | 7cce49dc1a | ||
|  | 2dfaafb20b | ||
|  | 6138a5bf54 | ||
|  | 828c67cc00 | ||
|  | e70cd44e18 | ||
|  | efa5ac5edd | ||
|  | 788b11e759 | ||
|  | d049d7a61f | ||
|  | 075c833b58 | ||
|  | e9309c2a96 | ||
|  | a592d2b397 | ||
|  | 3ad1805ac0 | ||
|  | dbc2bab698 | ||
|  | 79eec5c299 | ||
|  | 7754b0c575 | ||
|  | be4289ce76 | ||
|  | 67f5226270 | ||
|  | b6d77c581b | ||
|  | d84bf47d04 | ||
|  | aba3a7bb9e | ||
|  | 6281736d89 | ||
|  | 94d96f89d3 | ||
|  | 4b55f9dead | ||
|  | 5c6dce94df | ||
|  | f7d8f9c7f5 | ||
|  | 053df24f9c | ||
|  | 1dc470e434 | ||
|  | cfd8773267 | ||
|  | 67045cf6c1 | ||
|  | ddfb9e7239 | ||
|  | 9f6eed5472 | ||
|  | 15a1e2ebcb | ||
|  | fcfe450b07 | ||
|  | a69bbb3bc9 | ||
|  | 6d2559cfc1 | ||
|  | b3a62615f3 | ||
|  | 57f5cca1cb | ||
|  | 6b9851f540 | ||
|  | 36fd203a88 | ||
|  | 3f5cb5d61c | ||
|  | 862fc6a946 | ||
|  | 92c386ac0e | ||
|  | 98a11a3645 | ||
|  | 62be0ed936 | ||
|  | b7de73fd8a | ||
|  | e2413f1af2 | ||
|  | 0e77d575c4 | ||
|  | 6a06734192 | ||
|  | 5e26a406b7 | ||
|  | b6dd03138d | ||
|  | cf03ee03ee | ||
|  | 0e665b6bf0 | ||
|  | e3d0de7313 | ||
|  | bcf3a543a1 | ||
|  | b27f17c74a | ||
|  | 75d864771e | ||
|  | 6420060f2a | ||
|  | c149ae71b9 | ||
|  | 3a49dd034c | ||
|  | b26d7e82e3 | ||
|  | 415abdf0ce | ||
|  | f7f6f6ecb2 | ||
|  | 43d54f134a | ||
|  | 0d2606a13b | ||
|  | 1deb10dc88 | ||
|  | 1236d55544 | ||
|  | ecccf39455 | ||
|  | 8e0316825a | ||
|  | aa45fa87af | ||
|  | 4766477c58 | ||
|  | d97e49ff2b | ||
|  | 6b9d775cb9 | ||
|  | e521f580d7 | ||
|  | 25e7cf7db0 | ||
|  | 0cab33787d | ||
|  | d46ae55863 | ||
|  | bbd900ab25 | ||
|  | 129ae93e2b | ||
|  | 44dd59fa3f | ||
|  | ec4e7559b0 | ||
|  | dce40611cf | ||
|  | e71b8546f9 | ||
|  | f827348467 | ||
|  | f3978343db | ||
|  | 2654a7ea70 | ||
|  | 1068bf4ef7 | ||
|  | e7fccc97cc | ||
|  | 733e289852 | ||
|  | 29d71a104c | ||
|  | 05200420ad | ||
|  | eb762d4bfd | ||
|  | 58ace9eda1 | ||
|  | eeb2623be0 | ||
|  | cfa242c2fe | ||
|  | ec0441ccc2 | ||
|  | ae2782a8fe | ||
|  | 58ff570251 | ||
|  | 7b554b12c7 | ||
|  | 58f7603d4f | ||
|  | 8895994c54 | ||
|  | de8f7e36d5 | ||
|  | 88d7a50265 | ||
|  | 21e19fc7e5 | ||
|  | faf4935a69 | ||
|  | 71a1f9d74a | ||
|  | bd8d523e10 | ||
|  | 60cae0e3ac | ||
|  | 5a342ac012 | ||
|  | bb8767dfc3 | ||
|  | fcb2779c15 | ||
|  | 77dd6c1f61 | ||
|  | 8118eef300 | ||
|  | 802d1489fe | ||
|  | 443a029185 | ||
|  | 4ee508fdd0 | ||
|  | aa5608f7e8 | ||
|  | cc472b4613 | ||
|  | 764b945ddc | ||
|  | fd2206ce4c | ||
|  | 48c0ac9f00 | ||
|  | 84eb4fe9ed | ||
|  | 4a5428812c | ||
|  | 023f98a89d | ||
|  | 66893dd0c1 | ||
|  | 25a6666e35 | ||
|  | 19d75309b5 | ||
|  | 11110d65c1 | ||
|  | a348f58fe2 | ||
|  | 13851dd976 | ||
|  | 2ec37c5da9 | ||
|  | 8c127160de | ||
|  | 2af820de9a | ||
|  | 55fb0bb3a0 | ||
|  | 9f9ecc521f | ||
|  | dfd01df5ba | ||
|  | 474090698c | ||
|  | 6b71cdeea4 | ||
|  | 581e974236 | ||
|  | ba3c3a42ce | ||
|  | c8bc5671c5 | ||
|  | ff9401a040 | ||
|  | 5e1bc1989f | ||
|  | a1dc91cd7d | ||
|  | 99f2772bb3 | ||
|  | e5d0e42655 | ||
|  | 2c914cc374 | ||
|  | 9bceb62381 | ||
|  | de7518a800 | ||
|  | 304fb63453 | ||
|  | 0f7ef60ca0 | ||
|  | 07c74e4641 | ||
|  | de7f325cfb | ||
|  | 6beb6be131 | ||
|  | fa4fc2a708 | ||
|  | 2db9758260 | ||
|  | d00cd4453a | ||
|  | 429c08c24a | ||
|  | 6a71490e20 | ||
|  | 9bceda0646 | ||
|  | a1027a6773 | ||
|  | 302d4b75f9 | ||
|  | 5f6ee0e883 | ||
|  | 27f9720de1 | ||
|  | 22aa3fdbbc | ||
|  | 069ecdd33f | ||
|  | dd545ae933 | ||
|  | 6650b705c4 | ||
|  | 59b0350289 | ||
|  | 1ad159f820 | ||
|  | 0bf42190e9 | ||
|  | d2fa836232 | ||
|  | c387774093 | ||
|  | e99736ba3c | ||
|  | 16cb54fcc9 | 
| @@ -23,6 +23,9 @@ 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 | ||||
| DOCKER_NGINX_IP=172.21.0.20 | ||||
| NATS_PORTS=4222:4222 | ||||
|   | ||||
| @@ -1,4 +1,11 @@ | ||||
| FROM python:3.9.2-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,15 +17,22 @@ 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 Dev python reqs | ||||
| COPY ./requirements.txt / | ||||
| # copy community scripts | ||||
| COPY --from=GET_SCRIPTS_STAGE /community-scripts /community-scripts | ||||
|  | ||||
| # Copy Docker Entrypoint | ||||
| COPY ./entrypoint.sh / | ||||
| # Copy dev python reqs | ||||
| COPY .devcontainer/requirements.txt / | ||||
|  | ||||
| # Copy docker entrypoint.sh | ||||
| COPY .devcontainer/entrypoint.sh / | ||||
| RUN chmod +x /entrypoint.sh | ||||
|  | ||||
| ENTRYPOINT ["/entrypoint.sh"] | ||||
|  | ||||
| WORKDIR ${WORKSPACE_DIR}/api/tacticalrmm | ||||
|   | ||||
| @@ -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: ./api.dockerfile | ||||
|     command: ["tactical-api"] | ||||
|       context: .. | ||||
|       dockerfile: .devcontainer/api.dockerfile | ||||
|     command: [ "tactical-api" ] | ||||
|     environment: | ||||
|       API_PORT: ${API_PORT} | ||||
|     ports: | ||||
| @@ -18,35 +19,21 @@ 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} | ||||
|       DEV: 1 | ||||
|     ports: | ||||
|       - "4222:4222" | ||||
|       - "${NATS_PORTS}" | ||||
|     volumes: | ||||
|       - tactical-data-dev:/opt/tactical | ||||
|       - ..:/workspace:cached | ||||
| @@ -61,13 +48,14 @@ 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} | ||||
|       MONGODB_USER: ${MONGODB_USER} | ||||
|       MONGODB_PASSWORD: ${MONGODB_PASSWORD} | ||||
|       NGINX_HOST_IP: 172.21.0.20 | ||||
|       NGINX_HOST_IP: ${DOCKER_NGINX_IP} | ||||
|     networks: | ||||
|       dev: | ||||
|         aliases: | ||||
| @@ -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,7 +104,11 @@ services: | ||||
|   redis-dev: | ||||
|     container_name: trmm-redis-dev | ||||
|     restart: always | ||||
|     user: 1000:1000 | ||||
|     command: redis-server | ||||
|     image: redis:6.0-alpine | ||||
|     volumes: | ||||
|       - redis-data-dev:/data | ||||
|     networks: | ||||
|       dev: | ||||
|         aliases: | ||||
| @@ -124,11 +117,8 @@ services: | ||||
|   init-dev: | ||||
|     container_name: trmm-init-dev | ||||
|     image: api-dev | ||||
|     build: | ||||
|       context: . | ||||
|       dockerfile: ./api.dockerfile | ||||
|     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,17 +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 | ||||
|     build: | ||||
|       context: . | ||||
|       dockerfile: ./api.dockerfile | ||||
|     command: ["tactical-celery-dev"] | ||||
|     command: [ "tactical-celery-dev" ] | ||||
|     restart: always | ||||
|     user: 1000:1000 | ||||
|     networks: | ||||
|       - dev | ||||
|     volumes: | ||||
| @@ -171,11 +163,9 @@ services: | ||||
|   celerybeat-dev: | ||||
|     container_name: trmm-celerybeat-dev | ||||
|     image: api-dev | ||||
|     build: | ||||
|       context: . | ||||
|       dockerfile: ./api.dockerfile | ||||
|     command: ["tactical-celerybeat-dev"] | ||||
|     command: [ "tactical-celerybeat-dev" ] | ||||
|     restart: always | ||||
|     user: 1000:1000 | ||||
|     networks: | ||||
|       - dev | ||||
|     volumes: | ||||
| @@ -189,11 +179,9 @@ services: | ||||
|   websockets-dev: | ||||
|     container_name: trmm-websockets-dev | ||||
|     image: api-dev | ||||
|     build: | ||||
|       context: . | ||||
|       dockerfile: ./api.dockerfile | ||||
|     command: ["tactical-websockets-dev"] | ||||
|     command: [ "tactical-websockets-dev" ] | ||||
|     restart: always | ||||
|     user: 1000:1000 | ||||
|     networks: | ||||
|       dev: | ||||
|         aliases: | ||||
| @@ -210,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} | ||||
| @@ -218,35 +207,23 @@ services: | ||||
|       CERT_PRIV_KEY: ${CERT_PRIV_KEY} | ||||
|       APP_PORT: ${APP_PORT} | ||||
|       API_PORT: ${API_PORT} | ||||
|       DEV: 1 | ||||
|     networks: | ||||
|       dev: | ||||
|         ipv4_address: 172.21.0.20 | ||||
|         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 | ||||
|     build: | ||||
|       context: . | ||||
|       dockerfile: ./api.dockerfile | ||||
|     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: | ||||
|   tactical-data-dev: null | ||||
|   postgres-data-dev: null | ||||
|   mongo-dev-data: null | ||||
|   mesh-data-dev: null | ||||
|   redis-data-dev: null | ||||
|  | ||||
| networks: | ||||
|   dev: | ||||
| @@ -254,4 +231,4 @@ networks: | ||||
|     ipam: | ||||
|       driver: default | ||||
|       config: | ||||
|         - subnet: 172.21.0.0/24   | ||||
|         - subnet: ${DOCKER_NETWORK} | ||||
|   | ||||
| @@ -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,47 +87,47 @@ 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' | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| REST_FRAMEWORK = { | ||||
|     'DATETIME_FORMAT': '%b-%d-%Y - %H:%M', | ||||
|  | ||||
|     'DEFAULT_PERMISSION_CLASSES': ( | ||||
|         'rest_framework.permissions.IsAuthenticated', | ||||
|     ), | ||||
|     'DEFAULT_AUTHENTICATION_CLASSES': ( | ||||
|         'knox.auth.TokenAuthentication', | ||||
|     ), | ||||
| } | ||||
|  | ||||
| if not DEBUG: | ||||
|     REST_FRAMEWORK.update({ | ||||
|         'DEFAULT_RENDERER_CLASSES': ( | ||||
|             'rest_framework.renderers.JSONRenderer', | ||||
|         ) | ||||
|     }) | ||||
|  | ||||
| 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 | ||||
|   "${VIRTUAL_ENV}"/bin/python manage.py load_chocos | ||||
|   "${VIRTUAL_ENV}"/bin/python manage.py load_community_scripts | ||||
|   "${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 | ||||
| } | ||||
|  | ||||
| @@ -126,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}" | ||||
| @@ -170,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,36 +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 | ||||
| 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: | ||||
|   | ||||
							
								
								
									
										11
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -48,3 +48,14 @@ nats-rmm.conf | ||||
| .mypy_cache | ||||
| 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. | ||||
							
								
								
									
										41
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								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://rmm.tacticalrmm.io/) | ||||
| Demo database resets every hour. Alot of features are disabled for obvious reasons due to the nature of this app. | ||||
| # [LIVE DEMO](https://demo.tacticalrmm.com/) | ||||
|  | ||||
| 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",) | ||||
|   | ||||
| @@ -0,0 +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):  # 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( | ||||
|             username=uuid.uuid4().hex, | ||||
|             is_installer_user=True, | ||||
|             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,4 +1,7 @@ | ||||
| from getpass import getpass | ||||
|  | ||||
| from django.core.management.base import BaseCommand | ||||
|  | ||||
| from accounts.models import User | ||||
|  | ||||
|  | ||||
| @@ -16,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): | ||||
|   | ||||
| @@ -0,0 +1,18 @@ | ||||
| # Generated by Django 3.2.4 on 2021-06-17 04:29 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('accounts', '0020_role_can_manage_roles'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_view_core_settings', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,18 @@ | ||||
| # Generated by Django 3.2.4 on 2021-06-28 05:01 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('accounts', '0021_role_can_view_core_settings'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='user', | ||||
|             name='clear_search_when_switching', | ||||
|             field=models.BooleanField(default=True), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,18 @@ | ||||
| # Generated by Django 3.2.4 on 2021-06-30 03:22 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('accounts', '0022_user_clear_search_when_switching'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='user', | ||||
|             name='is_installer_user', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,18 @@ | ||||
| # Generated by Django 3.2.1 on 2021-07-20 20:26 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('accounts', '0023_user_is_installer_user'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='user', | ||||
|             name='last_login_ip', | ||||
|             field=models.GenericIPAddressField(blank=True, default=None, null=True), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,33 @@ | ||||
| # Generated by Django 3.2.1 on 2021-07-21 04:24 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('accounts', '0024_user_last_login_ip'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='created_by', | ||||
|             field=models.CharField(blank=True, max_length=100, null=True), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='created_time', | ||||
|             field=models.DateTimeField(auto_now_add=True, null=True), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='modified_by', | ||||
|             field=models.CharField(blank=True, max_length=100, null=True), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='modified_time', | ||||
|             field=models.DateTimeField(auto_now=True, null=True), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,34 @@ | ||||
| # Generated by Django 3.2.6 on 2021-09-01 12:47 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('accounts', '0025_auto_20210721_0424'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='APIKey', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('created_by', models.CharField(blank=True, max_length=100, null=True)), | ||||
|                 ('created_time', models.DateTimeField(auto_now_add=True, null=True)), | ||||
|                 ('modified_by', models.CharField(blank=True, max_length=100, null=True)), | ||||
|                 ('modified_time', models.DateTimeField(auto_now=True, null=True)), | ||||
|                 ('name', models.CharField(max_length=25, unique=True)), | ||||
|                 ('key', models.CharField(blank=True, max_length=48, unique=True)), | ||||
|                 ('expiration', models.DateTimeField(blank=True, default=None, null=True)), | ||||
|             ], | ||||
|             options={ | ||||
|                 'abstract': False, | ||||
|             }, | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_manage_api_keys', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,25 @@ | ||||
| # Generated by Django 3.2.6 on 2021-09-03 00:54 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('accounts', '0026_auto_20210901_1247'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='apikey', | ||||
|             name='user', | ||||
|             field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='api_key', to='accounts.user'), | ||||
|             preserve_default=False, | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='user', | ||||
|             name='block_dashboard_login', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										150
									
								
								api/tacticalrmm/accounts/migrations/0028_auto_20211010_0249.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								api/tacticalrmm/accounts/migrations/0028_auto_20211010_0249.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| # Generated by Django 3.2.6 on 2021-10-10 02:49 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('clients', '0018_auto_20211010_0249'), | ||||
|         ('accounts', '0027_auto_20210903_0054'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_accounts', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_agent_history', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_agents', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_alerts', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_api_keys', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_automation_policies', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_autotasks', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_checks', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_clients', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_deployments', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_notes', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_pendingactions', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_roles', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_scripts', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_sites', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_software', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_ping_agents', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_recover_agents', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_view_clients', | ||||
|             field=models.ManyToManyField(blank=True, related_name='role_clients', to='clients.Client'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_view_sites', | ||||
|             field=models.ManyToManyField(blank=True, related_name='role_sites', to='clients.Site'), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='apikey', | ||||
|             name='created_by', | ||||
|             field=models.CharField(blank=True, max_length=255, null=True), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='apikey', | ||||
|             name='modified_by', | ||||
|             field=models.CharField(blank=True, max_length=255, null=True), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='role', | ||||
|             name='created_by', | ||||
|             field=models.CharField(blank=True, max_length=255, null=True), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='role', | ||||
|             name='modified_by', | ||||
|             field=models.CharField(blank=True, max_length=255, null=True), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='user', | ||||
|             name='created_by', | ||||
|             field=models.CharField(blank=True, max_length=255, null=True), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='user', | ||||
|             name='modified_by', | ||||
|             field=models.CharField(blank=True, max_length=255, null=True), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='user', | ||||
|             name='role', | ||||
|             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='users', to='accounts.role'), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,28 @@ | ||||
| # Generated by Django 3.2.6 on 2021-10-22 22:45 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('accounts', '0028_auto_20211010_0249'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_list_alerttemplates', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_manage_alerttemplates', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_run_urlactions', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,23 @@ | ||||
| # Generated by Django 3.2.6 on 2021-11-04 02:21 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('accounts', '0029_auto_20211022_2245'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_manage_customfields', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='role', | ||||
|             name='can_view_customfields', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										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,34 +1,28 @@ | ||||
| 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): | ||||
|     is_active = models.BooleanField(default=True) | ||||
|     block_dashboard_login = models.BooleanField(default=False) | ||||
|     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", | ||||
| @@ -38,14 +32,22 @@ 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) | ||||
|  | ||||
|     agent = models.OneToOneField( | ||||
|         "agents.Agent", | ||||
| @@ -59,10 +61,23 @@ class User(AbstractUser, BaseAuditModel): | ||||
|         "accounts.Role", | ||||
|         null=True, | ||||
|         blank=True, | ||||
|         related_name="roles", | ||||
|         related_name="users", | ||||
|         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 | ||||
| @@ -70,12 +85,30 @@ 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}") | ||||
|  | ||||
| class Role(models.Model): | ||||
|         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) | ||||
|     is_superuser = models.BooleanField(default=False) | ||||
|  | ||||
|     # agents | ||||
|     can_list_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) | ||||
| @@ -87,91 +120,121 @@ class Role(models.Model): | ||||
|     can_install_agents = models.BooleanField(default=False) | ||||
|     can_run_scripts = models.BooleanField(default=False) | ||||
|     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) | ||||
|     can_manage_notes = models.BooleanField(default=False) | ||||
|     can_view_core_settings = models.BooleanField(default=False) | ||||
|     can_edit_core_settings = models.BooleanField(default=False) | ||||
|     can_do_server_maint = models.BooleanField(default=False) | ||||
|     can_code_sign = models.BooleanField(default=False) | ||||
|     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) | ||||
|     can_manage_checks = models.BooleanField(default=False) | ||||
|     can_run_checks = models.BooleanField(default=False) | ||||
|  | ||||
|     # clients | ||||
|     can_list_clients = models.BooleanField(default=False) | ||||
|     can_manage_clients = models.BooleanField(default=False) | ||||
|     can_list_sites = models.BooleanField(default=False) | ||||
|     can_manage_sites = models.BooleanField(default=False) | ||||
|     can_list_deployments = models.BooleanField(default=False) | ||||
|     can_manage_deployments = models.BooleanField(default=False) | ||||
|     can_view_clients = models.ManyToManyField( | ||||
|         "clients.Client", related_name="role_clients", blank=True | ||||
|     ) | ||||
|     can_view_sites = models.ManyToManyField( | ||||
|         "clients.Site", related_name="role_sites", blank=True | ||||
|     ) | ||||
|  | ||||
|     # automation | ||||
|     can_list_automation_policies = models.BooleanField(default=False) | ||||
|     can_manage_automation_policies = models.BooleanField(default=False) | ||||
|  | ||||
|     # automated tasks | ||||
|     can_list_autotasks = models.BooleanField(default=False) | ||||
|     can_manage_autotasks = models.BooleanField(default=False) | ||||
|     can_run_autotasks = models.BooleanField(default=False) | ||||
|  | ||||
|     # logs | ||||
|     can_view_auditlogs = models.BooleanField(default=False) | ||||
|     can_list_pendingactions = models.BooleanField(default=False) | ||||
|     can_manage_pendingactions = models.BooleanField(default=False) | ||||
|     can_view_debuglogs = models.BooleanField(default=False) | ||||
|  | ||||
|     # scripts | ||||
|     can_list_scripts = models.BooleanField(default=False) | ||||
|     can_manage_scripts = models.BooleanField(default=False) | ||||
|  | ||||
|     # alerts | ||||
|     can_list_alerts = models.BooleanField(default=False) | ||||
|     can_manage_alerts = models.BooleanField(default=False) | ||||
|     can_list_alerttemplates = models.BooleanField(default=False) | ||||
|     can_manage_alerttemplates = models.BooleanField(default=False) | ||||
|  | ||||
|     # win services | ||||
|     can_manage_winsvcs = models.BooleanField(default=False) | ||||
|  | ||||
|     # software | ||||
|     can_list_software = models.BooleanField(default=False) | ||||
|     can_manage_software = models.BooleanField(default=False) | ||||
|  | ||||
|     # windows updates | ||||
|     can_manage_winupdates = models.BooleanField(default=False) | ||||
|  | ||||
|     # accounts | ||||
|     can_list_accounts = models.BooleanField(default=False) | ||||
|     can_manage_accounts = models.BooleanField(default=False) | ||||
|     can_list_roles = models.BooleanField(default=False) | ||||
|     can_manage_roles = models.BooleanField(default=False) | ||||
|  | ||||
|     # authentication | ||||
|     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 perms(): | ||||
|         return [ | ||||
|             "is_superuser", | ||||
|             "can_use_mesh", | ||||
|             "can_uninstall_agents", | ||||
|             "can_update_agents", | ||||
|             "can_edit_agent", | ||||
|             "can_manage_procs", | ||||
|             "can_view_eventlogs", | ||||
|             "can_send_cmd", | ||||
|             "can_reboot_agents", | ||||
|             "can_install_agents", | ||||
|             "can_run_scripts", | ||||
|             "can_run_bulk", | ||||
|             "can_manage_notes", | ||||
|             "can_edit_core_settings", | ||||
|             "can_do_server_maint", | ||||
|             "can_code_sign", | ||||
|             "can_manage_checks", | ||||
|             "can_run_checks", | ||||
|             "can_manage_clients", | ||||
|             "can_manage_sites", | ||||
|             "can_manage_deployments", | ||||
|             "can_manage_automation_policies", | ||||
|             "can_manage_autotasks", | ||||
|             "can_run_autotasks", | ||||
|             "can_view_auditlogs", | ||||
|             "can_manage_pendingactions", | ||||
|             "can_view_debuglogs", | ||||
|             "can_manage_scripts", | ||||
|             "can_manage_alerts", | ||||
|             "can_manage_winsvcs", | ||||
|             "can_manage_software", | ||||
|             "can_manage_winupdates", | ||||
|             "can_manage_accounts", | ||||
|             "can_manage_roles", | ||||
|         ] | ||||
|     def serialize(role): | ||||
|         # serializes the agent and returns json | ||||
|         from .serializers import RoleAuditSerializer | ||||
|  | ||||
|         return RoleAuditSerializer(role).data | ||||
|  | ||||
|  | ||||
| class APIKey(BaseAuditModel): | ||||
|     name = CharField(unique=True, max_length=25) | ||||
|     key = CharField(unique=True, blank=True, max_length=48) | ||||
|     expiration = DateTimeField(blank=True, null=True, default=None) | ||||
|     user = models.ForeignKey( | ||||
|         "accounts.User", | ||||
|         related_name="api_key", | ||||
|         on_delete=models.CASCADE, | ||||
|     ) | ||||
|  | ||||
|     @staticmethod | ||||
|     def serialize(apikey): | ||||
|         from .serializers import APIKeyAuditSerializer | ||||
|  | ||||
|         return APIKeyAuditSerializer(apikey).data | ||||
|   | ||||
| @@ -1,19 +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 True | ||||
|             return _has_perm(r, "can_list_accounts") | ||||
|  | ||||
|         # 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 | ||||
|  | ||||
|             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") | ||||
|  | ||||
|  | ||||
| class RolesPerms(permissions.BasePermission): | ||||
|     def has_permission(self, r, view): | ||||
|     def has_permission(self, r, view) -> bool: | ||||
|         if r.method == "GET": | ||||
|             return True | ||||
|             return _has_perm(r, "can_list_roles") | ||||
|  | ||||
|         return _has_perm(r, "can_manage_roles") | ||||
|  | ||||
|  | ||||
| class APIKeyPerms(permissions.BasePermission): | ||||
|     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,7 +1,14 @@ | ||||
| import pyotp | ||||
| from rest_framework.serializers import ModelSerializer, SerializerMethodField | ||||
| from django.conf import settings | ||||
| from rest_framework.serializers import ( | ||||
|     ModelSerializer, | ||||
|     ReadOnlyField, | ||||
|     SerializerMethodField, | ||||
| ) | ||||
|  | ||||
| from .models import User, Role | ||||
| from tacticalrmm.util_settings import get_webdomain | ||||
|  | ||||
| from .models import APIKey, Role, User | ||||
|  | ||||
|  | ||||
| class UserUISerializer(ModelSerializer): | ||||
| @@ -16,6 +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", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| @@ -30,12 +44,14 @@ class UserSerializer(ModelSerializer): | ||||
|             "email", | ||||
|             "is_active", | ||||
|             "last_login", | ||||
|             "last_login_ip", | ||||
|             "role", | ||||
|             "block_dashboard_login", | ||||
|             "date_format", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class TOTPSetupSerializer(ModelSerializer): | ||||
|  | ||||
|     qr_url = SerializerMethodField() | ||||
|  | ||||
|     class Meta: | ||||
| @@ -48,11 +64,42 @@ 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]) | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class RoleSerializer(ModelSerializer): | ||||
|     user_count = SerializerMethodField() | ||||
|  | ||||
|     class Meta: | ||||
|         model = Role | ||||
|         fields = "__all__" | ||||
|  | ||||
|     def get_user_count(self, obj): | ||||
|         return obj.users.count() | ||||
|  | ||||
|  | ||||
| class RoleAuditSerializer(ModelSerializer): | ||||
|     class Meta: | ||||
|         model = Role | ||||
|         fields = "__all__" | ||||
|  | ||||
|  | ||||
| class APIKeySerializer(ModelSerializer): | ||||
|     username = ReadOnlyField(source="user.username") | ||||
|  | ||||
|     class Meta: | ||||
|         model = APIKey | ||||
|         fields = "__all__" | ||||
|  | ||||
|  | ||||
| class APIKeyAuditSerializer(ModelSerializer): | ||||
|     username = ReadOnlyField(source="user.username") | ||||
|  | ||||
|     class Meta: | ||||
|         model = APIKey | ||||
|         fields = [ | ||||
|             "name", | ||||
|             "username", | ||||
|             "expiration", | ||||
|         ] | ||||
|   | ||||
| @@ -1,47 +1,57 @@ | ||||
| from unittest.mock import patch | ||||
|  | ||||
| from django.test import override_settings | ||||
| from model_bakery import baker, seq | ||||
|  | ||||
| from accounts.models import User | ||||
| 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") | ||||
|         self.assertEqual(r.status_code, 400) | ||||
|         self.assertEqual(r.data, "bad credentials") | ||||
|         self.assertEqual(r.data, "Bad credentials") | ||||
|  | ||||
|         data = {"username": "billy", "password": "hunter2"} | ||||
|         r = self.client.post(url, data, format="json") | ||||
|         self.assertEqual(r.status_code, 400) | ||||
|         self.assertEqual(r.data, "bad credentials") | ||||
|         self.assertEqual(r.data, "Bad credentials") | ||||
|  | ||||
|         self.bob.totp_key = "AB5RI6YPFTZAS52G" | ||||
|         self.bob.save() | ||||
|         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 | ||||
|         self.bob.save() | ||||
|         r = self.client.post(url, data, format="json") | ||||
|         self.assertEqual(r.status_code, 400) | ||||
|  | ||||
|     @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"} | ||||
| @@ -53,7 +63,7 @@ class TestAccounts(TacticalTestCase): | ||||
|         mock_verify.return_value = False | ||||
|         r = self.client.post(url, data, format="json") | ||||
|         self.assertEqual(r.status_code, 400) | ||||
|         self.assertEqual(r.data, "bad credentials") | ||||
|         self.assertEqual(r.data, "Bad credentials") | ||||
|  | ||||
|         mock_verify.return_value = True | ||||
|         data = {"username": "bob", "password": "asd234234asd", "twofactor": "123456"} | ||||
| @@ -61,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): | ||||
| @@ -188,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) | ||||
|  | ||||
| @@ -275,11 +285,12 @@ 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, | ||||
|         } | ||||
|         r = self.client.patch(url, data, format="json") | ||||
|         self.assertEqual(r.status_code, 200) | ||||
| @@ -287,6 +298,89 @@ 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() | ||||
|         self.authenticate() | ||||
|  | ||||
|     def test_get_api_keys(self): | ||||
|         url = "/accounts/apikeys/" | ||||
|         apikeys = baker.make("accounts.APIKey", key=seq("APIKEY"), _quantity=3) | ||||
|  | ||||
|         serializer = APIKeySerializer(apikeys, many=True) | ||||
|         resp = self.client.get(url, format="json") | ||||
|         self.assertEqual(resp.status_code, 200) | ||||
|         self.assertEqual(serializer.data, resp.data) | ||||
|  | ||||
|         self.check_not_authenticated("get", url) | ||||
|  | ||||
|     def test_add_api_keys(self): | ||||
|         url = "/accounts/apikeys/" | ||||
|  | ||||
|         user = baker.make("accounts.User") | ||||
|         data = {"name": "Name", "user": user.id, "expiration": None} | ||||
|  | ||||
|         resp = self.client.post(url, data, format="json") | ||||
|         self.assertEqual(resp.status_code, 200) | ||||
|         self.assertTrue(APIKey.objects.filter(name="Name").exists()) | ||||
|         self.assertTrue(APIKey.objects.get(name="Name").key) | ||||
|  | ||||
|         self.check_not_authenticated("post", url) | ||||
|  | ||||
|     def test_modify_api_key(self): | ||||
|         # test a call where api key doesn't exist | ||||
|         resp = self.client.put("/accounts/apikeys/500/", format="json") | ||||
|         self.assertEqual(resp.status_code, 404) | ||||
|  | ||||
|         apikey = baker.make("accounts.APIKey", name="Test") | ||||
|         url = f"/accounts/apikeys/{apikey.pk}/" | ||||
|  | ||||
|         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) | ||||
|         self.assertEqual(apikey.name, "New Name") | ||||
|  | ||||
|         self.check_not_authenticated("put", url) | ||||
|  | ||||
|     def test_delete_api_key(self): | ||||
|         # test a call where api key doesn't exist | ||||
|         resp = self.client.delete("/accounts/apikeys/500/", format="json") | ||||
|         self.assertEqual(resp.status_code, 404) | ||||
|  | ||||
|         # test delete api key | ||||
|         apikey = baker.make("accounts.APIKey") | ||||
|         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()) | ||||
|  | ||||
|         self.check_not_authenticated("delete", url) | ||||
|  | ||||
|  | ||||
| class TestTOTPSetup(TacticalTestCase): | ||||
|     def setUp(self): | ||||
|         self.authenticate() | ||||
| @@ -311,4 +405,30 @@ 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): | ||||
|     def setUp(self): | ||||
|         # create User and associate to API Key | ||||
|         self.user = User.objects.create(username="api_user", is_superuser=True) | ||||
|         self.api_key = APIKey.objects.create( | ||||
|             name="Test Token", key="123456", user=self.user | ||||
|         ) | ||||
|  | ||||
|         self.setup_client() | ||||
|  | ||||
|     def test_api_auth(self): | ||||
|         url = "/clients/" | ||||
|         # auth should fail if no header set | ||||
|         self.check_not_authenticated("get", url) | ||||
|  | ||||
|         # invalid api key in header should return code 400 | ||||
|         self.client.credentials(HTTP_X_API_KEY="000000") | ||||
|         r = self.client.get(url, format="json") | ||||
|         self.assertEqual(r.status_code, 401) | ||||
|  | ||||
|         # valid api key in header should return code 200 | ||||
|         self.client.credentials(HTTP_X_API_KEY="123456") | ||||
|         r = self.client.get(url, format="json") | ||||
|         self.assertEqual(r.status_code, 200) | ||||
|   | ||||
| @@ -5,11 +5,18 @@ 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()), | ||||
|     path("users/ui/", views.UserUI.as_view()), | ||||
|     path("permslist/", views.PermsList.as_view()), | ||||
|     path("roles/", views.GetAddRoles.as_view()), | ||||
|     path("<int:pk>/role/", views.GetUpdateDeleteRole.as_view()), | ||||
|     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,62 +1,85 @@ | ||||
| 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 django.utils import timezone as djangotime | ||||
| from knox.models import AuthToken | ||||
| from knox.views import LoginView as KnoxLoginView | ||||
| 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 accounts.utils import is_root_user | ||||
| from core.tasks import sync_mesh_perms_task | ||||
| from logs.models import AuditLog | ||||
| from tacticalrmm.utils import notify_error | ||||
| from tacticalrmm.helpers import notify_error | ||||
| from tacticalrmm.utils import get_core_settings | ||||
|  | ||||
| from .models import User, Role | ||||
| from .permissions import AccountsPerms, RolesPerms | ||||
| from .models import APIKey, Role, User | ||||
| from .permissions import ( | ||||
|     AccountsPerms, | ||||
|     APIKeyPerms, | ||||
|     LocalUserPerms, | ||||
|     RolesPerms, | ||||
|     SelfResetSSOPerms, | ||||
| ) | ||||
| from .serializers import ( | ||||
|     APIKeySerializer, | ||||
|     RoleSerializer, | ||||
|     TOTPSetupSerializer, | ||||
|     UserSerializer, | ||||
|     UserUISerializer, | ||||
|     RoleSerializer, | ||||
| ) | ||||
|  | ||||
|  | ||||
| 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(): | ||||
|             AuditLog.audit_user_failed_login(request.data["username"]) | ||||
|             return Response("bad credentials", status=status.HTTP_400_BAD_REQUEST) | ||||
|             AuditLog.audit_user_failed_login( | ||||
|                 request.data["username"], debug_info={"ip": request._client_ip} | ||||
|             ) | ||||
|             return notify_error("Bad credentials") | ||||
|  | ||||
|         user = serializer.validated_data["user"] | ||||
|  | ||||
|         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): | ||||
| @@ -66,30 +89,157 @@ class LoginView(KnoxLoginView): | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|         user = serializer.validated_data["user"] | ||||
|  | ||||
|         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 | ||||
|  | ||||
|         if valid: | ||||
|             login(request, user) | ||||
|             AuditLog.audit_user_login_successful(request.data["username"]) | ||||
|             return super(LoginView, self).post(request, format=None) | ||||
|  | ||||
|             # save ip information | ||||
|             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} | ||||
|             ) | ||||
|             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"]) | ||||
|             return Response("bad credentials", status=status.HTTP_400_BAD_REQUEST) | ||||
|             AuditLog.audit_user_failed_twofactor( | ||||
|                 request.data["username"], debug_info={"ip": request._client_ip} | ||||
|             ) | ||||
|             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] | ||||
|  | ||||
|     def get(self, request): | ||||
|         users = User.objects.filter(agent=None) | ||||
|     class UserSerializerSSO(ModelSerializer): | ||||
|         social_accounts = SerializerMethodField() | ||||
|  | ||||
|         return Response(UserSerializer(users, many=True).data) | ||||
|         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) | ||||
|  | ||||
|         if search: | ||||
|             users = User.objects.filter(agent=None, is_installer_user=False).filter( | ||||
|                 username__icontains=search | ||||
|             ) | ||||
|         else: | ||||
|             users = User.objects.filter(agent=None, is_installer_user=False) | ||||
|  | ||||
|         return Response(self.UserSerializerSSO(users, many=True).data) | ||||
|  | ||||
|     def post(self, request): | ||||
|         # add new user | ||||
| @@ -104,13 +254,16 @@ class GetAddUsers(APIView): | ||||
|                 f"ERROR: User {request.data['username']} already exists!" | ||||
|             ) | ||||
|  | ||||
|         user.first_name = request.data["first_name"] | ||||
|         user.last_name = request.data["last_name"] | ||||
|         if "first_name" in request.data.keys(): | ||||
|             user.first_name = request.data["first_name"] | ||||
|         if "last_name" in request.data.keys(): | ||||
|             user.last_name = request.data["last_name"] | ||||
|         if "role" in request.data.keys() and isinstance(request.data["role"], int): | ||||
|             role = get_object_or_404(Role, pk=request.data["role"]) | ||||
|             user.role = role | ||||
|  | ||||
|         user.save() | ||||
|         sync_mesh_perms_task.delay() | ||||
|         return Response(user.username) | ||||
|  | ||||
|  | ||||
| @@ -125,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"]) | ||||
| @@ -160,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 = "" | ||||
| @@ -172,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() | ||||
| @@ -183,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): | ||||
| @@ -196,11 +349,6 @@ class UserUI(APIView): | ||||
|         return Response("ok") | ||||
|  | ||||
|  | ||||
| class PermsList(APIView): | ||||
|     def get(self, request): | ||||
|         return Response(Role.perms()) | ||||
|  | ||||
|  | ||||
| class GetAddRoles(APIView): | ||||
|     permission_classes = [IsAuthenticated, RolesPerms] | ||||
|  | ||||
| @@ -212,7 +360,7 @@ class GetAddRoles(APIView): | ||||
|         serializer = RoleSerializer(data=request.data) | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|         serializer.save() | ||||
|         return Response("ok") | ||||
|         return Response("Role was added") | ||||
|  | ||||
|  | ||||
| class GetUpdateDeleteRole(APIView): | ||||
| @@ -227,9 +375,70 @@ class GetUpdateDeleteRole(APIView): | ||||
|         serializer = RoleSerializer(instance=role, data=request.data) | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|         serializer.save() | ||||
|         return Response("ok") | ||||
|         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() | ||||
|         return Response("ok") | ||||
|         sync_mesh_perms_task.delay() | ||||
|         return Response("Role was removed") | ||||
|  | ||||
|  | ||||
| class GetAddAPIKeys(APIView): | ||||
|     permission_classes = [IsAuthenticated, APIKeyPerms] | ||||
|  | ||||
|     def get(self, request): | ||||
|         apikeys = APIKey.objects.all() | ||||
|         return Response(APIKeySerializer(apikeys, many=True).data) | ||||
|  | ||||
|     def post(self, request): | ||||
|         # generate a random API Key | ||||
|         from django.utils.crypto import get_random_string | ||||
|  | ||||
|         request.data["key"] = get_random_string(length=32).upper() | ||||
|         serializer = APIKeySerializer(data=request.data) | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|         serializer.save() | ||||
|         return Response("The API Key was added") | ||||
|  | ||||
|  | ||||
| class GetUpdateDeleteAPIKey(APIView): | ||||
|     permission_classes = [IsAuthenticated, APIKeyPerms] | ||||
|  | ||||
|     def put(self, request, pk): | ||||
|         apikey = get_object_or_404(APIKey, pk=pk) | ||||
|  | ||||
|         # remove API key is present in request data | ||||
|         if "key" in request.data.keys(): | ||||
|             request.data.pop("key") | ||||
|  | ||||
|         serializer = APIKeySerializer(instance=apikey, data=request.data, partial=True) | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|         serializer.save() | ||||
|         return Response("The API Key was edited") | ||||
|  | ||||
|     def delete(self, request, pk): | ||||
|         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,8 +1,8 @@ | ||||
| from django.contrib import admin | ||||
|  | ||||
| from .models import Agent, AgentCustomField, Note, RecoveryAction | ||||
| 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,24 +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("asdkj3h4234-1234hg3h4g34-234jjh34|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) | ||||
| @@ -76,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}") | ||||
|   | ||||
							
								
								
									
										26
									
								
								api/tacticalrmm/agents/management/commands/update_agents.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								api/tacticalrmm/agents/management/commands/update_agents.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| from django.conf import settings | ||||
| from django.core.management.base import BaseCommand | ||||
| from packaging import version as pyver | ||||
|  | ||||
| from agents.models import Agent | ||||
| from agents.tasks import send_agent_update_task | ||||
| 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) | ||||
|         ] | ||||
|         token, _ = token_is_valid() | ||||
|         send_agent_update_task.delay(agent_ids=agent_ids, token=token, force=False) | ||||
							
								
								
									
										23
									
								
								api/tacticalrmm/agents/migrations/0037_auto_20210627_0014.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								api/tacticalrmm/agents/migrations/0037_auto_20210627_0014.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| # Generated by Django 3.2.4 on 2021-06-27 00:14 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('agents', '0036_agent_block_policy_inheritance'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='agent', | ||||
|             name='has_patches_pending', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='agent', | ||||
|             name='pending_actions_count', | ||||
|             field=models.PositiveIntegerField(default=0), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										27
									
								
								api/tacticalrmm/agents/migrations/0038_agenthistory.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								api/tacticalrmm/agents/migrations/0038_agenthistory.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| # Generated by Django 3.2.1 on 2021-07-06 02:01 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('agents', '0037_auto_20210627_0014'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='AgentHistory', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('time', models.DateTimeField(auto_now_add=True)), | ||||
|                 ('type', models.CharField(choices=[('task_run', 'Task Run'), ('script_run', 'Script Run'), ('cmd_run', 'CMD Run')], default='cmd_run', max_length=50)), | ||||
|                 ('command', models.TextField(blank=True, null=True)), | ||||
|                 ('status', models.CharField(choices=[('success', 'Success'), ('failure', 'Failure')], default='success', max_length=50)), | ||||
|                 ('username', models.CharField(default='system', max_length=50)), | ||||
|                 ('results', models.TextField(blank=True, null=True)), | ||||
|                 ('agent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='history', to='agents.agent')), | ||||
|             ], | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										25
									
								
								api/tacticalrmm/agents/migrations/0039_auto_20210714_0738.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								api/tacticalrmm/agents/migrations/0039_auto_20210714_0738.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| # Generated by Django 3.2.5 on 2021-07-14 07:38 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('scripts', '0008_script_guid'), | ||||
|         ('agents', '0038_agenthistory'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='agenthistory', | ||||
|             name='script', | ||||
|             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='history', to='scripts.script'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='agenthistory', | ||||
|             name='script_results', | ||||
|             field=models.JSONField(blank=True, null=True), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										28
									
								
								api/tacticalrmm/agents/migrations/0040_auto_20211010_0249.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								api/tacticalrmm/agents/migrations/0040_auto_20211010_0249.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| # Generated by Django 3.2.6 on 2021-10-10 02:49 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('agents', '0039_auto_20210714_0738'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='agent', | ||||
|             name='agent_id', | ||||
|             field=models.CharField(max_length=200, unique=True), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='agent', | ||||
|             name='created_by', | ||||
|             field=models.CharField(blank=True, max_length=255, null=True), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='agent', | ||||
|             name='modified_by', | ||||
|             field=models.CharField(blank=True, max_length=255, null=True), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,18 @@ | ||||
| # Generated by Django 3.2.6 on 2021-10-18 03:04 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('agents', '0040_auto_20211010_0249'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='agenthistory', | ||||
|             name='username', | ||||
|             field=models.CharField(default='system', max_length=255), | ||||
|         ), | ||||
|     ] | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										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, | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user