Merge pull request #22 from sadnub/feature-policies-alerts
Policy Check Finish
This commit is contained in:
commit
8dbf5d37c2
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
default_app_config = 'automation.apps.AutomationConfig'
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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')),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -9,7 +9,7 @@ urlpatterns = [
|
|||
path("policies/<int:pk>/", views.GetUpdateDeletePolicy.as_view()),
|
||||
path("<int:pk>/policychecks/", views.PolicyCheck.as_view()),
|
||||
path("<int:pk>/policyautomatedtasks/", views.PolicyAutoTask.as_view()),
|
||||
path("<int:policy>/policycheckstatus/<int:check>/check/", views.PolicyCheck.as_view()),
|
||||
path("<int:policy>/policyautomatedtaskstatus/<int:task>/task/", views.PolicyAutoTask.as_view()),
|
||||
path("runwintask/<int:pk>/", views.RunPolicyTask.as_view()),
|
||||
path("policycheckstatus/<int:check>/check/", views.PolicyCheck.as_view()),
|
||||
path("policyautomatedtaskstatus/<int:task>/task/", views.PolicyAutoTask.as_view()),
|
||||
path("runwintask/<int:pk>/", views.PolicyAutoTask.as_view()),
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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!")
|
||||
|
||||
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -83,6 +83,9 @@
|
|||
<template v-slot:header-cell-statusicon="props">
|
||||
<q-th auto-width :props="props"></q-th>
|
||||
</template>
|
||||
<template v-slot:header-cell-policystatus="props">
|
||||
<q-th auto-width :props="props"></q-th>
|
||||
</template>
|
||||
<!-- body slots -->
|
||||
<template slot="body" slot-scope="props" :props="props">
|
||||
<q-tr @contextmenu="checkpk = props.row.id">
|
||||
|
@ -128,6 +131,18 @@
|
|||
v-model="props.row.email_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<!-- policy check icon -->
|
||||
<q-td v-if="props.row.managed_by_policy">
|
||||
<q-icon style="font-size: 1.3rem;" name="policy">
|
||||
<q-tooltip>This check is managed by a policy</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
<q-td v-else-if="props.row.overriden_by_policy">
|
||||
<q-icon style="font-size: 1.3rem;" name="remove_circle_outline">
|
||||
<q-tooltip>This check is overriden by a policy</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
<q-td v-else></q-td>
|
||||
<!-- status icon -->
|
||||
<q-td v-if="props.row.status === 'pending'"></q-td>
|
||||
<q-td v-else-if="props.row.status === 'passing'">
|
||||
|
@ -292,6 +307,7 @@ export default {
|
|||
columns: [
|
||||
{ name: "smsalert", field: "text_alert", align: "left" },
|
||||
{ name: "emailalert", field: "email_alert", align: "left" },
|
||||
{ name: "policystatus", align: "left" },
|
||||
{ name: "statusicon", align: "left" },
|
||||
{ name: "desc", label: "Description", align: "left" },
|
||||
{ name: "status", label: "Status", field: "status", align: "left" },
|
||||
|
|
|
@ -62,21 +62,38 @@
|
|||
class="settings-tbl-sticky"
|
||||
:data="policies"
|
||||
:columns="columns"
|
||||
:visible-columns="visibleColumns"
|
||||
:pagination.sync="pagination"
|
||||
:selected.sync="selected"
|
||||
@selection="policyRowSelected"
|
||||
selection="single"
|
||||
@selection="policyRowSelected"
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
hide-bottom
|
||||
flat
|
||||
>
|
||||
<!-- header slots -->
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">{{ col.label }}</q-th>
|
||||
<template v-for="col in props.cols">
|
||||
|
||||
<q-th v-if="col.name === 'active'" auto-width :key="col.name">
|
||||
<q-icon name="power_settings_new" size="1.5em">
|
||||
<q-tooltip>Enable Policy</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
|
||||
<q-th v-else-if="col.name === 'enforced'" auto-width :key="col.name">
|
||||
<q-icon name="security" size="1.5em">
|
||||
<q-tooltip>Enforce Policy (Will override Agent checks)</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
|
||||
<q-th v-else :key="col.name" :props="props">
|
||||
{{ col.label }}
|
||||
</q-th>
|
||||
|
||||
</template>
|
||||
</q-tr>
|
||||
</template>
|
||||
</template>
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props" class="cursor-pointer" @click="props.selected = true">
|
||||
<!-- context menu -->
|
||||
|
@ -126,15 +143,30 @@
|
|||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
<!-- enabled checkbox -->
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@input="toggleCheckbox(props.row, 'Active')"
|
||||
v-model="props.row.active"
|
||||
/>
|
||||
</q-td>
|
||||
<!-- enforced checkbox -->
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@input="toggleCheckbox(props.row, 'Enforced')"
|
||||
v-model="props.row.enforced"
|
||||
/>
|
||||
</q-td>
|
||||
<q-td>{{ props.row.name }}</q-td>
|
||||
<q-td>{{ props.row.desc }}</q-td>
|
||||
<q-td>{{ props.row.active }}</q-td>
|
||||
<q-td>
|
||||
<span
|
||||
style="cursor:pointer;color:blue;text-decoration:underline"
|
||||
@click="showRelationsModal(props.row)"
|
||||
>
|
||||
{{ `Show Relations (${props.row.clients.length + props.row.sites.length + props.row.agents.length}+)` }}
|
||||
{{ `Show Relations (${props.row.clients_count + props.row.sites_count + props.row.agents_count}+)` }}
|
||||
</span>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
|
@ -196,13 +228,9 @@ export default {
|
|||
policy: null,
|
||||
editPolicyId: null,
|
||||
selected: [],
|
||||
pagination: {
|
||||
rowsPerPage: 0,
|
||||
sortBy: "id",
|
||||
descending: false
|
||||
},
|
||||
columns: [
|
||||
{ name: "id", label: "ID", field: "id" },
|
||||
{ name: "active", label: "Active", field: "active", align: "left" },
|
||||
{ name: "enforced", label: "Enforced", field: "enforced", align: "left" },
|
||||
{
|
||||
name: "name",
|
||||
label: "Name",
|
||||
|
@ -215,24 +243,17 @@ export default {
|
|||
label: "Description",
|
||||
field: "desc",
|
||||
align: "left",
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
name: "active",
|
||||
label: "Active",
|
||||
field: "active",
|
||||
align: "left",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: "actions",
|
||||
label: "Actions",
|
||||
field: "actions",
|
||||
align: "left",
|
||||
sortable: false
|
||||
}
|
||||
],
|
||||
visibleColumns: ["name", "desc", "active", "actions"]
|
||||
pagination: {
|
||||
rowsPerPage: 9999
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -295,6 +316,32 @@ export default {
|
|||
showPolicyOverview() {
|
||||
this.showPolicyOverviewModal = true
|
||||
this.clearRow();
|
||||
},
|
||||
toggleCheckbox(policy, type) {
|
||||
let text = "";
|
||||
|
||||
if (type === "Active") {
|
||||
text = policy.active ? "Policy enabled successfully" : "Policy disabled successfully";
|
||||
} else if (type === "Enforced") {
|
||||
text = policy.enforced ? "Policy enforced successfully" : "Policy enforcement disabled";
|
||||
}
|
||||
|
||||
const data ={
|
||||
id: policy.id,
|
||||
name: policy.name,
|
||||
desc: policy.desc,
|
||||
active: policy.active,
|
||||
enforced: policy.enforced
|
||||
}
|
||||
|
||||
this.$store
|
||||
.dispatch("automation/editPolicy", data)
|
||||
.then(response => {
|
||||
this.$q.notify(notifySuccessConfig(text));
|
||||
})
|
||||
.catch(error => {
|
||||
this.$q.notify(notifyErrorConfig("An Error occured while editing policy"));
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -105,19 +105,18 @@ export default {
|
|||
processTreeDataFromApi(data) {
|
||||
/* Structure
|
||||
* [{
|
||||
* "client_name_1": {
|
||||
* "policies": [
|
||||
* {
|
||||
* id: 1,
|
||||
* name: "Policy Name 1"
|
||||
* }
|
||||
* ],
|
||||
* sites: {
|
||||
* "site_name_1": {
|
||||
* "policies": []
|
||||
* }
|
||||
* client: Client Name 1,
|
||||
* policy: {
|
||||
* id: 1,
|
||||
* name: "Policy Name 1"
|
||||
* },
|
||||
* sites: [{
|
||||
* name: "Site Name 1",
|
||||
* policy: {
|
||||
* id: 2,
|
||||
* name: "Policy Name 2"
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
* }]
|
||||
*/
|
||||
|
||||
|
@ -129,7 +128,7 @@ export default {
|
|||
for (let client in data) {
|
||||
var client_temp = {};
|
||||
|
||||
client_temp["label"] = client;
|
||||
client_temp["label"] = data[client].client;
|
||||
client_temp["id"] = unique_id;
|
||||
client_temp["icon"] = "business";
|
||||
client_temp["selectable"] = false;
|
||||
|
@ -139,27 +138,25 @@ export default {
|
|||
|
||||
// Add any policies assigned to client
|
||||
|
||||
if (data[client].policies.length > 0) {
|
||||
for (let policy in data[client].policies) {
|
||||
let disabled = "";
|
||||
if (data[client].policy !== null) {
|
||||
let disabled = "";
|
||||
|
||||
// Indicate if the policy is active or not
|
||||
if (!data[client].policies[policy].active) {
|
||||
disabled = " (disabled)";
|
||||
}
|
||||
|
||||
client_temp["children"].push({
|
||||
label: data[client].policies[policy].name + disabled,
|
||||
icon: "policy",
|
||||
id: data[client].policies[policy].id
|
||||
});
|
||||
// Indicate if the policy is active or not
|
||||
if (!data[client].policy.active) {
|
||||
disabled = " (disabled)";
|
||||
}
|
||||
|
||||
client_temp["children"].push({
|
||||
label: data[client].policy.name + disabled,
|
||||
icon: "policy",
|
||||
id: data[client].policy.id
|
||||
});
|
||||
}
|
||||
|
||||
// Iterate through Sites
|
||||
for (let site in data[client].sites) {
|
||||
var site_temp = {};
|
||||
site_temp["label"] = site;
|
||||
site_temp["label"] = data[client].sites[site].site;
|
||||
site_temp["id"] = unique_id;
|
||||
site_temp["icon"] = "apartment";
|
||||
site_temp["selectable"] = false;
|
||||
|
@ -167,23 +164,21 @@ export default {
|
|||
unique_id--;
|
||||
|
||||
// Add any policies assigned to site
|
||||
if (data[client].sites[site].policies.length > 0) {
|
||||
if (data[client].sites[site].policy !== null) {
|
||||
site_temp["children"] = [];
|
||||
|
||||
for (let policy in data[client].sites[site].policies) {
|
||||
|
||||
// Indicate if the policy is active or not
|
||||
let disabled = "";
|
||||
if (!data[client].sites[site].policies[policy].active) {
|
||||
disabled = " (disabled)";
|
||||
}
|
||||
|
||||
site_temp["children"].push({
|
||||
label: data[client].sites[site].policies[policy].name + disabled,
|
||||
icon: "policy",
|
||||
id: data[client].sites[site].policies[policy].id
|
||||
});
|
||||
|
||||
// Indicate if the policy is active or not
|
||||
let disabled = "";
|
||||
if (!data[client].sites[site].policy.active) {
|
||||
disabled = " (disabled)";
|
||||
}
|
||||
|
||||
site_temp["children"].push({
|
||||
label: data[client].sites[site].policy.name + disabled,
|
||||
icon: "policy",
|
||||
id: data[client].sites[site].policy.id
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Add Site to Client children array
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<q-card style="width: 60vw" >
|
||||
<q-card-section class="row items-center">
|
||||
<div class="text-h6">Edit policies assigned to {{ type }}</div>
|
||||
<div class="text-h6">Edit policy assigned to {{ type }}</div>
|
||||
<q-space />
|
||||
<q-btn icon="close" flat round dense v-close-popup />
|
||||
</q-card-section>
|
||||
|
@ -11,10 +11,9 @@
|
|||
v-model="selected"
|
||||
:options="options"
|
||||
filled
|
||||
multiple
|
||||
use-chips
|
||||
options-selected-class="text-green"
|
||||
dense
|
||||
clearable
|
||||
>
|
||||
<template v-slot:option="props">
|
||||
<q-item
|
||||
|
@ -32,7 +31,7 @@
|
|||
</q-select>
|
||||
</q-card-section>
|
||||
<q-card-section class="row items-center">
|
||||
<q-btn label="Add Polices" color="primary" type="submit" />
|
||||
<q-btn label="Add Policy" color="primary" type="submit" />
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
</q-card>
|
||||
|
@ -57,7 +56,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
selected: [],
|
||||
selected: null,
|
||||
options: []
|
||||
}
|
||||
},
|
||||
|
@ -73,7 +72,7 @@ export default {
|
|||
let data = {};
|
||||
data.pk = this.pk,
|
||||
data.type = this.type;
|
||||
data.policies = this.selected.map(policy => policy.value);
|
||||
data.policy = this.selected === null ? 0 : this.selected.value;
|
||||
|
||||
this.$store
|
||||
.dispatch("automation/updateRelatedPolicies", data)
|
||||
|
@ -102,14 +101,16 @@ export default {
|
|||
});;
|
||||
|
||||
},
|
||||
getRelations(pk, type) {
|
||||
getRelation(pk, type) {
|
||||
this.$store
|
||||
.dispatch("automation/getRelatedPolicies", {pk, type})
|
||||
.then(r => {
|
||||
this.selected = r.data.map(item => ({
|
||||
label: item.name,
|
||||
value: item.id
|
||||
}))
|
||||
if (r.data.id !== undefined) {
|
||||
this.selected = {
|
||||
label: r.data.name,
|
||||
value: r.data.id
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.notify(notifyErrorConfig("Add error occured while loading"));
|
||||
|
@ -118,7 +119,7 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
this.getPolicies();
|
||||
this.getRelations(this.pk, this.type);
|
||||
this.getRelation(this.pk, this.type);
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -25,69 +25,9 @@
|
|||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-2">Clients:</div>
|
||||
<div class="col-2">Enforced:</div>
|
||||
<div class="col-10">
|
||||
<q-select
|
||||
v-model="selectedClients"
|
||||
:options="clientOptions"
|
||||
filled
|
||||
multiple
|
||||
use-chips
|
||||
options-selected-class="text-green"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">No Results</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
<template v-slot:option="props">
|
||||
<q-item
|
||||
v-bind="props.itemProps"
|
||||
v-on="props.itemEvents"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-icon v-if="props.selected" name="check" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label v-html="props.opt.label" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-2">Sites:</div>
|
||||
<div class="col-10">
|
||||
<q-select
|
||||
v-model="selectedSites"
|
||||
:options="siteOptions"
|
||||
filled
|
||||
multiple
|
||||
use-chips
|
||||
options-selected-class="text-green"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">No Results</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
<template v-slot:option="props">
|
||||
<q-item
|
||||
v-bind="props.itemProps"
|
||||
v-on="props.itemEvents"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-icon v-if="props.selected" name="check" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<!-- <q-item-label overline>{{ props.opt.client }}</q-item-label> -->
|
||||
<q-item-label v-html="props.opt.label" />
|
||||
<q-item-label caption>{{ props.opt.client }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
<q-toggle v-model="enforced" color="green" />
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section class="row items-center">
|
||||
|
@ -99,21 +39,17 @@
|
|||
|
||||
<script>
|
||||
import mixins, { notifySuccessConfig, notifyErrorConfig } from "@/mixins/mixins";
|
||||
import dropdown_formatter from "@/mixins/dropdown_formatter";
|
||||
|
||||
export default {
|
||||
name: "PolicyForm",
|
||||
mixins: [mixins, dropdown_formatter],
|
||||
mixins: [mixins],
|
||||
props: { pk: Number },
|
||||
data() {
|
||||
return {
|
||||
name: "",
|
||||
desc: "",
|
||||
active: false,
|
||||
selectedSites: [],
|
||||
selectedClients: [],
|
||||
clientOptions: [],
|
||||
siteOptions: []
|
||||
enforced: false,
|
||||
active: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -134,14 +70,7 @@ export default {
|
|||
this.name = r.data.name;
|
||||
this.desc = r.data.desc;
|
||||
this.active = r.data.active;
|
||||
this.selectedSites = r.data.sites.map(site => ({
|
||||
label: site.site,
|
||||
value: site.id
|
||||
}) );
|
||||
this.selectedClients = r.data.clients.map(client => ({
|
||||
label: client.client,
|
||||
value: client.id
|
||||
}) );
|
||||
this.enforced = r.data.enforced;
|
||||
});
|
||||
},
|
||||
submit() {
|
||||
|
@ -157,8 +86,7 @@ export default {
|
|||
name: this.name,
|
||||
desc: this.desc,
|
||||
active: this.active,
|
||||
sites: this.selectedSites.map(site => site.value),
|
||||
clients: this.selectedClients.map(client => client.value)
|
||||
enforced: this.enforced
|
||||
};
|
||||
|
||||
if (this.pk) {
|
||||
|
@ -186,20 +114,6 @@ export default {
|
|||
this.$q.notify(notifyErrorConfig(e.response.data));
|
||||
});
|
||||
}
|
||||
},
|
||||
getClients() {
|
||||
this.$store
|
||||
.dispatch("loadClients")
|
||||
.then(r => {
|
||||
this.clientOptions = this.formatClients(r.data);
|
||||
});
|
||||
},
|
||||
getSites() {
|
||||
this.$store
|
||||
.dispatch("loadSites")
|
||||
.then(r => {
|
||||
this.siteOptions = this.formatSites(r.data);
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
@ -207,9 +121,6 @@ export default {
|
|||
if (this.pk) {
|
||||
this.getPolicy();
|
||||
}
|
||||
|
||||
this.getClients();
|
||||
this.getSites();
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -144,7 +144,7 @@ export default {
|
|||
getCheckData() {
|
||||
this.$q.loading.show();
|
||||
this.$store
|
||||
.dispatch("automation/loadCheckStatus", { policypk: this.item.policy, checkpk: this.item.id })
|
||||
.dispatch("automation/loadCheckStatus", { checkpk: this.item.id })
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
this.tableData = r.data
|
||||
|
@ -157,7 +157,7 @@ export default {
|
|||
getTaskData() {
|
||||
this.$q.loading.show();
|
||||
this.$store
|
||||
.dispatch("automation/loadAutomatedTaskStatus", { policypk: this.item.policy, taskpk: this.item.id })
|
||||
.dispatch("automation/loadAutomatedTaskStatus", { taskpk: this.item.id })
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
this.tableData = r.data
|
||||
|
@ -174,7 +174,23 @@ export default {
|
|||
closeScriptOutput() {
|
||||
this.showScriptOutput = false;
|
||||
this.scriptInfo = {}
|
||||
}
|
||||
},
|
||||
pingInfo(desc, output) {
|
||||
this.$q.dialog({
|
||||
title: desc,
|
||||
style: "width: 50vw; max-width: 60vw",
|
||||
message: `<pre>${output}</pre>`,
|
||||
html: true
|
||||
});
|
||||
},
|
||||
scriptMoreInfo(props) {
|
||||
this.scriptInfo = props;
|
||||
this.showScriptOutput = true;
|
||||
},
|
||||
eventLogMoreInfo(props) {
|
||||
this.evtLogData = props;
|
||||
this.showEventLogOutput = true;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.type === "task") {
|
||||
|
|
|
@ -55,11 +55,11 @@ export default {
|
|||
context.commit("setPolicyChecks", r.data);
|
||||
});
|
||||
},
|
||||
loadCheckStatus(context, { policypk, checkpk }) {
|
||||
return axios.patch(`/automation/${policypk}/policycheckstatus/${checkpk}/check/`);
|
||||
loadCheckStatus(context, { checkpk }) {
|
||||
return axios.patch(`/automation/policycheckstatus/${checkpk}/check/`);
|
||||
},
|
||||
loadAutomatedTaskStatus(context, { policypk, taskpk }) {
|
||||
return axios.patch(`/automation/${policypk}/policyautomatedtaskstatus/${taskpk}/task/`);
|
||||
loadAutomatedTaskStatus(context, { taskpk }) {
|
||||
return axios.patch(`/automation/policyautomatedtaskstatus/${taskpk}/task/`);
|
||||
},
|
||||
loadPolicy(context, pk) {
|
||||
return axios.get(`/automation/policies/${pk}/`);
|
||||
|
@ -76,7 +76,10 @@ export default {
|
|||
});
|
||||
},
|
||||
runPolicyTask(context, pk) {
|
||||
return axios.get(`/automation/runwintask/${pk}/`);
|
||||
return axios.put(`/automation/runwintask/${pk}/`);
|
||||
},
|
||||
runPolicyCheck(context, pk) {
|
||||
return axios.put(`/automation/runpolicycheck/${pk}/`);
|
||||
},
|
||||
getRelated(context, pk) {
|
||||
return axios.get(`/automation/policies/${pk}/related/`);
|
||||
|
|
|
@ -241,6 +241,8 @@ describe("AutomationManager.vue", () => {
|
|||
|
||||
});
|
||||
|
||||
// TODO Test Checkboxes on table
|
||||
|
||||
// TODO: Test @close and @hide events
|
||||
|
||||
});
|
||||
|
|
|
@ -8,48 +8,16 @@ const localVue = createLocalVue();
|
|||
localVue.use(Vuex);
|
||||
|
||||
/*** TEST DATA ***/
|
||||
const clients = [
|
||||
{
|
||||
id: 1,
|
||||
client: "Test Client"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
client: "Test Client2"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
client: "Test Client3"
|
||||
}
|
||||
];
|
||||
const sites = [
|
||||
{
|
||||
id: 1,
|
||||
site: "Site Name",
|
||||
client_name: "Test Client"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
site: "Site Name2",
|
||||
client_name: "Test Client2"
|
||||
}
|
||||
];
|
||||
|
||||
const policy = {
|
||||
id: 1,
|
||||
name: "Test Policy",
|
||||
desc: "Test Desc",
|
||||
active: true,
|
||||
clients: [],
|
||||
sites: []
|
||||
enforced: false,
|
||||
active: true
|
||||
};
|
||||
|
||||
let actions, rootActions, store;
|
||||
beforeEach(() => {
|
||||
rootActions = {
|
||||
loadClients: jest.fn(() => new Promise(res => res({ data: clients }))),
|
||||
loadSites: jest.fn(() => new Promise(res => res({ data: sites }))),
|
||||
};
|
||||
|
||||
actions = {
|
||||
loadPolicy: jest.fn(() => new Promise(res => res({ data: policy }))),
|
||||
|
@ -87,8 +55,6 @@ describe("PolicyForm.vue when editting", () => {
|
|||
/*** TESTS ***/
|
||||
it("calls vuex actions on mount with pk prop set", () => {
|
||||
|
||||
expect(rootActions.loadClients).toHaveBeenCalled();
|
||||
expect(rootActions.loadSites).toHaveBeenCalled();
|
||||
expect(actions.loadPolicy).toHaveBeenCalledWith(expect.anything(), 1);
|
||||
|
||||
});
|
||||
|
@ -127,24 +93,11 @@ describe("PolicyForm.vue when adding", () => {
|
|||
/*** TESTS ***/
|
||||
it("calls vuex actions on mount", () => {
|
||||
|
||||
expect(rootActions.loadClients).toHaveBeenCalled();
|
||||
expect(rootActions.loadSites).toHaveBeenCalled();
|
||||
|
||||
// Not called unless pk prop is set
|
||||
expect(actions.loadPolicy).not.toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
it("Sets client and site options correctly", async () => {
|
||||
|
||||
// Make sure the promises are resolved
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.vm.clientOptions).toHaveLength(3);
|
||||
expect(wrapper.vm.siteOptions).toHaveLength(2);
|
||||
|
||||
});
|
||||
|
||||
it("sends the correct add action on submit", async () => {
|
||||
|
||||
wrapper.setData({name: "Test Policy"});
|
||||
|
|
|
@ -7,23 +7,21 @@ import "../../utils/quasar.js";
|
|||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
const related = [
|
||||
{
|
||||
const related = {
|
||||
id: 1,
|
||||
name: "Test Policy"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Test Policy 2"
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
let state, actions, getters, store;
|
||||
beforeEach(() => {
|
||||
|
||||
state = {
|
||||
policies: [
|
||||
...related,
|
||||
related,
|
||||
{
|
||||
id: 2,
|
||||
name: "Test Policy 2"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "TestPolicy 3"
|
||||
|
@ -34,7 +32,7 @@ beforeEach(() => {
|
|||
actions = {
|
||||
updateRelatedPolicies: jest.fn(),
|
||||
loadPolicies: jest.fn(),
|
||||
getRelatedPolicies: jest.fn(() => new Promise(res => res({ data: related }))),
|
||||
getRelatedPolicies: jest.fn(() => Promise.resolve({ data: related })),
|
||||
};
|
||||
|
||||
getters = {
|
||||
|
@ -86,7 +84,7 @@ describe.each([
|
|||
|
||||
it("renders title correctly", () => {
|
||||
|
||||
expect(wrapper.find(".text-h6").text()).toBe(`Edit policies assigned to ${type}`);
|
||||
expect(wrapper.find(".text-h6").text()).toBe(`Edit policy assigned to ${type}`);
|
||||
});
|
||||
|
||||
it("renders correct amount of policies in dropdown", async () => {
|
||||
|
@ -95,10 +93,10 @@ describe.each([
|
|||
expect(wrapper.vm.options).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("renders correct amount of related policies in selected", async () => {
|
||||
it("renders correct policy in selected", async () => {
|
||||
|
||||
await flushpromises();
|
||||
expect(wrapper.vm.selected).toHaveLength(2);
|
||||
expect(wrapper.vm.selected).toStrictEqual({label: related.name, value: related.id});
|
||||
});
|
||||
|
||||
it("sends correct data on form submit", async () => {
|
||||
|
@ -109,8 +107,8 @@ describe.each([
|
|||
await form.vm.$emit("submit");
|
||||
|
||||
expect(actions.updateRelatedPolicies).toHaveBeenCalledWith(expect.anything(),
|
||||
{ pk: pk, type: type, policies: [1,2] }
|
||||
{ pk: pk, type: type, policy: related.id }
|
||||
);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,44 +8,44 @@ localVue.use(Vuex);
|
|||
|
||||
describe("PolicyOverview.vue", () => {
|
||||
|
||||
const policyTreeData = {
|
||||
// node 0
|
||||
"Client Name 1": {
|
||||
policies: [
|
||||
const policyTreeData = [
|
||||
{
|
||||
// node 0
|
||||
client: "Client Name 1",
|
||||
policy: {
|
||||
id: 1,
|
||||
name: "Policy Name 1",
|
||||
active: true
|
||||
},
|
||||
// node -1
|
||||
sites: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Policy Name 1",
|
||||
active: true
|
||||
site: "Site Name 1",
|
||||
policy: null
|
||||
}
|
||||
],
|
||||
sites: {
|
||||
// node -1
|
||||
"Site Name 1": { policies: []}
|
||||
}
|
||||
]
|
||||
},
|
||||
// node -2
|
||||
"Client Name 2": {
|
||||
policies: [
|
||||
{
|
||||
// node -2
|
||||
client: "Client Name 2",
|
||||
policy: {
|
||||
id: 2,
|
||||
name: "Policy Name 2",
|
||||
active: true
|
||||
},
|
||||
sites: [
|
||||
{
|
||||
id: 2,
|
||||
name: "Policy Name 2",
|
||||
active: true
|
||||
// node -3
|
||||
site: "Site Name 2",
|
||||
policy: {
|
||||
id: 3,
|
||||
name: "Policy Name 3",
|
||||
active: false
|
||||
}
|
||||
}
|
||||
],
|
||||
sites: {
|
||||
// node -3
|
||||
"Site Name 2": {
|
||||
policies: [
|
||||
{
|
||||
id: 3,
|
||||
name: "Policy Name 3",
|
||||
active: false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
];
|
||||
|
||||
let wrapper, actions, mutations, store;
|
||||
|
||||
|
|
Loading…
Reference in New Issue