Compare commits

...

474 Commits

Author SHA1 Message Date
wh1te909
442f09d0fe Release 0.19.0 2024-07-12 19:33:45 +00:00
wh1te909
50af28b2aa bump versions 2024-07-12 18:53:05 +00:00
wh1te909
28ad74a68e fix lint 2024-07-09 22:58:58 +00:00
wh1te909
13cdbae38f disable unused websocket endpoint 2024-07-09 22:54:58 +00:00
wh1te909
55c77df5ae update reqs 2024-07-09 18:22:07 +00:00
wh1te909
9b1d2fd985 bump web ver [skip ci] 2024-07-08 21:10:16 +00:00
wh1te909
91b7ea0367 more reqs updates 2024-07-08 20:35:38 +00:00
wh1te909
96d3926d09 update bins 2024-07-08 20:34:57 +00:00
wh1te909
c709b5a7eb update nats-api reqs 2024-07-08 20:33:55 +00:00
wh1te909
df82914005 make sure server scripts start with shebang 2024-07-08 19:00:44 +00:00
wh1te909
b1bdc38283 update reqs 2024-07-08 19:00:23 +00:00
sadnub
beb1215329 stop sending resolved message if the alert severity isn't configured to do so 2024-07-08 00:21:12 -04:00
wh1te909
51784388b9 add global option for handling info/warning notifications closes #1834 2024-07-05 21:18:14 +00:00
wh1te909
dbbbd53a4d wording 2024-07-05 21:16:27 +00:00
wh1te909
f9d992c969 add error handling 2024-07-02 16:52:05 +00:00
wh1te909
29a4d61e90 fix auditing/perms for webhook testing 2024-07-02 00:17:32 +00:00
wh1te909
2667cdb26c lower workers on smaller instances 2024-07-01 19:05:55 +00:00
wh1te909
a1669a5104 disabled in hosted 2024-07-01 18:45:54 +00:00
wh1te909
059f1bd63d bump test vers [skip ci] 2024-06-28 23:09:44 +00:00
wh1te909
82ae5e442c use homedir as cwd 2024-06-28 21:50:55 +00:00
Dan
b10114cd7c Merge pull request #1823 from sadnub/urlaction-rework
Serverside actions and cli
2024-06-28 13:30:08 -07:00
wh1te909
33f730aac4 redo migrations 2024-06-28 20:23:24 +00:00
wh1te909
92fdfdb05c delete migrations 2024-06-28 20:21:13 +00:00
wh1te909
fbaf3f3623 update reqs 2024-06-28 18:57:31 +00:00
wh1te909
5f400bc513 fix auditing 2024-06-28 17:13:12 +00:00
wh1te909
0fc59645fc alerts should still be created even if no notifications are selected 2024-06-28 17:13:12 +00:00
wh1te909
e2dee272b8 update pylance settings 2024-06-28 17:13:12 +00:00
wh1te909
364cf362f4 blacked 2024-06-28 17:13:12 +00:00
sadnub
8394a263c4 add auditing to new views 2024-06-28 17:13:12 +00:00
wh1te909
0e9aa26cfc enforce server script perms when handling alert templates 2024-06-28 17:13:12 +00:00
wh1te909
6a23d63266 use constant 2024-06-28 17:13:12 +00:00
wh1te909
af2fc15964 update reqs 2024-06-28 17:13:12 +00:00
wh1te909
5919037a4a fix deprecation warning 2024-06-28 17:13:12 +00:00
wh1te909
a761dab229 simplify query and add logging 2024-06-28 17:13:12 +00:00
wh1te909
fa656e1f56 add missing returns 2024-06-28 17:13:12 +00:00
wh1te909
77e141e84a return error if disabled 2024-06-28 17:13:12 +00:00
wh1te909
2439965fa8 disable web terminal by default 2024-06-28 17:13:12 +00:00
wh1te909
f66afbee90 make default method post and move imports 2024-06-28 17:13:12 +00:00
wh1te909
5a89d23a67 make description textfield 2024-06-28 17:13:12 +00:00
wh1te909
07c8dad1c3 redo migrations 2024-06-28 17:13:12 +00:00
wh1te909
beb8b18e98 remove migrations 2024-06-28 17:13:12 +00:00
wh1te909
887bb5d7cc rename model fields 2024-06-28 17:13:12 +00:00
wh1te909
4a9542d970 still need the old login views for frontend transition 2024-06-28 17:13:12 +00:00
wh1te909
c049d9d5ff alerts should not be created if agent in maintenance mode fixes #1849 2024-06-28 17:13:12 +00:00
wh1te909
c2cc4389a0 add test 2024-06-28 17:13:12 +00:00
wh1te909
12b5011266 fix tests 2024-06-28 17:13:12 +00:00
wh1te909
6e3cad454c add error handling for server script 2024-06-28 17:13:12 +00:00
sadnub
8251bd028c add error handling to webhook test function 2024-06-28 17:13:12 +00:00
sadnub
da87d452c2 fix tests 2024-06-28 17:13:12 +00:00
wh1te909
9bca0dfb3c fix action/resolved name if webhook 2024-06-28 17:13:12 +00:00
wh1te909
57904c4a97 also disable in demo 2024-06-28 17:13:12 +00:00
wh1te909
4e74d851e9 add test server script and start making server scripts/webterm optional 2024-06-28 17:13:12 +00:00
wh1te909
e5c1f69b02 use sigkill instead of sigterm 2024-06-28 17:13:12 +00:00
sadnub
9d390d064c flake 2024-06-28 17:13:12 +00:00
sadnub
4994d7892c black 2024-06-28 17:13:12 +00:00
sadnub
1ea06e3c42 fixes some tests for auth, fixes the recursive property lookup, fixes the replacement of alert variables 2024-06-28 17:13:12 +00:00
sadnub
a4b7a6dfc7 code formatting 2024-06-28 17:13:12 +00:00
sadnub
7fe1cce606 remove some unused imports 2024-06-28 17:13:12 +00:00
sadnub
7e5abe32e0 remove more server task stuff 2024-06-28 17:13:12 +00:00
wh1te909
47caf7c142 blacked 2024-06-28 17:13:12 +00:00
sadnub
cf4d777344 remove run_server_task command 2024-06-28 17:13:12 +00:00
sadnub
255927c346 remove autotasks rework 2024-06-28 17:13:12 +00:00
sadnub
e8c5fc79a6 added check to make sure instance_type == 'none' doesn't trigger a Model lookup and added json.dumps on body 2024-06-28 17:13:12 +00:00
sadnub
b309b24d0b Fix string replacement function and fix flaw in regex to match {{model.prop}} tags 2024-06-28 17:13:12 +00:00
sadnub
13f4cca9d5 allow strings in instance id for Agent hostname 2024-06-28 17:13:12 +00:00
sadnub
b3c0273e0c cleanup model resolution and potential fix for nested object and array properties in requets body 2024-06-28 17:13:12 +00:00
wh1te909
1df7fdf703 fix request body and url 2024-06-28 17:13:12 +00:00
wh1te909
cbf38309e2 blacked 2024-06-28 17:13:12 +00:00
sadnub
2ec7257dd7 add view for web hook test and add recursion to the dictionary data replacer 2024-06-28 17:13:12 +00:00
wh1te909
531aac6923 harden connect method 2024-06-28 17:13:12 +00:00
wh1te909
59b4604c77 wrong role name 2024-06-28 17:13:12 +00:00
sadnub
52aa269af9 modify totp setup view 2024-06-28 17:13:12 +00:00
wh1te909
8a03d9c498 set term 2024-06-28 17:13:12 +00:00
wh1te909
a36fc7ecfd fix webhooks 2024-06-28 17:13:12 +00:00
sadnub
7b0c269bce fix flake 2024-06-28 17:13:12 +00:00
sadnub
c10bf9b357 black 2024-06-28 17:13:12 +00:00
sadnub
0606642953 fix failure action not saving correctly if a server script 2024-06-28 17:13:12 +00:00
sadnub
d1b2cae201 add migrations 2024-06-28 17:13:12 +00:00
sadnub
097e567122 init 2024-06-28 17:13:12 +00:00
wh1te909
d22e1d6a24 update nats-server 2024-06-28 17:12:11 +00:00
wh1te909
2827069bd9 handle expired nginx signing key 2024-06-25 16:06:26 +00:00
Dan
614e3bd2a0 Merge pull request #1903 from silversword411/develop
troubleshoot_server.sh - Checking for resolvconf and giving helper text
2024-06-24 13:48:01 -07:00
silversword411
ff756a01d2 Added version tracking header info 2024-06-24 16:31:56 -04:00
silversword411
db14606dbe troubleshoot_server: Add helper for resolvconf error 2024-06-24 16:24:14 -04:00
wh1te909
de0a69ede5 replace expired nginx key 2024-06-24 05:55:31 +00:00
wh1te909
5bf5065d9a replace expired nginx key 2024-06-24 05:51:36 +00:00
wh1te909
0235dadbf7 fix alert template not assigned on new agent fixes #1896 2024-06-19 04:23:44 +00:00
wh1te909
203a15b447 cleanup pid file on start 2024-06-11 00:31:53 +00:00
wh1te909
fe4dfe2194 update reqs 2024-06-08 08:33:21 +00:00
wh1te909
c2eb93abe0 switch to localhost to download mesh exe 2024-06-08 08:07:05 +00:00
wh1te909
d32b834ae7 fix snippet bug fixes #1702 2024-05-29 06:29:00 +00:00
Dan
cecf45a698 Merge pull request #1824 from dinger1986/dinger1986-added-passwordless-sudo-verify
added passwordless sudo verify for backup scheduling
2024-05-18 22:28:22 -07:00
Dan
69cd348cc3 Merge branch 'develop' into dinger1986-added-passwordless-sudo-verify 2024-05-18 22:23:59 -07:00
wh1te909
868025ffa3 update reqs 2024-05-16 19:40:22 +00:00
wh1te909
60126a8cc5 update reqs 2024-05-07 02:30:06 +00:00
wh1te909
8cfba49559 add noninteractive 2024-04-25 22:03:22 +00:00
wh1te909
168f053c6f revert, already fixed in #1823 2024-04-22 18:12:02 +00:00
wh1te909
897e1d4539 fix script name fixes #1852 2024-04-22 17:32:05 +00:00
wh1te909
5ef6a0f4ea update reqs 2024-04-19 21:21:13 +00:00
wh1te909
eb80e32812 no-owner for pg_dump 2024-04-19 20:36:11 +00:00
wh1te909
620dadafe4 back to dev [skip ci] 2024-04-09 03:14:10 +00:00
wh1te909
e76fa878d2 Release 0.18.2 2024-04-09 01:02:48 +00:00
wh1te909
376b421eb9 bump versions 2024-04-09 00:37:07 +00:00
wh1te909
e1643aca80 revert DRF for now until we do more testing 2024-04-08 23:35:48 +00:00
wh1te909
4e97c0c5c9 add note about where to find bulk output results 2024-04-08 23:27:30 +00:00
dinger1986
2d51b122af Update backup.sh 2024-04-02 16:59:48 +01:00
wh1te909
05b88a3c73 fix for usernames with spaces in them fixes #1820 2024-03-30 22:08:15 +00:00
wh1te909
3c087d49e9 update reqs 2024-03-30 06:32:59 +00:00
wh1te909
d81fcccf10 add guest sharing perm 2024-03-30 05:56:24 +00:00
wh1te909
ee3a7bbbfc fix run urlactions perms fixes #1819 2024-03-30 05:52:09 +00:00
wh1te909
82d9e2fb16 back to dev 2024-03-30 05:49:56 +00:00
wh1te909
6ab39d6f70 Release 0.18.1 2024-03-29 21:07:45 +00:00
wh1te909
4aa413e697 bump version 2024-03-29 21:07:33 +00:00
wh1te909
04b3fc54b0 add nonalpha chars to mesh password #1814 2024-03-29 20:10:35 +00:00
wh1te909
e4c5a4e886 fix rights 2024-03-29 08:39:18 +00:00
wh1te909
a0ee7a59eb remove old funcs 2024-03-29 08:36:03 +00:00
wh1te909
b4a05160df skip if no mesh node id #1814 2024-03-28 23:43:18 +00:00
Dan
1a437b3961 Merge pull request #1815 from silversword411/develop
Tweaking bug report template
2024-03-28 14:10:14 -07:00
wh1te909
bda8555190 remove lambda 2024-03-28 07:32:54 +00:00
silversword411
10ca38f91d Tweaking bug report template 2024-03-28 02:24:18 -04:00
wh1te909
a468faad20 fix lint 2024-03-28 04:30:31 +00:00
wh1te909
7a20be4aff fix for mesh sync if trmm username is an email 2024-03-28 04:18:25 +00:00
wh1te909
06b974c8a4 back to dev 2024-03-28 04:18:01 +00:00
wh1te909
7284d9fcd8 Release 0.18.0 2024-03-27 18:16:28 +00:00
wh1te909
515394049a bump version 2024-03-27 18:09:17 +00:00
wh1te909
35c8b4f535 add mgmt command to get mesh login url 2024-03-27 17:28:32 +00:00
wh1te909
1a325a66b4 bump versions 2024-03-25 17:35:44 +00:00
wh1te909
7d82116fb9 add home endpoint 2024-03-25 17:29:43 +00:00
wh1te909
8a7bd4f21b update bins 2024-03-24 19:31:12 +00:00
wh1te909
2e5a2ef12d update nats 2024-03-24 19:29:18 +00:00
wh1te909
89aceda65a update reqs 2024-03-21 18:28:59 +00:00
Dan
39fd83aa16 Merge pull request #1810 from dinger1986/dinger1986-add-mesh-coname-to-initial
Update views.py
2024-03-20 17:03:07 -07:00
dinger1986
a23d811fe8 Update tests.py 2024-03-20 23:29:33 +00:00
dinger1986
a238779724 Update tests.py 2024-03-20 23:24:51 +00:00
dinger1986
3a848bc037 Update views.py 2024-03-20 20:52:31 +00:00
wh1te909
0528ecb454 fix iter logic 2024-03-18 09:12:18 +00:00
wh1te909
141835593c ensure email always verified 2024-03-16 09:03:20 +00:00
wh1te909
3d06200368 update deno 2024-03-16 09:02:54 +00:00
wh1te909
729bef9a77 update reqs 2024-03-15 07:53:28 +00:00
wh1te909
94f33bd642 force sync in hosted 2024-03-13 02:00:54 +00:00
wh1te909
7e010cdbca nodesource added their installation scripts back 2024-03-13 01:06:07 +00:00
wh1te909
8887bcd941 disable auto login no longer needed with mesh sync 2024-03-12 05:26:40 +00:00
wh1te909
56aeeee04c add stdout 2024-03-12 05:22:32 +00:00
wh1te909
98eb3c7287 fix mgmt commands 2024-03-11 20:27:12 +00:00
wh1te909
6819c1989b move to mgmt commands 2024-03-11 19:05:20 +00:00
wh1te909
7e01dd3e97 change to run ever 2 hours 2024-03-11 16:49:02 +00:00
wh1te909
ea4f2c3de8 break sync into chunks 2024-03-10 22:29:00 +00:00
wh1te909
b2f63b8761 should have been 10mb default 2024-03-10 21:26:12 +00:00
wh1te909
65865101ce handle large requests 2024-03-10 02:05:38 +00:00
wh1te909
c3637afe69 max websocket max size customizable 2024-03-10 00:14:04 +00:00
wh1te909
ab543ddf0c add option to use own cert during install 2024-03-09 19:21:21 +00:00
wh1te909
80595e76e7 cleanup orphaned checkhistory results fixes #1789 2024-03-09 08:31:25 +00:00
wh1te909
d49e68737a update reqs 2024-03-09 08:30:53 +00:00
wh1te909
712e15ba80 just try returning str for all 2024-03-05 20:45:34 +00:00
wh1te909
986160e667 also allow accessing floats 2024-03-05 20:27:32 +00:00
wh1te909
1ae4e23db1 more sync mesh fixes 2024-03-04 10:05:45 +00:00
wh1te909
bad646141c rework mesh sync #182 2024-03-03 11:37:24 +00:00
wh1te909
7911235b68 fix serializer/tests 2024-02-29 07:53:05 +00:00
wh1te909
12dee4d14d py 3.11.8 and update reqs 2024-02-29 02:09:33 +00:00
wh1te909
cba841beb8 don't show in hosted 2024-02-29 02:07:53 +00:00
wh1te909
4e3ebf7078 remove from local settings 2024-02-29 01:56:19 +00:00
wh1te909
1c34969f64 fix redis 2024-02-25 23:42:08 +00:00
wh1te909
dc26cabacd make sure to cleanup if sync is toggled off 2024-02-25 07:17:54 +00:00
wh1te909
a7bffcd471 install by default 2024-02-25 06:41:40 +00:00
wh1te909
6ae56ac2cc increase max ws response size for instances with large agent counts 2024-02-25 02:18:40 +00:00
wh1te909
03c087020c exclude inactive users from the sync 2024-02-25 02:17:07 +00:00
wh1te909
857a1ab9c4 handle old node and add mgmt command for sync mesh 2024-02-24 23:19:03 +00:00
wh1te909
64d9530e13 fixes to sync mesh #182 2024-02-24 07:53:05 +00:00
wh1te909
5dac1efc30 sync mesh users/perms with trmm #182 2024-02-23 21:17:24 +00:00
wh1te909
18bc74bc96 match more flags 2024-02-23 18:56:23 +00:00
wh1te909
f64efc63f8 allow access to jsonfields in script vars 2024-02-23 02:48:32 +00:00
Dan
e84b897991 Merge pull request #1766 from conlan0/develop
Add agent shutdown endpoint and nats
2024-02-22 13:48:03 -08:00
wh1te909
519647ef93 exit on install if existing 2024-02-22 21:25:45 +00:00
wh1te909
f694fe00e4 allow getting pk/id 2024-02-22 21:18:50 +00:00
wh1te909
0b951f27b6 add defaults 2024-02-22 21:18:06 +00:00
wh1te909
8aa082c9df exit restore if existing install 2024-02-22 21:17:05 +00:00
wh1te909
f2c5d47bd8 add migration 2024-02-22 04:52:05 +00:00
Dan
ac7642cc15 Merge pull request #1676 from NiceGuyIT/feature/cross-platform-scripting
[Feature] Add cross site scripting
2024-02-21 20:48:24 -08:00
conlan0
8f34865dab Add shutdown url 2024-02-21 21:29:53 -05:00
conlan0
c762d12a40 Add shutdown class 2024-02-21 21:29:29 -05:00
wh1te909
fe1e71dc07 update vscode settings 2024-02-21 17:34:44 +00:00
wh1te909
85b0350ed4 update reqs 2024-02-21 17:34:32 +00:00
wh1te909
a980491455 update reqs 2024-02-20 22:28:10 +00:00
wh1te909
5798c0ccaa wrong branch 2024-02-20 22:22:16 +00:00
wh1te909
742f49ca1f update reqs 2024-02-19 06:06:57 +00:00
wh1te909
5560fc805b switch to bigint for pk 2024-02-19 06:01:01 +00:00
wh1te909
9d4f8a4e8c update reqs 2024-02-09 17:39:55 +00:00
wh1te909
b4d25d6285 revert, prevent recursion 2024-02-09 17:31:59 +00:00
wh1te909
a504a376bd avoid db call and add test 2024-02-09 16:59:50 +00:00
wh1te909
f61ea6e90a fix super calls 2024-02-09 16:58:59 +00:00
wh1te909
b2651df36f wrong model, and don't need to pass class 2024-02-09 16:49:52 +00:00
wh1te909
b56c086841 back to dev [skip ci] 2024-02-06 06:46:32 +00:00
wh1te909
0b92fee42e Release 0.17.5 2024-02-06 06:42:28 +00:00
wh1te909
4343478c7b bump version 2024-02-06 06:41:51 +00:00
wh1te909
94649cbfc7 handle localhost bind issues on some instances 2024-02-06 06:19:57 +00:00
wh1te909
fb83f84d84 back to dev [skip ci] 2024-02-06 04:20:28 +00:00
wh1te909
e099a5a32e Release 0.17.4 2024-02-05 17:32:42 +00:00
wh1te909
84c2632d40 bump versions 2024-02-05 09:06:15 +00:00
wh1te909
3417ee25eb update reqs 2024-02-03 06:15:44 +00:00
wh1te909
6ada30102c bump web ver [skip ci] 2024-02-02 01:15:27 +00:00
wh1te909
ac86ca7266 forgot to add year 2024-02-01 17:15:47 +00:00
wh1te909
bb1d3edf71 make workers consistent with standard install [skip ci] 2024-01-30 19:05:17 +00:00
wh1te909
97b9253017 handle alert template when montype/site changes fixes #1733 2024-01-30 08:56:44 +00:00
wh1te909
971c2180c9 update mesh [skip ci] 2024-01-28 03:54:45 +00:00
wh1te909
f96dc6991e feat: hide custom fields in summary tab only closes #1745 2024-01-28 03:24:47 +00:00
wh1te909
6855493b2f feat: add serial number to linux/mac #1683 2024-01-27 02:54:26 +00:00
wh1te909
ff0d1f7c42 feat: show cpu cores/threads in summary tab closes #1715 2024-01-27 01:32:09 +00:00
wh1te909
3ae5824761 internal only now 2024-01-26 20:55:32 +00:00
wh1te909
702e865715 format 2024-01-26 20:55:08 +00:00
wh1te909
6bcf64c83f fix func 2024-01-26 19:35:33 +00:00
wh1te909
18b270c9d0 fixes to nats rework and add tests 2024-01-26 19:19:38 +00:00
wh1te909
783376acb0 node 20 2024-01-26 18:35:39 +00:00
wh1te909
81dab470d2 blacked 2024-01-26 07:38:52 +00:00
wh1te909
a12f0feb66 rework nats 2024-01-26 07:26:50 +00:00
wh1te909
d3c99d9c1c update bins 2024-01-26 07:09:00 +00:00
wh1te909
3eb3586c0f ioutil is deprecated 2024-01-26 07:08:18 +00:00
wh1te909
fdde16cf56 feat: add from name to email closes #1726 2024-01-26 00:39:45 +00:00
wh1te909
b8bc5596fd feat: add time and ret code to script test #1713 2024-01-26 00:03:11 +00:00
wh1te909
47842a79c7 update reqs 2024-01-26 00:02:08 +00:00
wh1te909
391d5bc386 update nats-api 2024-01-21 03:42:02 +00:00
wh1te909
ba8561e357 update reqs 2024-01-21 03:17:13 +00:00
wh1te909
6aa1170cef fix for redis 5 2024-01-16 03:18:10 +00:00
wh1te909
6d4363e685 prep for celery 6 2024-01-16 02:53:45 +00:00
wh1te909
6b02b1e1e8 update reqs 2024-01-15 03:16:58 +00:00
wh1te909
df3e68fbaf debian repo issue #1721
(cherry picked from commit 58a5550989)
2023-12-30 01:21:11 +00:00
wh1te909
58a5550989 debian repo issue #1721 2023-12-30 01:20:40 +00:00
wh1te909
ccc9e44ace nodesource no longer installs npm on node 18
(cherry picked from commit f225c5cf9a)
2023-12-29 05:27:59 +00:00
wh1te909
f225c5cf9a nodesource no longer installs npm on node 18 2023-12-29 05:24:43 +00:00
Dan
5c62c7992c Merge pull request #1717 from alexcmatm/patch-1
Add gmail relay handling for emails
2023-12-27 17:03:30 -08:00
wh1te909
70b8f09ccb fix logic 2023-12-28 00:50:24 +00:00
Alexandra Stone
abfeafa026 Add gmail relay handling for emails
This change adds ehlo and starttls when the server hostname is smtp-relay.gmail.com and authentication is disabled.
Just sending the message and quitting isn't enough for gmail specifically.
2023-12-27 14:07:57 -07:00
wh1te909
aa029b005f back to dev [skip ci] 2023-12-24 01:36:38 +00:00
wh1te909
6cc55e8f36 Release 0.17.3 2023-12-24 01:22:06 +00:00
wh1te909
b753d2ca1e bump agent version 2023-12-24 01:10:59 +00:00
wh1te909
1e50329c9e bump version 2023-12-22 17:40:36 +00:00
wh1te909
4942811694 update reqs 2023-12-22 17:38:16 +00:00
wh1te909
59e37e0ccb also make sudo changes to restore 2023-12-22 17:38:04 +00:00
Dan
20aa86d8a9 Merge pull request #1712 from Tenebor/ubuntu-psql-fix
Ubuntu psql fix and cert folder chown
2023-12-21 12:44:51 -08:00
Tenebor
64c5ab7042 fix: chown on ssl cert
Exec chown on /etc/letsencrypt only in case of secure installation.
2023-12-21 20:51:16 +01:00
Tenebor
d210f5171a fix: use interactive shell to run psql
Using ubuntu "sudo -u postgres psql" returns a permission error
2023-12-21 16:47:39 +01:00
wh1te909
c7eee0f14d update reqs 2023-12-11 19:27:28 +00:00
wh1te909
221753b62e update hash_bucket_size 2023-12-11 18:36:08 +00:00
wh1te909
d213e4d37f vscode 2023-12-11 18:35:21 +00:00
wh1te909
f8695f21d3 back to dev 2023-12-11 18:34:15 +00:00
David Randall
4ac1030289 Fix: Unused import
Signed-off-by: David Randall <David@NiceGuyIT.biz>
2023-12-10 18:41:30 -05:00
David Randall
93c7117319 Fix: Whitespace formatting 2023-12-10 18:06:09 -05:00
David Randall
974afd92ce Merge remote-tracking branch 'upstream/develop' into feature/cross-platform-scripting 2023-12-05 19:32:55 -05:00
wh1te909
dd1d15f1a4 Release 0.17.2 2023-12-04 21:50:28 +00:00
wh1te909
be847baaed bump versions 2023-12-04 21:37:56 +00:00
wh1te909
2b819e6751 clarify wording 2023-12-04 19:55:47 +00:00
wh1te909
66247cc005 add version check for onboarding tasks 2023-12-04 18:41:19 +00:00
David Randall
eafd38d3f2 Merge branch 'feature/cross-platform-scripting' of github.com:NiceGuyIT/tacticalrmm into feature/cross-platform-scripting 2023-12-03 23:20:00 -05:00
David Randall
c4e590e7a0 Add: Server variables are opt-out by default
- Pull the Nushell and Deno versions from the server.
- Support downloading Nushell and Deno from a url (not GitHUb).
- Add support for nu config.nu and env.nu files.
- Add support for default Deno permissions.
2023-12-03 23:19:43 -05:00
wh1te909
b92a594114 doesn't support go 1.21 yet, removing for now 2023-12-01 20:26:02 +00:00
wh1te909
9dfb16f6b8 update ci 2023-12-01 20:07:04 +00:00
wh1te909
4b74866d85 update bins 2023-12-01 19:50:07 +00:00
wh1te909
f532c85247 update natsapi 2023-12-01 19:49:10 +00:00
wh1te909
b1cc00c1bc dynamically import custom filters 2023-12-01 19:25:36 +00:00
wh1te909
5696aa49d5 update reqs 2023-12-01 19:24:54 +00:00
wh1te909
e12dc936fd fix tests 2023-11-27 21:58:27 +00:00
sadnub
6d39a7fb75 add onboarding task and revert runonce 2023-11-22 23:42:45 -05:00
sadnub
c87c312349 set insecure nats mode for docker dev 2023-11-22 23:42:45 -05:00
wh1te909
e9c1886cdd bump webver [skip ci] 2023-11-23 00:52:15 +00:00
wh1te909
13e4b1a781 increase timeout and change logger 2023-11-22 23:14:31 +00:00
wh1te909
3766fb14ef increase timeout 2023-11-22 22:55:35 +00:00
sadnub
29ee50e38b fix flake8 2023-11-22 16:52:48 -05:00
sadnub
d1ab69dc31 change nats task payload for run once task change 2023-11-22 16:47:22 -05:00
wh1te909
e3c4a54193 small fixes 2023-11-22 10:23:54 +00:00
wh1te909
2abbd2e3cf switch logger 2023-11-22 10:13:43 +00:00
wh1te909
f9387a5851 update reqs 2023-11-21 19:50:22 +00:00
wh1te909
7a9fb74b54 remove log 2023-11-21 19:49:27 +00:00
David Randall
d754f3dd4c Merge branch 'develop' into feature/cross-platform-scripting 2023-11-18 20:08:49 -05:00
David Randall
f54fc9e990 Fix: Linux uninstall script
Signed-off-by: David Randall <David@NiceGuyIT.biz>
2023-11-18 19:51:59 -05:00
wh1te909
8952095da5 fix payload and skip posix 2023-11-15 07:56:10 +00:00
wh1te909
597240d501 fixes to async rework 2023-11-15 02:53:10 +00:00
wh1te909
7377906d02 update reqs 2023-11-14 23:46:46 +00:00
wh1te909
ce6da1bce3 async rework of sync scheduled tasks 2023-11-13 02:44:37 +00:00
David Randall
1bf8ff73f8 [Feature] Add cross site scripting 2023-11-12 15:10:18 -05:00
wh1te909
564aaaf3df rework agent uninstall perms fixes #1673 2023-11-09 20:01:42 +00:00
wh1te909
64ba69b2d0 fix sorted migrations 2023-11-09 19:59:49 +00:00
wh1te909
ce5ada42af django-ipware is deprecated, switch to python-ipware 2023-11-08 21:34:24 +00:00
wh1te909
1ce5973713 call task directly and remove note about debug log 2023-11-08 21:21:35 +00:00
wh1te909
b035b53092 remove pytz 2023-11-08 08:17:23 +00:00
sadnub
7d0e02358c fix json output with custom fields 2023-11-07 17:34:20 -05:00
sadnub
374ff0aeb5 increase max text for report template and base template name field 2023-11-07 17:34:20 -05:00
wh1te909
947a43111e back to dev [skip ci] 2023-11-07 18:56:34 +00:00
wh1te909
9970911249 Release 0.17.1 2023-11-07 18:52:49 +00:00
wh1te909
5fed81c27b bump versions 2023-11-07 17:19:35 +00:00
wh1te909
dce4f1a5ae update reqs 2023-11-07 17:19:23 +00:00
wh1te909
7e1fc32a1c forgot to bump version of backup script last update 2023-11-07 17:17:37 +00:00
sadnub
a69f14f504 add loop controls and expression extensions to jinja 2023-11-03 15:49:10 -04:00
wh1te909
931069458d add custom filter for local_ips and rework imports 2023-11-03 17:17:54 +00:00
wh1te909
a5259baab0 start adding support for custom jinja filters 2023-11-03 16:58:43 +00:00
wh1te909
8aaa27350d expose ZoneInfo to template 2023-11-03 16:22:11 +00:00
wh1te909
6db6eb70da remove debug stuff
(cherry picked from commit ac74d2b7c2)
2023-11-02 21:27:06 +00:00
wh1te909
ac74d2b7c2 remove debug stuff 2023-11-02 21:25:24 +00:00
sadnub
2b316aeae9 expose datetime and re modules to template 2023-11-02 16:37:23 -04:00
wh1te909
aff96a45c6 back to dev [skip ci] 2023-11-01 23:31:26 +00:00
wh1te909
9ee246440f Release 0.17.0 2023-11-01 19:30:39 +00:00
wh1te909
e2f524ce7a this is a major version duh [skip ci] 2023-11-01 19:30:25 +00:00
wh1te909
a58b054292 bump version 2023-11-01 19:00:53 +00:00
wh1te909
ea9e5be1fc bump script versions [skip ci] 2023-10-31 18:32:01 +00:00
wh1te909
760ea4727c update reqs 2023-10-31 18:14:51 +00:00
wh1te909
f57f2e53a0 better scaling 2023-10-30 00:09:14 +00:00
Dan
136a393a17 Merge pull request #1663 from lcsnetworks/docker_allow_custom_uwsgi_configs
Add option to skip uWSGI config in Docker environments
2023-10-29 16:22:41 -07:00
wh1te909
8bbaab78b7 update markdown 2023-10-29 22:11:13 +00:00
wh1te909
067cd59637 daphne needed for tests 2023-10-29 21:46:21 +00:00
wh1te909
ce6ac7bf53 replace daphne with uvicorn 2023-10-29 21:38:33 +00:00
wh1te909
99271c4477 comment flaky test for now 2023-10-29 21:38:06 +00:00
wh1te909
156142ed58 bump web ver [skip ci] 2023-10-29 19:34:47 +00:00
wh1te909
4b5516c0eb update reqs 2023-10-29 19:01:55 +00:00
wh1te909
c3d8d2d240 change datetime 2023-10-29 18:54:13 +00:00
wh1te909
c29cf70025 back to uwsgi 2023-10-28 20:07:50 +00:00
wh1te909
6ebce55be3 update for weasyprint 2023-10-28 01:41:50 +00:00
sadnub
01c4a85bc0 move from uwsgi to gunicorn in docker. fix pulling dynamic web tar 2023-10-27 09:37:26 -04:00
sadnub
12d4206d84 update dockerfile image versions 2023-10-27 09:36:15 -04:00
wh1te909
946de18bea move import 2023-10-27 06:33:10 +00:00
wh1te909
904eb3538c fix grep 2023-10-27 06:32:14 +00:00
wh1te909
c851ca9328 switch to gunicorn due to issues with uwsgi and reporting 2023-10-27 02:22:16 +00:00
wh1te909
0ac415ad83 lower max requests per worker 2023-10-26 06:11:14 +00:00
sadnub
b3ba34d980 update docker to support reporting 2023-10-25 23:30:20 -04:00
wh1te909
52740271d9 nginx updates and python 3.11.6 2023-10-26 01:08:52 +00:00
wh1te909
c2e444249a add helper 2023-10-25 20:21:06 +00:00
wh1te909
97310b091e update reqs 2023-10-25 15:56:10 +00:00
Dan
4dda9cc3a1 Merge pull request #1086 from sadnub/feat-reports
Reporting Feature
2023-10-24 18:24:24 -07:00
wh1te909
a0538b57e2 more refurb 2023-10-25 01:10:56 +00:00
wh1te909
d7f394eeb6 refurb 2023-10-25 00:38:07 +00:00
wh1te909
1bc4571d42 isort 2023-10-25 00:18:54 +00:00
wh1te909
22e878502a return error 2023-10-25 00:12:18 +00:00
wh1te909
03c1b6e30c update repo 2023-10-24 22:29:10 +00:00
Joel DeTeves
374a434d98 Add option to skip uWSGI config in Docker environments 2023-10-24 14:35:16 -07:00
wh1te909
f1e85ff0e9 update license 2023-10-24 05:34:00 +00:00
wh1te909
6b010f76ea add download 2023-10-24 05:18:47 +00:00
wh1te909
0c3e9f7824 update reqs 2023-10-23 23:32:17 +00:00
wh1te909
ccca578622 test with superuser 2023-10-20 22:47:26 +00:00
wh1te909
56f7c18550 add reporting perms 2023-10-20 22:24:07 +00:00
wh1te909
d438f71bbb add assets 2023-10-20 20:25:43 +00:00
wh1te909
ca5df24b6d add pending actions to reporting 2023-10-18 22:38:22 +00:00
sadnub
4a6c2d106f fix and add some tests for csv data queries 2023-10-15 18:26:02 -04:00
sadnub
cd25a9568b remove reporting user and configuration 2023-10-15 12:24:50 -04:00
sadnub
f78a787adb initial wip shared report templates 2023-10-14 23:08:59 -04:00
sadnub
dc520fa77c allow overwriting templates on name conflicts. Remove 'make_dataqueries_inline' 2023-10-14 20:51:36 -04:00
sadnub
8f06d4dd9d add csv option to data source 2023-10-14 19:49:32 -04:00
sadnub
a7047183e1 use django timezone to get current time 2023-10-13 18:07:01 -04:00
sadnub
c0b145da24 add yaml extension to get the current date and also subtract/add time 2023-10-13 17:43:00 -04:00
sadnub
52e7fd6f72 add plain text template type 2023-10-05 12:59:42 -04:00
wh1te909
4bbe22b1c7 small fixes 2023-10-04 16:51:26 +00:00
sadnub
4747ffc08b fix dockerfile in dev and remove chart rendering if data query is empty 2023-10-04 11:01:28 -04:00
wh1te909
9d07131fd6 remove duplicate entry 2023-10-03 22:51:47 +00:00
wh1te909
721126d3db function renamed 2023-10-03 20:15:57 +00:00
wh1te909
2b65f5e3dc update reqs 2023-10-03 20:13:56 +00:00
Dan
57f10cf387 using psycopg3 now 2023-10-03 12:45:11 -07:00
wh1te909
f60c8a173b add redis ping to monitoring endpoint 2023-10-02 17:14:13 +00:00
Dan
857cd690be Merge pull request #1643 from bc24fl/develop
Added optional web port override settings
2023-10-02 10:05:31 -07:00
sadnub
a407b60152 fix report preview 2023-10-02 12:32:02 -04:00
sadnub
2c3c55adc0 fix test 2023-10-02 12:32:02 -04:00
sadnub
f586b4da17 fix flake8 errors 2023-10-02 12:32:02 -04:00
sadnub
0b7eb41049 finish up tests and some code rework 2023-10-02 12:32:02 -04:00
sadnub
bd19c4e2bd add json support for data sources 2023-10-02 12:32:02 -04:00
sadnub
e8a73087d6 fix custom fields 2023-10-02 12:32:02 -04:00
sadnub
dde4fd82f4 update json schema and add custom fields to data sources 2023-10-02 12:32:02 -04:00
wh1te909
0420c393f3 fix grep 2023-10-02 12:32:02 -04:00
wh1te909
c88dac6437 fix mkdir 2023-10-02 12:32:02 -04:00
wh1te909
cd450f55e2 fix command 2023-10-02 12:32:02 -04:00
wh1te909
190ee7f9fb add query schema view 2023-10-02 12:32:02 -04:00
wh1te909
fd057300cc black and isort 2023-10-02 12:32:02 -04:00
wh1te909
56791089c1 generate must come before collectstatic 2023-10-02 12:32:02 -04:00
wh1te909
e91cb32ca3 redo migrations and fix hardcoded url 2023-10-02 12:32:02 -04:00
wh1te909
9ab20df8d2 update pandas 2023-10-02 12:32:02 -04:00
sadnub
050350501c fix some issues and improve report import/export 2023-10-02 12:32:02 -04:00
sadnub
d078acdf73 fix error messages and resolve data frames in charts 2023-10-02 12:32:02 -04:00
sadnub
b786a688b5 fix up json schema with new options 2023-10-02 12:32:02 -04:00
sadnub
6b7fe40dd2 limited any variable analysis queries to 1 result 2023-10-02 12:32:02 -04:00
sadnub
6f6c422246 add variables length to sidebar 2023-10-02 12:32:02 -04:00
sadnub
d371ff4f60 variables introspection 2023-10-02 12:32:02 -04:00
sadnub
d1a8348912 fix report preview without debug 2023-10-02 12:32:02 -04:00
sadnub
be956d3cb6 allow traversing relations in debug view 2023-10-02 12:32:02 -04:00
sadnub
ba5beb81b7 some fixes 2023-10-02 12:32:02 -04:00
sadnub
106bbe5244 add debug mode for preview. add template import/export. other fixes 2023-10-02 12:32:02 -04:00
sadnub
f39d0e7ba2 send template errors to frontend 2023-10-02 12:32:02 -04:00
sadnub
de7a1fd8ff more improvements 2023-10-02 12:32:02 -04:00
sadnub
1ac2b25876 send error messages to UI when generating reports 2023-10-02 12:32:02 -04:00
sadnub
9e014d1371 put yaml data source in variables to support variables 2023-10-02 12:32:02 -04:00
sadnub
93b274a113 fix the variable replacement in variables 2023-10-02 12:32:02 -04:00
sadnub
474c7ae873 Update config.py 2023-10-02 12:32:02 -04:00
sadnub
31690d4cad charts 2023-10-02 12:32:02 -04:00
sadnub
bbfc7e7e49 create DB user in mgmt command for docker build 2023-10-02 12:32:02 -04:00
sadnub
1c0aa55e7a more improvements 2023-10-02 12:32:02 -04:00
sadnub
29778ca19e fix report assets over https and add an endpoint for asset selection 2023-10-02 12:32:02 -04:00
sadnub
9e87318cc5 get jinja templates 100% compatible with reporting 2023-10-02 12:32:02 -04:00
sadnub
c645be6b70 fix data lookups 2023-10-02 12:32:02 -04:00
sadnub
57fc5ac088 docker and install script fixes 2023-10-02 12:32:02 -04:00
sadnub
924774f52a fix report asset path 2023-10-02 12:32:02 -04:00
sadnub
446a7a0844 fix url 2023-10-02 12:32:02 -04:00
sadnub
5cfeed76d0 fix 2023-10-02 12:32:02 -04:00
sadnub
de419319d8 fix branch 2023-10-02 12:32:02 -04:00
sadnub
7a3d36899b fix permissions 2023-10-02 12:32:02 -04:00
sadnub
f5dbb363f4 install script fixes 2023-10-02 12:32:00 -04:00
sadnub
2bbc59a212 fix install/update script 2023-10-02 12:31:31 -04:00
sadnub
3403d76aae reporting wip 2023-10-02 12:31:29 -04:00
bc24fl
58399cedb6 Update docker-compose.yml
Added optional web port override settings for those who prefer to use tactical behind a proxy.
2023-10-02 00:46:18 -04:00
bc24fl
9bca7e9e11 Update .env.example
Added optional web port override settings for those who prefer to use tactical behind a proxy.
2023-10-02 00:43:25 -04:00
wh1te909
3a61430e44 back to dev [skip ci] 2023-10-02 01:58:49 +00:00
wh1te909
7d8c783a7d Release 0.16.5 2023-10-02 01:50:44 +00:00
wh1te909
a2e996b550 bump version 2023-10-02 01:49:57 +00:00
wh1te909
cfc1c31050 rename setting 2023-10-02 00:12:39 +00:00
wh1te909
45106bf6f9 remove apt-key [skip ci] 2023-10-01 15:59:14 +00:00
wh1te909
6e3cfe491b update chocos fixes #1538 2023-09-30 23:33:52 +00:00
wh1te909
12f2158afd feat: make env vars expand custom fields closes #1609 2023-09-30 22:05:52 +00:00
wh1te909
6d78773c55 bump web ver 2023-09-30 22:02:49 +00:00
wh1te909
43a62d4eb6 update reqs 2023-09-30 20:53:08 +00:00
wh1te909
cc08dfda96 make beta api optional 2023-09-30 19:33:22 +00:00
Dan
622e33588e Merge pull request #1636 from redanthrax/beta-api
beta api clients, agents, sites with paging
2023-09-30 12:16:54 -07:00
wh1te909
67980b58a0 fix docker 2023-09-29 08:29:59 +00:00
redanthrax
027e444955 beta api clients, agents, sites with paging
formatted with black

