Compare commits

...

339 Commits

Author SHA1 Message Date
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
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
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
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
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
wh1te909
2baf119299 Release 0.16.0 2023-07-05 17:20:19 +00:00
wh1te909
6fe4c5a2ed bump versions 2023-07-05 02:03:34 +00:00
wh1te909
4abc8e41d8 only chown if exists 2023-07-05 02:02:58 +00:00
wh1te909
af694f1ce9 arm64/deb12/ubuntu22 support, remove mongo, postgres 15 and node 18 2023-07-04 08:53:06 +00:00
wh1te909
7c3a5fcb83 update reqs 2023-07-03 23:35:45 +00:00
wh1te909
57f64b18c6 bump web ver [skip ci] 2023-06-30 20:34:12 +00:00
wh1te909
4cccc7c2f8 fix escape error 2023-06-27 23:00:12 +00:00
wh1te909
903a2d6a6e flaky test 2023-06-25 02:16:19 +00:00
wh1te909
34c674487a update reqs 2023-06-24 22:20:59 +00:00
Dan
d15a8c5af3 Merge pull request #1543 from dinger1986/develop
Update troubleshoot_server.sh
2023-06-23 20:23:51 -07:00
dinger1986
3e0dec9383 Update troubleshoot_server.sh 2023-06-24 00:02:41 +01:00
wh1te909
8b810aad81 add arm64 bin [skip ci] 2023-06-19 06:31:50 +00:00
wh1te909
e676bcb4f4 update reqs 2023-06-19 05:44:00 +00:00
wh1te909
a7aed77764 python 3.11.4 2023-06-19 05:29:10 +00:00
wh1te909
88875c0257 update reqs 2023-06-19 05:25:52 +00:00
wh1te909
f711a0c91a update reqs 2023-06-05 05:40:36 +00:00
wh1te909
d8a076cc6e update demo 2023-06-04 01:59:46 +00:00
wh1te909
c900831ee9 back to dev 2023-06-04 01:59:25 +00:00
wh1te909
76a30c7ef4 Release 0.15.12 2023-05-31 00:19:00 +00:00
wh1te909
ae5d0b1d81 bump versions 2023-05-30 23:42:49 +00:00
wh1te909
cd5e87be34 update reqs 2023-05-30 18:33:31 +00:00
wh1te909
3e967f58d2 ansible fixes [skip ci] 2023-05-29 07:51:16 +00:00
wh1te909
1ea005ba7e add cert expiring soon indicator #722 2023-05-27 23:34:51 +00:00
wh1te909
092772ba90 add wake-on-lan closes #1180 2023-05-27 00:45:30 +00:00
wh1te909
b959854a76 add serial number to search closes #1355 2023-05-26 22:55:44 +00:00
wh1te909
8ccb1ebe4f make cmd placeholder text customizable amidaware/tacticalrmm-web#5 2023-05-26 22:16:26 +00:00
wh1te909
91b3be6467 update reqs 2023-05-26 21:29:19 +00:00
wh1te909
d79d5feacc update requests 2023-05-25 20:56:04 +00:00
wh1te909
5cc78ef9d5 allow customizing dashboard colors #1514 2023-05-25 20:41:06 +00:00
wh1te909
8639cd5a72 back to django 4.1 2023-05-25 20:36:20 +00:00
wh1te909
021ddc17e7 remove mypy 2023-05-25 20:35:35 +00:00
wh1te909
ee47b8d004 update nats server 2023-05-23 04:54:01 +00:00
wh1te909
55d267c935 remove ignore 2023-05-23 04:48:29 +00:00
Dan
0fd0b9128d Merge pull request #1511 from dinger1986/develop
Updates to the Update script and backup script
2023-05-18 22:43:59 -07:00
wh1te909
d9cf505b50 change default to mixed closes #1513 2023-05-17 07:13:09 +00:00
wh1te909
6079332dda remove redundant ws close 2023-05-17 07:07:52 +00:00
wh1te909
929ec20365 remove deprecated django func 2023-05-14 06:30:25 +00:00
wh1te909
d0cad3055f comment flaky test for now 2023-05-14 06:22:00 +00:00
wh1te909
4974a13bc0 test django 4.2 2023-05-14 05:50:36 +00:00
wh1te909
bd048df225 update demo 2023-05-14 05:47:50 +00:00
dinger1986
ed83cbd574 Update update.sh 2023-05-11 20:02:25 +01:00
dinger1986
7230207853 Update backup.sh 2023-05-11 20:00:49 +01:00
dinger1986
1ead8a72ab Update update.sh 2023-05-11 19:59:39 +01:00
dinger1986
36a2e9d931 Update backup.sh 2023-05-11 19:58:29 +01:00
dinger1986
0f147a5518 Update backup.sh 2023-05-11 19:54:07 +01:00
dinger1986
fce511a18b Update backup.sh 2023-05-11 19:45:41 +01:00
wh1te909
64bb61b009 back to dev [skip ci] 2023-05-11 02:29:41 +00:00
wh1te909
c6eefec5ce Release 0.15.11 2023-05-11 02:25:38 +00:00
wh1te909
4c6f829c92 bump versions 2023-05-11 02:16:45 +00:00
wh1te909
8c5cdd2acb back to dev [skip ci] 2023-05-10 20:04:26 +00:00
wh1te909
e5357599c4 Release 0.15.10 2023-05-10 19:57:13 +00:00
wh1te909
3800f19966 bump versions [skip ci] 2023-05-10 19:56:20 +00:00
wh1te909
7336f84a4b update reqs 2023-05-08 22:01:45 +00:00
wh1te909
7bf4a5b2b5 begin pytz removal 2023-05-08 21:12:08 +00:00
wh1te909
43a7b97218 update reqs 2023-05-07 02:22:28 +00:00
wh1te909
9f95c57a09 fix timezone logic bug for run once tasks 2023-04-23 21:47:01 +00:00
wh1te909
8f6056ae66 remove deprecated package 2023-04-13 20:52:23 +00:00
wh1te909
9bcac6b10e improve superseded update detection fixes #647 fixes #820 2023-04-13 20:35:03 +00:00
wh1te909
86318e1b7d async rework of bulk script and more async refactor 2023-04-11 07:14:16 +00:00
wh1te909
a8a1458833 update demo 2023-04-11 06:57:13 +00:00
wh1te909
942c1e2dfe catch exception and code cleanup 2023-04-11 06:56:35 +00:00
sadnub
a6b6814eae Merge pull request #1479 from sadnub/develop
open ai integration
2023-04-10 19:06:14 -04:00
sadnub
0af95aa9b1 add error handling 2023-04-10 18:57:44 -04:00
sadnub
b4b9256867 open ai integration 2023-04-09 22:36:57 -04:00
wh1te909
a6f1281a98 back to dev [skip ci] 2023-04-09 04:52:00 +00:00
163 changed files with 9809 additions and 1320 deletions

View File

@@ -1,11 +1,11 @@
# pulls community scripts from git repo
FROM python:3.11.3-slim AS GET_SCRIPTS_STAGE
FROM python:3.11.6-slim AS GET_SCRIPTS_STAGE
RUN apt-get update && \
apt-get install -y --no-install-recommends git && \
git clone https://github.com/amidaware/community-scripts.git /community-scripts
FROM python:3.11.3-slim
FROM python:3.11.6-slim
ENV TACTICAL_DIR /opt/tactical
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
@@ -18,7 +18,7 @@ ENV PYTHONUNBUFFERED=1
EXPOSE 8000 8383 8005
RUN apt-get update && \
apt-get install -y build-essential
apt-get install -y build-essential weasyprint
RUN groupadd -g 1000 tactical && \
useradd -u 1000 -g 1000 tactical
@@ -27,7 +27,7 @@ RUN groupadd -g 1000 tactical && \
COPY --from=GET_SCRIPTS_STAGE /community-scripts /community-scripts
# Copy dev python reqs
COPY .devcontainer/requirements.txt /
COPY .devcontainer/requirements.txt /
# Copy docker entrypoint.sh
COPY .devcontainer/entrypoint.sh /

View File

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

View File

@@ -78,6 +78,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 +98,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 +107,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 +133,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 +142,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,14 +14,14 @@ jobs:
name: Tests
strategy:
matrix:
python-version: ["3.11.3"]
python-version: ["3.11.6"]
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

24
.vscode/settings.json vendored
View File

