Skip to content

Commit

Permalink
Release 0.16.5
Browse files Browse the repository at this point in the history
  • Loading branch information
wh1te909 committed Oct 2, 2023
2 parents fd80ccd + a2e996b commit 7d8c783
Show file tree
Hide file tree
Showing 30 changed files with 461 additions and 47 deletions.
3 changes: 2 additions & 1 deletion api/tacticalrmm/agents/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ def run_script(
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",
Expand All @@ -566,7 +567,7 @@ def run_script(
"shell": script.shell,
},
"run_as_user": run_as_user,
"env_vars": env_vars,
"env_vars": parsed_env_vars,
}

if history_pk != 0:
Expand Down
3 changes: 1 addition & 2 deletions api/tacticalrmm/alerts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,8 +627,7 @@ def parse_script_args(self, args: List[str]) -> List[str]:
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
Expand Down
6 changes: 5 additions & 1 deletion api/tacticalrmm/autotasks/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,11 @@ def get_task_actions(self, obj):
"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:
Expand Down
Empty file.
Empty file.
37 changes: 37 additions & 0 deletions api/tacticalrmm/beta/v1/agent/filter.py
Original file line number Diff line number Diff line change
@@ -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
40 changes: 40 additions & 0 deletions api/tacticalrmm/beta/v1/agent/views.py
Original file line number Diff line number Diff line change
@@ -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
Empty file.
13 changes: 13 additions & 0 deletions api/tacticalrmm/beta/v1/client/views.py
Original file line number Diff line number Diff line change
@@ -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"]
7 changes: 7 additions & 0 deletions api/tacticalrmm/beta/v1/pagination.py
Original file line number Diff line number Diff line change
@@ -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
73 changes: 73 additions & 0 deletions api/tacticalrmm/beta/v1/serializers.py
Original file line number Diff line number Diff line change
@@ -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__"
21 changes: 21 additions & 0 deletions api/tacticalrmm/beta/v1/site/views.py
Original file line number Diff line number Diff line change
@@ -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"]
12 changes: 12 additions & 0 deletions api/tacticalrmm/beta/v1/urls.py
Original file line number Diff line number Diff line change
@@ -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
10 changes: 8 additions & 2 deletions api/tacticalrmm/checks/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,14 @@ def get_env_vars(self, obj):
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
Expand Down
3 changes: 3 additions & 0 deletions api/tacticalrmm/core/agent_linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ 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

Expand Down
5 changes: 4 additions & 1 deletion api/tacticalrmm/core/management/commands/pre_update_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ def handle(self, *args, **kwargs):
self.stdout.write(self.style.WARNING("Cleaning the cache"))
clear_entire_cache()
self.stdout.write(self.style.SUCCESS("Cache was cleared!"))
call_command("fix_dupe_agent_customfields")
try:
call_command("fix_dupe_agent_customfields")
except:
pass
23 changes: 12 additions & 11 deletions api/tacticalrmm/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
adrf==0.1.1
adrf==0.1.2
asgiref==3.7.2
celery==5.3.1
certifi==2023.7.22
cffi==1.15.1
channels==4.0.0
channels_redis==4.1.0
cryptography==41.0.3
cryptography==41.0.4
daphne==4.0.0
Django==4.2.4
Django==4.2.5
django-cors-headers==4.2.0
django-filter==23.3
django-ipware==5.0.0
django-rest-knox==4.2.0
djangorestframework==3.14.0
drf-spectacular==0.26.4
drf-spectacular==0.26.5
hiredis==2.2.3
meshctrl==0.1.15
msgpack==1.0.5
nats-py==2.3.1
msgpack==1.0.7
nats-py==2.4.0
packaging==23.1
psutil==5.9.5
psycopg[binary]==3.1.10
psycopg[binary]==3.1.12
pycparser==2.21
pycryptodome==3.18.0
pycryptodome==3.19.0
pyotp==2.9.0
pyparsing==3.1.1
pytz==2023.3
Expand All @@ -30,10 +31,10 @@ redis==4.5.5
requests==2.31.0
six==1.16.0
sqlparse==0.4.4
twilio==8.5.0
urllib3==2.0.4
twilio==8.9.0
urllib3==2.0.5
uWSGI==2.0.22
validators==0.20.0
vine==5.0.0
websockets==11.0.3
zipp==3.16.2
zipp==3.17.0
40 changes: 38 additions & 2 deletions api/tacticalrmm/scripts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ def serialize(script):
return ScriptSerializer(script).data

@classmethod
# TODO refactor common functionality of parse functions
def parse_script_args(cls, agent, shell: str, args: List[str] = []) -> list:
if not args:
return []
Expand All @@ -204,8 +205,7 @@ def parse_script_args(cls, agent, shell: str, args: List[str] = []) -> list:
pattern = re.compile(".*\\{\\{(.*)\\}\\}.*")

for arg in args:
match = pattern.match(arg)
if match:
if match := pattern.match(arg):
# only get the match between the () in regex
string = match.group(1)
value = replace_db_values(
Expand All @@ -231,6 +231,42 @@ def parse_script_args(cls, agent, shell: str, args: List[str] = []) -> list:

return temp_args

@classmethod
# TODO refactor common functionality of parse functions
def parse_script_env_vars(cls, agent, shell: str, env_vars: list[str] = []) -> list:
if not env_vars:
return []

temp_env_vars = []
pattern = re.compile(".*\\{\\{(.*)\\}\\}.*")
for env_var in env_vars:
# must be in format KEY=VALUE
try:
env_key = env_var.split("=")[0]
env_val = env_var.split("=")[1]
except:
continue
if match := pattern.match(env_val):
string = match.group(1)
value = replace_db_values(
string=string,
instance=agent,
shell=shell,
quotes=False,
)

if value:
try:
new_val = re.sub("\\{\\{.*\\}\\}", value, env_val)
except re.error:
new_val = re.sub("\\{\\{.*\\}\\}", re.escape(value), env_val)
temp_env_vars.append(f"{env_key}={new_val}")
else:
# pass parameter unaltered
temp_env_vars.append(env_var)

return temp_env_vars


class ScriptSnippet(models.Model):
name = CharField(max_length=40, unique=True)
Expand Down
Loading

0 comments on commit 7d8c783

Please sign in to comment.