django filter requirement

updated beta api, restricted to get and put
2023-09-27 14:36:14 -07:00
wh1te909
d838750389 update reqs 2023-09-27 17:25:56 +00:00
wh1te909
71d8bd5266 update reqs 2023-09-20 03:24:40 +00:00
wh1te909
ec4ae24bbd add note about x forwarding 2023-09-20 03:21:15 +00:00
wh1te909
1128149359 fix docker mesh npm install 2023-09-20 03:20:15 +00:00
wh1te909
bdfc6634ec fix tempdir cleanup [skip ci] 2023-09-13 20:29:33 +00:00
wh1te909
ca4d19667b update reqs 2023-09-11 02:43:50 +00:00
wh1te909
c71aa7baa7 back to dev 2023-09-11 02:40:28 +00:00
wh1te909
fd80ccd2c5 Release 0.16.4 2023-09-02 00:20:54 +00:00
wh1te909
9dc0b24399 bump versions 2023-09-01 23:48:31 +00:00
wh1te909
747954e6fb wording 2023-09-01 22:03:51 +00:00
wh1te909
274f4f227e node install script is deprecated [skip ci] 2023-09-01 21:12:45 +00:00
wh1te909
92197d8d49 change to localhost 2023-09-01 18:56:09 +00:00
wh1te909
aee06920eb more self signed stuff 2023-09-01 18:55:34 +00:00
wh1te909
5111b17d3c bump web ver [skip ci] 2023-08-30 04:29:36 +00:00
wh1te909
2849d8f45d update scripts for self signed 2023-08-29 23:53:19 +00:00
wh1te909
bac60d9bd4 feat: reset all checks status closes amidaware/tacticalrmm#1615 2023-08-29 20:36:20 +00:00
wh1te909
9c797162f4 only Manual is supported in insecure mode 2023-08-29 20:33:58 +00:00
wh1te909
09d184e2f8 update installers 2023-08-25 18:25:09 +00:00
wh1te909
7bca618906 allow self-signed certs 2023-08-24 21:40:51 +00:00
wh1te909
67607103e9 back to dev [skip ci] 2023-08-24 21:05:50 +00:00
wh1te909
73c9956fe4 Release 0.16.3 2023-08-18 04:33:01 +00:00
wh1te909
b42f2ffe33 bump version [skip ci] 2023-08-18 04:29:41 +00:00
wh1te909
30a3f185ef fix npm #1604 [skip ci] 2023-08-18 04:28:58 +00:00
wh1te909
4f1b41227f Release 0.16.2 2023-08-14 20:57:52 +00:00
wh1te909
83b9d13ec9 bump version [skip ci] 2023-08-14 20:57:14 +00:00
wh1te909
cee7896c37 back to dev [skip ci] 2023-08-14 17:06:40 +00:00
wh1te909
0377009d2b Release 0.16.1 2023-08-14 17:05:27 +00:00
wh1te909
b472f3644e bump versions 2023-08-14 16:42:32 +00:00
wh1te909
5d8ea837c8 fix posix restart 2023-08-12 00:28:20 +00:00
wh1te909
82de6bc849 syntax fix [skip ci] 2023-08-11 23:05:14 +00:00
wh1te909
cb4bc68c48 fix syntax [skip ci] 2023-08-11 22:07:59 +00:00
wh1te909
3ce6b38247 bump dev vers [skip ci] 2023-08-11 07:08:48 +00:00
wh1te909
716c0fe979 handle cloud init hosts file 2023-08-11 05:14:43 +00:00
wh1te909
c993790b7a bump web ver [skip ci] 2023-08-06 23:08:35 +00:00
wh1te909
aa32286531 update reqs 2023-08-06 22:31:40 +00:00
wh1te909
6f94abde00 fix alert filtering fixes #1572 2023-07-30 20:05:34 +00:00
wh1te909
fa19538c9d handle custom certs in backup/restore 2023-07-30 07:14:44 +00:00
wh1te909
84c858b878 fix issuer name 2023-07-30 07:12:51 +00:00
wh1te909
865de142d4 update reqs 2023-07-30 07:11:33 +00:00
wh1te909
9118162553 fix duplicate agent customfields 2023-07-18 22:23:34 +00:00
wh1te909
f4fc6ee9b4 update nats 2023-07-18 22:22:19 +00:00
wh1te909
108c38d57b update reqs 2023-07-18 22:20:38 +00:00
wh1te909
a1d73eb830 noexec 2023-07-18 22:10:29 +00:00
wh1te909
997906a610 formatting 2023-07-18 22:06:36 +00:00
wh1te909
b6e5d120d3 mongo check [skip ci] 2023-07-11 01:06:45 +00:00
wh1te909
d469d0b435 format [skip ci] 2023-07-11 01:05:10 +00:00
Dan
e9f823e000 Merge pull request #1560 from dinger1986/develop
change script to work with debian 12
2023-07-10 17:58:02 -07:00
dinger1986
d7fb76ba74 Update troubleshoot_server.sh 2023-07-10 23:35:57 +01:00
dinger1986
b7dde1a0d9 Merge branch 'amidaware:develop' into develop 2023-07-10 23:06:41 +01:00
dinger1986
15095d8c23 Update troubleshoot_server.sh 2023-07-10 10:13:06 +01:00
wh1te909
dfbebc7606 testing psycopg3 2023-07-09 07:04:38 +00:00
wh1te909
895309d93d pg 15 for ci 2023-07-09 07:03:48 +00:00
wh1te909
bcf50e821a update ansible for 0.16.0 2023-07-07 09:07:43 +00:00
wh1te909
30195800dd handle new dir 2023-07-07 03:59:48 +00:00
wh1te909
6532b0f149 back to dev 2023-07-07 03:58:36 +00:00
sadnub
5e108e4057 fix dockerfile 2023-07-05 21:16:43 -04:00
Dan
c2b2f4d222 Merge pull request #1550 from dinger1986/develop
changed find and delete of old backups
2023-07-05 17:49:12 -07:00
dinger1986
bc4329ad21 Update backup.sh 2023-07-05 22:38:50 +01:00
dinger1986
aec6d1b2f6 Update backup.sh 2023-07-05 22:38:30 +01:00
195 changed files with 11750 additions and 1374 deletions