@@ -1,10 +1,7 @@
{
"python.defaultInterpreterPath": "api/env/bin/python",
"python.languageServer": "Pylance",
"python.analysis.extraPaths": [
"api/tacticalrmm",
"api/env"
],
"python.analysis.extraPaths": ["api/tacticalrmm", "api/env"],
"python.analysis.diagnosticSeverityOverrides": {
"reportUnusedImport": "error",
"reportDuplicateImport": "error",
@@ -24,11 +21,11 @@
".vscode/*.py",
"**env/**"
],
"python.formatting.provider": "black",
"mypy.targets": [
"api/tacticalrmm"
],
"mypy.runUsingActiveInterpreter": true,
"python.formatting.provider": "none",
//"mypy.targets": [
//"api/tacticalrmm"
//],
//"mypy.runUsingActiveInterpreter": true,
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
@@ -61,18 +58,21 @@
"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": {
"usePlaceholders": true,
"completeUnimported": true,
"staticcheck": true
},
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
}
}
}

View File

@@ -1,7 +1,7 @@
---
user: "tactical"
python_ver: "3.11.3"
go_ver: "1.20.3"
python_ver: "3.11.6"
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

@@ -1,4 +1,13 @@
---
- name: Append subdomains to hosts
tags: hosts
become: yes
ansible.builtin.lineinfile:
path: /etc/hosts
backrefs: yes
regexp: '^(127\.0\.1\.1 .*)$'
line: "\\1 {{ api }} {{ mesh }} {{ rmm }}"
- name: set mouse mode for vim
tags: vim
become: yes
@@ -32,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
@@ -102,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
@@ -119,7 +132,7 @@
tags: postgres
become: yes
ansible.builtin.apt:
pkg: postgresql-14
pkg: postgresql-15
state: present
update_cache: yes
@@ -131,7 +144,7 @@
enabled: yes
state: started
- name: setup database
- name: setup trmm database
tags: postgres
become: yes
become_user: postgres
@@ -144,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
@@ -193,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
@@ -202,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 }}"
@@ -218,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"
@@ -305,8 +335,8 @@
- 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"
@@ -382,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 }}"
@@ -473,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

@@ -2,10 +2,6 @@ SECRET_KEY = "{{ django_secret }}"
DEBUG = True
ALLOWED_HOSTS = ['{{ api }}']
ADMIN_URL = "admin/"
CORS_ORIGIN_WHITELIST = [
"http://{{ rmm }}:8080",
"https://{{ rmm }}:8080",
]
CORS_ORIGIN_ALLOW_ALL = True
DATABASES = {
'default': {
@@ -19,7 +15,7 @@ DATABASES = {
}
REDIS_HOST = "localhost"
ADMIN_ENABLED = True
CERT_FILE = "{{ fullchain_src }}"
KEY_FILE = "{{ privkey_src }}"
CERT_FILE = "{{ fullchain_dest }}"
KEY_FILE = "{{ privkey_dest }}"
MESH_USERNAME = "{{ mesh_user }}"
MESH_SITE = "https://{{ mesh }}"

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

@@ -1,4 +1,4 @@
DEV_URL = "http://{{ api }}:8000"
DEV_HOST = "{{ rmm }}"
DEV_HOST = "0.0.0.0"
DEV_PORT = "8080"
USE_HTTPS = false

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

@@ -3,6 +3,7 @@ import uuid
from django.core.management.base import BaseCommand
from accounts.models import User
from tacticalrmm.helpers import make_random_password
class Command(BaseCommand):
@@ -17,7 +18,7 @@ class Command(BaseCommand):
User.objects.create_user(
username=uuid.uuid4().hex,
is_installer_user=True,
password=User.objects.make_random_password(60),
password=make_random_password(len=60),
block_dashboard_login=True,
)
self.stdout.write("Installer user has been created")

View File

@@ -0,0 +1,25 @@
# Generated by Django 4.2.1 on 2023-05-17 07:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("accounts", "0031_user_date_format"),
]
operations = [
migrations.AlterField(
model_name="user",
name="default_agent_tbl_tab",
field=models.CharField(
choices=[
("server", "Servers"),
("workstation", "Workstations"),
("mixed", "Mixed"),
],
default="mixed",
max_length=50,
),
),
]

View File

@@ -0,0 +1,32 @@
# Generated by Django 4.2.1 on 2023-05-23 04:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("accounts", "0032_alter_user_default_agent_tbl_tab"),
]
operations = [
migrations.AddField(
model_name="user",
name="dash_info_color",
field=models.CharField(default="info", max_length=255),
),
migrations.AddField(
model_name="user",
name="dash_negative_color",
field=models.CharField(default="negative", max_length=255),
),
migrations.AddField(
model_name="user",
name="dash_positive_color",
field=models.CharField(default="positive", max_length=255),
),
migrations.AddField(
model_name="user",
name="dash_warning_color",
field=models.CharField(default="warning", max_length=255),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.1.9 on 2023-05-26 23:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("accounts", "0033_user_dash_info_color_user_dash_negative_color_and_more"),
]
operations = [
migrations.AddField(
model_name="role",
name="can_send_wol",
field=models.BooleanField(default=False),
),
]

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

@@ -31,7 +31,7 @@ class User(AbstractUser, BaseAuditModel):
on_delete=models.SET_NULL,
)
default_agent_tbl_tab = models.CharField(
max_length=50, choices=AgentTableTabs.choices, default=AgentTableTabs.SERVER
max_length=50, choices=AgentTableTabs.choices, default=AgentTableTabs.MIXED
)
agents_per_page = models.PositiveIntegerField(default=50) # not currently used
client_tree_sort = models.CharField(
@@ -39,6 +39,10 @@ class User(AbstractUser, BaseAuditModel):
)
client_tree_splitter = models.PositiveIntegerField(default=11)
loading_bar_color = models.CharField(max_length=255, default="red")
dash_info_color = models.CharField(max_length=255, default="info")
dash_positive_color = models.CharField(max_length=255, default="positive")
dash_negative_color = models.CharField(max_length=255, default="negative")
dash_warning_color = models.CharField(max_length=255, default="warning")
clear_search_when_switching = models.BooleanField(default=True)
date_format = models.CharField(max_length=30, blank=True, null=True)
is_installer_user = models.BooleanField(default=False)
@@ -91,7 +95,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)
@@ -105,6 +108,7 @@ class Role(BaseAuditModel):
can_run_bulk = models.BooleanField(default=False)
can_recover_agents = models.BooleanField(default=False)
can_list_agent_history = models.BooleanField(default=False)
can_send_wol = models.BooleanField(default=False)
# core
can_list_notes = models.BooleanField(default=False)
@@ -181,6 +185,10 @@ 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

View File

@@ -5,6 +5,8 @@ from rest_framework.serializers import (
SerializerMethodField,
)
from tacticalrmm.helpers import get_webdomain
from .models import APIKey, Role, User
@@ -20,6 +22,10 @@ class UserUISerializer(ModelSerializer):
"client_tree_sort",
"client_tree_splitter",
"loading_bar_color",
"dash_info_color",
"dash_positive_color",
"dash_negative_color",
"dash_warning_color",
"clear_search_when_switching",
"block_dashboard_login",
"date_format",
@@ -57,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

@@ -3,13 +3,14 @@ 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 logs.models import AuditLog
from tacticalrmm.helpers import notify_error
@@ -22,7 +23,6 @@ from .serializers import (
UserSerializer,
UserUISerializer,
)
from accounts.utils import is_root_user
class CheckCreds(KnoxLoginView):
@@ -79,9 +79,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}

View File

