diff --git a/api/tacticalrmm/agents/models.py b/api/tacticalrmm/agents/models.py index 3aff49ef..340f7fa3 100644 --- a/api/tacticalrmm/agents/models.py +++ b/api/tacticalrmm/agents/models.py @@ -534,14 +534,22 @@ class Agent(BaseAuditModel): ): templates.append(core.workstation_policy.alert_template) - # check if client, site, or agent has been excluded from templates in order and return if not + # go through the templates and return the first one that isn't excluded for template in templates: + # check if client, site, or agent has been excluded from template if ( client.pk in template.excluded_clients.all() or site.pk in template.excluded_sites.all() or self.pk in template.excluded_agents.all() ): continue + + # see if template is excluding desktops + if ( + self.monitoring_type == "workstation" + and not template.agent_include_desktops + ): + continue else: return template @@ -751,7 +759,7 @@ class Agent(BaseAuditModel): alert.resolved_action_execution_time = "{:.4f}".format( r["execution_time"] ) - alert.resolved_action_run = True + alert.resolved_action_run = djangotime.now() alert.save() else: logger.error( @@ -825,7 +833,7 @@ class Agent(BaseAuditModel): alert.action_stdout = r["stdout"] alert.action_stderr = r["stderr"] alert.action_execution_time = "{:.4f}".format(r["execution_time"]) - alert.action_run = True + alert.action_run = djangotime.now() alert.save() else: logger.error( diff --git a/api/tacticalrmm/alerts/migrations/0004_auto_20210207_1426.py b/api/tacticalrmm/alerts/migrations/0004_auto_20210212_1408.py similarity index 61% rename from api/tacticalrmm/alerts/migrations/0004_auto_20210207_1426.py rename to api/tacticalrmm/alerts/migrations/0004_auto_20210212_1408.py index 815e2965..2d8d42ff 100644 --- a/api/tacticalrmm/alerts/migrations/0004_auto_20210207_1426.py +++ b/api/tacticalrmm/alerts/migrations/0004_auto_20210212_1408.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1.4 on 2021-02-07 14:26 +# Generated by Django 3.1.4 on 2021-02-12 14:08 import django.contrib.postgres.fields from django.db import migrations, models @@ -8,14 +8,44 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('scripts', '0006_auto_20201210_2145'), - ('autotasks', '0016_automatedtask_status'), - ('agents', '0028_auto_20210206_1534'), + ('agents', '0029_delete_agentoutage'), ('clients', '0008_auto_20201103_1430'), + ('autotasks', '0017_auto_20210210_1512'), + ('scripts', '0005_auto_20201207_1606'), ('alerts', '0003_auto_20201021_1815'), ] operations = [ + migrations.AddField( + model_name='alert', + name='action_execution_time', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name='alert', + name='action_retcode', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='alert', + name='action_run', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='alert', + name='action_stderr', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='alert', + name='action_stdout', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='alert', + name='action_timeout', + field=models.PositiveIntegerField(blank=True, null=True), + ), migrations.AddField( model_name='alert', name='alert_type', @@ -26,11 +56,66 @@ class Migration(migrations.Migration): name='assigned_task', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='alert', to='autotasks.automatedtask'), ), + migrations.AddField( + model_name='alert', + name='email_sent', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='alert', + name='hidden', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='alert', + name='resolved_action_execution_time', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name='alert', + name='resolved_action_retcode', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='alert', + name='resolved_action_run', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='alert', + name='resolved_action_stderr', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='alert', + name='resolved_action_stdout', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='alert', + name='resolved_action_timeout', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='alert', + name='resolved_email_sent', + field=models.DateTimeField(blank=True, null=True), + ), migrations.AddField( model_name='alert', name='resolved_on', field=models.DateTimeField(blank=True, null=True), ), + migrations.AddField( + model_name='alert', + name='resolved_sms_sent', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='alert', + name='sms_sent', + field=models.DateTimeField(blank=True, null=True), + ), migrations.AddField( model_name='alert', name='snoozed', @@ -47,6 +132,8 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=100)), ('is_active', models.BooleanField(default=True)), + ('action_args', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=255, null=True), blank=True, default=list, null=True, size=None)), + ('resolved_action_args', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=255, null=True), blank=True, default=list, null=True, size=None)), ('email_recipients', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=100), blank=True, default=list, null=True, size=None)), ('text_recipients', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=100), blank=True, default=list, null=True, size=None)), ('email_from', models.EmailField(blank=True, max_length=254, null=True)), @@ -75,10 +162,11 @@ class Migration(migrations.Migration): ('task_always_text', models.BooleanField(blank=True, default=False, null=True)), ('task_always_alert', models.BooleanField(blank=True, default=False, null=True)), ('task_periodic_alert_days', models.PositiveIntegerField(blank=True, default=0, null=True)), - ('actions', models.ManyToManyField(blank=True, related_name='alert_templates', to='scripts.Script')), + ('action', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='alert_template', to='scripts.script')), ('excluded_agents', models.ManyToManyField(blank=True, related_name='alert_exclusions', to='agents.Agent')), ('excluded_clients', models.ManyToManyField(blank=True, related_name='alert_exclusions', to='clients.Client')), ('excluded_sites', models.ManyToManyField(blank=True, related_name='alert_exclusions', to='clients.Site')), + ('resolved_action', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='resolved_alert_template', to='scripts.script')), ], ), ] diff --git a/api/tacticalrmm/alerts/migrations/0005_alerttemplate_resolved_actions.py b/api/tacticalrmm/alerts/migrations/0005_alerttemplate_resolved_actions.py deleted file mode 100644 index 5015de53..00000000 --- a/api/tacticalrmm/alerts/migrations/0005_alerttemplate_resolved_actions.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.1.4 on 2021-02-07 18:36 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('scripts', '0006_auto_20201210_2145'), - ('alerts', '0004_auto_20210207_1426'), - ] - - operations = [ - migrations.AddField( - model_name='alerttemplate', - name='resolved_actions', - field=models.ManyToManyField(blank=True, related_name='alert_templates_resolved', to='scripts.Script'), - ), - ] diff --git a/api/tacticalrmm/alerts/migrations/0006_auto_20210210_1512.py b/api/tacticalrmm/alerts/migrations/0006_auto_20210210_1512.py deleted file mode 100644 index 95fbb5e3..00000000 --- a/api/tacticalrmm/alerts/migrations/0006_auto_20210210_1512.py +++ /dev/null @@ -1,129 +0,0 @@ -# Generated by Django 3.1.4 on 2021-02-10 15:12 - -import django.contrib.postgres.fields -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('scripts', '0006_auto_20201210_2145'), - ('alerts', '0005_alerttemplate_resolved_actions'), - ] - - operations = [ - migrations.RemoveField( - model_name='alerttemplate', - name='actions', - ), - migrations.RemoveField( - model_name='alerttemplate', - name='resolved_actions', - ), - migrations.AddField( - model_name='alert', - name='action_execution_time', - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AddField( - model_name='alert', - name='action_retcode', - field=models.IntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='alert', - name='action_run', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='alert', - name='action_stderr', - field=models.TextField(blank=True, null=True), - ), - migrations.AddField( - model_name='alert', - name='action_stdout', - field=models.TextField(blank=True, null=True), - ), - migrations.AddField( - model_name='alert', - name='action_timeout', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='alert', - name='email_sent', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name='alert', - name='hidden', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='alert', - name='resolved_action_execution_time', - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AddField( - model_name='alert', - name='resolved_action_retcode', - field=models.IntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='alert', - name='resolved_action_run', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='alert', - name='resolved_action_stderr', - field=models.TextField(blank=True, null=True), - ), - migrations.AddField( - model_name='alert', - name='resolved_action_stdout', - field=models.TextField(blank=True, null=True), - ), - migrations.AddField( - model_name='alert', - name='resolved_action_timeout', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='alert', - name='resolved_email_sent', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name='alert', - name='resolved_sms_sent', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name='alert', - name='sms_sent', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name='alerttemplate', - name='action', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='alert_template', to='scripts.script'), - ), - migrations.AddField( - model_name='alerttemplate', - name='action_args', - field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=255, null=True), blank=True, default=list, null=True, size=None), - ), - migrations.AddField( - model_name='alerttemplate', - name='resolved_action', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='resolved_alert_template', to='scripts.script'), - ), - migrations.AddField( - model_name='alerttemplate', - name='resolved_action_args', - field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=255, null=True), blank=True, default=list, null=True, size=None), - ), - ] diff --git a/api/tacticalrmm/alerts/models.py b/api/tacticalrmm/alerts/models.py index 54cbc468..d31afb95 100644 --- a/api/tacticalrmm/alerts/models.py +++ b/api/tacticalrmm/alerts/models.py @@ -3,8 +3,6 @@ from django.contrib.postgres.fields import ArrayField from django.db.models.fields import BooleanField, PositiveIntegerField from django.utils import timezone as djangotime -from typing import Union, Type - SEVERITY_CHOICES = [ ("info", "Informational"), ("warning", "Warning"), @@ -56,13 +54,13 @@ class Alert(models.Model): sms_sent = models.DateTimeField(null=True, blank=True) resolved_sms_sent = models.DateTimeField(null=True, blank=True) hidden = models.BooleanField(default=False) - action_run = models.BooleanField(default=False) + action_run = models.DateTimeField(null=True, blank=True) action_timeout = models.PositiveIntegerField(null=True, blank=True) action_stdout = models.TextField(null=True, blank=True) action_stderr = models.TextField(null=True, blank=True) action_retcode = models.IntegerField(null=True, blank=True) action_execution_time = models.CharField(max_length=100, null=True, blank=True) - resolved_action_run = models.BooleanField(default=False) + resolved_action_run = models.DateTimeField(null=True, blank=True) resolved_action_timeout = models.PositiveIntegerField(null=True, blank=True) resolved_action_stdout = models.TextField(null=True, blank=True) resolved_action_stderr = models.TextField(null=True, blank=True) diff --git a/api/tacticalrmm/automation/migrations/0007_policy_alert_template.py b/api/tacticalrmm/automation/migrations/0007_policy_alert_template.py index 022f8abd..d19db273 100644 --- a/api/tacticalrmm/automation/migrations/0007_policy_alert_template.py +++ b/api/tacticalrmm/automation/migrations/0007_policy_alert_template.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1.4 on 2021-02-07 14:26 +# Generated by Django 3.1.4 on 2021-02-12 14:08 from django.db import migrations, models import django.db.models.deletion @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('alerts', '0004_auto_20210207_1426'), + ('alerts', '0004_auto_20210212_1408'), ('automation', '0006_delete_policyexclusions'), ] diff --git a/api/tacticalrmm/automation/models.py b/api/tacticalrmm/automation/models.py index 9e77d1be..f67471b0 100644 --- a/api/tacticalrmm/automation/models.py +++ b/api/tacticalrmm/automation/models.py @@ -35,7 +35,7 @@ class Policy(BaseAuditModel): def delete(self, *args, **kwargs): from automation.tasks import generate_agent_checks_task - agents = list(self.related_agents().values_list("pk", flat=True)) + agents = list(self.related_agents().only("pk").values_list("pk", flat=True)) super(BaseAuditModel, self).delete(*args, **kwargs) generate_agent_checks_task.delay(agents, create_tasks=True) diff --git a/api/tacticalrmm/automation/tasks.py b/api/tacticalrmm/automation/tasks.py index 14a22be4..0db0e986 100644 --- a/api/tacticalrmm/automation/tasks.py +++ b/api/tacticalrmm/automation/tasks.py @@ -115,7 +115,7 @@ def generate_agent_tasks_from_policies_task(policypk): "pk", "monitoring_type" ) else: - agents = policy.related_agents() + agents = policy.related_agents().only("pk") for agent in agents: agent.generate_tasks_from_policies() diff --git a/api/tacticalrmm/autotasks/models.py b/api/tacticalrmm/autotasks/models.py index 028af7f0..3dca95fd 100644 --- a/api/tacticalrmm/autotasks/models.py +++ b/api/tacticalrmm/autotasks/models.py @@ -3,6 +3,7 @@ import random import string import datetime as dt +from django.utils import timezone as djangotime from django.conf import settings from django.db import models from django.contrib.postgres.fields import ArrayField @@ -289,7 +290,7 @@ class AutomatedTask(BaseAuditModel): alert.resolved_action_execution_time = "{:.4f}".format( r["execution_time"] ) - alert.resolved_action_run = True + alert.resolved_action_run = djangotime.now() alert.save() else: logger.error( @@ -303,6 +304,11 @@ class AutomatedTask(BaseAuditModel): else: alert = Alert.objects.get(assigned_task=self, resolved=False) + # check if alert severity changed on task and update the alert + if self.alert_severity != alert.severity: + alert.severity = self.alert_severity + alert.save(update_fields=["severity"]) + # create alert in dashboard if enabled if ( self.dashboard_alert @@ -359,7 +365,7 @@ class AutomatedTask(BaseAuditModel): alert.action_stdout = r["stdout"] alert.action_stderr = r["stderr"] alert.action_execution_time = "{:.4f}".format(r["execution_time"]) - alert.action_run = True + alert.action_run = djangotime.now() alert.save() else: logger.error( diff --git a/api/tacticalrmm/checks/migrations/0021_auto_20210212_1429.py b/api/tacticalrmm/checks/migrations/0021_auto_20210212_1429.py new file mode 100644 index 00000000..865f7b76 --- /dev/null +++ b/api/tacticalrmm/checks/migrations/0021_auto_20210212_1429.py @@ -0,0 +1,24 @@ +# Generated by Django 3.1.4 on 2021-02-12 14:29 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('checks', '0020_auto_20210210_1512'), + ] + + operations = [ + migrations.AlterField( + model_name='check', + name='error_threshold', + field=models.PositiveIntegerField(blank=True, default=0, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(99)]), + ), + migrations.AlterField( + model_name='check', + name='warning_threshold', + field=models.PositiveIntegerField(blank=True, default=0, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(99)]), + ), + ] diff --git a/api/tacticalrmm/checks/models.py b/api/tacticalrmm/checks/models.py index 1d2444c3..28005fe2 100644 --- a/api/tacticalrmm/checks/models.py +++ b/api/tacticalrmm/checks/models.py @@ -5,8 +5,10 @@ import json import pytz from statistics import mean +from django.utils import timezone as djangotime from django.db import models from django.conf import settings +from django.core.validators import MinValueValidator, MaxValueValidator from django.contrib.postgres.fields import ArrayField from rest_framework.fields import JSONField from typing import List, Any @@ -113,10 +115,17 @@ class Check(BaseAuditModel): # threshold percent for diskspace, cpuload or memory check error_threshold = models.PositiveIntegerField( + validators=[MinValueValidator(0), MaxValueValidator(99)], null=True, blank=True, + default=0, + ) + warning_threshold = models.PositiveIntegerField( + null=True, + blank=True, + validators=[MinValueValidator(0), MaxValueValidator(99)], + default=0, ) - warning_threshold = models.PositiveIntegerField(null=True, blank=True, default=0) # diskcheck i.e C:, D: etc disk = models.CharField(max_length=2, null=True, blank=True) # ping checks @@ -314,7 +323,7 @@ class Check(BaseAuditModel): alert.resolved_action_execution_time = "{:.4f}".format( r["execution_time"] ) - alert.resolved_action_run = True + alert.resolved_action_run = djangotime.now() alert.save() else: logger.error( @@ -327,6 +336,11 @@ class Check(BaseAuditModel): else: alert = Alert.objects.get(assigned_check=self, resolved=False) + # check if alert severity changed on check and update the alert + if self.alert_severity != alert.severity: + alert.severity = self.alert_severity + alert.save(update_fields=["severity"]) + # create alert in dashboard if enabled if ( self.dashboard_alert @@ -384,7 +398,7 @@ class Check(BaseAuditModel): alert.action_stdout = r["stdout"] alert.action_stderr = r["stderr"] alert.action_execution_time = "{:.4f}".format(r["execution_time"]) - alert.action_run = True + alert.action_run = djangotime.now() alert.save() else: logger.error( @@ -408,15 +422,12 @@ class Check(BaseAuditModel): avg = int(mean(self.history)) - if self.warning_threshold and avg > self.warning_threshold: - self.status = "failing" - self.alert_severity = "warning" - else: - self.status = "passing" - if self.error_threshold and avg > self.error_threshold: self.status = "failing" self.alert_severity = "error" + elif self.warning_threshold and avg > self.warning_threshold: + self.status = "failing" + self.alert_severity = "warning" else: self.status = "passing" @@ -430,25 +441,23 @@ class Check(BaseAuditModel): total = bytes2human(data["total"]) free = bytes2human(data["free"]) - if ( + if self.error_threshold and (100 - percent_used) < self.error_threshold: + self.status = "failing" + self.alert_severity = "error" + elif ( self.warning_threshold and (100 - percent_used) < self.warning_threshold ): self.status = "failing" self.alert_severity = "warning" - else: - self.status = "passing" - if self.error_threshold and (100 - percent_used) < self.error_threshold: - self.status = "failing" - self.alert_severity = "error" else: self.status = "passing" self.more_info = f"Total: {total}B, Free: {free}B" # add check history - self.add_check_history(percent_used) + self.add_check_history(100 - percent_used) else: self.status = "failing" self.alert_severity = "error" @@ -630,15 +639,11 @@ class Check(BaseAuditModel): # handle status if self.status == "failing": self.fail_count += 1 - self.save(update_fields=["status", "fail_count"]) + self.save(update_fields=["status", "fail_count", "alert_severity"]) elif self.status == "passing": - - if self.fail_count != 0: - self.fail_count = 0 - self.save(update_fields=["status", "fail_count"]) - else: - self.save(update_fields=["status"]) + self.fail_count = 0 + self.save(update_fields=["status", "fail_count", "alert_severity"]) self.handle_alert() diff --git a/api/tacticalrmm/checks/serializers.py b/api/tacticalrmm/checks/serializers.py index 745b01b8..d2315f0e 100644 --- a/api/tacticalrmm/checks/serializers.py +++ b/api/tacticalrmm/checks/serializers.py @@ -40,19 +40,30 @@ class CheckSerializer(serializers.ModelSerializer): check_type = val["check_type"] except KeyError: return val + # 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") - .exclude(managed_by_policy=True) - ) - for check in checks: - if val["disk"] in check.disk: - raise serializers.ValidationError( - f"A disk check for Drive {val['disk']} already exists!" - ) + if check_type == "diskspace": + if not self.instance: # only on create + checks = ( + Check.objects.filter(**self.context) + .filter(check_type="diskspace") + .exclude(managed_by_policy=True) + ) + for check in checks: + if val["disk"] in check.disk: + raise serializers.ValidationError( + f"A disk check for Drive {val['disk']} already exists!" + ) + + if ( + val["warning_threshold"] < val["error_threshold"] + and val["warning_threshold"] > 0 + and val["error_threshold"] > 0 + ): + raise serializers.ValidationError( + f"Warning threshold must be greater than Error Threshold" + ) # ping checks if check_type == "ping": @@ -74,6 +85,14 @@ class CheckSerializer(serializers.ModelSerializer): raise serializers.ValidationError( "A cpuload check for this agent already exists" ) + if ( + val["warning_threshold"] > val["error_threshold"] + and val["warning_threshold"] > 0 + and val["error_threshold"] > 0 + ): + raise serializers.ValidationError( + f"Warning threshold must be less than Error Threshold" + ) if check_type == "memory" and not self.instance: if ( @@ -85,6 +104,15 @@ class CheckSerializer(serializers.ModelSerializer): "A memory check for this agent already exists" ) + if ( + val["warning_threshold"] > val["error_threshold"] + and val["warning_threshold"] > 0 + and val["error_threshold"] > 0 + ): + raise serializers.ValidationError( + f"Warning threshold must be less than Error Threshold" + ) + return val diff --git a/api/tacticalrmm/clients/migrations/0009_auto_20210207_1426.py b/api/tacticalrmm/clients/migrations/0009_auto_20210212_1408.py similarity index 88% rename from api/tacticalrmm/clients/migrations/0009_auto_20210207_1426.py rename to api/tacticalrmm/clients/migrations/0009_auto_20210212_1408.py index 1d903a76..7777f2b8 100644 --- a/api/tacticalrmm/clients/migrations/0009_auto_20210207_1426.py +++ b/api/tacticalrmm/clients/migrations/0009_auto_20210212_1408.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1.4 on 2021-02-07 14:26 +# Generated by Django 3.1.4 on 2021-02-12 14:08 from django.db import migrations, models import django.db.models.deletion @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('alerts', '0004_auto_20210207_1426'), + ('alerts', '0004_auto_20210212_1408'), ('clients', '0008_auto_20201103_1430'), ] diff --git a/api/tacticalrmm/core/migrations/0013_coresettings_alert_template.py b/api/tacticalrmm/core/migrations/0013_coresettings_alert_template.py index 79b29a51..77cf4516 100644 --- a/api/tacticalrmm/core/migrations/0013_coresettings_alert_template.py +++ b/api/tacticalrmm/core/migrations/0013_coresettings_alert_template.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1.4 on 2021-02-07 14:50 +# Generated by Django 3.1.4 on 2021-02-12 14:08 from django.db import migrations, models import django.db.models.deletion @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('alerts', '0004_auto_20210207_1426'), + ('alerts', '0004_auto_20210212_1408'), ('core', '0012_coresettings_check_history_prune_days'), ] diff --git a/api/tacticalrmm/scripts/migrations/0006_auto_20201210_2145.py b/api/tacticalrmm/scripts/migrations/0006_auto_20201210_2145.py deleted file mode 100644 index c9ecfd33..00000000 --- a/api/tacticalrmm/scripts/migrations/0006_auto_20201210_2145.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 3.1.4 on 2020-12-10 21:45 - -from django.db import migrations -from django.conf import settings -import os -import base64 -from pathlib import Path - - -def move_scripts_to_db(apps, schema_editor): - print("") - Script = apps.get_model("scripts", "Script") - for script in Script.objects.all(): - if not script.script_type == "builtin": - - if script.filename: - filepath = f"{settings.SCRIPTS_DIR}/userdefined/{script.filename}" - else: - print(f"No filename on script found. Skipping") - continue - - # test if file exists - if os.path.exists(filepath): - print(f"Found script {script.name}. Importing code.") - - with open(filepath, "rb") as f: - script_bytes = f.read().decode("utf-8").encode("ascii", "ignore") - script.code_base64 = base64.b64encode(script_bytes).decode("ascii") - script.save(update_fields=["code_base64"]) - else: - print( - f"Script file {script.name} was not found on the disk. You will need to edit the script in the UI" - ) - - -class Migration(migrations.Migration): - - dependencies = [ - ("scripts", "0005_auto_20201207_1606"), - ] - - operations = [migrations.RunPython(move_scripts_to_db, migrations.RunPython.noop)] diff --git a/web/src/components/AutomatedTasksTab.vue b/web/src/components/AutomatedTasksTab.vue index cf8091df..67f4928b 100644 --- a/web/src/components/AutomatedTasksTab.vue +++ b/web/src/components/AutomatedTasksTab.vue @@ -172,7 +172,7 @@ output @@ -191,16 +191,6 @@ - - - - @@ -214,15 +204,13 @@ import ScriptOutput from "@/components/modals/checks/ScriptOutput"; export default { name: "AutomatedTasksTab", - components: { AddAutomatedTask, ScriptOutput }, + components: { AddAutomatedTask }, mixins: [mixins], data() { return { showAddAutomatedTask: false, showEditAutomatedTask: false, - showScriptOutput: false, editTaskPk: null, - showScriptOutput: false, scriptInfo: {}, columns: [ { name: "enabled", align: "left", field: "enabled" }, @@ -310,9 +298,12 @@ export default { refreshTasks(id) { this.$store.dispatch("loadAutomatedTasks", id); }, - scriptMoreInfo(props) { - this.scriptInfo = props; - this.showScriptOutput = true; + showScriptOutput(script) { + this.$q.dialog({ + component: ScriptOutput, + parent: this, + scriptInfo: script, + }); }, showEditTask(task) { this.$q diff --git a/web/src/components/ChecksTab.vue b/web/src/components/ChecksTab.vue index f8c600d2..f7c1c7a0 100644 --- a/web/src/components/ChecksTab.vue +++ b/web/src/components/ChecksTab.vue @@ -214,7 +214,7 @@ v-else-if="props.row.check_type === 'script'" style="cursor: pointer; text-decoration: underline" class="text-primary" - @click="scriptMoreInfo(props.row)" + @click="showScriptOutput(props.row)" >Last Output Last Output - {{ props.row.last_run }} + {{ props.row.last_run || "Never" }} {{ props.row.assigned_task.length }} Tasks @@ -258,15 +258,6 @@ - - - output @@ -103,9 +103,6 @@ - - - @@ -120,7 +117,6 @@ import EventLogCheckOutput from "@/components/modals/checks/EventLogCheckOutput" export default { name: "PolicyStatus", components: { - ScriptOutput, EventLogCheckOutput, }, props: { @@ -139,10 +135,8 @@ export default { }, data() { return { - showScriptOutput: false, showEventLogOutput: false, evtLogData: {}, - scriptInfo: {}, data: [], columns: [ { name: "agent", label: "Hostname", field: "agent", align: "left", sortable: true }, @@ -225,14 +219,17 @@ export default { html: true, }); }, - scriptMoreInfo(check) { - this.scriptInfo = check; - this.showScriptOutput = true; - }, eventLogMoreInfo(check) { this.evtLogData = check; this.showEventLogOutput = true; }, + showScriptOutput(script) { + this.$q.dialog({ + component: ScriptOutput, + parent: this, + scriptInfo: script, + }); + }, show() { this.$refs.dialog.show(); }, diff --git a/web/src/components/graphs/CheckGraph.vue b/web/src/components/graphs/CheckGraph.vue index 2448db7a..352a2941 100644 --- a/web/src/components/graphs/CheckGraph.vue +++ b/web/src/components/graphs/CheckGraph.vue @@ -208,6 +208,10 @@ export default { }, }, }; + + if (this.check.check_type === "diskspace") { + this.chartOptions["yaxis"]["reversed"] = true; + } } else { // Set the y-axis labels to Failing and Passing this.chartOptions["yaxis"] = { diff --git a/web/src/components/modals/alerts/AlertTemplateForm.vue b/web/src/components/modals/alerts/AlertTemplateForm.vue index 057f1d58..97772473 100644 --- a/web/src/components/modals/alerts/AlertTemplateForm.vue +++ b/web/src/components/modals/alerts/AlertTemplateForm.vue @@ -56,6 +56,21 @@ /> +
Failure action timeout
+
+ +
+
Resolved action @@ -92,6 +107,21 @@ new-value-mode="add" />
+ +
Resolved action timeout
+
+ +
Email Settings (Overrides global email settings)
@@ -222,7 +252,7 @@ type="number" v-model.number="template.agent_periodic_alert_days" dense - :rules="[val => val >= 0]" + :rules="[val => val >= 0 || 'Periodic days must be 0 or greater']" /> @@ -338,7 +368,7 @@ type="number" v-model.number="template.check_periodic_alert_days" dense - :rules="[val => val >= 0]" + :rules="[val => val >= 0 || 'Periodic days must be 0 or greater']" /> @@ -482,8 +512,10 @@ export default { is_active: true, action: null, action_args: [], + action_timeout: 15, resolved_action: null, resolved_action_args: [], + resolved_action_timeout: 15, email_recipients: [], email_from: "", text_recipients: [], diff --git a/web/src/components/modals/alerts/AlertsOverview.vue b/web/src/components/modals/alerts/AlertsOverview.vue index cca66428..76da5df6 100644 --- a/web/src/components/modals/alerts/AlertsOverview.vue +++ b/web/src/components/modals/alerts/AlertsOverview.vue @@ -113,6 +113,24 @@ Resolve alert + + Show failure action run results + + + Show resolved action run results + @@ -130,6 +148,7 @@ \ No newline at end of file diff --git a/web/src/mixins/mixins.js b/web/src/mixins/mixins.js index 853ada13..ec3a2fae 100644 --- a/web/src/mixins/mixins.js +++ b/web/src/mixins/mixins.js @@ -84,6 +84,25 @@ export default { notifyInfo(msg, timeout = 2000) { Notify.create(notifyInfoConfig(msg, timeout)); }, + + isValidThreshold(warning, error, diskcheck = false) { + if (warning === 0 && error === 0) { + Notify.create(notifyErrorConfig("Warning Threshold or Error Threshold need to be set", 2000)); + return false + } + + if (!diskcheck && warning > error && warning > 0 && error > 0) { + Notify.create(notifyErrorConfig("Warning Threshold must be less than Error Threshold", 2000)); + return false + } + + if (diskcheck && warning < error && warning > 0 && error > 0) { + Notify.create(notifyErrorConfig("Warning Threshold must be more than Error Threshold", 2000)); + return false + } + + return true; + }, isValidEmail(val) { const email = /^(?=[a-zA-Z0-9@._%+-]{6,254}$)[a-zA-Z0-9._%+-]{1,64}@(?:[a-zA-Z0-9-]{1,63}\.){1,8}[a-zA-Z]{2,63}$/; return email.test(val);