View File

@@ -1,11 +1,11 @@
# pulls community scripts from git repo
FROM python:3.11.4-slim AS GET_SCRIPTS_STAGE
FROM python:3.11.8-slim AS GET_SCRIPTS_STAGE
RUN apt-get update &&
apt-get install -y --no-install-recommends git &&
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.4-slim
FROM python:3.11.8-slim
ENV TACTICAL_DIR /opt/tactical
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
@@ -17,10 +17,10 @@ ENV PYTHONUNBUFFERED=1
EXPOSE 8000 8383 8005
RUN apt-get update &&
apt-get install -y build-essential
RUN apt-get update && \
apt-get install -y build-essential weasyprint
RUN groupadd -g 1000 tactical &&
RUN groupadd -g 1000 tactical && \
useradd -u 1000 -g 1000 tactical
# copy community scripts

View File

@@ -216,6 +216,7 @@ services:
- "443:4443"
volumes:
- tactical-data-dev:/opt/tactical
- ..:/workspace:cached
volumes:
tactical-data-dev: null

View File

@@ -69,6 +69,7 @@ ALLOWED_HOSTS = ['${API_HOST}', '*']
ADMIN_URL = 'admin/'
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = ['https://${API_HOST}']
DATABASES = {
'default': {
@@ -78,6 +79,17 @@ DATABASES = {
'PASSWORD': '${POSTGRES_PASS}',
'HOST': '${POSTGRES_HOST}',
'PORT': '${POSTGRES_PORT}',
},
'reporting': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': '${POSTGRES_DB}',
'USER': 'reporting_user',
'PASSWORD': 'read_password',
'HOST': '${POSTGRES_HOST}',
'PORT': '${POSTGRES_PORT}',
'OPTIONS': {
'options': '-c default_transaction_read_only=on'
}
}
}
@@ -87,6 +99,7 @@ MESH_TOKEN_KEY = '${MESH_TOKEN}'
REDIS_HOST = '${REDIS_HOST}'
MESH_WS_URL = '${MESH_WS_URL}'
ADMIN_ENABLED = True
TRMM_INSECURE = True
EOF
)"
@@ -95,6 +108,7 @@ EOF
# 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
@@ -120,6 +134,8 @@ if [ "$1" = 'tactical-init-dev' ]; then
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
@@ -127,6 +143,7 @@ if [ "$1" = 'tactical-init-dev' ]; then
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

View File

@@ -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.

View File

@@ -14,14 +14,14 @@ jobs:
name: Tests
strategy:
matrix:
python-version: ["3.11.4"]
python-version: ["3.11.8"]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: harmon758/postgresql-action@v1
with:
postgresql version: "14"
postgresql version: "15"
postgresql db: "pipeline"
postgresql user: "pipeline"
postgresql password: "pipeline123456"

View File

@@ -1,70 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ develop ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ develop ]
schedule:
- cron: '19 14 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -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:

2
.gitignore vendored
View File

@@ -57,3 +57,5 @@ daphne.sock.lock
coverage.xml
setup_dev.yml
11env/
query_schema.json
gunicorn_config.py

27
.vscode/settings.json vendored
View File

@@ -5,27 +5,10 @@
"python.analysis.diagnosticSeverityOverrides": {
"reportUnusedImport": "error",
"reportDuplicateImport": "error",
"reportGeneralTypeIssues": "none"
"reportGeneralTypeIssues": "none",
"reportOptionalMemberAccess": "none",
},
"python.analysis.typeCheckingMode": "basic",
"python.linting.enabled": true,
"python.linting.mypyEnabled": true,
"python.linting.mypyArgs": [
"--ignore-missing-imports",
"--follow-imports=silent",
"--show-column-numbers",
"--strict"
],
"python.linting.ignorePatterns": [
"**/site-packages/**/*.py",
".vscode/*.py",
"**env/**"
],
"python.formatting.provider": "none",
//"mypy.targets": [
//"api/tacticalrmm"
//],
//"mypy.runUsingActiveInterpreter": true,
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
@@ -34,7 +17,6 @@
"**/docker/**/docker-compose*.yml": "dockercompose"
},
"files.watcherExclude": {
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/node_modules/": true,
@@ -53,18 +35,17 @@
"**/*.parquet*": true,
"**/*.pyc": true,
"**/*.zip": true
}
},
"go.useLanguageServer": true,
"[go]": {
"editor.codeActionsOnSave": {
"source.organizeImports": false
"source.organizeImports": "never"
},
"editor.snippetSuggestions": "none"
},
"[go.mod]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
"source.organizeImports": "explicit"
}
},
"gopls": {

View File

@@ -19,7 +19,7 @@ Demo database resets every hour. A lot of features are disabled for obvious reas
- 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

View File

@@ -1,7 +1,7 @@
---
user: "tactical"
python_ver: "3.11.4"
go_ver: "1.20.4"
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"

View File

@@ -13,7 +13,7 @@ http {
server_tokens off;
tcp_nopush on;
types_hash_max_size 2048;
server_names_hash_bucket_size 64;
server_names_hash_bucket_size 256;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1.2 TLSv1.3;

View File

@@ -1,2 +0,0 @@
deb https://nginx.org/packages/debian/ bullseye nginx
deb-src https://nginx.org/packages/debian/ bullseye nginx

View File

@@ -41,11 +41,15 @@
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-amd64.tar.gz"
src: "https://go.dev/dl/go{{ go_ver }}.linux-{{ goarch }}.tar.gz"
dest: /usr/local
remote_src: yes
@@ -111,7 +115,7 @@
tags: postgres
become: yes
ansible.builtin.copy:
content: "deb http://apt.postgresql.org/pub/repos/apt bullseye-pgdg main"
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
@@ -128,7 +132,7 @@
tags: postgres
become: yes
ansible.builtin.apt:
pkg: postgresql-14
pkg: postgresql-15
state: present
update_cache: yes
@@ -140,7 +144,7 @@
enabled: yes
state: started
- name: setup database
- name: setup trmm database
tags: postgres
become: yes
become_user: postgres
@@ -153,6 +157,23 @@
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
@@ -202,7 +223,7 @@
- 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-amd64.tar.gz"
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
@@ -211,7 +232,7 @@
become: yes
ansible.builtin.copy:
remote_src: yes
src: "{{ nats_tmp.path }}/nats-server-v{{ nats_server_ver.stdout }}-linux-amd64/nats-server"
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 }}"
@@ -227,7 +248,7 @@
- name: download nodejs setup
tags: nodejs
ansible.builtin.get_url:
url: https://deb.nodesource.com/setup_16.x
url: https://deb.nodesource.com/setup_18.x
dest: "{{ nodejs_tmp.path }}/setup_node.sh"
mode: "0755"
@@ -308,14 +329,14 @@
tags: nginx
become: yes
ansible.builtin.apt_key:
url: https://nginx.org/packages/keys/nginx_signing.key
url: https://nginx.org/keys/nginx_signing.key
state: present
- name: add nginx repo
tags: nginx
become: yes
ansible.builtin.copy:
src: nginx.repo
ansible.builtin.template:
src: nginx.repo.j2
dest: /etc/apt/sources.list.d/nginx.list
owner: "root"
group: "root"
@@ -391,12 +412,16 @@
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/nats-api"
src: "{{ backend_dir }}/natsapi/bin/{{ natsapi }}"
dest: /usr/local/bin/nats-api
owner: "{{ user }}"
group: "{{ user }}"
@@ -482,39 +507,6 @@
- { src: nats-server.systemd.j2, dest: /etc/systemd/system/nats.service }
- { src: mesh.systemd.j2, dest: /etc/systemd/system/meshcentral.service }
- name: import mongodb repo signing key
tags: mongo
become: yes
ansible.builtin.apt_key:
url: https://www.mongodb.org/static/pgp/server-4.4.asc
state: present
- name: setup mongodb repo
tags: mongo
become: yes
ansible.builtin.copy:
content: "deb https://repo.mongodb.org/apt/debian buster/mongodb-org/4.4 main"
dest: /etc/apt/sources.list.d/mongodb-org-4.4.list
owner: root
group: root
mode: "0644"
- name: install mongodb
tags: mongo
become: yes
ansible.builtin.apt:
pkg: mongodb-org
state: present
update_cache: yes
- name: ensure mongodb enabled and started
tags: mongo
become: yes
ansible.builtin.service:
name: mongod
enabled: yes
state: started
- name: get mesh_ver
tags: mesh
ansible.builtin.shell: grep "^MESH_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'

View File

@@ -13,7 +13,6 @@ DATABASES = {
'PORT': '5432',
}
}
REDIS_HOST = "localhost"
ADMIN_ENABLED = True
CERT_FILE = "{{ fullchain_dest }}"
KEY_FILE = "{{ privkey_dest }}"

View File

@@ -1,8 +1,6 @@
{
"settings": {
"Cert": "{{ mesh }}",
"MongoDb": "mongodb://127.0.0.1:27017",
"MongoDbName": "meshcentral",
"WANonly": true,
"Minify": 1,
"Port": 4430,
@@ -10,19 +8,25 @@
"RedirPort": 800,
"AllowLoginToken": true,
"AllowFraming": true,
"AgentPong": 300,
"AgentPing": 35,
"AllowHighQualityDesktop": true,
"TlsOffload": "127.0.0.1",
"agentCoreDump": false,
"Compression": true,
"WsCompression": true,
"AgentWsCompression": true,
"MaxInvalidLogin": { "time": 5, "count": 5, "coolofftime": 30 }
"MaxInvalidLogin": { "time": 5, "count": 5, "coolofftime": 30 },
"postgres": {
"user": "{{ mesh_db_user }}",
"password": "{{ mesh_db_passwd }}",
"port": "5432",
"host": "localhost"
}
},
"domains": {
"": {
"Title": "Tactical RMM",
"Title2": "Tactical RMM",
"Title": "Tactical RMM Dev",
"Title2": "Tactical RMM Dev",
"NewAccounts": false,
"CertUrl": "https://{{ mesh }}:443/",
"GeoLocation": true,

View File

@@ -1,6 +1,6 @@
[Unit]
Description=MeshCentral Server
After=network.target mongod.service nginx.service
After=network.target postgresql.service nginx.service
[Service]
Type=simple

View 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

View File

@@ -13,6 +13,8 @@
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"

View File

@@ -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),
),
]

View File

@@ -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",
),
]

View File

@@ -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),
),
]

View File

@@ -64,6 +64,15 @@ class User(AbstractUser, BaseAuditModel):
on_delete=models.SET_NULL,
)
@property
def mesh_user_id(self):
return f"user//{self.mesh_username}"
@property
def mesh_username(self):
# lower() needed for mesh api
return f"{self.username.replace(' ', '').lower()}___{self.pk}"
@staticmethod
def serialize(user):
# serializes the task and returns json
@@ -95,7 +104,6 @@ class Role(BaseAuditModel):
# agents
can_list_agents = models.BooleanField(default=False)
can_ping_agents = models.BooleanField(default=False)
can_use_mesh = models.BooleanField(default=False)
can_uninstall_agents = models.BooleanField(default=False)
can_update_agents = models.BooleanField(default=False)
@@ -121,6 +129,8 @@ class Role(BaseAuditModel):
can_run_urlactions = models.BooleanField(default=False)
can_view_customfields = models.BooleanField(default=False)
can_manage_customfields = models.BooleanField(default=False)
can_run_server_scripts = models.BooleanField(default=False)
can_use_webterm = models.BooleanField(default=False)
# checks
can_list_checks = models.BooleanField(default=False)
@@ -186,13 +196,17 @@ class Role(BaseAuditModel):
can_list_api_keys = models.BooleanField(default=False)
can_manage_api_keys = models.BooleanField(default=False)
# reporting
can_view_reports = models.BooleanField(default=False)
can_manage_reports = models.BooleanField(default=False)
def __str__(self):
return self.name
def save(self, *args, **kwargs) -> None:
# delete cache on save
cache.delete(f"{ROLE_CACHE_PREFIX}{self.name}")
super(BaseAuditModel, self).save(*args, **kwargs)
super().save(*args, **kwargs)
@staticmethod
def serialize(role):

View File

@@ -5,6 +5,8 @@ from rest_framework.serializers import (
SerializerMethodField,
)
from tacticalrmm.helpers import get_webdomain
from .models import APIKey, Role, User
@@ -61,7 +63,7 @@ class TOTPSetupSerializer(ModelSerializer):
def get_qr_url(self, obj):
return pyotp.totp.TOTP(obj.totp_key).provisioning_uri(
obj.username, issuer_name="Tactical RMM"
obj.username, issuer_name=get_webdomain()
)

View File

@@ -17,13 +17,13 @@ class TestAccounts(TacticalTestCase):
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")
@@ -40,7 +40,7 @@ class TestAccounts(TacticalTestCase):
data = {"username": "bob", "password": "hunter2"}
r = self.client.post(url, data, format="json")
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "ok")
self.assertEqual(r.data["totp"], True)
# test user set to block dashboard logins
self.bob.block_dashboard_login = True
@@ -50,7 +50,7 @@ class TestAccounts(TacticalTestCase):
@patch("pyotp.TOTP.verify")
def test_login_view(self, mock_verify):
url = "/login/"
url = "/v2/login/"
mock_verify.return_value = True
data = {"username": "bob", "password": "hunter2", "twofactor": "123456"}
@@ -404,7 +404,7 @@ class TestTOTPSetup(TacticalTestCase):
r = self.client.post(url)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "totp token already set")
self.assertEqual(r.data, False)
class TestAPIAuthentication(TacticalTestCase):

View File

@@ -1,8 +1,10 @@
from typing import TYPE_CHECKING
from django.conf import settings
if TYPE_CHECKING:
from django.http import HttpRequest
from accounts.models import User
@@ -16,3 +18,7 @@ def is_root_user(*, request: "HttpRequest", user: "User") -> bool:
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")

View File

@@ -1,15 +1,19 @@
import datetime
import pyotp
from django.conf import settings
from django.contrib.auth import login
from django.db import IntegrityError
from django.shortcuts import get_object_or_404
from ipware import get_client_ip
from knox.views import LoginView as KnoxLoginView
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.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.helpers import notify_error
@@ -22,10 +26,89 @@ from .serializers import (
UserSerializer,
UserUISerializer,
)
from accounts.utils import is_root_user
class CheckCredsV2(KnoxLoginView):
permission_classes = (AllowAny,)
# 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"], debug_info={"ip": request._client_ip}
)
return notify_error("Bad credentials")
user = serializer.validated_data["user"]
if user.block_dashboard_login:
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().post(request, format=None)
response.data["totp"] = False
return response
return Response({"totp": True})
class LoginViewV2(KnoxLoginView):
permission_classes = (AllowAny,)
def post(self, request, format=None):
valid = False
serializer = AuthTokenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data["user"]
if user.block_dashboard_login:
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)
# 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
return Response(response.data)
else:
AuditLog.audit_user_failed_twofactor(
request.data["username"], debug_info={"ip": request._client_ip}
)
return notify_error("Bad credentials")
class CheckCreds(KnoxLoginView):
# TODO
# This view is deprecated as of 0.19.0
# Needed for the initial update to 0.19.0 so frontend code doesn't break on login
permission_classes = (AllowAny,)
def post(self, request, format=None):
@@ -53,6 +136,9 @@ class CheckCreds(KnoxLoginView):
class LoginView(KnoxLoginView):
# TODO
# This view is deprecated as of 0.19.0
# Needed for the initial update to 0.19.0 so frontend code doesn't break on login
permission_classes = (AllowAny,)
def post(self, request, format=None):
@@ -79,9 +165,11 @@ class LoginView(KnoxLoginView):
login(request, user)
# save ip information
client_ip, _ = get_client_ip(request)
user.last_login_ip = client_ip
user.save()
ipw = IpWare()
client_ip, _ = ipw.get_client_ip(request.META)
if client_ip:
user.last_login_ip = str(client_ip)
user.save()
AuditLog.audit_user_login_successful(
request.data["username"], debug_info={"ip": request._client_ip}
@@ -131,6 +219,7 @@ class GetAddUsers(APIView):
user.role = role
user.save()
sync_mesh_perms_task.delay()
return Response(user.username)
@@ -151,6 +240,7 @@ class GetUpdateDeleteUser(APIView):
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")
@@ -160,7 +250,7 @@ class GetUpdateDeleteUser(APIView):
return notify_error("The root user cannot be deleted from the UI")
user.delete()
sync_mesh_perms_task.delay()
return Response("ok")
@@ -202,7 +292,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):
@@ -241,11 +331,13 @@ class GetUpdateDeleteRole(APIView):
serializer = RoleSerializer(instance=role, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
sync_mesh_perms_task.delay()
return Response("Role was edited")
def delete(self, request, pk):
role = get_object_or_404(Role, pk=pk)
role.delete()
sync_mesh_perms_task.delay()
return Response("Role was removed")

View File

@@ -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()

View File

@@ -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")},
),
]