@@ -47,7 +47,7 @@ class SendCMD(AsyncJsonWebsocketConsumer):
await self.send_json({"ret": ret})
async def disconnect(self, _):
await self.close()
pass
def _has_perm(self, perm: str) -> bool:
if self.user.is_superuser or (

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

@@ -1,14 +1,13 @@
import asyncio
import logging
import re
from collections import Counter
from contextlib import suppress
from distutils.version import LooseVersion
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union, cast
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
@@ -16,6 +15,7 @@ from django.db import models
from django.utils import timezone as djangotime
from nats.errors import TimeoutError
from packaging import version as pyver
from packaging.version import Version as LooseVersion
from agents.utils import get_agent_url
from checks.models import CheckResult
@@ -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"]
@@ -408,6 +439,19 @@ class Agent(BaseAuditModel):
except:
return ["unknown disk"]
@property
def serial_number(self) -> str:
if self.is_posix:
try:
return self.wmi_detail["serialnumber"]
except:
return ""
try:
return self.wmi_detail["bios"][0][0]["SerialNumber"]
except:
return ""
@classmethod
def online_agents(cls, min_version: str = "") -> "List[Agent]":
if min_version:
@@ -495,24 +539,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:
@@ -546,6 +598,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",
@@ -556,7 +609,7 @@ class Agent(BaseAuditModel):
"shell": script.shell,
},
"run_as_user": run_as_user,
"env_vars": env_vars,
"env_vars": parsed_env_vars,
}
if history_pk != 0:
@@ -787,9 +840,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:
@@ -811,9 +861,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
@@ -876,8 +924,10 @@ class Agent(BaseAuditModel):
# extract the version from the title and sort from oldest to newest
# skip if no version info is available therefore nothing to parse
try:
matches = r"(Version|Versão)"
pattern = r"\(" + matches + r"(.*?)\)"
vers = [
re.search(r"\(Version(.*?)\)", i).group(1).strip()
re.search(pattern, i, flags=re.IGNORECASE).group(2).strip()
for i in titles
]
sorted_vers = sorted(vers, key=LooseVersion)
@@ -999,6 +1049,9 @@ class AgentCustomField(models.Model):
default=list,
)
class Meta:
unique_together = (("agent", "field"),)
def __str__(self) -> str:
return self.field.name

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(
@@ -122,3 +115,13 @@ class AgentHistoryPerms(permissions.BasePermission):
)
return _has_perm(r, "can_list_agent_history")
class AgentWOLPerms(permissions.BasePermission):
def has_permission(self, r, view) -> bool:
if "agent_id" in view.kwargs.keys():
return _has_perm(r, "can_send_wol") and _has_perm_on_agent(
r.user, view.kwargs["agent_id"]
)
return _has_perm(r, "can_send_wol")

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
@@ -95,6 +94,7 @@ class AgentTableSerializer(serializers.ModelSerializer):
local_ips = serializers.ReadOnlyField()
make_model = serializers.ReadOnlyField()
physical_disks = serializers.ReadOnlyField()
serial_number = serializers.ReadOnlyField()
custom_fields = AgentCustomFieldSerializer(many=True, read_only=True)
def get_alert_template(self, obj):
@@ -155,6 +155,7 @@ class AgentTableSerializer(serializers.ModelSerializer):
"make_model",
"physical_disks",
"custom_fields",
"serial_number",
]
depth = 2

View File

@@ -3,8 +3,8 @@ import os
from itertools import cycle
from typing import TYPE_CHECKING
from unittest.mock import patch
from zoneinfo import ZoneInfo
import pytz
from django.conf import settings
from django.utils import timezone as djangotime
from model_bakery import baker
@@ -866,7 +866,7 @@ class TestAgentViews(TacticalTestCase):
# test pulling data
r = self.client.get(url, format="json")
ctx = {"default_tz": pytz.timezone("America/Los_Angeles")}
ctx = {"default_tz": ZoneInfo("America/Los_Angeles")}
data = AgentHistorySerializer(history, many=True, context=ctx).data
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, data) # type:ignore
@@ -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

@@ -43,4 +43,5 @@ urlpatterns = [
path("installer/", views.install_agent),
path("bulkrecovery/", views.bulk_agent_recovery),
path("scripthistory/", views.ScriptRunHistory.as_view()),
path("<agent:agent_id>/wol/", views.wol),
]

View File

