diff --git a/api/tacticalrmm/agents/migrations/0003_agent_checks_last_generated.py b/api/tacticalrmm/agents/migrations/0003_agent_checks_last_generated.py new file mode 100644 index 00000000..3eb2a4fa --- /dev/null +++ b/api/tacticalrmm/agents/migrations/0003_agent_checks_last_generated.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-06-04 17:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('agents', '0002_auto_20200531_2058'), + ] + + operations = [ + migrations.AddField( + model_name='agent', + name='checks_last_generated', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/api/tacticalrmm/agents/migrations/0004_auto_20200604_1721.py b/api/tacticalrmm/agents/migrations/0004_auto_20200604_1721.py new file mode 100644 index 00000000..5112c4c3 --- /dev/null +++ b/api/tacticalrmm/agents/migrations/0004_auto_20200604_1721.py @@ -0,0 +1,22 @@ +# Generated by Django 3.0.6 on 2020-06-04 17:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('agents', '0003_agent_checks_last_generated'), + ] + + operations = [ + migrations.RemoveField( + model_name='agent', + name='checks_last_generated', + ), + migrations.AddField( + model_name='agent', + name='policies_pending', + field=models.BooleanField(default=False), + ), + ] diff --git a/api/tacticalrmm/agents/migrations/0005_agent_policy.py b/api/tacticalrmm/agents/migrations/0005_agent_policy.py new file mode 100644 index 00000000..478271b5 --- /dev/null +++ b/api/tacticalrmm/agents/migrations/0005_agent_policy.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.7 on 2020-06-09 16:07 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('automation', '0003_auto_20200609_1607'), + ('agents', '0004_auto_20200604_1721'), + ] + + operations = [ + migrations.AddField( + model_name='agent', + name='policy', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='agents', to='automation.Policy'), + ), + ] diff --git a/api/tacticalrmm/agents/models.py b/api/tacticalrmm/agents/models.py index 1883fca6..0107eb4b 100644 --- a/api/tacticalrmm/agents/models.py +++ b/api/tacticalrmm/agents/models.py @@ -16,6 +16,8 @@ from django.contrib.postgres.fields import JSONField from core.models import TZ_CHOICES +import automation + class Agent(models.Model): version = models.CharField(default="0.1.0", max_length=255) @@ -50,9 +52,17 @@ class Agent(models.Model): is_updating = models.BooleanField(default=False) choco_installed = models.BooleanField(default=False) wmi_detail = JSONField(null=True) + policies_pending = models.BooleanField(default=False) time_zone = models.CharField( max_length=255, choices=TZ_CHOICES, null=True, blank=True ) + policy = models.ForeignKey( + "automation.Policy", + related_name="agents", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) def __str__(self): return self.hostname @@ -185,6 +195,21 @@ class Agent(models.Model): except: return [{"model": "unknown", "size": "unknown", "interfaceType": "unknown"}] + + def generate_checks_from_policies(self): + # Clear agent checks managed by policy + self.agentchecks.filter(managed_by_policy=True).delete() + + # Clear agent checks that have overriden_by_policy set + self.agentchecks.update(overriden_by_policy=False) + + # Generate checks based on policies + automation.models.Policy.generate_policy_checks(self) + + # Set policies_pending to false to disable policy generation on next checkin + self.policies_pending = False + self.save() + # https://github.com/Ylianst/MeshCentral/issues/59#issuecomment-521965347 def get_login_token(self, key, user, action=3): key = bytes.fromhex(key) diff --git a/api/tacticalrmm/api/views.py b/api/tacticalrmm/api/views.py index 3d9e8e0f..f87964f6 100644 --- a/api/tacticalrmm/api/views.py +++ b/api/tacticalrmm/api/views.py @@ -311,7 +311,11 @@ class CheckRunner(APIView): def get(self, request, pk): agent = get_object_or_404(Agent, pk=pk) - checks = Check.objects.filter(agent__pk=pk) + + if agent.policies_pending: + agent.generate_checks_from_policies() + + checks = Check.objects.filter(agent__pk=pk, overriden_by_policy=False) ret = { "agent": agent.pk, diff --git a/api/tacticalrmm/automation/__init__.py b/api/tacticalrmm/automation/__init__.py index e69de29b..03c8c50f 100644 --- a/api/tacticalrmm/automation/__init__.py +++ b/api/tacticalrmm/automation/__init__.py @@ -0,0 +1 @@ +default_app_config = 'automation.apps.AutomationConfig' diff --git a/api/tacticalrmm/automation/admin.py b/api/tacticalrmm/automation/admin.py index 9d02b116..e9b99f89 100644 --- a/api/tacticalrmm/automation/admin.py +++ b/api/tacticalrmm/automation/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin -from .models import Policy +from .models import Policy, PolicyExclusions admin.site.register(Policy) +admin.site.register(PolicyExclusions) diff --git a/api/tacticalrmm/automation/apps.py b/api/tacticalrmm/automation/apps.py index dc3a0a7d..c34c8d53 100644 --- a/api/tacticalrmm/automation/apps.py +++ b/api/tacticalrmm/automation/apps.py @@ -1,5 +1,9 @@ from django.apps import AppConfig - class AutomationConfig(AppConfig): name = "automation" + + def ready(self): + + # registering signals defined in signals.py + import automation.signals \ No newline at end of file diff --git a/api/tacticalrmm/automation/migrations/0002_auto_20200604_1713.py b/api/tacticalrmm/automation/migrations/0002_auto_20200604_1713.py new file mode 100644 index 00000000..001229d1 --- /dev/null +++ b/api/tacticalrmm/automation/migrations/0002_auto_20200604_1713.py @@ -0,0 +1,31 @@ +# Generated by Django 3.0.6 on 2020-06-04 17:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('clients', '0002_auto_20200531_2058'), + ('agents', '0003_agent_checks_last_generated'), + ('automation', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='policy', + name='enforced', + field=models.BooleanField(default=False), + ), + migrations.CreateModel( + name='PolicyExclusions', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('agents', models.ManyToManyField(related_name='policy_exclusions', to='agents.Agent')), + ('clients', models.ManyToManyField(related_name='policy_exclusions', to='clients.Client')), + ('policy', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='exclusions', to='automation.Policy')), + ('sites', models.ManyToManyField(related_name='policy_exclusions', to='clients.Site')), + ], + ), + ] diff --git a/api/tacticalrmm/automation/migrations/0003_auto_20200609_1607.py b/api/tacticalrmm/automation/migrations/0003_auto_20200609_1607.py new file mode 100644 index 00000000..6add9f38 --- /dev/null +++ b/api/tacticalrmm/automation/migrations/0003_auto_20200609_1607.py @@ -0,0 +1,29 @@ +# Generated by Django 3.0.7 on 2020-06-09 16:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('automation', '0002_auto_20200604_1713'), + ] + + operations = [ + migrations.RemoveField( + model_name='policy', + name='agents', + ), + migrations.RemoveField( + model_name='policy', + name='clients', + ), + migrations.RemoveField( + model_name='policy', + name='sites', + ), + migrations.RemoveField( + model_name='policyexclusions', + name='clients', + ), + ] diff --git a/api/tacticalrmm/automation/models.py b/api/tacticalrmm/automation/models.py index e3ada217..6f370410 100644 --- a/api/tacticalrmm/automation/models.py +++ b/api/tacticalrmm/automation/models.py @@ -6,9 +6,7 @@ class Policy(models.Model): name = models.CharField(max_length=255, unique=True) desc = models.CharField(max_length=255) active = models.BooleanField(default=False) - agents = models.ManyToManyField(Agent, related_name="policies") - sites = models.ManyToManyField(Site, related_name="policies") - clients = models.ManyToManyField(Client, related_name="policies") + enforced = models.BooleanField(default=False) def __str__(self): return self.name @@ -30,8 +28,160 @@ class Policy(models.Model): for site in client.sites.all(): filtered_sites_ids.append(site.site) - site_agents = Agent.objects.filter(site__in=filtered_sites_ids) - client_agents = Agent.objects.filter(client__in=client_ids) - - # Combine querysets and remove duplicates - return explicit_agents.union(site_agents, client_agents) + return Agent.objects.filter(models.Q(pk__in=explicit_agents.only("pk")) | models.Q(site__in=filtered_sites_ids) | models.Q(client__in=client_ids)).distinct() + + @staticmethod + def cascade_policy_checks(agent): + # Get checks added to agent directly + agent_checks = list(agent.agentchecks.filter(managed_by_policy=False)) + + # Get policies applied to agent and agent site and client + client_policy = Client.objects.get(client=agent.client).policy + site_policy = Site.objects.get(site=agent.site).policy + agent_policy = agent.policy + + # Used to hold the policies that will be applied and the order in which they are applied + # Enforced policies are applied first + enforced_checks = list() + policy_checks = list() + + if agent_policy != None: + if agent_policy.active: + if agent_policy.enforced: + for check in agent_policy.policychecks.all(): + enforced_checks.append(check) + else: + for check in agent_policy.policychecks.all(): + policy_checks.append(check) + + if site_policy != None: + if site_policy.active: + if site_policy.enforced: + for check in site_policy.policychecks.all(): + enforced_checks.append(check) + else: + for check in site_policy.policychecks.all(): + policy_checks.append(check) + + if client_policy != None: + if client_policy.active: + if client_policy.enforced: + for check in client_policy.policychecks.all(): + enforced_checks.append(check) + else: + for check in client_policy.policychecks.all(): + policy_checks.append(check) + + # Sorted Checks already added + added_diskspace_checks = list() + added_ping_checks = list() + added_winsvc_checks = list() + added_script_checks = list() + added_eventlog_checks = list() + added_cpuload_checks = list() + added_memory_checks = list() + + # Lists all agent and policy checks that will be created + diskspace_checks = list() + ping_checks = list() + winsvc_checks = list() + script_checks = list() + eventlog_checks = list() + cpuload_checks = list() + memory_checks = list() + + # Loop over checks in with enforced policies first, then non-enforced policies + for check in enforced_checks + agent_checks + policy_checks: + if check.check_type == "diskspace": + # Check if drive letter was already added + if check.disk not in added_diskspace_checks: + added_diskspace_checks.append(check.disk) + # Dont create the check if it is an agent check + if check.agent == None: + diskspace_checks.append(check) + elif check.agent != None: + check.overriden_by_policy = True + check.save() + + if check.check_type == "ping": + # Check if IP/host was already added + if check.ip not in added_ping_checks: + added_ping_checks.append(check.ip) + # Dont create the check if it is an agent check + if check.agent == None: + ping_checks.append(check) + added_ping_checks.append(check.ip) + elif check.agent != None: + check.overriden_by_policy = True + check.save() + + if check.check_type == "cpuload": + # Check if cpuload check exists + if len(added_cpuload_checks) == 0: + added_cpuload_checks.append(check) + # Dont create the check if it is an agent check + if check.agent == None: + cpuload_checks.append(check) + elif check.agent != None: + check.overriden_by_policy = True + check.save() + + if check.check_type == "memory": + # Check if memory check exists + if len(added_memory_checks) == 0: + added_memory_checks.append(check) + # Dont create the check if it is an agent check + if check.agent == None: + memory_checks.append(check) + elif check.agent != None: + check.overriden_by_policy = True + check.save() + + if check.check_type == "winsvc": + # Check if service name was already added + if check.svc_name not in added_winsvc_checks: + added_winsvc_checks.append(check.svc_name) + # Dont create the check if it is an agent check + if check.agent == None: + winsvc_checks.append(check) + elif check.agent != None: + check.overriden_by_policy = True + check.save() + + if check.check_type == "script": + # Check if script id was already added + if check.script not in added_script_checks: + added_script_checks.append(check.script) + # Dont create the check if it is an agent check + if check.agent == None: + script_checks.append(check) + elif check.agent != None: + check.overriden_by_policy = True + check.save() + + if check.check_type == "eventlog": + # Check if events were already added + if [check.log_name, check.event_id] not in added_eventlog_checks: + added_eventlog_checks.append([check.log_name, check.event_id]) + if check.agent == None: + eventlog_checks.append(check) + elif check.agent != None: + check.overriden_by_policy = True + check.save() + + return diskspace_checks + ping_checks + cpuload_checks + memory_checks + winsvc_checks + script_checks + eventlog_checks + + @staticmethod + def generate_policy_checks(agent): + checks = Policy.cascade_policy_checks(agent) + + if checks != None: + if len(checks) > 0: + for check in checks: + check.create_policy_check(agent) + + +class PolicyExclusions(models.Model): + policy = models.ForeignKey(Policy, related_name="exclusions", on_delete=models.CASCADE) + agents = models.ManyToManyField(Agent, related_name="policy_exclusions") + sites = models.ManyToManyField(Site, related_name="policy_exclusions") diff --git a/api/tacticalrmm/automation/serializers.py b/api/tacticalrmm/automation/serializers.py index 81151e4f..5da19fbb 100644 --- a/api/tacticalrmm/automation/serializers.py +++ b/api/tacticalrmm/automation/serializers.py @@ -1,25 +1,73 @@ -from rest_framework import serializers +from rest_framework.serializers import ( + ModelSerializer, + SerializerMethodField, + StringRelatedField, + ReadOnlyField, + ValidationError +) from .models import Policy from autotasks.models import AutomatedTask +from checks.models import Check +from clients.models import Client from autotasks.serializers import TaskSerializer from checks.serializers import CheckSerializer -class PolicySerializer(serializers.ModelSerializer): +class PolicySerializer(ModelSerializer): class Meta: model = Policy fields = "__all__" -class PolicyRelationSerializer(serializers.ModelSerializer): +class PolicyTableSerializer(ModelSerializer): + + clients = StringRelatedField(many=True, read_only=True) + sites = StringRelatedField(many=True, read_only=True) + agents = StringRelatedField(many=True, read_only=True) + + clients_count = SerializerMethodField(read_only=True) + sites_count = SerializerMethodField(read_only=True) + agents_count = SerializerMethodField(read_only=True) + class Meta: model = Policy fields = "__all__" + depth = 1 + + def get_clients_count(self, policy): + return policy.clients.count() + + def get_sites_count(self, policy): + return policy.sites.count() + + def get_agents_count(self, policy): + return policy.agents.count() + + +class PolicyOverviewSerializer(ModelSerializer): + + class Meta: + model = Client + fields = ( + "pk", + "client", + "sites", + "policy" + ) depth = 2 -class AutoTaskPolicySerializer(serializers.ModelSerializer): +class PolicyCheckStatusSerializer(ModelSerializer): + + hostname = ReadOnlyField(source="agent.hostname") + + class Meta: + model = Check + fields = "__all__" + + +class AutoTaskPolicySerializer(ModelSerializer): autotasks = TaskSerializer(many=True, read_only=True) diff --git a/api/tacticalrmm/automation/signals.py b/api/tacticalrmm/automation/signals.py new file mode 100644 index 00000000..e495d1c6 --- /dev/null +++ b/api/tacticalrmm/automation/signals.py @@ -0,0 +1,51 @@ +from django.db.models.signals import post_save, post_delete, m2m_changed +from django.dispatch import receiver + +from automation.models import Policy +from agents.models import Agent + +def set_agent_policy_update_field(policy_list, many=False): + + if many: + for policy in policy_list: + policy.related_agents().update(policies_pending=True) + else: + policy_list.related_agents().update(policies_pending=True) + +@receiver(post_save, sender="checks.Check") +def post_save_check_handler(sender, instance, created, **kwargs): + + # don't run when policy managed check is saved + if instance.managed_by_policy == True: + return + + # For created checks + if created: + if instance.policy != None: + set_agent_policy_update_field(instance.policy) + elif instance.agent != None: + instance.agent.policies_pending=True + instance.agent.save() + + # Checks that are updated except for agent + else: + if instance.policy != None: + set_agent_policy_update_field(instance.policy) + +@receiver(post_delete, sender="checks.Check") +def post_delete_check_handler(sender, instance, **kwargs): + + # don't run when policy managed check is saved + if instance.managed_by_policy == True: + return + + if instance.policy != None: + set_agent_policy_update_field(instance.policy) + elif instance.agent != None: + instance.agent.policies_pending=True + instance.agent.save() + +@receiver([post_save, post_delete], sender="automation.Policy") +def post_save_policy_handler(sender, instance, **kwargs): + + set_agent_policy_update_field(instance) diff --git a/api/tacticalrmm/automation/urls.py b/api/tacticalrmm/automation/urls.py index d287280a..e3a695ec 100644 --- a/api/tacticalrmm/automation/urls.py +++ b/api/tacticalrmm/automation/urls.py @@ -9,7 +9,7 @@ urlpatterns = [ path("policies//", views.GetUpdateDeletePolicy.as_view()), path("/policychecks/", views.PolicyCheck.as_view()), path("/policyautomatedtasks/", views.PolicyAutoTask.as_view()), - path("/policycheckstatus//check/", views.PolicyCheck.as_view()), - path("/policyautomatedtaskstatus//task/", views.PolicyAutoTask.as_view()), - path("runwintask//", views.RunPolicyTask.as_view()), + path("policycheckstatus//check/", views.PolicyCheck.as_view()), + path("policyautomatedtaskstatus//task/", views.PolicyAutoTask.as_view()), + path("runwintask//", views.PolicyAutoTask.as_view()), ] diff --git a/api/tacticalrmm/automation/views.py b/api/tacticalrmm/automation/views.py index f0457ba0..37c9d0bc 100644 --- a/api/tacticalrmm/automation/views.py +++ b/api/tacticalrmm/automation/views.py @@ -20,8 +20,10 @@ from agents.serializers import AgentHostnameSerializer from .serializers import ( PolicySerializer, - PolicyRelationSerializer, - AutoTaskPolicySerializer, + PolicyTableSerializer, + PolicyOverviewSerializer, + PolicyCheckStatusSerializer, + AutoTaskPolicySerializer ) from checks.serializers import CheckSerializer @@ -31,30 +33,24 @@ class GetAddPolicies(APIView): policies = Policy.objects.all() - return Response(PolicyRelationSerializer(policies, many=True).data) + return Response(PolicyTableSerializer(policies, many=True).data) def post(self, request): name = request.data["name"].strip() desc = request.data["desc"].strip() active = request.data["active"] + enforced = active = request.data["enforced"] if Policy.objects.filter(name=name): content = {"error": f"Policy {name} already exists"} return Response(content, status=status.HTTP_400_BAD_REQUEST) try: - policy = Policy.objects.create(name=name, desc=desc, active=active) + policy = Policy.objects.create(name=name, desc=desc, active=active, enforced=enforced) except DataError: content = {"error": "Policy name too long (max 255 chars)"} return Response(content, status=status.HTTP_400_BAD_REQUEST) - # Add Clients, Sites to Policy - if len(request.data["clients"]) > 0: - policy.clients.set(request.data["clients"]) - - if len(request.data["sites"]) > 0: - policy.sites.set(request.data["sites"]) - return Response("ok") @@ -63,7 +59,7 @@ class GetUpdateDeletePolicy(APIView): policy = get_object_or_404(Policy, pk=pk) - return Response(PolicyRelationSerializer(policy).data) + return Response(PolicySerializer(policy).data) def put(self, request, pk): @@ -72,24 +68,14 @@ class GetUpdateDeletePolicy(APIView): policy.name = request.data["name"] policy.desc = request.data["desc"] policy.active = request.data["active"] + policy.enforced = request.data["enforced"] try: - policy.save(update_fields=["name", "desc", "active"]) + policy.save(update_fields=["name", "desc", "active", "enforced"]) except DataError: content = {"error": "Policy name too long (max 255 chars)"} return Response(content, status=status.HTTP_400_BAD_REQUEST) - # Update Clients, Sites to Policy - if len(request.data["clients"]) > 0: - policy.clients.set(request.data["clients"]) - else: - policy.clients.clear() - - if len(request.data["sites"]) > 0: - policy.sites.set(request.data["sites"]) - else: - policy.sites.clear() - return Response("ok") def delete(self, request, pk): @@ -109,26 +95,30 @@ class PolicyAutoTask(APIView): # TODO pull agents and status for policy task return Response(list()) - -class RunPolicyTask(APIView): - def get(self, request, pk): - - # TODO: Run task for all Agents under policy + + def put(self, request, pk): return Response("ok") + class PolicyCheck(APIView): def get(self, request, pk): - checks = Check.objects.filter(policy__pk=pk) + checks = Check.objects.filter(policy__pk=pk, agent=None) return Response(CheckSerializer(checks, many=True).data) - def patch(self, request, policy, check): + def patch(self, request, check): - # TODO pull agents and status for policy check + checks = Check.objects.filter(parent_check=check) + return Response(PolicyCheckStatusSerializer(checks, many=True).data) + + def put(self, request): + + # TODO run policy check manually for all agents return Response(list()) + class OverviewPolicy(APIView): def get(self, request): - + """ clients = Client.objects.all() response = {} @@ -154,8 +144,11 @@ class OverviewPolicy(APIView): response[client.client] = client_sites - return Response(response) + """ + clients = Client.objects.all() + return Response(PolicyOverviewSerializer(clients, many=True).data) + class GetRelated(APIView): def get(self, request, pk): @@ -182,35 +175,29 @@ class GetRelated(APIView): def post(self, request): # Update Agents, Clients, Sites to Policy - policies = request.data["policies"] related_type = request.data["type"] pk = request.data["pk"] - if len(policies) > 0: + if request.data["policy"] != 0: + policy = Policy.objects.get(pk=request.data["policy"]) if related_type == "client": - client = get_object_or_404(Client, pk=pk) - client.policies.set(policies) + Client.objects.filter(pk=pk).update(policy=policy) if related_type == "site": - site = get_object_or_404(Site, pk=pk) - site.policies.set(policies) + Site.objects.filter(pk=pk).update(policy=policy) if related_type == "agent": - agent = get_object_or_404(Agent, pk=pk) - agent.policies.set(policies) + Agent.objects.filter(pk=pk).update(policy=policy, policies_pending=True) else: if related_type == "client": - client = get_object_or_404(Client, pk=pk) - client.policies.clear() + Client.objects.filter(pk=pk).update(policy=None) if related_type == "site": - site = get_object_or_404(Site, pk=pk) - site.policies.clear() + Site.objects.filter(pk=pk).update(policy=None) if related_type == "agent": - agent = get_object_or_404(Agent, pk=pk) - agent.policies.clear() + Agent.objects.filter(pk=pk).update(policy=None, policies_pending=True) return Response("ok") @@ -219,16 +206,16 @@ class GetRelated(APIView): pk = request.data["pk"] if related_type == "agent": - agent = get_object_or_404(Agent, pk=pk) - return Response(PolicySerializer(agent.policies.all(), many=True).data) + policy = Policy.objects.filter(agents__pk=pk).first() + return Response(PolicySerializer(policy).data) if related_type == "site": - site = get_object_or_404(Site, pk=pk) - return Response(PolicySerializer(site.policies.all(), many=True).data) + policy = Policy.objects.filter(sites__pk=pk).first() + return Response(PolicySerializer(policy).data) if related_type == "client": - client = get_object_or_404(Client, pk=pk) - return Response(PolicySerializer(client.policies.all(), many=True).data) + policy = Policy.objects.filter(clients__pk=pk).first() + return Response(PolicySerializer(policy).data) content = {"error": "Data was submitted incorrectly"} return Response(content, status=status.HTTP_400_BAD_REQUEST) diff --git a/api/tacticalrmm/checks/migrations/0002_check_managed_by_policy.py b/api/tacticalrmm/checks/migrations/0002_check_managed_by_policy.py new file mode 100644 index 00000000..63239269 --- /dev/null +++ b/api/tacticalrmm/checks/migrations/0002_check_managed_by_policy.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-06-04 17:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('checks', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='check', + name='managed_by_policy', + field=models.BooleanField(default=False), + ), + ] diff --git a/api/tacticalrmm/checks/migrations/0003_check_overriden_by_policy.py b/api/tacticalrmm/checks/migrations/0003_check_overriden_by_policy.py new file mode 100644 index 00000000..1e62a427 --- /dev/null +++ b/api/tacticalrmm/checks/migrations/0003_check_overriden_by_policy.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-06-04 17:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('checks', '0002_check_managed_by_policy'), + ] + + operations = [ + migrations.AddField( + model_name='check', + name='overriden_by_policy', + field=models.BooleanField(default=False), + ), + ] diff --git a/api/tacticalrmm/checks/migrations/0004_check_parent_check.py b/api/tacticalrmm/checks/migrations/0004_check_parent_check.py new file mode 100644 index 00000000..f6cdf00c --- /dev/null +++ b/api/tacticalrmm/checks/migrations/0004_check_parent_check.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-06-07 00:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('checks', '0003_check_overriden_by_policy'), + ] + + operations = [ + migrations.AddField( + model_name='check', + name='parent_check', + field=models.PositiveIntegerField(blank=True, null=True), + ), + ] diff --git a/api/tacticalrmm/checks/models.py b/api/tacticalrmm/checks/models.py index 5a0007cd..e50ec37e 100644 --- a/api/tacticalrmm/checks/models.py +++ b/api/tacticalrmm/checks/models.py @@ -10,6 +10,8 @@ from django.core.validators import MinValueValidator, MaxValueValidator from core.models import CoreSettings +import agents + from .tasks import handle_check_email_alert_task CHECK_TYPE_CHOICES = [ @@ -66,6 +68,9 @@ class Check(models.Model): blank=True, on_delete=models.CASCADE, ) + managed_by_policy = models.BooleanField(default=False) + overriden_by_policy = models.BooleanField(default=False) + parent_check = models.PositiveIntegerField(null=True, blank=True) name = models.CharField(max_length=255, null=True, blank=True) check_type = models.CharField( max_length=50, choices=CHECK_TYPE_CHOICES, default="diskspace" @@ -212,6 +217,35 @@ class Check(models.Model): return default_services + def create_policy_check(self, agent): + Check.objects.create( + agent=agent, + policy=self.policy, + managed_by_policy=True, + parent_check=self.pk, + name=self.name, + check_type=self.check_type, + email_alert=self.email_alert, + text_alert=self.text_alert, + fails_b4_alert=self.fails_b4_alert, + extra_details=self.extra_details, + threshold=self.threshold, + disk=self.disk, + ip=self.ip, + script=self.script, + timeout=self.timeout, + svc_name=self.svc_name, + svc_display_name=self.svc_display_name, + pass_if_start_pending=self.pass_if_start_pending, + restart_if_stopped=self.restart_if_stopped, + svc_policy_mode=self.svc_policy_mode, + log_name=self.log_name, + event_id=self.event_id, + event_type=self.event_type, + fail_when=self.fail_when, + search_last_days=self.search_last_days, + ) + def send_email(self): CORE = CoreSettings.objects.first() diff --git a/api/tacticalrmm/checks/serializers.py b/api/tacticalrmm/checks/serializers.py index e82e056b..662c64ce 100644 --- a/api/tacticalrmm/checks/serializers.py +++ b/api/tacticalrmm/checks/serializers.py @@ -38,7 +38,7 @@ class CheckSerializer(serializers.ModelSerializer): # disk checks # make sure no duplicate diskchecks exist for an agent/policy if check_type == "diskspace" and not self.instance: # only on create - checks = Check.objects.filter(**self.context).filter(check_type="diskspace") + checks = Check.objects.filter(**self.context).filter(check_type="diskspace").exclude(managed_by_policy=True) if checks: for check in checks: if val["disk"] in check.disk: diff --git a/api/tacticalrmm/checks/views.py b/api/tacticalrmm/checks/views.py index 89311092..cfa8af14 100644 --- a/api/tacticalrmm/checks/views.py +++ b/api/tacticalrmm/checks/views.py @@ -68,6 +68,7 @@ class GetUpdateDeleteCheck(APIView): def delete(self, request, pk): check = get_object_or_404(Check, pk=pk) check.delete() + return Response(f"{check.readable_desc} was deleted!") diff --git a/api/tacticalrmm/clients/migrations/0003_auto_20200609_1607.py b/api/tacticalrmm/clients/migrations/0003_auto_20200609_1607.py new file mode 100644 index 00000000..c78a862b --- /dev/null +++ b/api/tacticalrmm/clients/migrations/0003_auto_20200609_1607.py @@ -0,0 +1,25 @@ +# Generated by Django 3.0.7 on 2020-06-09 16:07 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('automation', '0003_auto_20200609_1607'), + ('clients', '0002_auto_20200531_2058'), + ] + + operations = [ + migrations.AddField( + model_name='client', + name='policy', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='clients', to='automation.Policy'), + ), + migrations.AddField( + model_name='site', + name='policy', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sites', to='automation.Policy'), + ), + ] diff --git a/api/tacticalrmm/clients/models.py b/api/tacticalrmm/clients/models.py index 9e0e9fc5..37be469d 100644 --- a/api/tacticalrmm/clients/models.py +++ b/api/tacticalrmm/clients/models.py @@ -4,6 +4,13 @@ from agents.models import Agent class Client(models.Model): client = models.CharField(max_length=255, unique=True) + policy = models.ForeignKey( + "automation.Policy", + related_name="clients", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) def __str__(self): return self.client @@ -22,6 +29,13 @@ class Client(models.Model): class Site(models.Model): client = models.ForeignKey(Client, related_name="sites", on_delete=models.CASCADE) site = models.CharField(max_length=255) + policy = models.ForeignKey( + "automation.Policy", + related_name="sites", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) def __str__(self): return self.site diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 4b9dda1c..ba699fad 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -6,7 +6,7 @@ services: # Container that hosts Vue frontend app: image: node:12 - command: /bin/bash -c "npm install --force && npm run serve -- --host 0.0.0.0 --port 80 --public ${APP_HOST}" + command: /bin/bash -c "npm install && npm run serve -- --host 0.0.0.0 --port 80 --public ${APP_HOST}" working_dir: /home/node volumes: - ../web:/home/node diff --git a/docker/readme.md b/docker/readme.md index 07b73f1c..7e3d3525 100644 --- a/docker/readme.md +++ b/docker/readme.md @@ -91,29 +91,9 @@ This allows you to edit the files locally and those changes will be presented to Files that need to be manually created are: - api/tacticalrmm/tacticalrmm/local_settings.py -- web/.env.local +- web/.env -For HMR to work with vue you may need to alter the web/vue.config.js file to with these changes - -``` - devServer: { - //host: "192.168.99.150", - disableHostCheck: true, - public: "YOUR_APP_URL" - }, -``` - -Since this file is checked into git you can configure git to ignore it and the changes will stay intact - -``` -git update-index --assume-unchanged ./web/vue.config.js -``` - -To revert this run - -``` -git update-index --no-assume-unchanged ./web/vue.config.js -``` +For HMR to work with vue you can copy .env.example and modify the setting to fit your dev environment. ### Create Python Virtual Env diff --git a/web/src/components/ChecksTab.vue b/web/src/components/ChecksTab.vue index 5192e81d..c8bd95c5 100644 --- a/web/src/components/ChecksTab.vue +++ b/web/src/components/ChecksTab.vue @@ -83,6 +83,9 @@ +