View 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,
),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.10 on 2024-02-19 05:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("agents", "0058_alter_agent_time_zone"),
]
operations = [
migrations.AlterField(
model_name="agenthistory",
name="id",
field=models.BigAutoField(primary_key=True, serialize=False),
),
]

View File

@@ -1,4 +1,5 @@
import asyncio
import logging
import re
from collections import Counter
from contextlib import suppress
@@ -7,7 +8,6 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union, ca
import msgpack
import nats
import validators
from asgiref.sync import sync_to_async
from django.conf import settings
from django.contrib.postgres.fields import ArrayField
from django.core.cache import cache
@@ -20,7 +20,7 @@ from packaging.version import Version as LooseVersion
from agents.utils import get_agent_url
from checks.models import CheckResult
from core.models import TZ_CHOICES
from core.utils import get_core_settings, send_command_with_mesh
from core.utils import _b64_to_hex, get_core_settings, send_command_with_mesh
from logs.models import BaseAuditModel, DebugLog, PendingAction
from tacticalrmm.constants import (
AGENT_STATUS_OFFLINE,
@@ -40,7 +40,7 @@ from tacticalrmm.constants import (
PAAction,
PAStatus,
)
from tacticalrmm.helpers import setup_nats_options
from tacticalrmm.helpers import has_script_actions, has_webhook, setup_nats_options
from tacticalrmm.models import PermissionQuerySet
if TYPE_CHECKING:
@@ -54,6 +54,8 @@ if TYPE_CHECKING:
# type helpers
Disk = Union[Dict[str, Any], str]
logger = logging.getLogger("trmm")
class Agent(BaseAuditModel):
class Meta:
@@ -124,6 +126,22 @@ class Agent(BaseAuditModel):
def __str__(self) -> str:
return self.hostname
def save(self, *args, **kwargs):
# prevent recursion since calling set_alert_template() also calls save()
if not hasattr(self, "_processing_set_alert_template"):
self._processing_set_alert_template = False
if self.pk and not self._processing_set_alert_template:
orig = Agent.objects.get(pk=self.pk)
mon_type_changed = self.monitoring_type != orig.monitoring_type
site_changed = self.site_id != orig.site_id
if mon_type_changed or site_changed:
self._processing_set_alert_template = True
self.set_alert_template()
self._processing_set_alert_template = False
super().save(*args, **kwargs)
@property
def client(self) -> "Client":
return self.site.client
@@ -280,7 +298,20 @@ class Agent(BaseAuditModel):
try:
cpus = self.wmi_detail["cpu"]
for cpu in cpus:
ret.append([x["Name"] for x in cpu if "Name" in x][0])
name = [x["Name"] for x in cpu if "Name" in x][0]
lp, nc = "", ""
with suppress(Exception):
lp = [
x["NumberOfLogicalProcessors"]
for x in cpu
if "NumberOfCores" in x
][0]
nc = [x["NumberOfCores"] for x in cpu if "NumberOfCores" in x][0]
if lp and nc:
cpu_string = f"{name}, {nc}C/{lp}T"
else:
cpu_string = name
ret.append(cpu_string)
return ret
except:
return ["unknown cpu model"]
@@ -411,13 +442,20 @@ class Agent(BaseAuditModel):
@property
def serial_number(self) -> str:
if self.is_posix:
return ""
try:
return self.wmi_detail["serialnumber"]
except:
return ""
try:
return self.wmi_detail["bios"][0][0]["SerialNumber"]
except:
return ""
@property
def hex_mesh_node_id(self) -> str:
return _b64_to_hex(self.mesh_node_id)
@classmethod
def online_agents(cls, min_version: str = "") -> "List[Agent]":
if min_version:
@@ -505,24 +543,32 @@ class Agent(BaseAuditModel):
)
return {
"agent_policy": self.policy
if self.policy and not self.policy.is_agent_excluded(self)
else None,
"site_policy": site_policy
if (site_policy and not site_policy.is_agent_excluded(self))
and not self.block_policy_inheritance
else None,
"client_policy": client_policy
if (client_policy and not client_policy.is_agent_excluded(self))
and not self.block_policy_inheritance
and not self.site.block_policy_inheritance
else None,
"default_policy": default_policy
if (default_policy and not default_policy.is_agent_excluded(self))
and not self.block_policy_inheritance
and not self.site.block_policy_inheritance
and not self.client.block_policy_inheritance
else None,
"agent_policy": (
self.policy
if self.policy and not self.policy.is_agent_excluded(self)
else None
),
"site_policy": (
site_policy
if (site_policy and not site_policy.is_agent_excluded(self))
and not self.block_policy_inheritance
else None
),
"client_policy": (
client_policy
if (client_policy and not client_policy.is_agent_excluded(self))
and not self.block_policy_inheritance
and not self.site.block_policy_inheritance
else None
),
"default_policy": (
default_policy
if (default_policy and not default_policy.is_agent_excluded(self))
and not self.block_policy_inheritance
and not self.site.block_policy_inheritance
and not self.client.block_policy_inheritance
else None
),
}
def check_run_interval(self) -> int:
@@ -556,6 +602,7 @@ class Agent(BaseAuditModel):
run_as_user = True
parsed_args = script.parse_script_args(self, script.shell, args)
parsed_env_vars = script.parse_script_env_vars(self, script.shell, env_vars)
data = {
"func": "runscriptfull" if full else "runscript",
@@ -566,7 +613,9 @@ class Agent(BaseAuditModel):
"shell": script.shell,
},
"run_as_user": run_as_user,
"env_vars": env_vars,
"env_vars": parsed_env_vars,
"nushell_enable_config": settings.NUSHELL_ENABLE_CONFIG,
"deno_default_permissions": settings.DENO_DEFAULT_PERMISSIONS,
}
if history_pk != 0:
@@ -797,9 +846,6 @@ class Agent(BaseAuditModel):
cache.set(cache_key, tasks, 600)
return tasks
def _do_nats_debug(self, agent: "Agent", message: str) -> None:
DebugLog.error(agent=agent, log_type=DebugLogType.AGENT_ISSUES, message=message)
async def nats_cmd(
self, data: Dict[Any, Any], timeout: int = 30, wait: bool = True
) -> Any:
@@ -821,9 +867,7 @@ class Agent(BaseAuditModel):
ret = msgpack.loads(msg.data)
except Exception as e:
ret = str(e)
await sync_to_async(self._do_nats_debug, thread_sensitive=False)(
agent=self, message=ret
)
logger.error(e)
await nc.close()
return ret
@@ -906,18 +950,22 @@ class Agent(BaseAuditModel):
def should_create_alert(
self, alert_template: "Optional[AlertTemplate]" = None
) -> bool:
return bool(
has_agent_notification = (
self.overdue_dashboard_alert
or self.overdue_email_alert
or self.overdue_text_alert
or (
alert_template
and (
alert_template.agent_always_alert
or alert_template.agent_always_email
or alert_template.agent_always_text
)
)
)
has_alert_template_notification = alert_template and (
alert_template.agent_always_alert
or alert_template.agent_always_email
or alert_template.agent_always_text
)
return bool(
has_agent_notification
or has_alert_template_notification
or has_webhook(alert_template)
or has_script_actions(alert_template)
)
def send_outage_email(self) -> None:
@@ -1011,6 +1059,9 @@ class AgentCustomField(models.Model):
default=list,
)
class Meta:
unique_together = (("agent", "field"),)
def __str__(self) -> str:
return self.field.name
@@ -1043,6 +1094,7 @@ class AgentCustomField(models.Model):
class AgentHistory(models.Model):
objects = PermissionQuerySet.as_manager()
id = models.BigAutoField(primary_key=True)
agent = models.ForeignKey(
Agent,
related_name="history",

View File

@@ -47,13 +47,6 @@ class UpdateAgentPerms(permissions.BasePermission):
return _has_perm(r, "can_update_agents")
class PingAgentPerms(permissions.BasePermission):
def has_permission(self, r, view) -> bool:
return _has_perm(r, "can_ping_agents") and _has_perm_on_agent(
r.user, view.kwargs["agent_id"]
)
class ManageProcPerms(permissions.BasePermission):
def has_permission(self, r, view) -> bool:
return _has_perm(r, "can_manage_procs") and _has_perm_on_agent(

View File

@@ -1,7 +1,6 @@
import pytz
from rest_framework import serializers
from tacticalrmm.constants import AGENT_STATUS_ONLINE
from tacticalrmm.constants import AGENT_STATUS_ONLINE, ALL_TIMEZONES
from winupdate.serializers import WinUpdatePolicySerializer
from .models import Agent, AgentCustomField, AgentHistory, Note
@@ -71,7 +70,7 @@ class AgentSerializer(serializers.ModelSerializer):
return policies
def get_all_timezones(self, obj):
return pytz.all_timezones
return ALL_TIMEZONES
class Meta:
model = Agent

View File

@@ -0,0 +1,61 @@
from unittest.mock import patch
from model_bakery import baker
from agents.models import Agent
from tacticalrmm.constants import AgentMonType
from tacticalrmm.test import TacticalTestCase
class AgentSaveTestCase(TacticalTestCase):
def setUp(self):
self.client1 = baker.make("clients.Client")
self.client2 = baker.make("clients.Client")
self.site1 = baker.make("clients.Site", client=self.client1)
self.site2 = baker.make("clients.Site", client=self.client2)
self.site3 = baker.make("clients.Site", client=self.client2)
self.agent = baker.make(
"agents.Agent",
site=self.site1,
monitoring_type=AgentMonType.SERVER,
)
@patch.object(Agent, "set_alert_template")
def test_set_alert_template_called_on_mon_type_change(
self, mock_set_alert_template
):
self.agent.monitoring_type = AgentMonType.WORKSTATION
self.agent.save()
mock_set_alert_template.assert_called_once()
@patch.object(Agent, "set_alert_template")
def test_set_alert_template_called_on_site_change(self, mock_set_alert_template):
self.agent.site = self.site2
self.agent.save()
mock_set_alert_template.assert_called_once()
@patch.object(Agent, "set_alert_template")
def test_set_alert_template_called_on_site_and_montype_change(
self, mock_set_alert_template
):
print(f"before: {self.agent.monitoring_type} site: {self.agent.site_id}")
self.agent.site = self.site3
self.agent.monitoring_type = AgentMonType.WORKSTATION
self.agent.save()
mock_set_alert_template.assert_called_once()
print(f"after: {self.agent.monitoring_type} site: {self.agent.site_id}")
@patch.object(Agent, "set_alert_template")
def test_set_alert_template_not_called_without_changes(
self, mock_set_alert_template
):
self.agent.save()
mock_set_alert_template.assert_not_called()
@patch.object(Agent, "set_alert_template")
def test_set_alert_template_not_called_on_non_relevant_field_change(
self, mock_set_alert_template
):
self.agent.hostname = "abc123"
self.agent.save()
mock_set_alert_template.assert_not_called()

View File

@@ -1020,7 +1020,6 @@ class TestAgentPermissions(TacticalTestCase):
{"method": "post", "action": "recover", "role": "can_recover_agents"},
{"method": "post", "action": "reboot", "role": "can_reboot_agents"},
{"method": "patch", "action": "reboot", "role": "can_reboot_agents"},
{"method": "get", "action": "ping", "role": "can_ping_agents"},
{"method": "get", "action": "meshcentral", "role": "can_use_mesh"},
{"method": "post", "action": "meshcentral/recover", "role": "can_use_mesh"},
{"method": "get", "action": "processes", "role": "can_manage_procs"},

View File

@@ -15,6 +15,7 @@ urlpatterns = [
path("<agent:agent_id>/wmi/", views.WMI.as_view()),
path("<agent:agent_id>/recover/", views.recover),
path("<agent:agent_id>/reboot/", views.Reboot.as_view()),
path("<agent:agent_id>/shutdown/", views.Shutdown.as_view()),
path("<agent:agent_id>/ping/", views.ping),
# alias for checks get view
path("<agent:agent_id>/checks/", GetAddChecks.as_view()),

View File

@@ -21,6 +21,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from core.tasks import sync_mesh_perms_task
from core.utils import (
get_core_settings,
get_mesh_ws_url,
@@ -65,7 +66,6 @@ from .permissions import (
InstallAgentPerms,
ManageProcPerms,
MeshPerms,
PingAgentPerms,
RebootAgentPerms,
RecoverAgentPerms,
RunBulkPerms,
@@ -259,6 +259,7 @@ class GetUpdateDeleteAgent(APIView):
serializer.is_valid(raise_exception=True)
serializer.save()
sync_mesh_perms_task.delay()
return Response("The agent was updated successfully")
# uninstall agent
@@ -284,6 +285,7 @@ class GetUpdateDeleteAgent(APIView):
message=f"Unable to remove agent {name} from meshcentral database: {e}",
log_type=DebugLogType.AGENT_ISSUES,
)
sync_mesh_perms_task.delay()
return Response(f"{name} will now be uninstalled.")
@@ -326,13 +328,13 @@ class AgentMeshCentral(APIView):
agent = get_object_or_404(Agent, agent_id=agent_id)
core = get_core_settings()
if not core.mesh_disable_auto_login:
token = get_login_token(
key=core.mesh_token, user=f"user//{core.mesh_username}"
)
token_param = f"login={token}&"
else:
token_param = ""
user = (
request.user.mesh_user_id
if core.sync_mesh_with_trmm
else f"user//{core.mesh_api_superuser}"
)
token = get_login_token(key=core.mesh_token, user=user)
token_param = f"login={token}&"
control = f"{core.mesh_site}/?{token_param}gotonode={agent.mesh_node_id}&viewmode=11&hide=31"
terminal = f"{core.mesh_site}/?{token_param}gotonode={agent.mesh_node_id}&viewmode=12&hide=31"
@@ -402,7 +404,7 @@ def update_agents(request):
@api_view(["GET"])
@permission_classes([IsAuthenticated, PingAgentPerms])
@permission_classes([IsAuthenticated, AgentPerms])
def ping(request, agent_id):
agent = get_object_or_404(Agent, agent_id=agent_id)
status = AGENT_STATUS_OFFLINE
@@ -492,6 +494,19 @@ def send_raw_cmd(request, agent_id):
return Response(r)
class Shutdown(APIView):
permission_classes = [IsAuthenticated, RebootAgentPerms]
# shutdown
def post(self, request, agent_id):
agent = get_object_or_404(Agent, agent_id=agent_id)
r = asyncio.run(agent.nats_cmd({"func": "shutdown"}, timeout=10))
if r != "ok":
return notify_error("Unable to contact the agent")
return Response("ok")
class Reboot(APIView):
permission_classes = [IsAuthenticated, RebootAgentPerms]
@@ -570,6 +585,13 @@ def install_agent(request):
from agents.utils import get_agent_url
from core.utils import token_is_valid
insecure = getattr(settings, "TRMM_INSECURE", False)
if insecure and request.data["installMethod"] in {"exe", "powershell"}:
return notify_error(
"Not available in insecure mode. Please use the 'Manual' method."
)
# TODO rework this ghetto validation hack
# https://github.com/amidaware/tacticalrmm/issues/1461
try:
@@ -672,6 +694,9 @@ def install_agent(request):
if int(request.data["power"]):
cmd.append("--power")
if insecure:
cmd.append("--insecure")
resp["cmd"] = " ".join(str(i) for i in cmd)
else:
install_flags.insert(0, f"sudo ./{inno}")
@@ -680,6 +705,8 @@ def install_agent(request):
resp["cmd"] = (
dl + f" && chmod +x {inno} && " + " ".join(str(i) for i in cmd)
)
if insecure:
resp["cmd"] += " --insecure"
resp["url"] = download_url
@@ -960,6 +987,8 @@ def bulk(request):
debug_info={"ip": request._client_ip},
)
ht = "Check the History tab on the agent to view the results."
if request.data["mode"] == "command":
if request.data["shell"] == "custom" and request.data["custom_shell"]:
shell = request.data["custom_shell"]
@@ -974,7 +1003,7 @@ def bulk(request):
username=request.user.username[:50],
run_as_user=request.data["run_as_user"],
)
return Response(f"Command will now be run on {len(agents)} agents")
return Response(f"Command will now be run on {len(agents)} agents. {ht}")
elif request.data["mode"] == "script":
script = get_object_or_404(Script, pk=request.data["script"])
@@ -989,7 +1018,7 @@ def bulk(request):
env_vars=request.data["env_vars"],
)
return Response(f"{script.name} will now be run on {len(agents)} agents")
return Response(f"{script.name} will now be run on {len(agents)} agents. {ht}")
elif request.data["mode"] == "patch":
if request.data["patchMode"] == "install":

View File

@@ -0,0 +1,55 @@
# Generated by Django 4.2.13 on 2024-06-28 20:21
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("core", "0045_coresettings_enable_server_scripts_and_more"),
("alerts", "0013_alerttemplate_action_env_vars_and_more"),
]
operations = [
migrations.AddField(
model_name="alerttemplate",
name="action_rest",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="url_action_alert_template",
to="core.urlaction",
),
),
migrations.AddField(
model_name="alerttemplate",
name="action_type",
field=models.CharField(
choices=[("script", "Script"), ("server", "Server"), ("rest", "Rest")],
default="script",
max_length=10,
),
),
migrations.AddField(
model_name="alerttemplate",
name="resolved_action_rest",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="resolved_url_action_alert_template",
to="core.urlaction",
),
),
migrations.AddField(
model_name="alerttemplate",
name="resolved_action_type",
field=models.CharField(
choices=[("script", "Script"), ("server", "Server"), ("rest", "Rest")],
default="script",
max_length=10,
),
),
]

View File