@@ -6,19 +6,12 @@ import time
from io import StringIO
from pathlib import Path
from core.utils import (
get_core_settings,
get_mesh_ws_url,
remove_mesh_agent,
token_is_valid,
)
from django.conf import settings
from django.db.models import Exists, OuterRef, Prefetch, Q
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone as djangotime
from django.utils.dateparse import parse_datetime
from logs.models import AuditLog, DebugLog, PendingAction
from meshctrl.utils import get_login_token
from packaging import version as pyver
from rest_framework import serializers
@@ -27,8 +20,17 @@ from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from core.utils import (
get_core_settings,
get_mesh_ws_url,
remove_mesh_agent,
token_is_valid,
wake_on_lan,
)
from logs.models import AuditLog, DebugLog, PendingAction
from scripts.models import Script
from scripts.tasks import handle_bulk_command_task, handle_bulk_script_task
from scripts.tasks import bulk_command_task, bulk_script_task
from tacticalrmm.constants import (
AGENT_DEFER,
AGENT_STATUS_OFFLINE,
@@ -58,11 +60,11 @@ from .permissions import (
AgentHistoryPerms,
AgentNotesPerms,
AgentPerms,
AgentWOLPerms,
EvtLogPerms,
InstallAgentPerms,
ManageProcPerms,
MeshPerms,
PingAgentPerms,
RebootAgentPerms,
RecoverAgentPerms,
RunBulkPerms,
@@ -399,7 +401,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
@@ -561,10 +563,18 @@ class Reboot(APIView):
@api_view(["POST"])
@permission_classes([IsAuthenticated, InstallAgentPerms])
def install_agent(request):
from knox.models import AuthToken
from accounts.models import User
from agents.utils import get_agent_url
from core.utils import token_is_valid
from knox.models import AuthToken
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
@@ -668,6 +678,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}")
@@ -676,6 +689,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
@@ -947,7 +962,7 @@ def bulk(request):
agents: list[int] = [agent.pk for agent in q]
if not agents:
return notify_error("No agents where found meeting the selected criteria")
return notify_error("No agents were found meeting the selected criteria")
AuditLog.audit_bulk_action(
request.user,
@@ -962,27 +977,29 @@ def bulk(request):
else:
shell = request.data["shell"]
handle_bulk_command_task.delay(
agents,
request.data["cmd"],
shell,
request.data["timeout"],
request.user.username[:50],
request.data["run_as_user"],
bulk_command_task.delay(
agent_pks=agents,
cmd=request.data["cmd"],
shell=shell,
timeout=request.data["timeout"],
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")
elif request.data["mode"] == "script":
script = get_object_or_404(Script, pk=request.data["script"])
handle_bulk_script_task.delay(
script.pk,
agents,
request.data["args"],
request.data["timeout"],
request.user.username[:50],
request.data["run_as_user"],
request.data["env_vars"],
bulk_script_task.delay(
script_pk=script.pk,
agent_pks=agents,
args=request.data["args"],
timeout=request.data["timeout"],
username=request.user.username[:50],
run_as_user=request.data["run_as_user"],
env_vars=request.data["env_vars"],
)
return Response(f"{script.name} will now be run on {len(agents)} agents")
elif request.data["mode"] == "patch":
@@ -1157,3 +1174,18 @@ class ScriptRunHistory(APIView):
ret = self.OutputSerializer(hists, many=True).data
return Response(ret)
@api_view(["POST"])
@permission_classes([IsAuthenticated, AgentWOLPerms])
def wol(request, agent_id):
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER),
agent_id=agent_id,
)
try:
uri = get_mesh_ws_url()
asyncio.run(wake_on_lan(uri=uri, mesh_node_id=agent.mesh_node_id))
except Exception as e:
return notify_error(str(e))
return Response(f"Wake-on-LAN sent to {agent.hostname}")

View File

@@ -169,15 +169,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,
),
@@ -627,8 +629,7 @@ class Alert(models.Model):
pattern = re.compile(".*\\{\\{alert\\.(.*)\\}\\}.*")
for arg in args:
match = pattern.match(arg)
if match:
if match := pattern.match(arg):
name = match.group(1)
# check if attr exists and isn't a function
@@ -639,6 +640,8 @@ class Alert(models.Model):
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

View File

@@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import timedelta
from itertools import cycle
from unittest.mock import patch
@@ -28,6 +28,7 @@ class TestAlertsViews(TacticalTestCase):
self.authenticate()
self.setup_coresettings()
"""
def test_get_alerts(self):
url = "/alerts/"
@@ -39,14 +40,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 +56,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 +64,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 +121,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",
@@ -363,7 +365,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 +373,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,
)

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)
.filter(resolved=False, snoozed=False, hidden=False)
.order_by("alert_time")[: int(request.data["top"])]
)
count = (
Alert.objects.filter_by_role(request.user)
.filter(resolved=False, snoozed=False, hidden=False)
.count()
)
return Response(
{
"alerts_count": count,

View File

@@ -41,7 +41,7 @@ from tacticalrmm.constants import (
MeshAgentIdent,
PAStatus,
)
from tacticalrmm.helpers import notify_error
from tacticalrmm.helpers import make_random_password, notify_error
from tacticalrmm.utils import reload_nats
from winupdate.models import WinUpdate, WinUpdatePolicy
@@ -457,7 +457,7 @@ class NewAgent(APIView):
user = User.objects.create_user( # type: ignore
username=request.data["agent_id"],
agent=agent,
password=User.objects.make_random_password(60), # type: ignore
password=make_random_password(len=60),
)
token = Token.objects.create(user=user)

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

@@ -1,10 +1,10 @@
import asyncio
import logging
import random
import string
from contextlib import suppress
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
import pytz
from django.core.cache import cache
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
@@ -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,
@@ -46,6 +45,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()
@@ -209,6 +211,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 +241,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
< djangotime.now().astimezone(pytz.timezone(agent.timezone))
):
self.run_time_date = (
djangotime.now() + djangotime.timedelta(minutes=5)
).astimezone(pytz.timezone(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 +338,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 +374,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 +420,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:

View File

@@ -252,7 +252,11 @@ 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,
),
}
)
if actions_to_remove:

View File

@@ -149,6 +149,7 @@ def remove_orphaned_win_tasks(self) -> str:
for item in items
]
await asyncio.gather(*tasks)
await nc.flush()
await nc.close()
asyncio.run(_run())

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

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

@@ -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,10 +1,7 @@
import asyncio
from datetime import datetime as dt
from typing import TYPE_CHECKING
import msgpack
import nats
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
@@ -16,17 +13,16 @@ 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.helpers import notify_error, setup_nats_options
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
from tacticalrmm.permissions import _has_perm_on_agent
from .models import Check, CheckHistory, CheckResult
from .permissions import BulkRunChecksPerms, ChecksPerms, RunChecksPerms
from .serializers import CheckHistorySerializer, CheckSerializer
if TYPE_CHECKING:
from nats.aio.client import Client as NATSClient
class GetAddChecks(APIView):
permission_classes = [IsAuthenticated, ChecksPerms]
@@ -126,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]
@@ -189,29 +224,22 @@ def bulk_run_checks(request, target, pk):
case "site":
q = Q(site__id=pk)
agents = list(
agent_ids = list(
Agent.objects.only("agent_id", "site")
.filter(q)
.values_list("agent_id", flat=True)
)
if not agents:
if not agent_ids:
return notify_error("No agents matched query")
async def _run_check(nc: "NATSClient", sub) -> None:
await nc.publish(subject=sub, payload=msgpack.dumps({"func": "runchecks"}))
payload = {"func": "runchecks"}
items = [(agent_id, payload) for agent_id in agent_ids]
async def _run() -> None:
opts = setup_nats_options()
try:
nc = await nats.connect(**opts)
except Exception as e:
return notify_error(str(e))
try:
asyncio.run(abulk_nats_command(items=items))
except NatsDown as e:
return notify_error(str(e))
tasks = [_run_check(nc=nc, sub=agent) for agent in agents]
await asyncio.gather(*tasks)
await nc.close()
asyncio.run(_run())
ret = f"Checks will now be run on {len(agents)} agents"
ret = f"Checks will now be run on {len(agent_ids)} agents"
return Response(ret)

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
@@ -288,6 +289,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 +347,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'
@@ -37,15 +50,15 @@ 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() {
@@ -67,8 +80,14 @@ RemoveOldAgent() {
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 +99,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 +118,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
@@ -120,6 +137,19 @@ if [ $# -ne 0 ] && [ $1 == 'uninstall' ]; then
exit 0
fi
while [[ "$#" -gt 0 ]]; do
case $1 in
--debug) DEBUG=1 ;;
--insecure) INSECURE=1 ;;
--nomesh) NOMESH=1 ;;
*)
echo "ERROR: Unknown parameter: $1"
exit 1
;;
esac
shift
done
RemoveOldAgent
echo "Downloading tactical agent..."
@@ -132,7 +162,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 +180,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 +219,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

@@ -9,6 +9,7 @@ from django.utils import timezone as djangotime
from agents.models import Agent
from tacticalrmm.constants import AgentMonType
from tacticalrmm.helpers import days_until_cert_expires
class DashInfo(AsyncJsonWebsocketConsumer):
@@ -27,7 +28,6 @@ class DashInfo(AsyncJsonWebsocketConsumer):
self.dash_info.cancel()
self.connected = False
await self.close()
async def receive_json(self, payload, **kwargs):
pass
@@ -68,6 +68,7 @@ class DashInfo(AsyncJsonWebsocketConsumer):
"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):

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 = 30
max_workers = 10
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

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

@@ -0,0 +1,23 @@
# Generated by Django 4.2 on 2023-04-09 15:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0036_alter_coresettings_default_time_zone'),
]
operations = [
migrations.AddField(
model_name='coresettings',
name='open_ai_model',
field=models.CharField(blank=True, default='gpt-3.5-turbo', max_length=255),
),
migrations.AddField(
model_name='coresettings',
name='open_ai_token',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

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

@@ -1,9 +1,9 @@
import smtplib
from contextlib import suppress
from email.message import EmailMessage
from email.headerregistry import Address
from typing import TYPE_CHECKING, List, Optional, cast
import pytz
import requests
from django.conf import settings
from django.contrib.postgres.fields import ArrayField
@@ -15,6 +15,7 @@ from twilio.rest import Client as TwClient
from logs.models import BaseAuditModel, DebugLog
from tacticalrmm.constants import (
ALL_TIMEZONES,
CORESETTINGS_CACHE_KEY,
CustomFieldModel,
CustomFieldType,
@@ -24,7 +25,7 @@ from tacticalrmm.constants import (
if TYPE_CHECKING:
from alerts.models import AlertTemplate
TZ_CHOICES = [(_, _) for _ in pytz.all_timezones]
TZ_CHOICES = [(_, _) for _ in ALL_TIMEZONES]
class CoreSettings(BaseAuditModel):
@@ -44,6 +45,7 @@ class CoreSettings(BaseAuditModel):
smtp_from_email = models.CharField(
max_length=255, blank=True, default="from@example.com"
)
smtp_from_name = models.CharField(max_length=255, null=True, blank=True)
smtp_host = models.CharField(max_length=255, blank=True, default="smtp.gmail.com")
smtp_host_user = models.CharField(
max_length=255, blank=True, default="admin@example.com"
@@ -98,6 +100,10 @@ class CoreSettings(BaseAuditModel):
date_format = models.CharField(
max_length=30, blank=True, default="MMM-DD-YYYY - HH:mm"
)
open_ai_token = models.CharField(max_length=255, null=True, blank=True)
open_ai_model = models.CharField(
max_length=255, blank=True, default="gpt-3.5-turbo"
)
def save(self, *args, **kwargs) -> None:
from alerts.tasks import cache_agents_alert_template
@@ -203,7 +209,14 @@ class CoreSettings(BaseAuditModel):
try:
msg = EmailMessage()
msg["Subject"] = subject
msg["From"] = from_address
if self.smtp_from_name:
msg["From"] = Address(
display_name=self.smtp_from_name, addr_spec=from_address
)
else:
msg["From"] = from_address
msg["To"] = email_recipients
msg.set_content(body)
@@ -218,9 +231,16 @@ class CoreSettings(BaseAuditModel):
server.send_message(msg)
server.quit()
else:
# smtp relay. no auth required
server.send_message(msg)
server.quit()
# gmail smtp relay specific handling.
if self.smtp_host == "smtp-relay.gmail.com":
server.ehlo()
server.starttls()
server.send_message(msg)
server.quit()
else:
# smtp relay. no auth required
server.send_message(msg)
server.quit()
except Exception as e:
DebugLog.error(message=f"Sending email failed with error: {e}")
@@ -294,6 +314,7 @@ class CustomField(BaseAuditModel):
default=list,
)
hide_in_ui = models.BooleanField(default=False)
hide_in_summary = models.BooleanField(default=False)
class Meta:
unique_together = (("model", "name"),)

View File

@@ -1,6 +1,7 @@
import pytz
from rest_framework import serializers
from tacticalrmm.constants import ALL_TIMEZONES
from .models import CodeSignToken, CoreSettings, CustomField, GlobalKVStore, URLAction
@@ -8,7 +9,7 @@ class CoreSettingsSerializer(serializers.ModelSerializer):
all_timezones = serializers.SerializerMethodField("all_time_zones")
def all_time_zones(self, obj):
return pytz.all_timezones
return ALL_TIMEZONES
class Meta:
model = CoreSettings

View File

@@ -1,8 +1,12 @@
import time
import asyncio
import logging
from contextlib import suppress
from typing import TYPE_CHECKING, Any
import nats
from django.conf import settings
from django.db.models import Prefetch
from django.db.utils import DatabaseError
from django.utils import timezone as djangotime
from packaging import version as pyver
@@ -30,12 +34,17 @@ from tacticalrmm.constants import (
PAStatus,
TaskStatus,
TaskSyncStatus,
TaskType,
)
from tacticalrmm.helpers import rand_range
from tacticalrmm.utils import DjangoConnectionThreadPoolExecutor, redis_lock
from tacticalrmm.helpers import setup_nats_options
from tacticalrmm.nats_utils import a_nats_cmd
from tacticalrmm.utils import redis_lock
if TYPE_CHECKING:
from django.db.models import QuerySet
from nats.aio.client import Client as NATSClient
logger = logging.getLogger("trmm")
@app.task
@@ -148,50 +157,150 @@ def sync_scheduled_tasks(self) -> str:
if not acquired:
return f"{self.app.oid} still running"
task_actions = [] # list of tuples
actions: list[tuple[str, int, Agent, Any, str, str]] = [] # list of tuples
for agent in _get_agent_qs():
if (
pyver.parse(agent.version) >= pyver.parse("1.6.0")
not agent.is_posix
and pyver.parse(agent.version) >= pyver.parse("1.6.0")
and agent.status == AGENT_STATUS_ONLINE
):
# create a list of tasks to be synced so we can run them in parallel later with thread pool executor
# create a list of tasks to be synced so we can run them asynchronously
for task in agent.get_tasks_with_policies():
agent_obj = agent if task.policy else None
# TODO can we just use agent??
agent_obj: "Agent" = agent if task.policy else task.agent
# onboarding tasks require agent >= 2.6.0
if task.task_type == TaskType.ONBOARDING and pyver.parse(
agent.version
) < pyver.parse("2.6.0"):
continue
# policy tasks will be an empty dict on initial
if (not task.task_result) or (
isinstance(task.task_result, TaskResult)
and task.task_result.sync_status == TaskSyncStatus.INITIAL
):
task_actions.append(("create", task.id, agent_obj))
actions.append(
(
"create",
task.id,
agent_obj,
task.generate_nats_task_payload(),
agent.agent_id,
agent.hostname,
)
)
elif (
isinstance(task.task_result, TaskResult)
and task.task_result.sync_status
== TaskSyncStatus.PENDING_DELETION
):
task_actions.append(("delete", task.id, agent_obj))
actions.append(
(
"delete",
task.id,
agent_obj,
{},
agent.agent_id,
agent.hostname,
)
)
elif (
isinstance(task.task_result, TaskResult)
and task.task_result.sync_status == TaskSyncStatus.NOT_SYNCED
):
task_actions.append(("modify", task.id, agent_obj))
actions.append(
(
"modify",
task.id,
agent_obj,
task.generate_nats_task_payload(),
agent.agent_id,
agent.hostname,
)
)
def _handle_task(actions: tuple[str, int, Any]) -> None:
time.sleep(rand_range(50, 600))
task: "AutomatedTask" = AutomatedTask.objects.get(id=actions[1])
if actions[0] == "create":
task.create_task_on_agent(agent=actions[2])
elif actions[0] == "modify":
task.modify_task_on_agent(agent=actions[2])
elif actions[0] == "delete":
task.delete_task_on_agent(agent=actions[2])
async def _handle_task_on_agent(
nc: "NATSClient", actions: tuple[str, int, Agent, Any, str, str]
) -> None:
# tuple: (0: action, 1: task.id, 2: agent object, 3: nats task payload, 4: agent_id, 5: agent hostname)
action = actions[0]
task_id = actions[1]
agent = actions[2]
payload = actions[3]
agent_id = actions[4]
hostname = actions[5]
# TODO this is a janky hack
# Rework this with asyncio. Need to rewrite all sync db operations with django's new async api
with DjangoConnectionThreadPoolExecutor(max_workers=50) as executor:
executor.map(_handle_task, task_actions)
task: "AutomatedTask" = await AutomatedTask.objects.aget(id=task_id)
try:
task_result = await TaskResult.objects.aget(agent=agent, task=task)
except TaskResult.DoesNotExist:
task_result = await TaskResult.objects.acreate(agent=agent, task=task)
return "completed"
if action in ("create", "modify"):
logger.debug(payload)
nats_data = {
"func": "schedtask",
"schedtaskpayload": payload,
}
r = await a_nats_cmd(nc=nc, sub=agent_id, data=nats_data, timeout=10)
if r != "ok":
if action == "create":
task_result.sync_status = TaskSyncStatus.INITIAL
else:
task_result.sync_status = TaskSyncStatus.NOT_SYNCED
logger.error(
f"Unable to {action} scheduled task {task.name} on {hostname}: {r}"
)
else:
task_result.sync_status = TaskSyncStatus.SYNCED
logger.info(
f"{hostname} task {task.name} was {'created' if action == 'create' else 'modified'}"
)
await task_result.asave(update_fields=["sync_status"])
# delete
else:
nats_data = {
"func": "delschedtask",
"schedtaskpayload": {"name": task.win_task_name},
}
r = await a_nats_cmd(nc=nc, sub=agent_id, data=nats_data, timeout=10)
if r != "ok" and "The system cannot find the file specified" not in r:
task_result.sync_status = TaskSyncStatus.PENDING_DELETION
with suppress(DatabaseError):
await task_result.asave(update_fields=["sync_status"])
logger.error(
f"Unable to {action} scheduled task {task.name} on {hostname}: {r}"
)
else:
task_name = task.name
await task.adelete()
logger.info(f"{hostname} task {task_name} was deleted.")
async def _run():
opts = setup_nats_options()
try:
nc = await nats.connect(**opts)
except Exception as e:
ret = str(e)
logger.error(ret)
return ret
if tasks := [_handle_task_on_agent(nc, task) for task in actions]:
await asyncio.gather(*tasks)
await nc.flush()
await nc.close()
asyncio.run(_run())
return "ok"
def _get_failing_data(agents: "QuerySet[Agent]") -> dict[str, bool]:

View File

@@ -3,28 +3,29 @@ from unittest.mock import patch
import requests
from channels.db import database_sync_to_async
from channels.testing import WebsocketCommunicator
from django.conf import settings
# from django.conf import settings
from django.core.management import call_command
from django.test import override_settings
from model_bakery import baker
from rest_framework.authtoken.models import Token
from agents.models import Agent
# from agents.models import Agent
from core.utils import get_core_settings, get_meshagent_url
from logs.models import PendingAction
from tacticalrmm.constants import (
# from logs.models import PendingAction
from tacticalrmm.constants import ( # PAAction,; PAStatus,
CONFIG_MGMT_CMDS,
CustomFieldModel,
MeshAgentIdent,
PAAction,
PAStatus,
)
from tacticalrmm.helpers import get_nats_hosts, get_nats_url
from tacticalrmm.test import TacticalTestCase
from .consumers import DashInfo
from .models import CustomField, GlobalKVStore, URLAction
from .serializers import CustomFieldSerializer, KeyStoreSerializer, URLActionSerializer
from .tasks import core_maintenance_tasks, resolve_pending_actions
from .tasks import core_maintenance_tasks # , resolve_pending_actions
class TestCodeSign(TacticalTestCase):
@@ -410,28 +411,28 @@ class TestCoreTasks(TacticalTestCase):
self.check_not_authenticated("get", url)
def test_resolved_pending_agentupdate_task(self):
online = baker.make_recipe("agents.online_agent", version="2.0.0", _quantity=20)
offline = baker.make_recipe(
"agents.offline_agent", version="2.0.0", _quantity=20
)
agents = online + offline
for agent in agents:
baker.make_recipe("logs.pending_agentupdate_action", agent=agent)
# def test_resolved_pending_agentupdate_task(self):
# online = baker.make_recipe("agents.online_agent", version="2.0.0", _quantity=20)
# offline = baker.make_recipe(
# "agents.offline_agent", version="2.0.0", _quantity=20
# )
# agents = online + offline
# for agent in agents:
# baker.make_recipe("logs.pending_agentupdate_action", agent=agent)
Agent.objects.update(version=settings.LATEST_AGENT_VER)
# Agent.objects.update(version=settings.LATEST_AGENT_VER)
resolve_pending_actions()
# resolve_pending_actions()
complete = PendingAction.objects.filter(
action_type=PAAction.AGENT_UPDATE, status=PAStatus.COMPLETED
).count()
old = PendingAction.objects.filter(
action_type=PAAction.AGENT_UPDATE, status=PAStatus.PENDING
).count()
# complete = PendingAction.objects.filter(
# action_type=PAAction.AGENT_UPDATE, status=PAStatus.COMPLETED
# ).count()
# old = PendingAction.objects.filter(
# action_type=PAAction.AGENT_UPDATE, status=PAStatus.PENDING
# ).count()
self.assertEqual(complete, 20)
self.assertEqual(old, 20)
# self.assertEqual(complete, 20)
# self.assertEqual(old, 20)
class TestCoreMgmtCommands(TacticalTestCase):
@@ -443,6 +444,38 @@ class TestCoreMgmtCommands(TacticalTestCase):
call_command("get_config", cmd)
class TestNatsUrls(TacticalTestCase):
def setUp(self):
self.setup_coresettings()
def test_standard_install(self):
self.assertEqual(get_nats_url(), "nats://127.0.0.1:4222")
@override_settings(
NATS_STANDARD_PORT=5000,
USE_NATS_STANDARD=True,
ALLOWED_HOSTS=["api.example.com"],
)
def test_custom_port_nats_standard(self):
self.assertEqual(get_nats_url(), "tls://api.example.com:5000")
@override_settings(DOCKER_BUILD=True, ALLOWED_HOSTS=["api.example.com"])
def test_docker_nats(self):
self.assertEqual(get_nats_url(), "nats://api.example.com:4222")
@patch.dict("os.environ", {"NATS_CONNECT_HOST": "172.20.4.3"})
@override_settings(ALLOWED_HOSTS=["api.example.com"])
def test_custom_connect_host_env(self):
self.assertEqual(get_nats_url(), "nats://172.20.4.3:4222")
def test_standard_nats_hosts(self):
self.assertEqual(get_nats_hosts(), ("127.0.0.1", "127.0.0.1", "127.0.0.1"))
@override_settings(DOCKER_BUILD=True, ALLOWED_HOSTS=["api.example.com"])
def test_docker_nats_hosts(self):
self.assertEqual(get_nats_hosts(), ("0.0.0.0", "0.0.0.0", "api.example.com"))
class TestCorePermissions(TacticalTestCase):
def setUp(self):
self.setup_client()
@@ -500,3 +533,27 @@ class TestCoreUtils(TacticalTestCase):
r,
"http://tactical-meshcentral:4443/meshagents?id=4&meshid=abc123&installflags=0",
)
@override_settings(TRMM_INSECURE=True)
def test_get_meshagent_url_insecure(self):
r = get_meshagent_url(
ident=MeshAgentIdent.DARWIN_UNIVERSAL,
plat="darwin",
mesh_site="https://mesh.example.com",
mesh_device_id="abc123",
)
self.assertEqual(
r,
"http://mesh.example.com:4430/meshagents?id=abc123&installflags=2&meshinstall=10005",
)
r = get_meshagent_url(
ident=MeshAgentIdent.WIN64,
plat="windows",
mesh_site="https://mesh.example.com",
mesh_device_id="abc123",
)
self.assertEqual(
r,
"http://mesh.example.com:4430/meshagents?id=4&meshid=abc123&installflags=0",
)

View File

@@ -19,4 +19,5 @@ urlpatterns = [
path("smstest/", views.TwilioSMSTest.as_view()),
path("clearcache/", views.clear_cache),
path("status/", views.status),
path("openai/generate/", views.OpenAICodeCompletion.as_view()),
]

View File

@@ -88,8 +88,12 @@ def get_mesh_ws_url() -> str:
if settings.DOCKER_BUILD:
uri = f"{settings.MESH_WS_URL}/control.ashx?auth={token}"
else:
site = core.mesh_site.replace("https", "wss")
uri = f"{site}/control.ashx?auth={token}"
if getattr(settings, "TRMM_INSECURE", False):
site = core.mesh_site.replace("https", "ws")
uri = f"{site}:4430/control.ashx?auth={token}"
else:
site = core.mesh_site.replace("https", "wss")
uri = f"{site}/control.ashx?auth={token}"
return uri
@@ -142,6 +146,20 @@ async def send_command_with_mesh(
)
async def wake_on_lan(*, uri: str, mesh_node_id: str) -> None:
node_id = _b64_to_hex(mesh_node_id)
async with websockets.connect(uri) as ws:
await ws.send(
json.dumps(
{
"action": "wakedevices",
"nodeids": [f"node//{node_id}"],
"responseid": "trmm",
}
)
)
async def remove_mesh_agent(uri: str, mesh_node_id: str) -> None:
node_id = _b64_to_hex(mesh_node_id)
async with websockets.connect(uri) as ws:
@@ -167,6 +185,8 @@ def get_meshagent_url(
) -> str:
if settings.DOCKER_BUILD:
base = settings.MESH_WS_URL.replace("ws://", "http://")
elif getattr(settings, "TRMM_INSECURE", False):
base = mesh_site.replace("https", "http") + ":4430"
else:
base = mesh_site

View File

@@ -1,17 +1,22 @@
import json
import re
from contextlib import suppress
from pathlib import Path
from zoneinfo import ZoneInfo
import psutil
import pytz
import requests
from cryptography import x509
from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone as djangotime
from django.views.decorators.csrf import csrf_exempt
from redis import from_url
from rest_framework.decorators import api_view, permission_classes
from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
@@ -75,8 +80,9 @@ def clear_cache(request):
@api_view()
def dashboard_info(request):
from core.utils import token_is_expired
from tacticalrmm.utils import get_latest_trmm_ver
from tacticalrmm.utils import get_latest_trmm_ver, runcmd_placeholder_text
core_settings = get_core_settings()
return Response(
{
"trmm_version": settings.TRMM_VERSION,
@@ -85,17 +91,23 @@ def dashboard_info(request):
"show_community_scripts": request.user.show_community_scripts,
"dbl_click_action": request.user.agent_dblclick_action,
"default_agent_tbl_tab": request.user.default_agent_tbl_tab,
"url_action": request.user.url_action.id
if request.user.url_action
else None,
"url_action": (
request.user.url_action.id if request.user.url_action else None
),
"client_tree_sort": request.user.client_tree_sort,
"client_tree_splitter": request.user.client_tree_splitter,
"loading_bar_color": request.user.loading_bar_color,
"clear_search_when_switching": request.user.clear_search_when_switching,
"hosted": getattr(settings, "HOSTED", False),
"date_format": request.user.date_format,
"default_date_format": get_core_settings().date_format,
"default_date_format": core_settings.date_format,
"token_is_expired": token_is_expired(),
"open_ai_integration_enabled": bool(core_settings.open_ai_token),
"dash_info_color": request.user.dash_info_color,
"dash_positive_color": request.user.dash_positive_color,
"dash_negative_color": request.user.dash_negative_color,
"dash_warning_color": request.user.dash_warning_color,
"run_cmd_placeholder_text": runcmd_placeholder_text(),
}
)
@@ -345,7 +357,7 @@ class RunURLAction(APIView):
from agents.models import Agent
from clients.models import Client, Site
from tacticalrmm.utils import replace_db_values
from tacticalrmm.utils import get_db_value
if "agent_id" in request.data.keys():
if not _has_perm_on_agent(request.user, request.data["agent_id"]):
@@ -372,7 +384,7 @@ class RunURLAction(APIView):
url_pattern = action.pattern
for string in re.findall(pattern, action.pattern):
value = replace_db_values(string=string, instance=instance, quotes=False)
value = get_db_value(string=string, instance=instance)
url_pattern = re.sub("\\{\\{" + string + "\\}\\}", str(value), url_pattern)
@@ -416,10 +428,17 @@ def status(request):
cert_bytes = Path(cert_file).read_bytes()
cert = x509.load_pem_x509_certificate(cert_bytes)
expires = pytz.utc.localize(cert.not_valid_after)
expires = cert.not_valid_after.replace(tzinfo=ZoneInfo("UTC"))
now = djangotime.now()
delta = expires - now
redis_url = f"redis://{settings.REDIS_HOST}"
redis_ping = False
with suppress(Exception):
with from_url(redis_url) as conn:
conn.ping()
redis_ping = True
ret = {
"version": settings.TRMM_VERSION,
"latest_agent_version": settings.LATEST_AGENT_VER,
@@ -430,6 +449,7 @@ def status(request):
"mem_usage_percent": mem_usage,
"days_until_cert_expires": delta.days,
"cert_expired": delta.days < 0,
"redis_ping": redis_ping,
}
if settings.DOCKER_BUILD:
@@ -449,3 +469,55 @@ def status(request):
"nginx": sysd_svc_is_running("nginx.service"),
}
return JsonResponse(ret, json_dumps_params={"indent": 2})
class OpenAICodeCompletion(APIView):
permission_classes = [IsAuthenticated]
def post(self, request: Request) -> Response:
settings = get_core_settings()
if not settings.open_ai_token:
return notify_error(
"Open AI API Key not found. Open Global Settings > Open AI."
)
if not request.data["prompt"]:
return notify_error("Not prompt field found")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {settings.open_ai_token}",
}
data = {
"messages": [
{
"role": "user",
"content": request.data["prompt"],
},
],
"model": settings.open_ai_model,
"temperature": 0.5,
"max_tokens": 1000,
"n": 1,
"stop": None,
}
try:
response = requests.post(
"https://api.openai.com/v1/chat/completions",
headers=headers,
data=json.dumps(data),
)
except Exception as e:
return notify_error(str(e))
response_data = json.loads(response.text)
if "error" in response_data:
return notify_error(
f"The Open AI API returned an error: {response_data['error']['message']}"
)
return Response(response_data["choices"][0]["message"]["content"])

View File

@@ -0,0 +1,30 @@
## Tactical RMM Enterprise Edition (EE) License Agreement (the "Agreement")
Copyright (c) 2023 Amidaware Inc. All rights reserved.
This Agreement is entered into between the licensee ("You" or the "Licensee") and Amidaware Inc. ("Amidaware") and governs the use of the enterprise features of the Tactical RMM Software (hereinafter referred to as the "Software").
The EE features of the Software, including but not limited to Reporting and White-labeling, are exclusively contained within directories named "ee," "enterprise," or "premium" in Amidaware's repositories, or in any files bearing the EE License header. The use of the Software is also governed by the terms and conditions set forth in the Tactical RMM License, available at https://license.tacticalrmm.com, which terms are incorporated herein by reference.
## License Grant
Subject to the terms of this Agreement and upon the Licensee's possession of a valid and authorized sponsorship token issued by Amidaware, Amidaware hereby grants to the Licensee a limited, non-exclusive, non-transferable, and revocable right and license to use the Software.
## Restrictions
The Licensee acknowledges and agrees that, notwithstanding any other provision of this Agreement:
a) The Licensee shall not copy, merge, publish, distribute, sublicense, or sell the Software or any derivative thereof;
b) The Licensee shall not, in any manner, circumvent, bypass, or tamper with the license key functionality embedded in the Software, nor shall the Licensee remove, alter, or obscure any features that are protected by such license keys. For the avoidance of doubt, the Software's code contains protective measures that enable specific EE features, and the Licensee is strictly prohibited from modifying or removing any licensing code with the intent to enable or unlock these EE features without proper authorization.
## Termination
1. **Breach**: If the Licensee breaches any term of this Agreement, Amidaware reserves the right to terminate this Agreement and the Licensee's rights granted hereunder immediately, without prior notice.
2. **Legal Action**: Any breach of this Agreement may result in Amidaware pursuing legal action against the Licensee. The Licensee acknowledges and agrees that, upon any breach, Amidaware may seek remedies, including injunctive relief, damages, and legal fees, and will be entitled to prosecute the Licensee to the full extent of the law.
3. **Effects of Termination**: Upon termination, the Licensee shall immediately cease all use of the Software and delete all copies of the Software from its systems and confirm such deletion in writing to Amidaware.
## Updates & Amendments
1. **Software Updates**: Amidaware may, from time to time, release updates or upgrades to the Software. These updates or upgrades might be subject to additional terms presented to you at the time of download or installation.
2. **Amendments to this Agreement**: Amidaware reserves the right to modify the terms of this Agreement at any given time. Any such modifications will be effective immediately upon posting on Amidaware's official website or by direct communication to the Licensee. The continued use of the Software after such modifications will constitute the Licensee's acceptance of the revised terms.

View File

@@ -0,0 +1,5 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""

View File

@@ -0,0 +1,33 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""
import re
from datetime import datetime, timedelta
import yaml
from django.utils import timezone
now_regex = re.compile(
r"^(weeks|days|hours|minutes|seconds|microseconds)=(-?\d*)$", re.VERBOSE
)
def construct_yaml_now(loader, node):
loader.construct_scalar(node)
match = now_regex.match(node.value)
now = timezone.now()
if match:
now = now + timedelta(**{match.group(1): int(match.group(2))})
return now
def represent_datetime_now(dumper, data):
value = data.isoformat(" ")
return dumper.represent_scalar("!now", value)
yaml.SafeLoader.add_constructor("!now", construct_yaml_now)
yaml.SafeDumper.add_representer(datetime, represent_datetime_now)

View File

@@ -0,0 +1,12 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""
from django.contrib import admin
from .models import ReportAsset, ReportTemplate
admin.site.register(ReportTemplate)
admin.site.register(ReportAsset)

View File

@@ -0,0 +1,12 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""
from django.apps import AppConfig
class ReportingConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "ee.reporting"

View File

@@ -0,0 +1,31 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""
# (Model, app)
REPORTING_MODELS = (
("Agent", "agents"),
("AgentCustomField", "agents"),
("AgentHistory", "agents"),
("Alert", "alerts"),
("Policy", "automation"),
("AutomatedTask", "autotasks"),
("TaskResult", "autotasks"),
("Check", "checks"),
("CheckResult", "checks"),
("CheckHistory", "checks"),
("Client", "clients"),
("ClientCustomField", "clients"),
("Site", "clients"),
("SiteCustomField", "clients"),
("GlobalKVStore", "core"),
("AuditLog", "logs"),
("DebugLog", "logs"),
("PendingAction", "logs"),
("ChocoSoftware", "software"),
("InstalledSoftware", "software"),
("WinUpdate", "winupdate"),
("WinUpdatePolicy", "winupdate"),
)

View File

@@ -0,0 +1,28 @@
from contextlib import suppress
from zoneinfo import ZoneInfo
import validators
def as_tz(date_obj, tz, format="%b %d %Y, %I:%M %p"):
return date_obj.astimezone(ZoneInfo(tz)).strftime(format)
def local_ips(wmi_detail):
ret = []
with suppress(Exception):
ips = wmi_detail["network_config"]
for i in ips:
try:
addr = [x["IPAddress"] for x in i if "IPAddress" in x][0]
except:
continue
if addr is None:
continue
for ip in addr:
if validators.ipv4(ip):
ret.append(ip)
return ret

View File

@@ -0,0 +1,5 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""

View File

@@ -0,0 +1,5 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""

View File

@@ -0,0 +1,158 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""
import json
from typing import TYPE_CHECKING, Any, Dict, List, Tuple
from django.apps import apps
from django.conf import settings as djangosettings
from django.core.management.base import BaseCommand
from ...constants import REPORTING_MODELS
if TYPE_CHECKING:
from django.db.models import Model
class Command(BaseCommand):
help = "Generate JSON Schemas"
def handle(self, *args: Tuple[Any, Any], **kwargs: Dict[str, Any]) -> None:
generate_schema()
# recursive function to traverse foreign keys and get values
def traverse_model_fields(
*, model: "Model", prefix: str = "", depth: int = 3
) -> Tuple[Dict[str, Any], Dict[str, Any], List[str], List[str]]:
filterObj: Dict[str, Any] = {}
patternObj: Dict[str, Any] = {}
select_related: List[str] = []
field_list: List[str] = []
if depth < 1:
return filterObj, patternObj, select_related, field_list
for field in model._meta.get_fields():
field_type = field.get_internal_type() # type: ignore
if field_type == "CharField" and field.choices: # type: ignore
propDefinition = {
"type": "string",
"enum": [index for index, _ in field.choices], # type: ignore
}
elif field_type == "BooleanField":
propDefinition = {
"type": "boolean",
}
elif field.many_to_many or field.one_to_many:
continue
elif (
field_type == "ForeignKey" or field.name == "id" or "Integer" in field_type
):
propDefinition = {
"type": "integer",
}
if field_type == "ForeignKey":
select_related.append(prefix + field.name)
related_model = field.related_model
# Get fields of the related model, recursively
filter, pattern, select, list = traverse_model_fields(
model=related_model, # type: ignore
prefix=prefix + field.name + "__",
depth=depth - 1,
)
filterObj = {**filterObj, **filter}
patternObj = {**patternObj, **pattern}
select_related += select
field_list += list
else:
propDefinition = {
"type": "string",
}
filterObj[prefix + field.name] = propDefinition
patternObj["^" + prefix + field.name + "(__[a-zA-Z]+)*$"] = propDefinition
field_list.append(prefix + field.name)
return filterObj, patternObj, select_related, field_list
def generate_schema() -> None:
oneOf = []
for model, app in REPORTING_MODELS:
Model = apps.get_model(app_label=app, model_name=model)
filterObj, patternObj, select_related, field_list = traverse_model_fields(
model=Model, depth=3
)
order_by = []
for field in field_list:
order_by.append(field)
order_by.append(f"-{field}")
oneOf.append(
{
"properties": {
"model": {"type": "string", "enum": [model.lower()]},
"filter": {
"type": "object",
"properties": filterObj,
"patternProperties": patternObj,
},
"exclude": {
"type": "object",
"properties": filterObj,
"patternProperties": patternObj,
},
"defer": {
"type": "array",
"items": {
"type": "string",
"minimum": 1,
"enum": field_list,
},
},
"only": {
"type": "array",
"items": {"type": "string", "minimum": 1, "enum": field_list},
},
"select_related": {
"type": "array",
"items": {
"type": "string",
"minimum": 1,
"enum": select_related,
},
},
"order_by": {"type": "string", "enum": order_by},
},
}
)
schema = {
"$id": f"https://{djangosettings.ALLOWED_HOSTS[0]}/static/reporting/schemas/query_schema.json",
"type": "object",
"properties": {
"model": {
"type": "string",
"enum": [model.lower() for model, _ in REPORTING_MODELS],
},
"custom_fields": {
"type": "array",
"items": {"type": "string", "minimum": 1},
},
"limit": {"type": "integer"},
"count": {"type": "boolean"},
"get": {"type": "boolean"},
"first": {"type": "boolean"},
},
"required": ["model"],
"oneOf": oneOf,
}
with open(
f"{djangosettings.STATICFILES_DIRS[0]}reporting/schemas/query_schema.json", "w"
) as outfile:
outfile.write(json.dumps(schema))

View File

@@ -0,0 +1,64 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""
import urllib.parse
from time import sleep
from typing import Any, Optional
import requests
from core.models import CodeSignToken
from django.conf import settings
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = "Get webtar url"
def handle(self, *args: tuple[Any, Any], **kwargs: dict[str, Any]) -> None:
webtar = f"trmm-web-v{settings.WEB_VERSION}.tar.gz"
url = f"https://github.com/amidaware/tacticalrmm-web/releases/download/v{settings.WEB_VERSION}/{webtar}"
t: "Optional[CodeSignToken]" = CodeSignToken.objects.first()
if not t or not t.token:
self.stdout.write(url)
return
attempts = 0
while 1:
try:
r = requests.post(
settings.REPORTING_CHECK_URL,
json={"token": t.token, "api": settings.ALLOWED_HOSTS[0]},
headers={"Content-type": "application/json"},
timeout=15,
)
except Exception as e:
self.stderr.write(str(e))
attempts += 1
sleep(3)
else:
if r.status_code // 100 in (3, 5):
self.stderr.write(f"Error getting web tarball: {r.status_code}")
attempts += 1
sleep(3)
else:
attempts = 0
if attempts == 0:
break
elif attempts > 5:
self.stdout.write(url)
return
if r.status_code == 200: # type: ignore
params = {
"token": t.token,
"webver": settings.WEB_VERSION,
"api": settings.ALLOWED_HOSTS[0],
}
url = settings.REPORTING_DL_URL + urllib.parse.urlencode(params)
self.stdout.write(url)

View File

@@ -0,0 +1,5 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""

View File

@@ -0,0 +1,25 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""
from typing import Optional, Sequence, Union
import markdown
from .ignorejinja_ext import IgnoreJinjaExtension
markdown_ext: "Optional[Sequence[Union[str, markdown.Extension]]]" = [
"ocxsect",
"tables",
"sane_lists",
"def_list",
"nl2br",
"fenced_code",
"attr_list",
IgnoreJinjaExtension(),
]
# import this into views
Markdown = markdown.Markdown(extensions=markdown_ext)

View File

@@ -0,0 +1,70 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""
import re
from typing import Any, List
from markdown import Extension, Markdown
from markdown.postprocessors import Postprocessor
from markdown.preprocessors import Preprocessor
class IgnoreJinjaExtension(Extension):
"""Extension for looking up {% block tag %}"""
def extendMarkdown(self, md: Markdown) -> None:
"""Add IgnoreJinjaExtension to Markdown instance."""
md.preprocessors.register(IgnoreJinjaPreprocessor(md), "preignorejinja", 0)
md.postprocessors.register(IgnoreJinjaPostprocessor(md), "postignorejinja", 0)
PRE_RE = re.compile(r"(\{\%.*\%\})")
class IgnoreJinjaPreprocessor(Preprocessor):
"""
Looks for {% block tag %} and wraps it in an html comment <!--- -->
"""
def run(self, lines: List[str]) -> List[str]:
new_lines: List[str] = []
for line in lines:
m = PRE_RE.search(line)
if m:
tag = m.group(1)
new_line = line.replace(tag, f"<!--- {tag} -->")
new_lines.append(new_line)
else:
new_lines.append(line)
return new_lines
POST_RE = re.compile(r"\<\!\-\-\-\s{1}(\{\%.*\%\})\s{1}\-\-\>")
class IgnoreJinjaPostprocessor(Postprocessor):
"""
Looks for <!-- {{% block tag %}} --> and removes the comment
"""
def run(self, text: str) -> str:
new_lines: List[str] = []
lines = text.split("\n")
for line in lines:
m = POST_RE.search(line)
if m:
tag = m.group(1)
new_line = line.replace(f"<!--- {tag} -->", tag)
new_lines.append(new_line)
else:
new_lines.append(line)
return "\n".join(new_lines)
def makeExtension(*args: Any, **kwargs: Any) -> IgnoreJinjaExtension:
"""set up extension."""
return IgnoreJinjaExtension(*args, **kwargs)

View File

@@ -0,0 +1,116 @@
# Generated by Django 4.2.3 on 2023-07-05 05:33
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
import ee.reporting.storage
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="ReportAsset",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
(
"file",
models.FileField(
storage=ee.reporting.storage.get_report_assets_fs,
unique=True,
upload_to="",
),
),
],
),
migrations.CreateModel(
name="ReportDataQuery",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=50, unique=True)),
("json_query", models.JSONField()),
],
),
migrations.CreateModel(
name="ReportHTMLTemplate",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=50, unique=True)),
("html", models.TextField()),
],
),
migrations.CreateModel(
name="ReportTemplate",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=50, unique=True)),
("template_md", models.TextField()),
("template_css", models.TextField(blank=True, null=True)),
(
"type",
models.CharField(
choices=[("markdown", "Markdown"), ("html", "Html")],
default="markdown",
max_length=15,
),
),
("template_variables", models.TextField(blank=True, default="")),
(
"depends_on",
django.contrib.postgres.fields.ArrayField(
base_field=models.CharField(blank=True, max_length=20),
blank=True,
default=list,
size=None,
),
),
(
"template_html",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="htmltemplate",
to="reporting.reporthtmltemplate",
),
),
],
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.5 on 2023-10-05 16:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('reporting', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='reporttemplate',
name='type',
field=models.CharField(choices=[('markdown', 'Markdown'), ('html', 'Html'), ('plaintext', 'Plain Text')], default='markdown', max_length=15),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.6 on 2023-11-07 18:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('reporting', '0002_alter_reporttemplate_type'),
]
operations = [
migrations.AlterField(
model_name='reporthtmltemplate',
name='name',
field=models.CharField(max_length=200, unique=True),
),
migrations.AlterField(
model_name='reporttemplate',
name='name',
field=models.CharField(max_length=200, unique=True),
),
]

View File

@@ -0,0 +1,5 @@
"""
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""

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