diff --git a/api/tacticalrmm/beta/v1/__init__.py b/api/tacticalrmm/beta/v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/tacticalrmm/beta/v1/agent/__init__.py b/api/tacticalrmm/beta/v1/agent/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/tacticalrmm/beta/v1/agent/filter.py b/api/tacticalrmm/beta/v1/agent/filter.py new file mode 100644 index 00000000..4d1153a1 --- /dev/null +++ b/api/tacticalrmm/beta/v1/agent/filter.py @@ -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 diff --git a/api/tacticalrmm/beta/v1/agent/views.py b/api/tacticalrmm/beta/v1/agent/views.py new file mode 100644 index 00000000..dfcc21e2 --- /dev/null +++ b/api/tacticalrmm/beta/v1/agent/views.py @@ -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 diff --git a/api/tacticalrmm/beta/v1/client/__init__.py b/api/tacticalrmm/beta/v1/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/tacticalrmm/beta/v1/client/views.py b/api/tacticalrmm/beta/v1/client/views.py new file mode 100644 index 00000000..c0539fa8 --- /dev/null +++ b/api/tacticalrmm/beta/v1/client/views.py @@ -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"] diff --git a/api/tacticalrmm/beta/v1/pagination.py b/api/tacticalrmm/beta/v1/pagination.py new file mode 100644 index 00000000..072c14d0 --- /dev/null +++ b/api/tacticalrmm/beta/v1/pagination.py @@ -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 diff --git a/api/tacticalrmm/beta/v1/serializers.py b/api/tacticalrmm/beta/v1/serializers.py new file mode 100644 index 00000000..595d41b1 --- /dev/null +++ b/api/tacticalrmm/beta/v1/serializers.py @@ -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__" diff --git a/api/tacticalrmm/beta/v1/site/views.py b/api/tacticalrmm/beta/v1/site/views.py new file mode 100644 index 00000000..0d061979 --- /dev/null +++ b/api/tacticalrmm/beta/v1/site/views.py @@ -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"] diff --git a/api/tacticalrmm/beta/v1/urls.py b/api/tacticalrmm/beta/v1/urls.py new file mode 100644 index 00000000..44e4c926 --- /dev/null +++ b/api/tacticalrmm/beta/v1/urls.py @@ -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 diff --git a/api/tacticalrmm/requirements.txt b/api/tacticalrmm/requirements.txt index 8c569273..8639a463 100644 --- a/api/tacticalrmm/requirements.txt +++ b/api/tacticalrmm/requirements.txt @@ -9,6 +9,7 @@ cryptography==41.0.4 daphne==4.0.0 Django==4.2.5 django-cors-headers==4.2.0 +django-filter==23.2 django-ipware==5.0.0 django-rest-knox==4.2.0 djangorestframework==3.14.0 diff --git a/api/tacticalrmm/tacticalrmm/urls.py b/api/tacticalrmm/tacticalrmm/urls.py index 9d745986..75db207f 100644 --- a/api/tacticalrmm/tacticalrmm/urls.py +++ b/api/tacticalrmm/tacticalrmm/urls.py @@ -38,6 +38,7 @@ urlpatterns = [ path("scripts/", include("scripts.urls")), path("alerts/", include("alerts.urls")), path("accounts/", include("accounts.urls")), + path("beta/v1/", include("beta.v1.urls")), ] if getattr(settings, "ADMIN_ENABLED", False):