@@ -1,6 +1,5 @@
from __future__ import annotations
import re
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
from django.contrib.postgres.fields import ArrayField
@@ -8,16 +7,20 @@ from django.db import models
from django.db.models.fields import BooleanField, PositiveIntegerField
from django.utils import timezone as djangotime
from core.utils import run_server_script, run_url_rest_action
from logs.models import BaseAuditModel, DebugLog
from tacticalrmm.constants import (
AgentHistoryType,
AgentMonType,
AlertSeverity,
AlertTemplateActionType,
AlertType,
CheckType,
DebugLogType,
)
from tacticalrmm.logger import logger
from tacticalrmm.models import PermissionQuerySet
from tacticalrmm.utils import RE_DB_VALUE, get_db_value
if TYPE_CHECKING:
from agents.models import Agent
@@ -95,6 +98,15 @@ class Alert(models.Model):
def client(self) -> "Client":
return self.agent.client
@property
def get_result(self):
if self.alert_type == AlertType.CHECK:
return self.assigned_check.checkresults.get(agent=self.agent)
elif self.alert_type == AlertType.TASK:
return self.assigned_task.taskresults.get(agent=self.agent)
return None
def resolve(self) -> None:
self.resolved = True
self.resolved_on = djangotime.now()
@@ -106,6 +118,9 @@ class Alert(models.Model):
def create_or_return_availability_alert(
cls, agent: Agent, skip_create: bool = False
) -> Optional[Alert]:
if agent.maintenance_mode:
return None
if not cls.objects.filter(
agent=agent, alert_type=AlertType.AVAILABILITY, resolved=False
).exists():
@@ -154,6 +169,9 @@ class Alert(models.Model):
alert_severity: Optional[str] = None,
skip_create: bool = False,
) -> "Optional[Alert]":
if agent.maintenance_mode:
return None
# need to pass agent if the check is a policy
if not cls.objects.filter(
assigned_check=check,
@@ -169,15 +187,17 @@ class Alert(models.Model):
assigned_check=check,
agent=agent,
alert_type=AlertType.CHECK,
severity=check.alert_severity
if check.check_type
not in {
CheckType.MEMORY,
CheckType.CPU_LOAD,
CheckType.DISK_SPACE,
CheckType.SCRIPT,
}
else alert_severity,
severity=(
check.alert_severity
if check.check_type
not in {
CheckType.MEMORY,
CheckType.CPU_LOAD,
CheckType.DISK_SPACE,
CheckType.SCRIPT,
}
else alert_severity
),
message=f"{agent.hostname} has a {check.check_type} check: {check.readable_desc} that failed.",
hidden=True,
),
@@ -216,6 +236,9 @@ class Alert(models.Model):
agent: "Agent",
skip_create: bool = False,
) -> "Optional[Alert]":
if agent.maintenance_mode:
return None
if not cls.objects.filter(
assigned_task=task,
agent=agent,
@@ -270,7 +293,9 @@ class Alert(models.Model):
from agents.models import Agent, AgentHistory
from autotasks.models import TaskResult
from checks.models import CheckResult
from core.models import CoreSettings
core = CoreSettings.objects.first()
# set variables
dashboard_severities = None
email_severities = None
@@ -420,12 +445,23 @@ class Alert(models.Model):
alert.hidden = False
alert.save(update_fields=["hidden"])
# TODO rework this
if alert.severity == AlertSeverity.INFO and not core.notify_on_info_alerts:
email_alert = False
always_email = False
elif (
alert.severity == AlertSeverity.WARNING
and not core.notify_on_warning_alerts
):
email_alert = False
always_email = False
# send email if enabled
if email_alert or always_email:
# check if alert template is set and specific severities are configured
if (
not alert_template
or alert_template
if not alert_template or (
alert_template
and email_severities
and alert.severity in email_severities
):
@@ -434,41 +470,91 @@ class Alert(models.Model):
alert_interval=alert_interval,
)
# TODO rework this
if alert.severity == AlertSeverity.INFO and not core.notify_on_info_alerts:
text_alert = False
always_text = False
elif (
alert.severity == AlertSeverity.WARNING
and not core.notify_on_warning_alerts
):
text_alert = False
always_text = False
# send text if enabled
if text_alert or always_text:
# check if alert template is set and specific severities are configured
if (
not alert_template
or alert_template
and text_severities
and alert.severity in text_severities
if not alert_template or (
alert_template and text_severities and alert.severity in text_severities
):
text_task.delay(pk=alert.pk, alert_interval=alert_interval)
# check if any scripts should be run
if (
alert_template
and alert_template.action
and run_script_action
and not alert.action_run
):
hist = AgentHistory.objects.create(
agent=agent,
type=AgentHistoryType.SCRIPT_RUN,
script=alert_template.action,
username="alert-action-failure",
)
r = agent.run_script(
scriptpk=alert_template.action.pk,
args=alert.parse_script_args(alert_template.action_args),
timeout=alert_template.action_timeout,
wait=True,
history_pk=hist.pk,
full=True,
run_on_any=True,
run_as_user=False,
env_vars=alert_template.action_env_vars,
)
# check if any scripts/webhooks should be run
if alert_template and not alert.action_run:
if (
alert_template.action_type == AlertTemplateActionType.SCRIPT
and alert_template.action
and run_script_action
):
hist = AgentHistory.objects.create(
agent=agent,
type=AgentHistoryType.SCRIPT_RUN,
script=alert_template.action,
username="alert-action-failure",
)
r = agent.run_script(
scriptpk=alert_template.action.pk,
args=alert.parse_script_args(alert_template.action_args),
timeout=alert_template.action_timeout,
wait=True,
history_pk=hist.pk,
full=True,
run_on_any=True,
run_as_user=False,
env_vars=alert.parse_script_args(alert_template.action_env_vars),
)
elif (
alert_template.action_type == AlertTemplateActionType.SERVER
and alert_template.action
and run_script_action
):
stdout, stderr, execution_time, retcode = run_server_script(
body=alert_template.action.script_body,
args=alert.parse_script_args(alert_template.action_args),
timeout=alert_template.action_timeout,
env_vars=alert.parse_script_args(alert_template.action_env_vars),
shell=alert_template.action.shell,
)
r = {
"retcode": retcode,
"stdout": stdout,
"stderr": stderr,
"execution_time": execution_time,
}
elif alert_template.action_type == AlertTemplateActionType.REST:
if (
alert.severity == AlertSeverity.INFO
and not core.notify_on_info_alerts
or alert.severity == AlertSeverity.WARNING
and not core.notify_on_warning_alerts
):
return
else:
output, status = run_url_rest_action(
action_id=alert_template.action_rest.id, instance=alert
)
logger.debug(f"{output=} {status=}")
r = {
"stdout": output,
"stderr": "",
"execution_time": 0,
"retcode": status,
}
else:
return
# command was successful
if isinstance(r, dict):
@@ -479,11 +565,17 @@ class Alert(models.Model):
alert.action_run = djangotime.now()
alert.save()
else:
DebugLog.error(
agent=agent,
log_type=DebugLogType.SCRIPTING,
message=f"Failure action: {alert_template.action.name} failed to run on any agent for {agent.hostname}({agent.pk}) failure alert",
)
if alert_template.action_type == AlertTemplateActionType.SCRIPT:
DebugLog.error(
agent=agent,
log_type=DebugLogType.SCRIPTING,
message=f"Failure action: {alert_template.action.name} failed to run on any agent for {agent.hostname}({agent.pk}) failure alert",
)
else:
DebugLog.error(
log_type=DebugLogType.SCRIPTING,
message=f"Failure action: {alert_template.action.name} failed to run on server for failure alert",
)
@classmethod
def handle_alert_resolve(
@@ -492,6 +584,9 @@ class Alert(models.Model):
from agents.models import Agent, AgentHistory
from autotasks.models import TaskResult
from checks.models import CheckResult
from core.models import CoreSettings
core = CoreSettings.objects.first()
# set variables
email_on_resolved = False
@@ -515,6 +610,8 @@ class Alert(models.Model):
email_on_resolved = alert_template.agent_email_on_resolved
text_on_resolved = alert_template.agent_text_on_resolved
run_script_action = alert_template.agent_script_actions
email_severities = [AlertSeverity.ERROR]
text_severities = [AlertSeverity.ERROR]
if agent.overdue_email_alert:
email_on_resolved = True
@@ -538,6 +635,14 @@ class Alert(models.Model):
email_on_resolved = alert_template.check_email_on_resolved
text_on_resolved = alert_template.check_text_on_resolved
run_script_action = alert_template.check_script_actions
email_severities = alert_template.check_email_alert_severity or [
AlertSeverity.ERROR,
AlertSeverity.WARNING,
]
text_severities = alert_template.check_text_alert_severity or [
AlertSeverity.ERROR,
AlertSeverity.WARNING,
]
elif isinstance(instance, TaskResult):
from autotasks.tasks import (
@@ -556,6 +661,14 @@ class Alert(models.Model):
email_on_resolved = alert_template.task_email_on_resolved
text_on_resolved = alert_template.task_text_on_resolved
run_script_action = alert_template.task_script_actions
email_severities = alert_template.task_email_alert_severity or [
AlertSeverity.ERROR,
AlertSeverity.WARNING,
]
text_severities = alert_template.task_text_alert_severity or [
AlertSeverity.ERROR,
AlertSeverity.WARNING,
]
else:
return
@@ -570,36 +683,101 @@ class Alert(models.Model):
# check if a resolved email notification should be send
if email_on_resolved and not alert.resolved_email_sent:
resolved_email_task.delay(pk=alert.pk)
if alert.severity == AlertSeverity.INFO and not core.notify_on_info_alerts:
pass
elif (
alert.severity == AlertSeverity.WARNING
and not core.notify_on_warning_alerts
):
pass
elif alert.severity not in email_severities:
pass
else:
resolved_email_task.delay(pk=alert.pk)
# check if resolved text should be sent
if text_on_resolved and not alert.resolved_sms_sent:
resolved_text_task.delay(pk=alert.pk)
if alert.severity == AlertSeverity.INFO and not core.notify_on_info_alerts:
pass
# check if resolved script should be run
if (
alert_template
and alert_template.resolved_action
and run_script_action
and not alert.resolved_action_run
):
hist = AgentHistory.objects.create(
agent=agent,
type=AgentHistoryType.SCRIPT_RUN,
script=alert_template.action,
username="alert-action-resolved",
)
r = agent.run_script(
scriptpk=alert_template.resolved_action.pk,
args=alert.parse_script_args(alert_template.resolved_action_args),
timeout=alert_template.resolved_action_timeout,
wait=True,
history_pk=hist.pk,
full=True,
run_on_any=True,
run_as_user=False,
env_vars=alert_template.resolved_action_env_vars,
)
elif (
alert.severity == AlertSeverity.WARNING
and not core.notify_on_warning_alerts
):
pass
elif alert.severity not in text_severities:
pass
else:
resolved_text_task.delay(pk=alert.pk)
# check if resolved script/webhook should be run
if alert_template and not alert.resolved_action_run:
if (
alert_template.resolved_action_type == AlertTemplateActionType.SCRIPT
and alert_template.resolved_action
and run_script_action
):
hist = AgentHistory.objects.create(
agent=agent,
type=AgentHistoryType.SCRIPT_RUN,
script=alert_template.resolved_action,
username="alert-action-resolved",
)
r = agent.run_script(
scriptpk=alert_template.resolved_action.pk,
args=alert.parse_script_args(alert_template.resolved_action_args),
timeout=alert_template.resolved_action_timeout,
wait=True,
history_pk=hist.pk,
full=True,
run_on_any=True,
run_as_user=False,
env_vars=alert_template.resolved_action_env_vars,
)
elif (
alert_template.resolved_action_type == AlertTemplateActionType.SERVER
and alert_template.resolved_action
and run_script_action
):
stdout, stderr, execution_time, retcode = run_server_script(
body=alert_template.resolved_action.script_body,
args=alert.parse_script_args(alert_template.resolved_action_args),
timeout=alert_template.resolved_action_timeout,
env_vars=alert.parse_script_args(
alert_template.resolved_action_env_vars
),
shell=alert_template.resolved_action.shell,
)
r = {
"stdout": stdout,
"stderr": stderr,
"execution_time": execution_time,
"retcode": retcode,
}
elif alert_template.action_type == AlertTemplateActionType.REST:
if (
alert.severity == AlertSeverity.INFO
and not core.notify_on_info_alerts
or alert.severity == AlertSeverity.WARNING
and not core.notify_on_warning_alerts
):
return
else:
output, status = run_url_rest_action(
action_id=alert_template.resolved_action_rest.id, instance=alert
)
logger.debug(f"{output=} {status=}")
r = {
"stdout": output,
"stderr": "",
"execution_time": 0,
"retcode": status,
}
else:
return
# command was successful
if isinstance(r, dict):
@@ -612,41 +790,36 @@ class Alert(models.Model):
alert.resolved_action_run = djangotime.now()
alert.save()
else:
DebugLog.error(
agent=agent,
log_type=DebugLogType.SCRIPTING,
message=f"Resolved action: {alert_template.action.name} failed to run on any agent for {agent.hostname}({agent.pk}) resolved alert",
)
if (
alert_template.resolved_action_type
== AlertTemplateActionType.SCRIPT
):
DebugLog.error(
agent=agent,
log_type=DebugLogType.SCRIPTING,
message=f"Resolved action: {alert_template.action.name} failed to run on any agent for {agent.hostname}({agent.pk}) resolved alert",
)
else:
DebugLog.error(
log_type=DebugLogType.SCRIPTING,
message=f"Resolved action: {alert_template.action.name} failed to run on server for resolved alert",
)
def parse_script_args(self, args: List[str]) -> List[str]:
if not args:
return []
temp_args = []
# pattern to match for injection
pattern = re.compile(".*\\{\\{alert\\.(.*)\\}\\}.*")
for arg in args:
match = pattern.match(arg)
if match:
name = match.group(1)
temp_arg = arg
for string, model, prop in RE_DB_VALUE.findall(arg):
value = get_db_value(string=f"{model}.{prop}", instance=self)
# check if attr exists and isn't a function
if hasattr(self, name) and not callable(getattr(self, name)):
value = f"'{getattr(self, name)}'"
else:
continue
if value is not None:
temp_arg = temp_arg.replace(string, f"'{str(value)}'")
try:
temp_args.append(re.sub("\\{\\{.*\\}\\}", value, arg))
except re.error:
temp_args.append(re.sub("\\{\\{.*\\}\\}", re.escape(value), arg))
except Exception as e:
DebugLog.error(log_type=DebugLogType.SCRIPTING, message=str(e))
continue
else:
temp_args.append(arg)
temp_args.append(temp_arg)
return temp_args
@@ -655,6 +828,11 @@ class AlertTemplate(BaseAuditModel):
name = models.CharField(max_length=100)
is_active = models.BooleanField(default=True)
action_type = models.CharField(
max_length=10,
choices=AlertTemplateActionType.choices,
default=AlertTemplateActionType.SCRIPT,
)
action = models.ForeignKey(
"scripts.Script",
related_name="alert_template",
@@ -662,6 +840,13 @@ class AlertTemplate(BaseAuditModel):
null=True,
on_delete=models.SET_NULL,
)
action_rest = models.ForeignKey(
"core.URLAction",
related_name="url_action_alert_template",
blank=True,
null=True,
on_delete=models.SET_NULL,
)
action_args = ArrayField(
models.CharField(max_length=255, null=True, blank=True),
null=True,
@@ -675,6 +860,9 @@ class AlertTemplate(BaseAuditModel):
default=list,
)
action_timeout = models.PositiveIntegerField(default=15)
resolved_action_type = models.CharField(
max_length=10, choices=AlertTemplateActionType.choices, default="script"
)
resolved_action = models.ForeignKey(
"scripts.Script",
related_name="resolved_alert_template",
@@ -682,6 +870,13 @@ class AlertTemplate(BaseAuditModel):
null=True,
on_delete=models.SET_NULL,
)
resolved_action_rest = models.ForeignKey(
"core.URLAction",
related_name="resolved_url_action_alert_template",
blank=True,
null=True,
on_delete=models.SET_NULL,
)
resolved_action_args = ArrayField(
models.CharField(max_length=255, null=True, blank=True),
null=True,

View File

@@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
from django.shortcuts import get_object_or_404
from rest_framework import permissions
from tacticalrmm.constants import AlertTemplateActionType
from tacticalrmm.permissions import _has_perm, _has_perm_on_agent
if TYPE_CHECKING:
@@ -53,4 +54,17 @@ class AlertTemplatePerms(permissions.BasePermission):
if r.method == "GET":
return _has_perm(r, "can_list_alerttemplates")
if r.method in ("POST", "PUT", "PATCH"):
# ensure only users with explicit run server script perms can add/modify alert templates
# while also still requiring the manage alert template perm
if isinstance(r.data, dict):
if (
r.data.get("action_type") == AlertTemplateActionType.SERVER
or r.data.get("resolved_action_type")
== AlertTemplateActionType.SERVER
):
return _has_perm(r, "can_run_server_scripts") and _has_perm(
r, "can_manage_alerttemplates"
)
return _has_perm(r, "can_manage_alerttemplates")

View File

@@ -3,6 +3,7 @@ from rest_framework.serializers import ModelSerializer, ReadOnlyField
from automation.serializers import PolicySerializer
from clients.serializers import ClientMinimumSerializer, SiteMinimumSerializer
from tacticalrmm.constants import AlertTemplateActionType
from .models import Alert, AlertTemplate
@@ -25,14 +26,29 @@ class AlertTemplateSerializer(ModelSerializer):
task_settings = ReadOnlyField(source="has_task_settings")
core_settings = ReadOnlyField(source="has_core_settings")
default_template = ReadOnlyField(source="is_default_template")
action_name = ReadOnlyField(source="action.name")
resolved_action_name = ReadOnlyField(source="resolved_action.name")
action_name = SerializerMethodField()
resolved_action_name = SerializerMethodField()
applied_count = SerializerMethodField()
class Meta:
model = AlertTemplate
fields = "__all__"
def get_action_name(self, obj):
if obj.action_type == AlertTemplateActionType.REST and obj.action_rest:
return obj.action_rest.name
return obj.action.name if obj.action else ""
def get_resolved_action_name(self, obj):
if (
obj.resolved_action_type == AlertTemplateActionType.REST
and obj.resolved_action_rest
):
return obj.resolved_action_rest.name
return obj.resolved_action.name if obj.resolved_action else ""
def get_applied_count(self, instance):
return (
instance.policies.count()

View File

@@ -1,16 +1,21 @@
from datetime import datetime, timedelta
from datetime import timedelta
from itertools import cycle
from unittest.mock import patch
from django.conf import settings
from django.utils import timezone as djangotime
from model_bakery import baker, seq
from alerts.tasks import cache_agents_alert_template
from autotasks.models import TaskResult
from core.tasks import cache_db_fields_task, resolve_alerts_task
from core.utils import get_core_settings
from tacticalrmm.constants import AgentMonType, AlertSeverity, AlertType, CheckStatus
from django.conf import settings
from django.utils import timezone as djangotime
from model_bakery import baker, seq
from tacticalrmm.constants import (
AgentMonType,
AlertSeverity,
AlertType,
CheckStatus,
URLActionType,
)
from tacticalrmm.test import TacticalTestCase
from .models import Alert, AlertTemplate
@@ -28,6 +33,7 @@ class TestAlertsViews(TacticalTestCase):
self.authenticate()
self.setup_coresettings()
"""
def test_get_alerts(self):
url = "/alerts/"
@@ -39,14 +45,14 @@ class TestAlertsViews(TacticalTestCase):
alerts = baker.make(
"alerts.Alert",
agent=agent,
alert_time=seq(datetime.now(), timedelta(days=15)),
alert_time=seq(djangotime.now(), timedelta(days=15)),
severity=AlertSeverity.WARNING,
_quantity=3,
)
baker.make(
"alerts.Alert",
assigned_check=check,
alert_time=seq(datetime.now(), timedelta(days=15)),
alert_time=seq(djangotime.now(), timedelta(days=15)),
severity=AlertSeverity.ERROR,
_quantity=7,
)
@@ -55,7 +61,7 @@ class TestAlertsViews(TacticalTestCase):
assigned_task=task,
snoozed=True,
snooze_until=djangotime.now(),
alert_time=seq(datetime.now(), timedelta(days=15)),
alert_time=seq(djangotime.now(), timedelta(days=15)),
_quantity=2,
)
baker.make(
@@ -63,7 +69,7 @@ class TestAlertsViews(TacticalTestCase):
agent=agent,
resolved=True,
resolved_on=djangotime.now(),
alert_time=seq(datetime.now(), timedelta(days=15)),
alert_time=seq(djangotime.now(), timedelta(days=15)),
_quantity=9,
)
@@ -120,13 +126,14 @@ class TestAlertsViews(TacticalTestCase):
self.assertEqual(len(resp.data), req["count"])
self.check_not_authenticated("patch", url)
"""
def test_add_alert(self):
url = "/alerts/"
agent = baker.make_recipe("agents.agent")
data = {
"alert_time": datetime.now(),
"alert_time": djangotime.now(),
"agent": agent.id,
"severity": "warning",
"alert_type": "availability",
@@ -275,12 +282,32 @@ class TestAlertsViews(TacticalTestCase):
resp = self.client.get("/alerts/templates/500/", format="json")
self.assertEqual(resp.status_code, 404)
alert_template = baker.make("alerts.AlertTemplate")
url = f"/alerts/templates/{alert_template.pk}/"
agent_script = baker.make("scripts.Script")
server_script = baker.make("scripts.Script")
webhook = baker.make("core.URLAction", action_type=URLActionType.REST)
alert_template_agent_script = baker.make(
"alerts.AlertTemplate", action=agent_script
)
url = f"/alerts/templates/{alert_template_agent_script.pk}/"
resp = self.client.get(url, format="json")
serializer = AlertTemplateSerializer(alert_template)
serializer = AlertTemplateSerializer(alert_template_agent_script)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.data, serializer.data)
alert_template_server_script = baker.make(
"alerts.AlertTemplate", action=server_script
)
url = f"/alerts/templates/{alert_template_server_script.pk}/"
resp = self.client.get(url, format="json")
serializer = AlertTemplateSerializer(alert_template_server_script)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.data, serializer.data)
alert_template_webhook = baker.make("alerts.AlertTemplate", action_rest=webhook)
url = f"/alerts/templates/{alert_template_webhook.pk}/"
resp = self.client.get(url, format="json")
serializer = AlertTemplateSerializer(alert_template_webhook)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.data, serializer.data)
@@ -363,7 +390,7 @@ class TestAlertTasks(TacticalTestCase):
not_snoozed = baker.make(
"alerts.Alert",
snoozed=True,
snooze_until=seq(datetime.now(), timedelta(days=15)),
snooze_until=seq(djangotime.now(), timedelta(days=15)),
_quantity=5,
)
@@ -371,7 +398,7 @@ class TestAlertTasks(TacticalTestCase):
snoozed = baker.make(
"alerts.Alert",
snoozed=True,
snooze_until=seq(datetime.now(), timedelta(days=-15)),
snooze_until=seq(djangotime.now(), timedelta(days=-15)),
_quantity=5,
)
@@ -1427,6 +1454,8 @@ class TestAlertTasks(TacticalTestCase):
"run_as_user": False,
"env_vars": ["hello=world", "foo=bar"],
"id": AgentHistory.objects.last().pk, # type: ignore
"nushell_enable_config": settings.NUSHELL_ENABLE_CONFIG,
"deno_default_permissions": settings.DENO_DEFAULT_PERMISSIONS,
}
nats_cmd.assert_called_with(data, timeout=30, wait=True)
@@ -1458,6 +1487,8 @@ class TestAlertTasks(TacticalTestCase):
"run_as_user": False,
"env_vars": ["resolved=action", "env=vars"],
"id": AgentHistory.objects.last().pk, # type: ignore
"nushell_enable_config": settings.NUSHELL_ENABLE_CONFIG,
"deno_default_permissions": settings.DENO_DEFAULT_PERMISSIONS,
}
nats_cmd.assert_called_with(data, timeout=35, wait=True)

View File

@@ -25,12 +25,16 @@ class GetAddAlerts(APIView):
def patch(self, request):
# top 10 alerts for dashboard icon
if "top" in request.data.keys():
alerts = Alert.objects.filter(
resolved=False, snoozed=False, hidden=False
).order_by("alert_time")[: int(request.data["top"])]
count = Alert.objects.filter(
resolved=False, snoozed=False, hidden=False
).count()
alerts = (
Alert.objects.filter_by_role(request.user) # type: ignore
.filter(resolved=False, snoozed=False, hidden=False)
.order_by("alert_time")[: int(request.data["top"])]
)
count = (
Alert.objects.filter_by_role(request.user) # type: ignore
.filter(resolved=False, snoozed=False, hidden=False)
.count()
)
return Response(
{
"alerts_count": count,

View File

@@ -22,4 +22,12 @@ def get_agent_config() -> AgentCheckInConfig:
*getattr(settings, "CHECKIN_SYNCMESH", (800, 1200))
),
limit_data=getattr(settings, "LIMIT_DATA", False),
install_nushell=getattr(settings, "INSTALL_NUSHELL", False),
install_nushell_version=getattr(settings, "INSTALL_NUSHELL_VERSION", ""),
install_nushell_url=getattr(settings, "INSTALL_NUSHELL_URL", ""),
nushell_enable_config=getattr(settings, "NUSHELL_ENABLE_CONFIG", False),
install_deno=getattr(settings, "INSTALL_DENO", False),
install_deno_version=getattr(settings, "INSTALL_DENO_VERSION", ""),
install_deno_url=getattr(settings, "INSTALL_DENO_URL", ""),
deno_default_permissions=getattr(settings, "DENO_DEFAULT_PERMISSIONS", ""),
)

View File

@@ -14,12 +14,14 @@ from rest_framework.views import APIView
from accounts.models import User
from agents.models import Agent, AgentHistory
from agents.serializers import AgentHistorySerializer
from alerts.tasks import cache_agents_alert_template
from apiv3.utils import get_agent_config
from autotasks.models import AutomatedTask, TaskResult
from autotasks.serializers import TaskGOGetSerializer, TaskResultSerializer
from checks.constants import CHECK_DEFER, CHECK_RESULT_DEFER
from checks.models import Check, CheckResult
from checks.serializers import CheckRunnerGetSerializer
from core.tasks import sync_mesh_perms_task
from core.utils import (
download_mesh_agent,
get_core_settings,
@@ -31,6 +33,8 @@ from logs.models import DebugLog, PendingAction
from software.models import InstalledSoftware
from tacticalrmm.constants import (
AGENT_DEFER,
TRMM_MAX_REQUEST_SIZE,
AgentHistoryType,
AgentMonType,
AgentPlat,
AuditActionType,
@@ -338,6 +342,12 @@ class TaskRunner(APIView):
AutomatedTask.objects.select_related("custom_field"), pk=pk
)
content_length = request.META.get("CONTENT_LENGTH")
if content_length and int(content_length) > TRMM_MAX_REQUEST_SIZE:
request.data["stdout"] = ""
request.data["stderr"] = "Content truncated due to excessive request size."
request.data["retcode"] = 1
# get task result or create if doesn't exist
try:
task_result = (
@@ -356,7 +366,7 @@ class TaskRunner(APIView):
AgentHistory.objects.create(
agent=agent,
type=AuditActionType.TASK_RUN,
type=AgentHistoryType.TASK_RUN,
command=task.name,
script_results=request.data,
)
@@ -426,8 +436,8 @@ class MeshExe(APIView):
try:
return download_mesh_agent(dl_url)
except:
return notify_error("Unable to download mesh agent exe")
except Exception as e:
return notify_error(f"Unable to download mesh agent: {e}")
class NewAgent(APIView):
@@ -481,6 +491,8 @@ class NewAgent(APIView):
)
ret = {"pk": agent.pk, "token": token.key}
sync_mesh_perms_task.delay()
cache_agents_alert_template.delay()
return Response(ret)
@@ -559,6 +571,15 @@ class AgentHistoryResult(APIView):
permission_classes = [IsAuthenticated]
def patch(self, request, agentid, pk):
content_length = request.META.get("CONTENT_LENGTH")
if content_length and int(content_length) > TRMM_MAX_REQUEST_SIZE:
request.data["script_results"]["stdout"] = ""
request.data["script_results"][
"stderr"
] = "Content truncated due to excessive request size."
request.data["script_results"]["retcode"] = 1
hist = get_object_or_404(
AgentHistory.objects.filter(agent__agent_id=agentid), pk=pk
)

View File

@@ -47,7 +47,7 @@ class Policy(BaseAuditModel):
old_policy: Optional[Policy] = (
type(self).objects.get(pk=self.pk) if self.pk else None
)
super(Policy, self).save(old_model=old_policy, *args, **kwargs)
super().save(old_model=old_policy, *args, **kwargs)
# check if alert template was changes and cache on agents
if old_policy:
@@ -68,10 +68,7 @@ class Policy(BaseAuditModel):
cache.delete_many_pattern("site_server_*")
cache.delete_many_pattern("agent_*")
super(Policy, self).delete(
*args,
**kwargs,
)
super().delete(*args, **kwargs)
def __str__(self) -> str:
return self.name

View File

@@ -126,7 +126,7 @@ class TestPolicyViews(TacticalTestCase):
resp = self.client.put(url, data, format="json")
self.assertEqual(resp.status_code, 200)
cache_alert_template.called_once()
cache_alert_template.assert_called_once()
self.check_not_authenticated("put", url)

View File

@@ -7,10 +7,4 @@ class Command(BaseCommand):
help = "Checks for orphaned tasks on all agents and removes them"
def handle(self, *args, **kwargs):
remove_orphaned_win_tasks.s()
self.stdout.write(
self.style.SUCCESS(
"The task has been initiated. Check the Debug Log in the UI for progress."
)
)
remove_orphaned_win_tasks()

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.7 on 2023-11-23 04:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('autotasks', '0038_add_missing_env_vars'),
]
operations = [
migrations.AlterField(
model_name='automatedtask',
name='task_type',
field=models.CharField(choices=[('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly'), ('monthlydow', 'Monthly Day of Week'), ('checkfailure', 'On Check Failure'), ('manual', 'Manual'), ('runonce', 'Run Once'), ('onboarding', 'Onboarding'), ('scheduled', 'Scheduled')], default='manual', max_length=100),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.10 on 2024-02-19 05:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("autotasks", "0039_alter_automatedtask_task_type"),
]
operations = [
migrations.AlterField(
model_name="taskresult",
name="id",
field=models.BigAutoField(primary_key=True, serialize=False),
),
]

View File

@@ -1,9 +1,9 @@
import asyncio
import logging
import random
import string
from contextlib import suppress
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from zoneinfo import ZoneInfo
from django.core.cache import cache
from django.core.validators import MaxValueValidator, MinValueValidator
@@ -14,12 +14,11 @@ from django.db.utils import DatabaseError
from django.utils import timezone as djangotime
from core.utils import get_core_settings
from logs.models import BaseAuditModel, DebugLog
from logs.models import BaseAuditModel
from tacticalrmm.constants import (
FIELDS_TRIGGER_TASK_UPDATE_AGENT,
POLICY_TASK_FIELDS_TO_COPY,
AlertSeverity,
DebugLogType,
TaskStatus,
TaskSyncStatus,
TaskType,
@@ -31,6 +30,7 @@ if TYPE_CHECKING:
from agents.models import Agent
from checks.models import Check
from tacticalrmm.helpers import has_script_actions, has_webhook
from tacticalrmm.models import PermissionQuerySet
from tacticalrmm.utils import (
bitdays_to_string,
@@ -46,6 +46,9 @@ def generate_task_name() -> str:
return "TacticalRMM_" + "".join(random.choice(chars) for i in range(35))
logger = logging.getLogger("trmm")
class AutomatedTask(BaseAuditModel):
objects = PermissionQuerySet.as_manager()
@@ -149,7 +152,7 @@ class AutomatedTask(BaseAuditModel):
# get old task if exists
old_task = AutomatedTask.objects.get(pk=self.pk) if self.pk else None
super(AutomatedTask, self).save(old_model=old_task, *args, **kwargs)
super().save(old_model=old_task, *args, **kwargs)
# check if fields were updated that require a sync to the agent and set status to notsynced
if old_task:
@@ -172,10 +175,7 @@ class AutomatedTask(BaseAuditModel):
cache.delete_many_pattern("site_*_tasks")
cache.delete_many_pattern("agent_*_tasks")
super(AutomatedTask, self).delete(
*args,
**kwargs,
)
super().delete(*args, **kwargs)
@property
def schedule(self) -> Optional[str]:
@@ -209,6 +209,9 @@ class AutomatedTask(BaseAuditModel):
weeks = bitweeks_to_string(self.monthly_weeks_of_month)
days = bitdays_to_string(self.run_time_bit_weekdays)
return f"Runs on {months} on {weeks} on {days} at {run_time_nice}"
elif self.task_type == TaskType.ONBOARDING:
return "Onboarding: Runs once on task creation."
return None
@property
def fields_that_trigger_task_update_on_agent(self) -> List[str]:
@@ -236,64 +239,56 @@ class AutomatedTask(BaseAuditModel):
task.save()
# agent version >= 1.8.0
def generate_nats_task_payload(
self, agent: "Optional[Agent]" = None, editing: bool = False
) -> Dict[str, Any]:
def generate_nats_task_payload(self) -> Dict[str, Any]:
task = {
"pk": self.pk,
"type": "rmm",
"name": self.win_task_name,
"overwrite_task": editing,
"overwrite_task": True,
"enabled": self.enabled,
"trigger": self.task_type
if self.task_type != TaskType.CHECK_FAILURE
else TaskType.MANUAL,
"trigger": (
self.task_type
if self.task_type != TaskType.CHECK_FAILURE
else TaskType.MANUAL
),
"multiple_instances": self.task_instance_policy or 0,
"delete_expired_task_after": self.remove_if_not_scheduled
if self.expire_date
else False,
"start_when_available": self.run_asap_after_missed
if self.task_type != TaskType.RUN_ONCE
else True,
"delete_expired_task_after": (
self.remove_if_not_scheduled if self.expire_date else False
),
"start_when_available": (
self.run_asap_after_missed
if self.task_type != TaskType.RUN_ONCE
else True
),
}
if self.task_type in (
TaskType.RUN_ONCE,
TaskType.DAILY,
TaskType.WEEKLY,
TaskType.MONTHLY,
TaskType.MONTHLY_DOW,
TaskType.RUN_ONCE,
):
# set runonce task in future if creating and run_asap_after_missed is set
if (
not editing
and self.task_type == TaskType.RUN_ONCE
and self.run_asap_after_missed
and agent
and self.run_time_date.replace(tzinfo=ZoneInfo(agent.timezone))
< djangotime.now().astimezone(ZoneInfo(agent.timezone))
):
self.run_time_date = (
djangotime.now() + djangotime.timedelta(minutes=5)
).astimezone(ZoneInfo(agent.timezone))
if not self.run_time_date:
self.run_time_date = djangotime.now()
task["start_year"] = int(self.run_time_date.strftime("%Y"))
task["start_month"] = int(self.run_time_date.strftime("%-m"))
task["start_day"] = int(self.run_time_date.strftime("%-d"))
task["start_hour"] = int(self.run_time_date.strftime("%-H"))
task["start_min"] = int(self.run_time_date.strftime("%-M"))
task["start_year"] = self.run_time_date.year
task["start_month"] = self.run_time_date.month
task["start_day"] = self.run_time_date.day
task["start_hour"] = self.run_time_date.hour
task["start_min"] = self.run_time_date.minute
if self.expire_date:
task["expire_year"] = int(self.expire_date.strftime("%Y"))
task["expire_month"] = int(self.expire_date.strftime("%-m"))
task["expire_day"] = int(self.expire_date.strftime("%-d"))
task["expire_hour"] = int(self.expire_date.strftime("%-H"))
task["expire_min"] = int(self.expire_date.strftime("%-M"))
task["expire_year"] = self.expire_date.year
task["expire_month"] = self.expire_date.month
task["expire_day"] = self.expire_date.day
task["expire_hour"] = self.expire_date.hour
task["expire_min"] = self.expire_date.minute
if self.random_task_delay:
task["random_delay"] = convert_to_iso_duration(self.random_task_delay)
if self.task_repetition_interval:
if self.task_repetition_interval and self.task_repetition_duration:
task["repetition_interval"] = convert_to_iso_duration(
self.task_repetition_interval
)
@@ -341,27 +336,24 @@ class AutomatedTask(BaseAuditModel):
nats_data = {
"func": "schedtask",
"schedtaskpayload": self.generate_nats_task_payload(agent),
"schedtaskpayload": self.generate_nats_task_payload(),
}
logger.debug(nats_data)
r = asyncio.run(task_result.agent.nats_cmd(nats_data, timeout=5))
r = asyncio.run(task_result.agent.nats_cmd(nats_data, timeout=10))
if r != "ok":
task_result.sync_status = TaskSyncStatus.INITIAL
task_result.save(update_fields=["sync_status"])
DebugLog.warning(
agent=agent,
log_type=DebugLogType.AGENT_ISSUES,
message=f"Unable to create scheduled task {self.name} on {task_result.agent.hostname}. It will be created when the agent checks in.",
logger.error(
f"Unable to create scheduled task {self.name} on {task_result.agent.hostname}: {r}"
)
return "timeout"
else:
task_result.sync_status = TaskSyncStatus.SYNCED
task_result.save(update_fields=["sync_status"])
DebugLog.info(
agent=agent,
log_type=DebugLogType.AGENT_ISSUES,
message=f"{task_result.agent.hostname} task {self.name} was successfully created",
logger.info(
f"{task_result.agent.hostname} task {self.name} was successfully created."
)
return "ok"
@@ -380,27 +372,24 @@ class AutomatedTask(BaseAuditModel):
nats_data = {
"func": "schedtask",
"schedtaskpayload": self.generate_nats_task_payload(editing=True),
"schedtaskpayload": self.generate_nats_task_payload(),
}
logger.debug(nats_data)
r = asyncio.run(task_result.agent.nats_cmd(nats_data, timeout=5))
r = asyncio.run(task_result.agent.nats_cmd(nats_data, timeout=10))
if r != "ok":
task_result.sync_status = TaskSyncStatus.NOT_SYNCED
task_result.save(update_fields=["sync_status"])
DebugLog.warning(
agent=agent,
log_type=DebugLogType.AGENT_ISSUES,
message=f"Unable to modify scheduled task {self.name} on {task_result.agent.hostname}({task_result.agent.agent_id}). It will try again on next agent checkin",
logger.error(
f"Unable to modify scheduled task {self.name} on {task_result.agent.hostname}: {r}"
)
return "timeout"
else:
task_result.sync_status = TaskSyncStatus.SYNCED
task_result.save(update_fields=["sync_status"])
DebugLog.info(
agent=agent,
log_type=DebugLogType.AGENT_ISSUES,
message=f"{task_result.agent.hostname} task {self.name} was successfully modified",
logger.info(
f"{task_result.agent.hostname} task {self.name} was successfully modified."
)
return "ok"
@@ -429,20 +418,13 @@ class AutomatedTask(BaseAuditModel):
with suppress(DatabaseError):
task_result.save(update_fields=["sync_status"])
DebugLog.warning(
agent=agent,
log_type=DebugLogType.AGENT_ISSUES,
message=f"{task_result.agent.hostname} task {self.name} will be deleted on next checkin",
logger.error(
f"Unable to delete task {self.name} on {task_result.agent.hostname}: {r}"
)
return "timeout"
else:
self.delete()
DebugLog.info(
agent=agent,
log_type=DebugLogType.AGENT_ISSUES,
message=f"{task_result.agent.hostname}({task_result.agent.agent_id}) task {self.name} was deleted",
)
logger.info(f"{task_result.agent.hostname} task {self.name} was deleted.")
return "ok"
def run_win_task(self, agent: "Optional[Agent]" = None) -> str:
@@ -465,18 +447,19 @@ class AutomatedTask(BaseAuditModel):
return "ok"
def should_create_alert(self, alert_template=None):
has_autotask_notification = (
self.dashboard_alert or self.email_alert or self.text_alert
)
has_alert_template_notification = alert_template and (
alert_template.task_always_alert
or alert_template.task_always_email
or alert_template.task_always_text
)
return (
self.dashboard_alert
or self.email_alert
or self.text_alert
or (
alert_template
and (
alert_template.task_always_alert
or alert_template.task_always_email
or alert_template.task_always_text
)
)
has_autotask_notification
or has_alert_template_notification
or has_webhook(alert_template)
or has_script_actions(alert_template)
)
@@ -486,6 +469,7 @@ class TaskResult(models.Model):
objects = PermissionQuerySet.as_manager()
id = models.BigAutoField(primary_key=True)
agent = models.ForeignKey(
"agents.Agent",
related_name="taskresults",

View File

@@ -2,6 +2,7 @@ from datetime import datetime
from django.utils import timezone as djangotime
from rest_framework import serializers
from django.conf import settings
from scripts.models import Script
from tacticalrmm.constants import TaskType
@@ -252,7 +253,13 @@ class TaskGOGetSerializer(serializers.ModelSerializer):
"shell": script.shell,
"timeout": action["timeout"],
"run_as_user": script.run_as_user,
"env_vars": env_vars,
"env_vars": Script.parse_script_env_vars(
agent=agent,
shell=script.shell,
env_vars=env_vars,
),
"nushell_enable_config": settings.NUSHELL_ENABLE_CONFIG,
"deno_default_permissions": settings.DENO_DEFAULT_PERMISSIONS,
}
)
if actions_to_remove:

View File

@@ -417,7 +417,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"pk": task1.pk,
"type": "rmm",
"name": task1.win_task_name,
"overwrite_task": False,
"overwrite_task": True,
"enabled": True,
"trigger": "daily",
"multiple_instances": 1,
@@ -431,7 +431,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"day_interval": 1,
},
},
timeout=5,
timeout=10,
)
nats_cmd.reset_mock()
self.assertEqual(
@@ -470,7 +470,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"pk": task1.pk,
"type": "rmm",
"name": task1.win_task_name,
"overwrite_task": False,
"overwrite_task": True,
"enabled": True,
"trigger": "weekly",
"multiple_instances": 2,
@@ -490,7 +490,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"days_of_week": 127,
},
},
timeout=5,
timeout=10,
)
nats_cmd.reset_mock()
@@ -518,7 +518,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"pk": task1.pk,
"type": "rmm",
"name": task1.win_task_name,
"overwrite_task": False,
"overwrite_task": True,
"enabled": True,
"trigger": "monthly",
"multiple_instances": 1,
@@ -538,7 +538,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"months_of_year": 1024,
},
},
timeout=5,
timeout=10,
)
nats_cmd.reset_mock()
@@ -562,7 +562,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"pk": task1.pk,
"type": "rmm",
"name": task1.win_task_name,
"overwrite_task": False,
"overwrite_task": True,
"enabled": True,
"trigger": "monthlydow",
"multiple_instances": 1,
@@ -578,7 +578,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"weeks_of_month": 3,
},
},
timeout=5,
timeout=10,
)
nats_cmd.reset_mock()
@@ -600,7 +600,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"pk": task1.pk,
"type": "rmm",
"name": task1.win_task_name,
"overwrite_task": False,
"overwrite_task": True,
"enabled": True,
"trigger": "runonce",
"multiple_instances": 1,
@@ -613,39 +613,10 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"start_min": int(task1.run_time_date.strftime("%-M")),
},
},
timeout=5,
timeout=10,
)
nats_cmd.reset_mock()
# test runonce with date in the past
task1 = baker.make(
"autotasks.AutomatedTask",
agent=agent,
name="test task 3",
task_type=TaskType.RUN_ONCE,
run_asap_after_missed=True,
run_time_date=djangotime.datetime(2018, 6, 1, 23, 23, 23),
)
nats_cmd.return_value = "ok"
create_win_task_schedule(pk=task1.pk)
nats_cmd.assert_called()
# check if task is scheduled for at most 5min in the future
_, args, _ = nats_cmd.mock_calls[0]
current_minute = int(djangotime.now().strftime("%-M"))
if current_minute >= 55 and current_minute < 60:
self.assertLess(
args[0]["schedtaskpayload"]["start_min"],
int(djangotime.now().strftime("%-M")),
)
else:
self.assertGreater(
args[0]["schedtaskpayload"]["start_min"],
int(djangotime.now().strftime("%-M")),
)
# test checkfailure task
nats_cmd.reset_mock()
check = baker.make_recipe("checks.diskspace_check", agent=agent)
@@ -665,7 +636,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"pk": task1.pk,
"type": "rmm",
"name": task1.win_task_name,
"overwrite_task": False,
"overwrite_task": True,
"enabled": True,
"trigger": "manual",
"multiple_instances": 1,
@@ -673,7 +644,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"start_when_available": False,
},
},
timeout=5,
timeout=10,
)
nats_cmd.reset_mock()
@@ -692,7 +663,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"pk": task1.pk,
"type": "rmm",
"name": task1.win_task_name,
"overwrite_task": False,
"overwrite_task": True,
"enabled": True,
"trigger": "manual",
"multiple_instances": 1,
@@ -700,7 +671,7 @@ class TestAutoTaskCeleryTasks(TacticalTestCase):
"start_when_available": False,
},
},
timeout=5,
timeout=10,
)

