diff --git a/api/tacticalrmm/agents/models.py b/api/tacticalrmm/agents/models.py index 525f8dbbc9..ce18228441 100644 --- a/api/tacticalrmm/agents/models.py +++ b/api/tacticalrmm/agents/models.py @@ -2,7 +2,6 @@ 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 @@ -16,6 +15,7 @@ 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 @@ -876,8 +876,10 @@ def delete_superseded_updates(self) -> None: # 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) diff --git a/api/tacticalrmm/agents/tests/test_agents.py b/api/tacticalrmm/agents/tests/test_agents.py index 2bf6ea328c..0a5a89c644 100644 --- a/api/tacticalrmm/agents/tests/test_agents.py +++ b/api/tacticalrmm/agents/tests/test_agents.py @@ -3,8 +3,8 @@ 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 @@ def test_get_agent_history(self): # 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 diff --git a/api/tacticalrmm/agents/views.py b/api/tacticalrmm/agents/views.py index 81a1ce4fee..83037e7cc8 100644 --- a/api/tacticalrmm/agents/views.py +++ b/api/tacticalrmm/agents/views.py @@ -6,19 +6,12 @@ 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,16 @@ 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, +) +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, @@ -561,10 +562,11 @@ def patch(self, request, agent_id): @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 # TODO rework this ghetto validation hack # https://github.com/amidaware/tacticalrmm/issues/1461 @@ -947,7 +949,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 +964,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": diff --git a/api/tacticalrmm/autotasks/models.py b/api/tacticalrmm/autotasks/models.py index e27a19a583..0389d9a443 100644 --- a/api/tacticalrmm/autotasks/models.py +++ b/api/tacticalrmm/autotasks/models.py @@ -3,8 +3,8 @@ import string from contextlib import suppress from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +from zoneinfo import ZoneInfo -import pytz from django.core.cache import cache from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -270,12 +270,12 @@ def generate_nats_task_payload( 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)) + and self.run_time_date.replace(tzinfo=ZoneInfo(agent.timezone)) + < djangotime.now().astimezone(ZoneInfo(agent.timezone)) ): self.run_time_date = ( djangotime.now() + djangotime.timedelta(minutes=5) - ).astimezone(pytz.timezone(agent.timezone)) + ).astimezone(ZoneInfo(agent.timezone)) task["start_year"] = int(self.run_time_date.strftime("%Y")) task["start_month"] = int(self.run_time_date.strftime("%-m")) diff --git a/api/tacticalrmm/autotasks/tasks.py b/api/tacticalrmm/autotasks/tasks.py index de57e19fbe..38c57e752e 100644 --- a/api/tacticalrmm/autotasks/tasks.py +++ b/api/tacticalrmm/autotasks/tasks.py @@ -149,6 +149,7 @@ async def _run() -> None: for item in items ] await asyncio.gather(*tasks) + await nc.flush() await nc.close() asyncio.run(_run()) diff --git a/api/tacticalrmm/checks/views.py b/api/tacticalrmm/checks/views.py index 3543b9c422..25f4902f7b 100644 --- a/api/tacticalrmm/checks/views.py +++ b/api/tacticalrmm/checks/views.py @@ -1,9 +1,6 @@ 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.shortcuts import get_object_or_404 from django.utils import timezone as djangotime @@ -17,16 +14,15 @@ 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.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] @@ -189,29 +185,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"})) - - async def _run() -> None: - opts = setup_nats_options() - try: - nc = await nats.connect(**opts) - except Exception as e: - return notify_error(str(e)) + payload = {"func": "runchecks"} + items = [(agent_id, payload) for agent_id in agent_ids] - tasks = [_run_check(nc=nc, sub=agent) for agent in agents] - await asyncio.gather(*tasks) - await nc.close() + try: + asyncio.run(abulk_nats_command(items=items)) + except NatsDown as e: + return notify_error(str(e)) - 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) diff --git a/api/tacticalrmm/core/migrations/0037_coresettings_open_ai_model_and_more.py b/api/tacticalrmm/core/migrations/0037_coresettings_open_ai_model_and_more.py new file mode 100644 index 0000000000..f502dd49fc --- /dev/null +++ b/api/tacticalrmm/core/migrations/0037_coresettings_open_ai_model_and_more.py @@ -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), + ), + ] diff --git a/api/tacticalrmm/core/models.py b/api/tacticalrmm/core/models.py index b43dd8f0c5..e0d75faca1 100644 --- a/api/tacticalrmm/core/models.py +++ b/api/tacticalrmm/core/models.py @@ -98,6 +98,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 diff --git a/api/tacticalrmm/core/urls.py b/api/tacticalrmm/core/urls.py index 1f491033e0..fcc4274cac 100644 --- a/api/tacticalrmm/core/urls.py +++ b/api/tacticalrmm/core/urls.py @@ -19,4 +19,5 @@ path("smstest/", views.TwilioSMSTest.as_view()), path("clearcache/", views.clear_cache), path("status/", views.status), + path("openai/generate/", views.OpenAICodeCompletion.as_view()), ] diff --git a/api/tacticalrmm/core/views.py b/api/tacticalrmm/core/views.py index b423067ec8..7f4ddff227 100644 --- a/api/tacticalrmm/core/views.py +++ b/api/tacticalrmm/core/views.py @@ -1,8 +1,10 @@ +import json import re 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 @@ -12,6 +14,7 @@ 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 @@ -77,6 +80,7 @@ def dashboard_info(request): from core.utils import token_is_expired from tacticalrmm.utils import get_latest_trmm_ver + core_settings = get_core_settings() return Response( { "trmm_version": settings.TRMM_VERSION, @@ -94,8 +98,9 @@ def dashboard_info(request): "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), } ) @@ -416,7 +421,7 @@ 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 @@ -449,3 +454,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"]) diff --git a/api/tacticalrmm/requirements-test.txt b/api/tacticalrmm/requirements-test.txt index 68d719408e..62d7da4f82 100644 --- a/api/tacticalrmm/requirements-test.txt +++ b/api/tacticalrmm/requirements-test.txt @@ -5,6 +5,5 @@ pytest pytest-django pytest-xdist pytest-cov -codecov refurb flake8 \ No newline at end of file diff --git a/api/tacticalrmm/requirements.txt b/api/tacticalrmm/requirements.txt index 9dfa4b2bce..83167f535e 100644 --- a/api/tacticalrmm/requirements.txt +++ b/api/tacticalrmm/requirements.txt @@ -1,24 +1,24 @@ adrf==0.1.1 celery==5.2.7 -certifi==2022.12.7 +certifi==2023.5.7 cffi==1.15.1 channels==4.0.0 channels_redis==4.1.0 chardet==4.0.0 -cryptography==40.0.1 +cryptography==40.0.2 daphne==4.0.0 -Django==4.1.8 +Django==4.1.9 django-cors-headers==3.14.0 django-ipware==5.0.0 django-rest-knox==4.2.0 djangorestframework==3.14.0 -drf-spectacular==0.26.1 -hiredis==2.2.2 +drf-spectacular==0.26.2 +hiredis==2.2.3 meshctrl==0.1.15 msgpack==1.0.5 nats-py==2.2.0 -packaging==23.0 -psutil==5.9.4 +packaging==23.1 +psutil==5.9.5 psycopg2-binary==2.9.6 pycparser==2.21 pycryptodome==3.17 @@ -26,14 +26,14 @@ pyotp==2.8.0 pyparsing==3.0.9 pytz==2023.3 qrcode==7.4.2 -redis==4.5.4 +redis==4.5.5 requests==2.28.2 six==1.16.0 -sqlparse==0.4.3 +sqlparse==0.4.4 twilio==7.17.0 urllib3==1.26.15 uWSGI==2.0.21 validators==0.20.0 vine==5.0.0 -websockets==11.0.1 +websockets==11.0.2 zipp==3.15.0 diff --git a/api/tacticalrmm/scripts/tasks.py b/api/tacticalrmm/scripts/tasks.py index a259d106c5..d5b8b18682 100644 --- a/api/tacticalrmm/scripts/tasks.py +++ b/api/tacticalrmm/scripts/tasks.py @@ -1,26 +1,20 @@ import asyncio -from typing import TYPE_CHECKING, List - -import msgpack -import nats from agents.models import Agent, AgentHistory from scripts.models import Script from tacticalrmm.celery import app from tacticalrmm.constants import AgentHistoryType -from tacticalrmm.helpers import setup_nats_options - -if TYPE_CHECKING: - from nats.aio.client import Client as NATSClient +from tacticalrmm.nats_utils import abulk_nats_command @app.task -def handle_bulk_command_task( - agentpks: list[int], +def bulk_command_task( + *, + agent_pks: list[int], cmd: str, shell: str, - timeout, - username, + timeout: int, + username: str, run_as_user: bool = False, ) -> None: items = [] @@ -34,7 +28,7 @@ def handle_bulk_command_task( "run_as_user": run_as_user, } agent: "Agent" - for agent in Agent.objects.filter(pk__in=agentpks): + for agent in Agent.objects.filter(pk__in=agent_pks): hist = AgentHistory.objects.create( agent=agent, type=AgentHistoryType.CMD_RUN, @@ -45,48 +39,47 @@ def handle_bulk_command_task( tmp["id"] = hist.pk items.append((agent.agent_id, tmp)) - async def _run_cmd(nc: "NATSClient", sub, data) -> None: - await nc.publish(subject=sub, payload=msgpack.dumps(data)) - - async def _run() -> None: - opts = setup_nats_options() - try: - nc = await nats.connect(**opts) - except Exception as e: - print(e) - return - - tasks = [_run_cmd(nc=nc, sub=item[0], data=item[1]) for item in items] - await asyncio.gather(*tasks) - await nc.close() - - asyncio.run(_run()) + asyncio.run(abulk_nats_command(items=items)) @app.task -def handle_bulk_script_task( - scriptpk: int, - agentpks: List[int], - args: List[str], +def bulk_script_task( + *, + script_pk: int, + agent_pks: list[int], + args: list[str] = [], timeout: int, username: str, run_as_user: bool = False, env_vars: list[str] = [], ) -> None: - script = Script.objects.get(pk=scriptpk) + script = Script.objects.get(pk=script_pk) + # always override if set on script model + if script.run_as_user: + run_as_user = True + + items = [] agent: "Agent" - for agent in Agent.objects.filter(pk__in=agentpks): + for agent in Agent.objects.filter(pk__in=agent_pks): hist = AgentHistory.objects.create( agent=agent, type=AgentHistoryType.SCRIPT_RUN, script=script, username=username, ) - agent.run_script( - scriptpk=script.pk, - args=args, - timeout=timeout, - history_pk=hist.pk, - run_as_user=run_as_user, - env_vars=env_vars, - ) + data = { + "func": "runscriptfull", + "id": hist.pk, + "timeout": timeout, + "script_args": script.parse_script_args(agent, script.shell, args), + "payload": { + "code": script.code, + "shell": script.shell, + }, + "run_as_user": run_as_user, + "env_vars": env_vars, + } + tup = (agent.agent_id, data) + items.append(tup) + + asyncio.run(abulk_nats_command(items=items)) diff --git a/api/tacticalrmm/tacticalrmm/constants.py b/api/tacticalrmm/tacticalrmm/constants.py index 141e0e5be0..1266c055d6 100644 --- a/api/tacticalrmm/tacticalrmm/constants.py +++ b/api/tacticalrmm/tacticalrmm/constants.py @@ -425,6 +425,7 @@ class DebugLogType(models.TextChoices): {"name": "clear_cache", "methods": ["GET"]}, {"name": "ResetPass", "methods": ["PUT"]}, {"name": "Reset2FA", "methods": ["PUT"]}, + {"name": "bulk_run_checks", "methods": ["GET"]}, ] CONFIG_MGMT_CMDS = ( diff --git a/api/tacticalrmm/tacticalrmm/exceptions.py b/api/tacticalrmm/tacticalrmm/exceptions.py new file mode 100644 index 0000000000..bdd20315cb --- /dev/null +++ b/api/tacticalrmm/tacticalrmm/exceptions.py @@ -0,0 +1,7 @@ +class NatsDown(Exception): + """ + Raised when a connection to NATS cannot be established. + """ + + def __str__(self) -> str: + return "Unable to connect to NATS" diff --git a/api/tacticalrmm/tacticalrmm/helpers.py b/api/tacticalrmm/tacticalrmm/helpers.py index d19674e4bf..b9ec822034 100644 --- a/api/tacticalrmm/tacticalrmm/helpers.py +++ b/api/tacticalrmm/tacticalrmm/helpers.py @@ -1,8 +1,8 @@ +import random from typing import TYPE_CHECKING, Any from urllib.parse import urlparse +from zoneinfo import ZoneInfo -import pytz -import random from django.conf import settings from django.utils import timezone as djangotime from rest_framework import status @@ -42,12 +42,10 @@ def date_is_in_past(*, datetime_obj: "datetime", agent_tz: str) -> bool: """ datetime_obj must be a naive datetime """ - now = djangotime.now() # convert agent tz to UTC to compare - agent_pytz = pytz.timezone(agent_tz) - localized = agent_pytz.localize(datetime_obj) - utc_time = localized.astimezone(pytz.utc) - return now > utc_time + localized = datetime_obj.replace(tzinfo=ZoneInfo(agent_tz)) + utc_time = localized.astimezone(ZoneInfo("UTC")) + return djangotime.now() > utc_time def get_webdomain() -> str: diff --git a/api/tacticalrmm/tacticalrmm/nats_utils.py b/api/tacticalrmm/tacticalrmm/nats_utils.py new file mode 100644 index 0000000000..0ccb41e463 --- /dev/null +++ b/api/tacticalrmm/tacticalrmm/nats_utils.py @@ -0,0 +1,38 @@ +import asyncio +from typing import TYPE_CHECKING, Any + +import msgpack +import nats + +from tacticalrmm.exceptions import NatsDown +from tacticalrmm.helpers import setup_nats_options + +if TYPE_CHECKING: + from nats.aio.client import Client as NClient + +NATS_DATA = dict[str, Any] + +BULK_NATS_TASKS = list[tuple[str, Any]] + + +async def _anats_message(*, nc: "NClient", subject: str, data: "NATS_DATA") -> None: + try: + payload = msgpack.dumps(data) + except: + return + + await nc.publish(subject=subject, payload=payload) + + +async def abulk_nats_command(*, items: "BULK_NATS_TASKS") -> None: + """Fire and forget""" + opts = setup_nats_options() + try: + nc = await nats.connect(**opts) + except Exception: + raise NatsDown + + tasks = [_anats_message(nc=nc, subject=item[0], data=item[1]) for item in items] + await asyncio.gather(*tasks) + await nc.flush() + await nc.close() diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index d6f76e3d90..b54742f755 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -20,21 +20,21 @@ AUTH_USER_MODEL = "accounts.User" # latest release -TRMM_VERSION = "0.15.9" +TRMM_VERSION = "0.15.10" # https://github.com/amidaware/tacticalrmm-web -WEB_VERSION = "0.101.18" +WEB_VERSION = "0.101.20" # bump this version everytime vue code is changed # to alert user they need to manually refresh their browser -APP_VER = "0.0.179" +APP_VER = "0.0.180" # https://github.com/amidaware/rmmagent -LATEST_AGENT_VER = "2.4.6" +LATEST_AGENT_VER = "2.4.7" MESH_VER = "1.1.4" -NATS_SERVER_VER = "2.9.15" +NATS_SERVER_VER = "2.9.16" # for the update script, bump when need to recreate venv PIP_VER = "36" @@ -43,9 +43,6 @@ WHEEL_VER = "0.40.0" AGENT_BASE_URL = "https://agents.tacticalrmm.com" -CHECK_TOKEN_URL = f"{AGENT_BASE_URL}/api/v2/checktoken" -AGENTS_URL = f"{AGENT_BASE_URL}/api/v2/agents/?" -EXE_GEN_URL = f"{AGENT_BASE_URL}/api/v2/exe" DEFAULT_AUTO_FIELD = "django.db.models.AutoField" @@ -77,6 +74,10 @@ with suppress(ImportError): from .local_settings import * # noqa +CHECK_TOKEN_URL = f"{AGENT_BASE_URL}/api/v2/checktoken" +AGENTS_URL = f"{AGENT_BASE_URL}/api/v2/agents/?" +EXE_GEN_URL = f"{AGENT_BASE_URL}/api/v2/exe" + if "GHACTIONS" in os.environ: DEBUG = False ADMIN_ENABLED = False diff --git a/api/tacticalrmm/tacticalrmm/utils.py b/api/tacticalrmm/tacticalrmm/utils.py index 8e8f287748..6c9c2f46fb 100644 --- a/api/tacticalrmm/tacticalrmm/utils.py +++ b/api/tacticalrmm/tacticalrmm/utils.py @@ -7,8 +7,8 @@ from contextlib import contextmanager from functools import wraps from typing import List, Optional, Union +from zoneinfo import ZoneInfo -import pytz import requests from channels.auth import AuthMiddlewareStack from channels.db import database_sync_to_async @@ -100,7 +100,7 @@ def generate_winagent_exe( def get_default_timezone(): - return pytz.timezone(get_core_settings().default_time_zone) + return ZoneInfo(get_core_settings().default_time_zone) def get_bit_days(days: list[str]) -> int: diff --git a/api/tacticalrmm/winupdate/baker_recipes.py b/api/tacticalrmm/winupdate/baker_recipes.py index d066972913..b597d12997 100644 --- a/api/tacticalrmm/winupdate/baker_recipes.py +++ b/api/tacticalrmm/winupdate/baker_recipes.py @@ -1,12 +1,12 @@ from datetime import datetime as dt from itertools import cycle +from zoneinfo import ZoneInfo -import pytz from model_bakery.recipe import Recipe, seq from .models import WinUpdate, WinUpdatePolicy -timezone = pytz.timezone("America/Los_Angeles") +timezone = ZoneInfo("America/Los_Angeles") severity = ["Critical", "Important", "Moderate", "Low", ""] winupdate = Recipe( diff --git a/api/tacticalrmm/winupdate/tasks.py b/api/tacticalrmm/winupdate/tasks.py index 8876d90748..e9de2e0eec 100644 --- a/api/tacticalrmm/winupdate/tasks.py +++ b/api/tacticalrmm/winupdate/tasks.py @@ -2,8 +2,8 @@ import datetime as dt import time from contextlib import suppress +from zoneinfo import ZoneInfo -import pytz from django.utils import timezone as djangotime from packaging import version as pyver @@ -58,7 +58,7 @@ def check_agent_update_schedule_task() -> None: or patch_policy.other == "approve" ): # get current time in agent local time - timezone = pytz.timezone(agent.timezone) + timezone = ZoneInfo(agent.timezone) agent_localtime_now = dt.datetime.now(timezone) weekday = agent_localtime_now.weekday() hour = agent_localtime_now.hour diff --git a/docker/containers/tactical-nats/dockerfile b/docker/containers/tactical-nats/dockerfile index 2f66bd918e..4e0abe6677 100644 --- a/docker/containers/tactical-nats/dockerfile +++ b/docker/containers/tactical-nats/dockerfile @@ -1,4 +1,4 @@ -FROM nats:2.9.15-alpine +FROM nats:2.9.16-alpine ENV TACTICAL_DIR /opt/tactical ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready diff --git a/go.mod b/go.mod index ec82006d81..2735f73187 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.20 require ( github.com/golang/protobuf v1.5.2 // indirect github.com/jmoiron/sqlx v1.3.5 - github.com/lib/pq v1.10.7 - github.com/nats-io/nats-server/v2 v2.9.15 // indirect + github.com/lib/pq v1.10.9 + github.com/nats-io/nats-server/v2 v2.9.16 // indirect github.com/nats-io/nats.go v1.25.0 github.com/ugorji/go/codec v1.2.11 github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139 @@ -19,7 +19,7 @@ require ( github.com/nats-io/nkeys v0.4.4 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/stretchr/testify v1.7.1 // indirect - golang.org/x/crypto v0.6.0 // indirect - golang.org/x/sys v0.5.0 // indirect + golang.org/x/crypto v0.8.0 // indirect + golang.org/x/sys v0.7.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index b1e29b9385..89a77505f2 100644 --- a/go.sum +++ b/go.sum @@ -9,16 +9,16 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= +github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= -github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI= -github.com/nats-io/nats-server/v2 v2.9.15 h1:MuwEJheIwpvFgqvbs20W8Ish2azcygjf4Z0liVu2I4c= -github.com/nats-io/nats-server/v2 v2.9.15/go.mod h1:QlCTy115fqpx4KSOPFIxSV7DdI6OxtZsGOL1JLdeRlE= +github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4= +github.com/nats-io/nats-server/v2 v2.9.16 h1:SuNe6AyCcVy0g5326wtyU8TdqYmcPqzTjhkHojAjprc= +github.com/nats-io/nats-server/v2 v2.9.16/go.mod h1:z1cc5Q+kqJkz9mLUdlcSsdYnId4pyImHjNgoh6zxSC0= github.com/nats-io/nats.go v1.25.0 h1:t5/wCPGciR7X3Mu8QOi4jiJaXaWM8qtkLu4lzGZvYHE= github.com/nats-io/nats.go v1.25.0/go.mod h1:D2WALIhz7V8M0pH8Scx8JZXlg6Oqz5VG+nQkK8nJdvg= github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= @@ -37,11 +37,11 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139 h1:PfOl03o+Y+svWrfXAAu1QWUDePu1yqTq0pf4rpnN8eA= github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139/go.mod h1:ILUz1utl5KgwrxmNHv0RpgMtKeh8gPAABvK2MiXBqv8= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= diff --git a/main.go b/main.go index ecdd42739e..0a2254791c 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ import ( ) var ( - version = "3.4.4" + version = "3.4.5" log = logrus.New() ) diff --git a/natsapi/bin/nats-api b/natsapi/bin/nats-api index 66e205710c..f4f63e480f 100755 Binary files a/natsapi/bin/nats-api and b/natsapi/bin/nats-api differ