View File

@@ -1,4 +1,5 @@
from django.shortcuts import get_object_or_404
from packaging import version as pyver
from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@@ -6,6 +7,8 @@ from rest_framework.views import APIView
from agents.models import Agent
from automation.models import Policy
from tacticalrmm.constants import TaskType
from tacticalrmm.helpers import notify_error
from tacticalrmm.permissions import _has_perm_on_agent
from .models import AutomatedTask
@@ -40,6 +43,11 @@ class GetAddAutoTasks(APIView):
if not _has_perm_on_agent(request.user, agent.agent_id):
raise PermissionDenied()
if data["task_type"] == TaskType.ONBOARDING and pyver.parse(
agent.version
) < pyver.parse("2.6.0"):
return notify_error("Onboarding tasks require agent >= 2.6.0")
data["agent"] = agent.pk
serializer = TaskSerializer(data=data)

View File

View File

@@ -0,0 +1,37 @@
import django_filters
from agents.models import Agent
class AgentFilter(django_filters.FilterSet):
last_seen_range = django_filters.DateTimeFromToRangeFilter(field_name="last_seen")
total_ram_range = django_filters.NumericRangeFilter(field_name="total_ram")
patches_last_installed_range = django_filters.DateTimeFromToRangeFilter(
field_name="patches_last_installed"
)
client_id = django_filters.NumberFilter(method="client_id_filter")
class Meta:
model = Agent
fields = [
"id",
"hostname",
"agent_id",
"operating_system",
"plat",
"monitoring_type",
"needs_reboot",
"logged_in_username",
"last_logged_in_user",
"alert_template",
"site",
"policy",
"last_seen_range",
"total_ram_range",
"patches_last_installed_range",
]
def client_id_filter(self, queryset, name, value):
if value:
return queryset.filter(site__client__id=value)
return queryset

View File

@@ -0,0 +1,40 @@
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
from rest_framework.request import Request
from rest_framework.serializers import BaseSerializer
from agents.models import Agent
from agents.permissions import AgentPerms
from beta.v1.agent.filter import AgentFilter
from beta.v1.pagination import StandardResultsSetPagination
from ..serializers import DetailAgentSerializer, ListAgentSerializer
class AgentViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, AgentPerms]
queryset = Agent.objects.all()
pagination_class = StandardResultsSetPagination
http_method_names = ["get", "put"]
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_class = AgentFilter
search_fields = ["hostname", "services"]
ordering_fields = ["id"]
ordering = ["id"]
def check_permissions(self, request: Request) -> None:
if "agent_id" in request.query_params:
self.kwargs["agent_id"] = request.query_params["agent_id"]
super().check_permissions(request)
def get_permissions(self):
if self.request.method == "POST":
self.permission_classes = [IsAuthenticated]
return super().get_permissions()
def get_serializer_class(self) -> type[BaseSerializer]:
if self.kwargs:
if self.kwargs["pk"]:
return DetailAgentSerializer
return ListAgentSerializer

View File

@@ -0,0 +1,13 @@
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from clients.models import Client
from clients.permissions import ClientsPerms
from ..serializers import ClientSerializer
class ClientViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, ClientsPerms]
queryset = Client.objects.all()
serializer_class = ClientSerializer
http_method_names = ["get", "put"]

View File

@@ -0,0 +1,7 @@
from rest_framework.pagination import PageNumberPagination
class StandardResultsSetPagination(PageNumberPagination):
page_size = 100
page_size_query_param = "page_size"
max_page_size = 1000

View File

@@ -0,0 +1,73 @@
from rest_framework import serializers
from agents.models import Agent
from clients.models import Client, Site
class ListAgentSerializer(serializers.ModelSerializer[Agent]):
class Meta:
model = Agent
fields = "__all__"
class DetailAgentSerializer(serializers.ModelSerializer[Agent]):
status = serializers.ReadOnlyField()
class Meta:
model = Agent
fields = (
"version",
"operating_system",
"plat",
"goarch",
"hostname",
"agent_id",
"last_seen",
"services",
"public_ip",
"total_ram",
"disks",
"boot_time",
"logged_in_username",
"last_logged_in_user",
"monitoring_type",
"description",
"mesh_node_id",
"overdue_email_alert",
"overdue_text_alert",
"overdue_dashboard_alert",
"offline_time",
"overdue_time",
"check_interval",
"needs_reboot",
"choco_installed",
"wmi_detail",
"patches_last_installed",
"time_zone",
"maintenance_mode",
"block_policy_inheritance",
"alert_template",
"site",
"policy",
"status",
"checks",
"pending_actions_count",
"cpu_model",
"graphics",
"local_ips",
"make_model",
"physical_disks",
"serial_number",
)
class ClientSerializer(serializers.ModelSerializer[Client]):
class Meta:
model = Client
fields = "__all__"
class SiteSerializer(serializers.ModelSerializer[Site]):
class Meta:
model = Site
fields = "__all__"

View File

@@ -0,0 +1,21 @@
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
from clients.models import Site
from clients.permissions import SitesPerms
from beta.v1.pagination import StandardResultsSetPagination
from ..serializers import SiteSerializer
class SiteViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, SitesPerms]
queryset = Site.objects.all()
serializer_class = SiteSerializer
pagination_class = StandardResultsSetPagination
http_method_names = ["get", "put"]
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
search_fields = ["name"]
ordering_fields = ["id"]
ordering = ["id"]

View File

@@ -0,0 +1,12 @@
from rest_framework import routers
from .agent import views as agent
from .client import views as client
from .site import views as site
router = routers.DefaultRouter()
router.register("agent", agent.AgentViewSet, basename="agent")
router.register("client", client.ClientViewSet, basename="client")
router.register("site", site.SiteViewSet, basename="site")
urlpatterns = router.urls

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.10 on 2024-02-19 05:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("checks", "0031_check_env_vars"),
]
operations = [
migrations.AlterField(
model_name="checkhistory",
name="id",
field=models.BigAutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="checkresult",
name="id",
field=models.BigAutoField(primary_key=True, serialize=False),
),
]

View File

@@ -19,6 +19,7 @@ from tacticalrmm.constants import (
EvtLogNames,
EvtLogTypes,
)
from tacticalrmm.helpers import has_script_actions, has_webhook
from tacticalrmm.models import PermissionQuerySet
if TYPE_CHECKING:
@@ -168,10 +169,7 @@ class Check(BaseAuditModel):
elif self.agent:
cache.delete(f"agent_{self.agent.agent_id}_checks")
super(Check, self).save(
*args,
**kwargs,
)
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
# if check is a policy check clear cache on everything
@@ -183,10 +181,7 @@ class Check(BaseAuditModel):
elif self.agent:
cache.delete(f"agent_{self.agent.agent_id}_checks")
super(Check, self).delete(
*args,
**kwargs,
)
super().delete(*args, **kwargs)
@property
def readable_desc(self):
@@ -236,18 +231,19 @@ class Check(BaseAuditModel):
check.save()
def should_create_alert(self, alert_template=None):
has_check_notifications = (
self.dashboard_alert or self.email_alert or self.text_alert
)
has_alert_template_notification = alert_template and (
alert_template.check_always_alert
or alert_template.check_always_email
or alert_template.check_always_text
)
return (
self.dashboard_alert
or self.email_alert
or self.text_alert
or (
alert_template
and (
alert_template.check_always_alert
or alert_template.check_always_email
or alert_template.check_always_text
)
)
has_check_notifications
or has_alert_template_notification
or has_webhook(alert_template)
or has_script_actions(alert_template)
)
def add_check_history(
@@ -290,6 +286,7 @@ class CheckResult(models.Model):
class Meta:
unique_together = (("agent", "assigned_check"),)
id = models.BigAutoField(primary_key=True)
agent = models.ForeignKey(
"agents.Agent",
related_name="checkresults",
@@ -338,10 +335,7 @@ class CheckResult(models.Model):
):
self.alert_severity = AlertSeverity.WARNING
super(CheckResult, self).save(
*args,
**kwargs,
)
super().save(*args, **kwargs)
@property
def history_info(self):
@@ -673,6 +667,7 @@ class CheckResult(models.Model):
class CheckHistory(models.Model):
objects = PermissionQuerySet.as_manager()
id = models.BigAutoField(primary_key=True)
check_id = models.PositiveIntegerField(default=0)
agent_id = models.CharField(max_length=200, null=True, blank=True)
x = models.DateTimeField(auto_now_add=True)

View File

@@ -172,8 +172,14 @@ class CheckRunnerGetSerializer(serializers.ModelSerializer):
if obj.check_type != CheckType.SCRIPT:
return []
# check's env_vars override the script's env vars
return obj.env_vars or obj.script.env_vars
agent = self.context["agent"] if "agent" in self.context.keys() else obj.agent
return Script.parse_script_env_vars(
agent=agent,
shell=obj.script.shell,
env_vars=obj.env_vars
or obj.script.env_vars, # check's env_vars override the script's env vars
)
class Meta:
model = Check

View File

@@ -8,6 +8,7 @@ from alerts.models import Alert
from checks.models import CheckResult
from tacticalrmm.celery import app
from tacticalrmm.helpers import rand_range
from tacticalrmm.logger import logger
@app.task
@@ -120,9 +121,9 @@ def handle_resolved_check_email_alert_task(pk: int) -> str:
def prune_check_history(older_than_days: int) -> str:
from .models import CheckHistory
CheckHistory.objects.filter(
x__lt=djangotime.make_aware(dt.datetime.today())
- djangotime.timedelta(days=older_than_days)
c, _ = CheckHistory.objects.filter(
x__lt=djangotime.now() - djangotime.timedelta(days=older_than_days)
).delete()
logger.info(f"Pruned {c} check history objects")
return "ok"

View File

@@ -172,6 +172,31 @@ class TestCheckViews(TacticalTestCase):
self.check_not_authenticated("post", url)
def test_reset_all_checks_status(self):
# setup data
agent = baker.make_recipe("agents.agent")
check = baker.make_recipe("checks.diskspace_check", agent=agent)
baker.make("checks.CheckResult", assigned_check=check, agent=agent)
baker.make(
"checks.CheckHistory",
check_id=check.id,
agent_id=agent.agent_id,
_quantity=30,
)
baker.make(
"checks.CheckHistory",
check_id=check.id,
agent_id=agent.agent_id,
_quantity=30,
)
url = f"{base_url}/{agent.agent_id}/resetall/"
resp = self.client.post(url)
self.assertEqual(resp.status_code, 200)
self.check_not_authenticated("post", url)
def test_add_memory_check(self):
url = f"{base_url}/"
agent = baker.make_recipe("agents.agent")

View File

@@ -6,6 +6,7 @@ urlpatterns = [
path("", views.GetAddChecks.as_view()),
path("<int:pk>/", views.GetUpdateDeleteCheck.as_view()),
path("<int:pk>/reset/", views.ResetCheck.as_view()),
path("<agent:agent_id>/resetall/", views.ResetAllChecksStatus.as_view()),
path("<agent:agent_id>/run/", views.run_checks),
path("<int:pk>/history/", views.GetCheckHistory.as_view()),
path("<str:target>/<int:pk>/csbulkrun/", views.bulk_run_checks),

View File

@@ -1,7 +1,7 @@
import asyncio
from datetime import datetime as dt
from django.db.models import Q
from django.db.models import Prefetch, Q
from django.shortcuts import get_object_or_404
from django.utils import timezone as djangotime
from rest_framework.decorators import api_view, permission_classes
@@ -13,7 +13,7 @@ from rest_framework.views import APIView
from agents.models import Agent
from alerts.models import Alert
from automation.models import Policy
from tacticalrmm.constants import CheckStatus, CheckType
from tacticalrmm.constants import AGENT_DEFER, CheckStatus, CheckType
from tacticalrmm.exceptions import NatsDown
from tacticalrmm.helpers import notify_error
from tacticalrmm.nats_utils import abulk_nats_command
@@ -122,15 +122,54 @@ class ResetCheck(APIView):
result.save()
# resolve any alerts that are open
alert = Alert.create_or_return_check_alert(
if alert := Alert.create_or_return_check_alert(
result.assigned_check, agent=result.agent, skip_create=True
)
if alert:
):
alert.resolve()
return Response("The check status was reset")
class ResetAllChecksStatus(APIView):
permission_classes = [IsAuthenticated, ChecksPerms]
def post(self, request, agent_id):
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER)
.select_related(
"policy",
"policy__alert_template",
"alert_template",
)
.prefetch_related(
Prefetch(
"checkresults",
queryset=CheckResult.objects.select_related("assigned_check"),
),
"agentchecks",
),
agent_id=agent_id,
)
if not _has_perm_on_agent(request.user, agent.agent_id):
raise PermissionDenied()
for check in agent.get_checks_with_policies():
try:
result = check.check_result
result.status = CheckStatus.PASSING
result.save()
if alert := Alert.create_or_return_check_alert(
result.assigned_check, agent=agent, skip_create=True
):
alert.resolve()
except:
# check hasn't run yet, no check result entry
continue
return Response("All checks status were reset")
class GetCheckHistory(APIView):
permission_classes = [IsAuthenticated, ChecksPerms]

View File

@@ -49,11 +49,7 @@ class Client(BaseAuditModel):
# get old client if exists
old_client = Client.objects.get(pk=self.pk) if self.pk else None
super(Client, self).save(
old_model=old_client,
*args,
**kwargs,
)
super().save(old_model=old_client, *args, **kwargs)
# check if polcies have changed and initiate task to reapply policies if so
if old_client and (
@@ -129,11 +125,7 @@ class Site(BaseAuditModel):
# get old client if exists
old_site = Site.objects.get(pk=self.pk) if self.pk else None
super(Site, self).save(
old_model=old_site,
*args,
**kwargs,
)
super().save(old_model=old_site, *args, **kwargs)
# check if polcies have changed and initiate task to reapply policies if so
if old_site:

View File

@@ -88,6 +88,7 @@ class TestClientViews(TacticalTestCase):
"client": {"name": "Setup Client"},
"site": {"name": "Setup Site"},
"timezone": "America/Los_Angeles",
"companyname": "TestCo Inc.",
"initialsetup": True,
}
r = self.client.post(url, payload, format="json")

View File

@@ -3,6 +3,7 @@ import re
import uuid
from contextlib import suppress
from django.conf import settings
from django.db.models import Count, Exists, OuterRef, Prefetch, prefetch_related_objects
from django.shortcuts import get_object_or_404
from django.utils import timezone as djangotime
@@ -91,7 +92,8 @@ class GetAddClients(APIView):
if "initialsetup" in request.data.keys():
core = get_core_settings()
core.default_time_zone = request.data["timezone"]
core.save(update_fields=["default_time_zone"])
core.mesh_company_name = request.data["companyname"]
core.save(update_fields=["default_time_zone", "mesh_company_name"])
# save custom fields
if "custom_fields" in request.data.keys():
@@ -288,6 +290,9 @@ class AgentDeployment(APIView):
return Response(DeploymentSerializer(deps, many=True).data)
def post(self, request):
if getattr(settings, "TRMM_INSECURE", False):
return notify_error("Not available in insecure mode")
from accounts.models import User
site = get_object_or_404(Site, pk=request.data["site"])
@@ -343,6 +348,9 @@ class GenerateAgent(APIView):
permission_classes = (AllowAny,)
def get(self, request, uid):
if getattr(settings, "TRMM_INSECURE", False):
return notify_error("Not available in insecure mode")
from tacticalrmm.utils import generate_winagent_exe
try:

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env bash
if [ $EUID -ne 0 ]; then
echo "ERROR: Must be run as root"
exit 1
echo "ERROR: Must be run as root"
exit 1
fi
HAS_SYSTEMD=$(ps --no-headers -o comm 1)
@@ -12,6 +12,19 @@ if [ "${HAS_SYSTEMD}" != 'systemd' ]; then
exit 1
fi
if [[ $DISPLAY ]]; then
echo "ERROR: Display detected. Installer only supports running headless, i.e from ssh."
echo "If you cannot ssh in then please run 'sudo systemctl isolate multi-user.target' to switch to a non-graphical user session and run the installer again."
echo "If you are already running headless, then you are probably running with X forwarding which is setting DISPLAY, if so then simply run"
echo "unset DISPLAY"
echo "to unset the variable and then try running the installer again"
exit 1
fi
DEBUG=0
INSECURE=0
NOMESH=0
agentDL='agentDLChange'
meshDL='meshDLChange'
@@ -28,6 +41,7 @@ agentBin="${agentBinPath}/${binName}"
agentConf='/etc/tacticalagent'
agentSvcName='tacticalagent.service'
agentSysD="/etc/systemd/system/${agentSvcName}"
agentDir='/opt/tacticalagent'
meshDir='/opt/tacticalmesh'
meshSystemBin="${meshDir}/meshagent"
meshSvcName='meshagent.service'
@@ -37,38 +51,48 @@ deb=(ubuntu debian raspbian kali linuxmint)
rhe=(fedora rocky centos rhel amzn arch opensuse)
set_locale_deb() {
locale-gen "en_US.UTF-8"
localectl set-locale LANG=en_US.UTF-8
. /etc/default/locale
locale-gen "en_US.UTF-8"
localectl set-locale LANG=en_US.UTF-8
. /etc/default/locale
}
set_locale_rhel() {
localedef -c -i en_US -f UTF-8 en_US.UTF-8 > /dev/null 2>&1
localectl set-locale LANG=en_US.UTF-8
. /etc/locale.conf
localedef -c -i en_US -f UTF-8 en_US.UTF-8 >/dev/null 2>&1
localectl set-locale LANG=en_US.UTF-8
. /etc/locale.conf
}
RemoveOldAgent() {
if [ -f "${agentSysD}" ]; then
systemctl disable ${agentSvcName}
systemctl stop ${agentSvcName}
rm -f ${agentSysD}
rm -f "${agentSysD}"
systemctl daemon-reload
fi
if [ -f "${agentConf}" ]; then
rm -f ${agentConf}
rm -f "${agentConf}"
fi
if [ -f "${agentBin}" ]; then
rm -f ${agentBin}
rm -f "${agentBin}"
fi
if [ -d "${agentDir}" ]; then
rm -rf "${agentDir}"
fi
}
InstallMesh() {
if [ -f /etc/os-release ]; then
distroID=$(. /etc/os-release; echo $ID)
distroIDLIKE=$(. /etc/os-release; echo $ID_LIKE)
distroID=$(
. /etc/os-release
echo $ID
)
distroIDLIKE=$(
. /etc/os-release
echo $ID_LIKE
)
if [[ " ${deb[*]} " =~ " ${distroID} " ]]; then
set_locale_deb
elif [[ " ${deb[*]} " =~ " ${distroIDLIKE} " ]]; then
@@ -80,11 +104,9 @@ InstallMesh() {
fi
fi
meshTmpDir=$(mktemp -d -t "mesh-XXXXXXXXX")
if [ $? -ne 0 ]; then
meshTmpDir='/root/meshtemp'
mkdir -p ${meshTmpDir}
fi
meshTmpDir='/root/meshtemp'
mkdir -p $meshTmpDir
meshTmpBin="${meshTmpDir}/meshagent"
wget --no-check-certificate -q -O ${meshTmpBin} ${meshDL}
chmod +x ${meshTmpBin}
@@ -101,8 +123,8 @@ RemoveMesh() {
fi
if [ -f "${meshSysD}" ]; then
systemctl stop ${meshSvcName} > /dev/null 2>&1
systemctl disable ${meshSvcName} > /dev/null 2>&1
systemctl stop ${meshSvcName} >/dev/null 2>&1
systemctl disable ${meshSvcName} >/dev/null 2>&1
rm -f ${meshSysD}
fi
@@ -115,11 +137,26 @@ Uninstall() {
RemoveOldAgent
}
if [ $# -ne 0 ] && [ $1 == 'uninstall' ]; then
if [ $# -ne 0 ] && [[ $1 =~ ^(uninstall|-uninstall|--uninstall)$ ]]; then
Uninstall
# Remove the current script
rm "$0"
exit 0
fi
while [[ "$#" -gt 0 ]]; do
case $1 in
-debug | --debug | debug) DEBUG=1 ;;
-insecure | --insecure | insecure) INSECURE=1 ;;
-nomesh | --nomesh | nomesh) NOMESH=1 ;;
*)
echo "ERROR: Unknown parameter: $1"
exit 1
;;
esac
shift
done
RemoveOldAgent
echo "Downloading tactical agent..."
@@ -132,7 +169,7 @@ chmod +x ${agentBin}
MESH_NODE_ID=""
if [ $# -ne 0 ] && [ $1 == '--nomesh' ]; then
if [[ $NOMESH -eq 1 ]]; then
echo "Skipping mesh install"
else
if [ -f "${meshSystemBin}" ]; then
@@ -150,23 +187,28 @@ if [ ! -d "${agentBinPath}" ]; then
mkdir -p ${agentBinPath}
fi
if [ $# -ne 0 ] && [ $1 == '--debug' ]; then
INSTALL_CMD="${agentBin} -m install -api ${apiURL} -client-id ${clientID} -site-id ${siteID} -agent-type ${agentType} -auth ${token} -log debug"
else
INSTALL_CMD="${agentBin} -m install -api ${apiURL} -client-id ${clientID} -site-id ${siteID} -agent-type ${agentType} -auth ${token}"
fi
INSTALL_CMD="${agentBin} -m install -api ${apiURL} -client-id ${clientID} -site-id ${siteID} -agent-type ${agentType} -auth ${token}"
if [ "${MESH_NODE_ID}" != '' ]; then
INSTALL_CMD+=" -meshnodeid ${MESH_NODE_ID}"
INSTALL_CMD+=" --meshnodeid ${MESH_NODE_ID}"
fi
if [[ $DEBUG -eq 1 ]]; then
INSTALL_CMD+=" --log debug"
fi
if [[ $INSECURE -eq 1 ]]; then
INSTALL_CMD+=" --insecure"
fi
if [ "${proxy}" != '' ]; then
INSTALL_CMD+=" -proxy ${proxy}"
INSTALL_CMD+=" --proxy ${proxy}"
fi
eval ${INSTALL_CMD}
tacticalsvc="$(cat << EOF
tacticalsvc="$(
cat <<EOF
[Unit]
Description=Tactical RMM Linux Agent
@@ -184,7 +226,7 @@ KillMode=process
WantedBy=multi-user.target
EOF
)"
echo "${tacticalsvc}" | tee ${agentSysD} > /dev/null
echo "${tacticalsvc}" | tee ${agentSysD} >/dev/null
systemctl daemon-reload
systemctl enable ${agentSvcName}

View File

@@ -1,15 +1,38 @@
import asyncio
import fcntl
import os
import pty
import select
import signal
import struct
import subprocess
import termios
import threading
import uuid
from contextlib import suppress
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from channels.generic.websocket import AsyncJsonWebsocketConsumer, JsonWebsocketConsumer
from django.contrib.auth.models import AnonymousUser
from django.db.models import F
from django.utils import timezone as djangotime
from agents.models import Agent
from core.models import CoreSettings
from tacticalrmm.constants import AgentMonType
from tacticalrmm.helpers import days_until_cert_expires
from tacticalrmm.logger import logger
def _has_perm(user, perm: str) -> bool:
if user.is_superuser or (user.role and getattr(user.role, "is_superuser")):
return True
# make sure non-superusers with empty roles aren't permitted
elif not user.role:
return False
return user.role and getattr(user.role, perm)
class DashInfo(AsyncJsonWebsocketConsumer):
@@ -18,6 +41,11 @@ class DashInfo(AsyncJsonWebsocketConsumer):
if isinstance(self.user, AnonymousUser):
await self.close()
return
if self.user.block_dashboard_login:
await self.close()
return
await self.accept()
self.connected = True
@@ -62,13 +90,15 @@ class DashInfo(AsyncJsonWebsocketConsumer):
)
.count()
)
return {
"total_server_offline_count": offline_server_agents_count,
"total_workstation_offline_count": offline_workstation_agents_count,
"total_server_count": total_server_agents_count,
"total_workstation_count": total_workstation_agents_count,
"days_until_cert_expires": days_until_cert_expires(),
"action": "dashboard.agentcount",
"data": {
"total_server_offline_count": offline_server_agents_count,
"total_workstation_offline_count": offline_workstation_agents_count,
"total_server_count": total_server_agents_count,
"total_workstation_count": total_workstation_agents_count,
"days_until_cert_expires": days_until_cert_expires(),
},
}
async def send_dash_info(self):
@@ -76,3 +106,137 @@ class DashInfo(AsyncJsonWebsocketConsumer):
c = await self.get_dashboard_info()
await self.send_json(c)
await asyncio.sleep(30)
class TerminalConsumer(JsonWebsocketConsumer):
child_pid = None
fd = None
shell = None
command = ["/bin/bash"]
user = None
subprocess = None
authorized = False
connected = False
def run_command(self):
master_fd, slave_fd = pty.openpty()
self.fd = master_fd
env = os.environ.copy()
env["TERM"] = "xterm"
with subprocess.Popen( # pylint: disable=subprocess-popen-preexec-fn
self.command,
stdin=slave_fd,
stdout=slave_fd,
stderr=slave_fd,
preexec_fn=os.setsid,
env=env,
cwd=os.getenv("HOME", os.getcwd()),
) as proc:
self.subprocess = proc
self.child_pid = proc.pid
proc.wait()
# Subprocess has finished, close the websocket
# happens when process exits, either via user exiting using exit() or by error
self.subprocess = None
self.child_pid = None
if self.connected:
self.connected = False
self.close(4030)
def connect(self):
if "user" not in self.scope:
self.close(4401)
return
self.user = self.scope["user"]
if isinstance(self.user, AnonymousUser):
self.close()
return
if not self.user.is_authenticated:
self.close(4401)
return
core: CoreSettings = CoreSettings.objects.first() # type: ignore
if not core.web_terminal_enabled:
self.close(4401)
return
if self.user.block_dashboard_login or not _has_perm(
self.user, "can_use_webterm"
):
self.close(4401)
return
if self.child_pid is not None:
return
self.connected = True
self.authorized = True
self.accept()
# Daemonize the thread so it automatically dies when the main thread exits
thread = threading.Thread(target=self.run_command, daemon=True)
thread.start()
thread = threading.Thread(target=self.read_from_pty, daemon=True)
thread.start()
def read_from_pty(self):
while True:
select.select([self.fd], [], [])
output = os.read(self.fd, 1024)
if not output:
break
message = output.decode(errors="ignore")
self.send_json(
{
"action": "trmmcli.output",
"data": {"output": message, "messageId": str(uuid.uuid4())},
}
)
def resize(self, row, col, xpix=0, ypix=0):
winsize = struct.pack("HHHH", row, col, xpix, ypix)
fcntl.ioctl(self.fd, termios.TIOCSWINSZ, winsize)
def write_to_pty(self, message):
os.write(self.fd, message.encode())
def kill_pty(self):
if self.subprocess is not None:
try:
os.killpg(os.getpgid(self.child_pid), signal.SIGKILL)
except Exception as e:
logger.error(f"Failed to kill process group: {str(e)}")
finally:
self.subprocess = None
self.child_pid = None
def disconnect(self, code):
self.connected = False
self.kill_pty()
def receive_json(self, data):
if not self.authorized:
return
action = data.get("action", None)
if not action:
return
if action == "trmmcli.resize":
self.resize(data["data"]["rows"], data["data"]["cols"])
elif action == "trmmcli.input":
message = data["data"]["input"]
self.write_to_pty(message)
elif action == "trmmcli.disconnect":
self.kill_pty()
self.send_json(
{"action": "trmmcli.output", "data": {"output": "Terminal killed!"}}
)

View File

@@ -27,7 +27,7 @@ class Command(BaseCommand):
self._warning("Mesh device group:", core.mesh_device_group)
try:
token = get_auth_token(core.mesh_username, core.mesh_token)
token = get_auth_token(core.mesh_api_superuser, core.mesh_token)
except Exception as e:
self._error("Error getting auth token:")
self._error(str(e))

View File

@@ -5,6 +5,7 @@ from tacticalrmm.constants import (
AGENT_OUTAGES_LOCK,
ORPHANED_WIN_TASK_LOCK,
RESOLVE_ALERTS_LOCK,
SYNC_MESH_PERMS_TASK_LOCK,
SYNC_SCHED_TASK_LOCK,
)
@@ -18,5 +19,6 @@ class Command(BaseCommand):
ORPHANED_WIN_TASK_LOCK,
RESOLVE_ALERTS_LOCK,
SYNC_SCHED_TASK_LOCK,
SYNC_MESH_PERMS_TASK_LOCK,
):
cache.delete(key)

View File

@@ -0,0 +1,70 @@
import multiprocessing
from django.conf import settings
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = "Generate conf for gunicorn"
def handle(self, *args, **kwargs):
self.stdout.write("Creating gunicorn conf...")
cpu_count = multiprocessing.cpu_count()
# worker processes
workers = getattr(settings, "TRMM_GUNICORN_WORKERS", cpu_count * 2 + 1)
threads = getattr(settings, "TRMM_GUNICORN_THREADS", cpu_count * 2)
worker_class = getattr(settings, "TRMM_GUNICORN_WORKER_CLASS", "gthread")
max_requests = getattr(settings, "TRMM_GUNICORN_MAX_REQUESTS", 50)
max_requests_jitter = getattr(settings, "TRMM_GUNICORN_MAX_REQUESTS_JITTER", 8)
worker_connections = getattr(settings, "TRMM_GUNICORN_WORKER_CONNS", 1000)
timeout = getattr(settings, "TRMM_GUNICORN_TIMEOUT", 300)
graceful_timeout = getattr(settings, "TRMM_GUNICORN_GRACEFUL_TIMEOUT", 300)
# socket
backlog = getattr(settings, "TRMM_GUNICORN_BACKLOG", 2048)
if getattr(settings, "DOCKER_BUILD", False):
bind = "0.0.0.0:8080"
else:
bind = f"unix:{settings.BASE_DIR / 'tacticalrmm.sock'}"
# security
limit_request_line = getattr(settings, "TRMM_GUNICORN_LIMIT_REQUEST_LINE", 0)
limit_request_fields = getattr(
settings, "TRMM_GUNICORN_LIMIT_REQUEST_FIELDS", 500
)
limit_request_field_size = getattr(
settings, "TRMM_GUNICORN_LIMIT_REQUEST_FIELD_SIZE", 0
)
# server
preload_app = getattr(settings, "TRMM_GUNICORN_PRELOAD_APP", True)
# log
loglevel = getattr(settings, "TRMM_GUNICORN_LOGLEVEL", "info")
cfg = [
f"bind = '{bind}'",
f"workers = {workers}",
f"threads = {threads}",
f"worker_class = '{worker_class}'",
f"backlog = {backlog}",
f"worker_connections = {worker_connections}",
f"timeout = {timeout}",
f"graceful_timeout = {graceful_timeout}",
f"limit_request_line = {limit_request_line}",
f"limit_request_fields = {limit_request_fields}",
f"limit_request_field_size = {limit_request_field_size}",
f"max_requests = {max_requests}",
f"max_requests_jitter = {max_requests_jitter}",
f"loglevel = '{loglevel}'",
f"chdir = '{settings.BASE_DIR}'",
f"preload_app = {preload_app}",
]
with open(settings.BASE_DIR / "gunicorn_config.py", "w") as fp:
for line in cfg:
fp.write(line + "\n")
self.stdout.write("Created gunicorn conf")

View File

@@ -4,7 +4,7 @@ import os
from django.conf import settings
from django.core.management.base import BaseCommand
from tacticalrmm.helpers import get_nats_ports
from tacticalrmm.helpers import get_nats_url
class Command(BaseCommand):
@@ -20,10 +20,9 @@ class Command(BaseCommand):
else:
ssl = "disable"
nats_std_port, _ = get_nats_ports()
config = {
"key": settings.SECRET_KEY,
"natsurl": f"tls://{settings.ALLOWED_HOSTS[0]}:{nats_std_port}",
"natsurl": get_nats_url(),
"user": db["USER"],
"pass": db["PASSWORD"],
"host": db["HOST"],

View File

@@ -1,7 +1,10 @@
import configparser
import math
import multiprocessing
import os
from pathlib import Path
import psutil
from django.conf import settings
from django.core.management.base import BaseCommand
@@ -12,6 +15,27 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs):
self.stdout.write("Creating uwsgi conf...")
try:
cpu_count = multiprocessing.cpu_count()
worker_initial = 3 if cpu_count == 1 else 4
except:
worker_initial = 4
try:
ram = math.ceil(psutil.virtual_memory().total / (1024**3))
if ram <= 2:
max_requests = 15
max_workers = 6
elif ram <= 4:
max_requests = 75
max_workers = 20
else:
max_requests = 100
max_workers = 40
except:
max_requests = 50
max_workers = 10
config = configparser.ConfigParser()
if getattr(settings, "DOCKER_BUILD", False):
@@ -35,15 +59,18 @@ class Command(BaseCommand):
"buffer-size": str(getattr(settings, "UWSGI_BUFFER_SIZE", 65535)),
"vacuum": str(getattr(settings, "UWSGI_VACUUM", True)).lower(),
"die-on-term": str(getattr(settings, "UWSGI_DIE_ON_TERM", True)).lower(),
"max-requests": str(getattr(settings, "UWSGI_MAX_REQUESTS", 500)),
"max-requests": str(getattr(settings, "UWSGI_MAX_REQUESTS", max_requests)),
"disable-logging": str(
getattr(settings, "UWSGI_DISABLE_LOGGING", True)
).lower(),
"worker-reload-mercy": str(getattr(settings, "UWSGI_RELOAD_MERCY", 30)),
"cheaper-algo": "busyness",
"cheaper": str(getattr(settings, "UWSGI_CHEAPER", 4)),
"cheaper-initial": str(getattr(settings, "UWSGI_CHEAPER_INITIAL", 4)),
"workers": str(getattr(settings, "UWSGI_MAX_WORKERS", 40)),
"cheaper-step": str(getattr(settings, "UWSGI_CHEAPER_STEP", 2)),
"cheaper-initial": str(
getattr(settings, "UWSGI_CHEAPER_INITIAL", worker_initial)
),
"workers": str(getattr(settings, "UWSGI_MAX_WORKERS", max_workers)),
"cheaper-step": str(getattr(settings, "UWSGI_CHEAPER_STEP", 1)),
"cheaper-overload": str(getattr(settings, "UWSGI_CHEAPER_OVERLOAD", 3)),
"cheaper-busyness-min": str(getattr(settings, "UWSGI_BUSYNESS_MIN", 5)),
"cheaper-busyness-max": str(getattr(settings, "UWSGI_BUSYNESS_MAX", 10)),

View File

@@ -4,6 +4,7 @@ from django.conf import settings
from django.core.management.base import BaseCommand
from tacticalrmm.helpers import get_webdomain
from tacticalrmm.utils import get_certs
class Command(BaseCommand):
@@ -59,3 +60,9 @@ class Command(BaseCommand):
obj = core.mesh_token
self.stdout.write(obj)
case "certfile" | "keyfile":
crt, key = get_certs()
if kwargs["name"] == "certfile":
self.stdout.write(crt)
elif kwargs["name"] == "keyfile":
self.stdout.write(key)

View File

@@ -5,13 +5,14 @@ import websockets
from django.core.management.base import BaseCommand
from core.utils import get_mesh_ws_url
from tacticalrmm.constants import TRMM_WS_MAX_SIZE
class Command(BaseCommand):
help = "Sets up initial mesh central configuration"
async def websocket_call(self, uri):
async with websockets.connect(uri) as websocket:
async with websockets.connect(uri, max_size=TRMM_WS_MAX_SIZE) as websocket:
# Get Invitation Link
await websocket.send(
json.dumps(

View File

@@ -0,0 +1,19 @@
from django.core.management.base import BaseCommand
from meshctrl.utils import get_login_token
from core.utils import get_core_settings
class Command(BaseCommand):
help = "generate a url to login to mesh as the superuser"
def handle(self, *args, **kwargs):
core = get_core_settings()
token = get_login_token(key=core.mesh_token, user=f"user//{core.mesh_username}")
token_param = f"login={token}&"
control = f"{core.mesh_site}/?{token_param}"
self.stdout.write(self.style.SUCCESS(control))

View File

@@ -6,13 +6,14 @@ from django.conf import settings
from django.core.management.base import BaseCommand
from core.utils import get_core_settings, get_mesh_ws_url
from tacticalrmm.constants import TRMM_WS_MAX_SIZE
class Command(BaseCommand):
help = "Sets up initial mesh central configuration"
async def websocket_call(self, uri):
async with websockets.connect(uri) as websocket:
async with websockets.connect(uri, max_size=TRMM_WS_MAX_SIZE) as websocket:
# Get Device groups to see if it exists
await websocket.send(json.dumps({"action": "meshes"}))

View File

@@ -6,6 +6,8 @@ from accounts.models import User
from agents.models import Agent
from autotasks.models import AutomatedTask
from checks.models import Check, CheckHistory
from core.models import CoreSettings
from core.tasks import remove_orphaned_history_results, sync_mesh_perms_task
from scripts.models import Script
from tacticalrmm.constants import AGENT_DEFER, ScriptType
@@ -54,4 +56,22 @@ class Command(BaseCommand):
agent.save(update_fields=["goarch"])
self.stdout.write(
self.style.SUCCESS("Checking for orphaned history results...")
)
count = remove_orphaned_history_results()
if count:
self.stdout.write(
self.style.SUCCESS(f"Removed {count} orphaned history results.")
)
core = CoreSettings.objects.first()
if core.sync_mesh_with_trmm:
self.stdout.write(
self.style.SUCCESS(
"Syncing trmm users/permissions with meshcentral, this might take a long time...please wait..."
)
)
sync_mesh_perms_task()
self.stdout.write("Post update tasks finished")

View File

@@ -1,3 +1,4 @@
from django.core.management import call_command
from django.core.management.base import BaseCommand
from core.utils import clear_entire_cache
@@ -10,3 +11,7 @@ class Command(BaseCommand):
self.stdout.write(self.style.WARNING("Cleaning the cache"))
clear_entire_cache()
self.stdout.write(self.style.SUCCESS("Cache was cleared!"))
try:
call_command("fix_dupe_agent_customfields")
except:
pass

View File

@@ -8,6 +8,7 @@ from core.tasks import (
core_maintenance_tasks,
resolve_alerts_task,
resolve_pending_actions,
sync_mesh_perms_task,
sync_scheduled_tasks,
)
from winupdate.tasks import auto_approve_updates_task, check_agent_update_schedule_task
@@ -28,3 +29,4 @@ class Command(BaseCommand):
remove_orphaned_win_tasks.delay()
auto_approve_updates_task.delay()
check_agent_update_schedule_task.delay()
sync_mesh_perms_task.delay()

View File

@@ -0,0 +1,15 @@
from django.core.management.base import BaseCommand
from core.tasks import sync_mesh_perms_task
class Command(BaseCommand):
help = "Sync mesh users/perms with trmm users/perms"
def handle(self, *args, **kwargs):
self.stdout.write(
self.style.SUCCESS(
"Syncing trmm users/permissions with meshcentral, this might take a long time...please wait..."
)
)
sync_mesh_perms_task()

View File

@@ -0,0 +1,183 @@
import asyncio
import json
import re
import secrets
import string
import traceback
from typing import TYPE_CHECKING, Any
import websockets
from accounts.utils import is_superuser
from tacticalrmm.constants import TRMM_WS_MAX_SIZE
from tacticalrmm.logger import logger
if TYPE_CHECKING:
from accounts.models import User
def build_mesh_display_name(
*, first_name: str | None, last_name: str | None, company_name: str | None
) -> str:
ret = ""
if first_name:
ret += first_name
if last_name:
ret += f" {last_name}"
if ret and company_name:
ret += f" - {company_name}"
elif company_name:
ret += company_name
return ret
def has_mesh_perms(*, user: "User") -> bool:
if user.is_superuser or is_superuser(user):
return True
return user.role and getattr(user.role, "can_use_mesh")
def make_mesh_password() -> str:
alpha = string.ascii_letters + string.digits
nonalpha = "!@#$"
passwd = [secrets.choice(alpha) for _ in range(29)] + [secrets.choice(nonalpha)]
secrets.SystemRandom().shuffle(passwd)
return "".join(passwd)
def transform_trmm(obj):
ret = []
try:
for node in obj:
node_id = node["node_id"]
user_ids = [link["_id"] for link in node["links"]]
ret.append({"node_id": node_id, "user_ids": user_ids})
except Exception:
logger.debug(traceback.format_exc)
return ret
def transform_mesh(obj):
pattern = re.compile(r".*___\d+")
ret = []
try:
for _, nodes in obj.items():
for node in nodes:
node_id = node["_id"]
try:
user_ids = [
user_id
for user_id in node["links"].keys()
if pattern.match(user_id)
]
except KeyError:
# will trigger on initial sync cuz no mesh users yet
# also triggers for invalid agents after sync
pass
else:
ret.append({"node_id": node_id, "user_ids": user_ids})
except Exception:
logger.debug(traceback.format_exc)
return ret
class MeshSync:
def __init__(self, uri: str):
self.uri = uri
self.mesh_users = self.get_trmm_mesh_users() # full list
def mesh_action(
self, *, payload: dict[str, Any], wait=True
) -> dict[str, Any] | None:
async def _do(payload):
async with websockets.connect(self.uri, max_size=TRMM_WS_MAX_SIZE) as ws:
await ws.send(json.dumps(payload))
if wait:
while 1:
try:
message = await asyncio.wait_for(ws.recv(), 120)
r = json.loads(message)
if r["action"] == payload["action"]:
return r
except asyncio.TimeoutError:
logger.error("Timeout reached.")
return None
else:
return None
payload["responseid"] = "meshctrl"
logger.debug(payload)
return asyncio.run(_do(payload))
def get_unique_mesh_users(
self, trmm_agents_list: list[dict[str, Any]]
) -> list[str]:
userids = [i["links"] for i in trmm_agents_list]
all_ids = [item["_id"] for sublist in userids for item in sublist]
return list(set(all_ids))
def get_trmm_mesh_users(self):
payload = {"action": "users"}
ret = {
i["_id"]: i
for i in self.mesh_action(payload=payload, wait=True)["users"]
if re.search(r".*___\d+", i["_id"])
}
return ret
def add_users_to_node(self, *, node_id: str, user_ids: list[str]):
payload = {
"action": "adddeviceuser",
"nodeid": node_id,
"usernames": [s.replace("user//", "") for s in user_ids],
"rights": 4088024,
"remove": False,
}
self.mesh_action(payload=payload, wait=False)
def delete_users_from_node(self, *, node_id: str, user_ids: list[str]):
payload = {
"action": "adddeviceuser",
"nodeid": node_id,
"userids": user_ids,
"rights": 0,
"remove": True,
}
self.mesh_action(payload=payload, wait=False)
def update_mesh_displayname(self, *, user_info: dict[str, Any]) -> None:
payload = {
"action": "edituser",
"id": user_info["_id"],
"realname": user_info["full_name"],
}
self.mesh_action(payload=payload, wait=False)
def add_user_to_mesh(self, *, user_info: dict[str, Any]) -> None:
payload = {
"action": "adduser",
"username": user_info["username"],
"email": user_info["email"],
"pass": make_mesh_password(),
"resetNextLogin": False,
"randomPassword": False,
"removeEvents": False,
"emailVerified": True,
}
self.mesh_action(payload=payload, wait=False)
if user_info["full_name"]:
self.update_mesh_displayname(user_info=user_info)
def delete_user_from_mesh(self, *, mesh_user_id: str) -> None:
payload = {
"action": "deleteuser",
"userid": mesh_user_id,
}
self.mesh_action(payload=payload, wait=False)

View File

@@ -0,0 +1,632 @@
# Generated by Django 4.2.7 on 2023-11-09 19:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0037_coresettings_open_ai_model_and_more"),
]
operations = [
migrations.AlterField(
model_name="coresettings",
name="default_time_zone",
field=models.CharField(
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"),
],
default="America/Los_Angeles",
max_length=255,
),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.2.9 on 2024-01-26 00:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0038_alter_coresettings_default_time_zone"),
]
operations = [
migrations.AddField(
model_name="coresettings",
name="smtp_from_name",
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.9 on 2024-01-28 02:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0039_coresettings_smtp_from_name"),
]
operations = [
migrations.AddField(
model_name="customfield",
name="hide_in_summary",
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.9 on 2024-01-28 03:01
from django.db import migrations
def update_hide_in_summary(apps, schema_editor):
CustomField = apps.get_model("core", "CustomField")
for field in CustomField.objects.filter(hide_in_ui=True):
field.hide_in_summary = True
field.save(update_fields=["hide_in_summary"])
class Migration(migrations.Migration):
dependencies = [
("core", "0040_customfield_hide_in_summary"),
]
operations = [migrations.RunPython(update_hide_in_summary)]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.10 on 2024-02-20 02:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0041_auto_20240128_0301"),
]
operations = [
migrations.AddField(
model_name="coresettings",
name="mesh_company_name",
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.10 on 2024-02-23 19:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0042_coresettings_mesh_company_name"),
]
operations = [
migrations.AddField(
model_name="coresettings",
name="sync_mesh_with_trmm",
field=models.BooleanField(default=True),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.2.11 on 2024-03-12 05:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("core", "0043_coresettings_sync_mesh_with_trmm"),
]
operations = [
migrations.RemoveField(
model_name="coresettings",
name="mesh_disable_auto_login",
),
]

View File

@@ -0,0 +1,65 @@
# Generated by Django 4.2.13 on 2024-06-28 20:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0044_remove_coresettings_mesh_disable_auto_login"),
]
operations = [
migrations.AddField(
model_name="coresettings",
name="enable_server_scripts",
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name="coresettings",
name="enable_server_webterminal",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="urlaction",
name="action_type",
field=models.CharField(
choices=[("web", "Web"), ("rest", "Rest")], default="web", max_length=10
),
),
migrations.AddField(
model_name="urlaction",
name="rest_body",
field=models.TextField(blank=True, default="", null=True),
),
migrations.AddField(
model_name="urlaction",
name="rest_headers",
field=models.TextField(blank=True, default="", null=True),
),
migrations.AddField(
model_name="urlaction",
name="rest_method",
field=models.CharField(
choices=[
("get", "Get"),
("post", "Post"),
("put", "Put"),
("delete", "Delete"),
("patch", "Patch"),
],
default="post",
max_length=10,
),
),
migrations.AlterField(
model_name="urlaction",
name="desc",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="urlaction",
name="name",
field=models.CharField(max_length=255),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.13 on 2024-07-05 19:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0045_coresettings_enable_server_scripts_and_more"),
]
operations = [
migrations.AddField(
model_name="coresettings",
name="notify_on_info_alerts",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="coresettings",
name="notify_on_warning_alerts",
field=models.BooleanField(default=False),
),
]

Some files were not shown because too many files have changed in this diff Show More