fix check threshold modals and add client/serverside validation. Allow viewing alert script results in alerts overview. Fix diskspace check history computation. other fixes and improvements

This commit is contained in:
sadnub
2021-02-12 12:37:53 -05:00
parent 526135629c
commit d180f1b2d5
26 changed files with 403 additions and 396 deletions

View File

@@ -534,14 +534,22 @@ class Agent(BaseAuditModel):
): ):
templates.append(core.workstation_policy.alert_template) 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: for template in templates:
# check if client, site, or agent has been excluded from template
if ( if (
client.pk in template.excluded_clients.all() client.pk in template.excluded_clients.all()
or site.pk in template.excluded_sites.all() or site.pk in template.excluded_sites.all()
or self.pk in template.excluded_agents.all() or self.pk in template.excluded_agents.all()
): ):
continue continue
# see if template is excluding desktops
if (
self.monitoring_type == "workstation"
and not template.agent_include_desktops
):
continue
else: else:
return template return template
@@ -751,7 +759,7 @@ class Agent(BaseAuditModel):
alert.resolved_action_execution_time = "{:.4f}".format( alert.resolved_action_execution_time = "{:.4f}".format(
r["execution_time"] r["execution_time"]
) )
alert.resolved_action_run = True alert.resolved_action_run = djangotime.now()
alert.save() alert.save()
else: else:
logger.error( logger.error(
@@ -825,7 +833,7 @@ class Agent(BaseAuditModel):
alert.action_stdout = r["stdout"] alert.action_stdout = r["stdout"]
alert.action_stderr = r["stderr"] alert.action_stderr = r["stderr"]
alert.action_execution_time = "{:.4f}".format(r["execution_time"]) alert.action_execution_time = "{:.4f}".format(r["execution_time"])
alert.action_run = True alert.action_run = djangotime.now()
alert.save() alert.save()
else: else:
logger.error( logger.error(

View File

@@ -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 import django.contrib.postgres.fields
from django.db import migrations, models from django.db import migrations, models
@@ -8,14 +8,44 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('scripts', '0006_auto_20201210_2145'), ('agents', '0029_delete_agentoutage'),
('autotasks', '0016_automatedtask_status'),
('agents', '0028_auto_20210206_1534'),
('clients', '0008_auto_20201103_1430'), ('clients', '0008_auto_20201103_1430'),
('autotasks', '0017_auto_20210210_1512'),
('scripts', '0005_auto_20201207_1606'),
('alerts', '0003_auto_20201021_1815'), ('alerts', '0003_auto_20201021_1815'),
] ]
operations = [ 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( migrations.AddField(
model_name='alert', model_name='alert',
name='alert_type', name='alert_type',
@@ -26,11 +56,66 @@ class Migration(migrations.Migration):
name='assigned_task', name='assigned_task',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='alert', to='autotasks.automatedtask'), 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( migrations.AddField(
model_name='alert', model_name='alert',
name='resolved_on', name='resolved_on',
field=models.DateTimeField(blank=True, null=True), 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( migrations.AddField(
model_name='alert', model_name='alert',
name='snoozed', name='snoozed',
@@ -47,6 +132,8 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)), ('name', models.CharField(max_length=100)),
('is_active', models.BooleanField(default=True)), ('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)), ('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)), ('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)), ('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_text', models.BooleanField(blank=True, default=False, null=True)),
('task_always_alert', 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)), ('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_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_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')), ('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')),
], ],
), ),
] ]

View File

@@ -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'),
),
]

View File

@@ -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),
),
]

View File

@@ -3,8 +3,6 @@ from django.contrib.postgres.fields import ArrayField
from django.db.models.fields import BooleanField, PositiveIntegerField from django.db.models.fields import BooleanField, PositiveIntegerField
from django.utils import timezone as djangotime from django.utils import timezone as djangotime
from typing import Union, Type
SEVERITY_CHOICES = [ SEVERITY_CHOICES = [
("info", "Informational"), ("info", "Informational"),
("warning", "Warning"), ("warning", "Warning"),
@@ -56,13 +54,13 @@ class Alert(models.Model):
sms_sent = models.DateTimeField(null=True, blank=True) sms_sent = models.DateTimeField(null=True, blank=True)
resolved_sms_sent = models.DateTimeField(null=True, blank=True) resolved_sms_sent = models.DateTimeField(null=True, blank=True)
hidden = models.BooleanField(default=False) 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_timeout = models.PositiveIntegerField(null=True, blank=True)
action_stdout = models.TextField(null=True, blank=True) action_stdout = models.TextField(null=True, blank=True)
action_stderr = models.TextField(null=True, blank=True) action_stderr = models.TextField(null=True, blank=True)
action_retcode = models.IntegerField(null=True, blank=True) action_retcode = models.IntegerField(null=True, blank=True)
action_execution_time = models.CharField(max_length=100, 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_timeout = models.PositiveIntegerField(null=True, blank=True)
resolved_action_stdout = models.TextField(null=True, blank=True) resolved_action_stdout = models.TextField(null=True, blank=True)
resolved_action_stderr = models.TextField(null=True, blank=True) resolved_action_stderr = models.TextField(null=True, blank=True)

View File

@@ -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 from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@@ -7,7 +7,7 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('alerts', '0004_auto_20210207_1426'), ('alerts', '0004_auto_20210212_1408'),
('automation', '0006_delete_policyexclusions'), ('automation', '0006_delete_policyexclusions'),
] ]

View File

@@ -35,7 +35,7 @@ class Policy(BaseAuditModel):
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
from automation.tasks import generate_agent_checks_task 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) super(BaseAuditModel, self).delete(*args, **kwargs)
generate_agent_checks_task.delay(agents, create_tasks=True) generate_agent_checks_task.delay(agents, create_tasks=True)

View File

@@ -115,7 +115,7 @@ def generate_agent_tasks_from_policies_task(policypk):
"pk", "monitoring_type" "pk", "monitoring_type"
) )
else: else:
agents = policy.related_agents() agents = policy.related_agents().only("pk")
for agent in agents: for agent in agents:
agent.generate_tasks_from_policies() agent.generate_tasks_from_policies()

View File

@@ -3,6 +3,7 @@ import random
import string import string
import datetime as dt import datetime as dt
from django.utils import timezone as djangotime
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
@@ -289,7 +290,7 @@ class AutomatedTask(BaseAuditModel):
alert.resolved_action_execution_time = "{:.4f}".format( alert.resolved_action_execution_time = "{:.4f}".format(
r["execution_time"] r["execution_time"]
) )
alert.resolved_action_run = True alert.resolved_action_run = djangotime.now()
alert.save() alert.save()
else: else:
logger.error( logger.error(
@@ -303,6 +304,11 @@ class AutomatedTask(BaseAuditModel):
else: else:
alert = Alert.objects.get(assigned_task=self, resolved=False) 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 # create alert in dashboard if enabled
if ( if (
self.dashboard_alert self.dashboard_alert
@@ -359,7 +365,7 @@ class AutomatedTask(BaseAuditModel):
alert.action_stdout = r["stdout"] alert.action_stdout = r["stdout"]
alert.action_stderr = r["stderr"] alert.action_stderr = r["stderr"]
alert.action_execution_time = "{:.4f}".format(r["execution_time"]) alert.action_execution_time = "{:.4f}".format(r["execution_time"])
alert.action_run = True alert.action_run = djangotime.now()
alert.save() alert.save()
else: else:
logger.error( logger.error(

View File

@@ -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)]),
),
]

View File

@@ -5,8 +5,10 @@ import json
import pytz import pytz
from statistics import mean from statistics import mean
from django.utils import timezone as djangotime
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.core.validators import MinValueValidator, MaxValueValidator
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from rest_framework.fields import JSONField from rest_framework.fields import JSONField
from typing import List, Any from typing import List, Any
@@ -113,10 +115,17 @@ class Check(BaseAuditModel):
# threshold percent for diskspace, cpuload or memory check # threshold percent for diskspace, cpuload or memory check
error_threshold = models.PositiveIntegerField( error_threshold = models.PositiveIntegerField(
validators=[MinValueValidator(0), MaxValueValidator(99)],
null=True, null=True,
blank=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 # diskcheck i.e C:, D: etc
disk = models.CharField(max_length=2, null=True, blank=True) disk = models.CharField(max_length=2, null=True, blank=True)
# ping checks # ping checks
@@ -314,7 +323,7 @@ class Check(BaseAuditModel):
alert.resolved_action_execution_time = "{:.4f}".format( alert.resolved_action_execution_time = "{:.4f}".format(
r["execution_time"] r["execution_time"]
) )
alert.resolved_action_run = True alert.resolved_action_run = djangotime.now()
alert.save() alert.save()
else: else:
logger.error( logger.error(
@@ -327,6 +336,11 @@ class Check(BaseAuditModel):
else: else:
alert = Alert.objects.get(assigned_check=self, resolved=False) 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 # create alert in dashboard if enabled
if ( if (
self.dashboard_alert self.dashboard_alert
@@ -384,7 +398,7 @@ class Check(BaseAuditModel):
alert.action_stdout = r["stdout"] alert.action_stdout = r["stdout"]
alert.action_stderr = r["stderr"] alert.action_stderr = r["stderr"]
alert.action_execution_time = "{:.4f}".format(r["execution_time"]) alert.action_execution_time = "{:.4f}".format(r["execution_time"])
alert.action_run = True alert.action_run = djangotime.now()
alert.save() alert.save()
else: else:
logger.error( logger.error(
@@ -408,15 +422,12 @@ class Check(BaseAuditModel):
avg = int(mean(self.history)) 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: if self.error_threshold and avg > self.error_threshold:
self.status = "failing" self.status = "failing"
self.alert_severity = "error" self.alert_severity = "error"
elif self.warning_threshold and avg > self.warning_threshold:
self.status = "failing"
self.alert_severity = "warning"
else: else:
self.status = "passing" self.status = "passing"
@@ -430,25 +441,23 @@ class Check(BaseAuditModel):
total = bytes2human(data["total"]) total = bytes2human(data["total"])
free = bytes2human(data["free"]) 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 self.warning_threshold
and (100 - percent_used) < self.warning_threshold and (100 - percent_used) < self.warning_threshold
): ):
self.status = "failing" self.status = "failing"
self.alert_severity = "warning" 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: else:
self.status = "passing" self.status = "passing"
self.more_info = f"Total: {total}B, Free: {free}B" self.more_info = f"Total: {total}B, Free: {free}B"
# add check history # add check history
self.add_check_history(percent_used) self.add_check_history(100 - percent_used)
else: else:
self.status = "failing" self.status = "failing"
self.alert_severity = "error" self.alert_severity = "error"
@@ -630,15 +639,11 @@ class Check(BaseAuditModel):
# handle status # handle status
if self.status == "failing": if self.status == "failing":
self.fail_count += 1 self.fail_count += 1
self.save(update_fields=["status", "fail_count"]) self.save(update_fields=["status", "fail_count", "alert_severity"])
elif self.status == "passing": elif self.status == "passing":
self.fail_count = 0
if self.fail_count != 0: self.save(update_fields=["status", "fail_count", "alert_severity"])
self.fail_count = 0
self.save(update_fields=["status", "fail_count"])
else:
self.save(update_fields=["status"])
self.handle_alert() self.handle_alert()

View File

@@ -40,19 +40,30 @@ class CheckSerializer(serializers.ModelSerializer):
check_type = val["check_type"] check_type = val["check_type"]
except KeyError: except KeyError:
return val return val
# disk checks # disk checks
# make sure no duplicate diskchecks exist for an agent/policy # make sure no duplicate diskchecks exist for an agent/policy
if check_type == "diskspace" and not self.instance: # only on create if check_type == "diskspace":
checks = ( if not self.instance: # only on create
Check.objects.filter(**self.context) checks = (
.filter(check_type="diskspace") Check.objects.filter(**self.context)
.exclude(managed_by_policy=True) .filter(check_type="diskspace")
) .exclude(managed_by_policy=True)
for check in checks: )
if val["disk"] in check.disk: for check in checks:
raise serializers.ValidationError( if val["disk"] in check.disk:
f"A disk check for Drive {val['disk']} already exists!" 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 # ping checks
if check_type == "ping": if check_type == "ping":
@@ -74,6 +85,14 @@ class CheckSerializer(serializers.ModelSerializer):
raise serializers.ValidationError( raise serializers.ValidationError(
"A cpuload check for this agent already exists" "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 check_type == "memory" and not self.instance:
if ( if (
@@ -85,6 +104,15 @@ class CheckSerializer(serializers.ModelSerializer):
"A memory check for this agent already exists" "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 return val

View File

@@ -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 from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@@ -7,7 +7,7 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('alerts', '0004_auto_20210207_1426'), ('alerts', '0004_auto_20210212_1408'),
('clients', '0008_auto_20201103_1430'), ('clients', '0008_auto_20201103_1430'),
] ]

View File

@@ -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 from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@@ -7,7 +7,7 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('alerts', '0004_auto_20210207_1426'), ('alerts', '0004_auto_20210212_1408'),
('core', '0012_coresettings_check_history_prune_days'), ('core', '0012_coresettings_check_history_prune_days'),
] ]

View File

@@ -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)]

View File

@@ -172,7 +172,7 @@
<span <span
style="cursor: pointer; text-decoration: underline" style="cursor: pointer; text-decoration: underline"
class="text-primary" class="text-primary"
@click="scriptMoreInfo(props.row)" @click="showScriptOutput(props.row)"
>output</span >output</span
> >
</q-td> </q-td>
@@ -191,16 +191,6 @@
<q-dialog v-model="showAddAutomatedTask" position="top"> <q-dialog v-model="showAddAutomatedTask" position="top">
<AddAutomatedTask @close="showAddAutomatedTask = false" /> <AddAutomatedTask @close="showAddAutomatedTask = false" />
</q-dialog> </q-dialog>
<q-dialog v-model="showScriptOutput">
<ScriptOutput
@close="
showScriptOutput = false;
scriptInfo = {};
"
:scriptInfo="scriptInfo"
/>
</q-dialog>
</div> </div>
</template> </template>
@@ -214,15 +204,13 @@ import ScriptOutput from "@/components/modals/checks/ScriptOutput";
export default { export default {
name: "AutomatedTasksTab", name: "AutomatedTasksTab",
components: { AddAutomatedTask, ScriptOutput }, components: { AddAutomatedTask },
mixins: [mixins], mixins: [mixins],
data() { data() {
return { return {
showAddAutomatedTask: false, showAddAutomatedTask: false,
showEditAutomatedTask: false, showEditAutomatedTask: false,
showScriptOutput: false,
editTaskPk: null, editTaskPk: null,
showScriptOutput: false,
scriptInfo: {}, scriptInfo: {},
columns: [ columns: [
{ name: "enabled", align: "left", field: "enabled" }, { name: "enabled", align: "left", field: "enabled" },
@@ -310,9 +298,12 @@ export default {
refreshTasks(id) { refreshTasks(id) {
this.$store.dispatch("loadAutomatedTasks", id); this.$store.dispatch("loadAutomatedTasks", id);
}, },
scriptMoreInfo(props) { showScriptOutput(script) {
this.scriptInfo = props; this.$q.dialog({
this.showScriptOutput = true; component: ScriptOutput,
parent: this,
scriptInfo: script,
});
}, },
showEditTask(task) { showEditTask(task) {
this.$q this.$q

View File

@@ -214,7 +214,7 @@
v-else-if="props.row.check_type === 'script'" v-else-if="props.row.check_type === 'script'"
style="cursor: pointer; text-decoration: underline" style="cursor: pointer; text-decoration: underline"
class="text-primary" class="text-primary"
@click="scriptMoreInfo(props.row)" @click="showScriptOutput(props.row)"
>Last Output</span >Last Output</span
> >
<span <span
@@ -225,7 +225,7 @@
>Last Output</span >Last Output</span
> >
</q-td> </q-td>
<q-td>{{ props.row.last_run }}</q-td> <q-td>{{ props.row.last_run || "Never" }}</q-td>
<q-td v-if="props.row.assigned_task !== null && props.row.assigned_task.length > 1" <q-td v-if="props.row.assigned_task !== null && props.row.assigned_task.length > 1"
>{{ props.row.assigned_task.length }} Tasks</q-td >{{ props.row.assigned_task.length }} Tasks</q-td
> >
@@ -258,15 +258,6 @@
<q-dialog v-model="showScriptCheck"> <q-dialog v-model="showScriptCheck">
<ScriptCheck @close="showScriptCheck = false" :agentpk="selectedAgentPk" :mode="mode" :checkpk="checkpk" /> <ScriptCheck @close="showScriptCheck = false" :agentpk="selectedAgentPk" :mode="mode" :checkpk="checkpk" />
</q-dialog> </q-dialog>
<q-dialog v-model="showScriptOutput">
<ScriptOutput
@close="
showScriptOutput = false;
scriptInfo = {};
"
:scriptInfo="scriptInfo"
/>
</q-dialog>
<q-dialog v-model="showEventLogOutput"> <q-dialog v-model="showEventLogOutput">
<EventLogCheckOutput <EventLogCheckOutput
@close=" @close="
@@ -304,7 +295,6 @@ export default {
WinSvcCheck, WinSvcCheck,
EventLogCheck, EventLogCheck,
ScriptCheck, ScriptCheck,
ScriptOutput,
EventLogCheckOutput, EventLogCheckOutput,
}, },
mixins: [mixins], mixins: [mixins],
@@ -319,9 +309,7 @@ export default {
showWinSvcCheck: false, showWinSvcCheck: false,
showEventLogCheck: false, showEventLogCheck: false,
showScriptCheck: false, showScriptCheck: false,
showScriptOutput: false,
showEventLogOutput: false, showEventLogOutput: false,
scriptInfo: {},
evtlogdata: {}, evtlogdata: {},
columns: [ columns: [
{ name: "smsalert", field: "text_alert", align: "left" }, { name: "smsalert", field: "text_alert", align: "left" },
@@ -425,10 +413,6 @@ export default {
html: true, html: true,
}); });
}, },
scriptMoreInfo(props) {
this.scriptInfo = props;
this.showScriptOutput = true;
},
eventLogMoreInfo(props) { eventLogMoreInfo(props) {
this.evtlogdata = props; this.evtlogdata = props;
this.showEventLogOutput = true; this.showEventLogOutput = true;
@@ -460,6 +444,13 @@ export default {
check: check, check: check,
}); });
}, },
showScriptOutput(script) {
this.$q.dialog({
component: ScriptOutput,
parent: this,
scriptInfo: script,
});
},
}, },
computed: { computed: {
...mapGetters(["selectedAgentPk", "checks", "tabsTableHeight"]), ...mapGetters(["selectedAgentPk", "checks", "tabsTableHeight"]),

View File

@@ -78,7 +78,7 @@
> >
<span <span
style="cursor: pointer; text-decoration: underline" style="cursor: pointer; text-decoration: underline"
@click="scriptMoreInfo(props.row)" @click="showScriptOutput(props.row)"
class="script-cell text-primary" class="script-cell text-primary"
>output</span >output</span
> >
@@ -103,9 +103,6 @@
</q-table> </q-table>
</q-card-section> </q-card-section>
<q-dialog v-model="showScriptOutput" @hide="closeScriptOutput">
<ScriptOutput @close="closeScriptOutput" :scriptInfo="scriptInfo" />
</q-dialog>
<q-dialog v-model="showEventLogOutput" @hide="closeEventLogOutput"> <q-dialog v-model="showEventLogOutput" @hide="closeEventLogOutput">
<EventLogCheckOutput @close="closeEventLogOutput" :evtlogdata="evtLogData" /> <EventLogCheckOutput @close="closeEventLogOutput" :evtlogdata="evtLogData" />
</q-dialog> </q-dialog>
@@ -120,7 +117,6 @@ import EventLogCheckOutput from "@/components/modals/checks/EventLogCheckOutput"
export default { export default {
name: "PolicyStatus", name: "PolicyStatus",
components: { components: {
ScriptOutput,
EventLogCheckOutput, EventLogCheckOutput,
}, },
props: { props: {
@@ -139,10 +135,8 @@ export default {
}, },
data() { data() {
return { return {
showScriptOutput: false,
showEventLogOutput: false, showEventLogOutput: false,
evtLogData: {}, evtLogData: {},
scriptInfo: {},
data: [], data: [],
columns: [ columns: [
{ name: "agent", label: "Hostname", field: "agent", align: "left", sortable: true }, { name: "agent", label: "Hostname", field: "agent", align: "left", sortable: true },
@@ -225,14 +219,17 @@ export default {
html: true, html: true,
}); });
}, },
scriptMoreInfo(check) {
this.scriptInfo = check;
this.showScriptOutput = true;
},
eventLogMoreInfo(check) { eventLogMoreInfo(check) {
this.evtLogData = check; this.evtLogData = check;
this.showEventLogOutput = true; this.showEventLogOutput = true;
}, },
showScriptOutput(script) {
this.$q.dialog({
component: ScriptOutput,
parent: this,
scriptInfo: script,
});
},
show() { show() {
this.$refs.dialog.show(); this.$refs.dialog.show();
}, },

View File

@@ -208,6 +208,10 @@ export default {
}, },
}, },
}; };
if (this.check.check_type === "diskspace") {
this.chartOptions["yaxis"]["reversed"] = true;
}
} else { } else {
// Set the y-axis labels to Failing and Passing // Set the y-axis labels to Failing and Passing
this.chartOptions["yaxis"] = { this.chartOptions["yaxis"] = {

View File

@@ -56,6 +56,21 @@
/> />
</div> </div>
<div class="col-2 q-my-sm">Failure action timeout</div>
<div class="col-10 q-mb-sm">
<q-input
outlined
type="number"
v-model.number="template.action_timeout"
dense
:rules="[
val => !!val || 'Failure action timeout is required',
val => val > 0 || 'Timeout must be greater than 0',
val => val > 60 || 'Timeout must be 60 or less',
]"
/>
</div>
<div class="col-2 q-my-sm"> <div class="col-2 q-my-sm">
<span style="text-decoration: underline; cursor: help" <span style="text-decoration: underline; cursor: help"
>Resolved action >Resolved action
@@ -92,6 +107,21 @@
new-value-mode="add" new-value-mode="add"
/> />
</div> </div>
<div class="col-2 q-my-sm">Resolved action timeout</div>
<div class="col-10 q-mb-sm">
<q-input
outlined
type="number"
v-model.number="template.resolved_action_timeout"
dense
:rules="[
val => !!val || 'Resolved action timeout is required',
val => val > 0 || 'Timeout must be greater than 0',
val => val > 60 || 'Timeout must be 60 or less',
]"
/>
</div>
</q-card-section> </q-card-section>
<div class="q-pl-md text-subtitle1">Email Settings (Overrides global email settings)</div> <div class="q-pl-md text-subtitle1">Email Settings (Overrides global email settings)</div>
@@ -222,7 +252,7 @@
type="number" type="number"
v-model.number="template.agent_periodic_alert_days" v-model.number="template.agent_periodic_alert_days"
dense dense
:rules="[val => val >= 0]" :rules="[val => val >= 0 || 'Periodic days must be 0 or greater']"
/> />
</div> </div>
</q-card-section> </q-card-section>
@@ -338,7 +368,7 @@
type="number" type="number"
v-model.number="template.check_periodic_alert_days" v-model.number="template.check_periodic_alert_days"
dense dense
:rules="[val => val >= 0]" :rules="[val => val >= 0 || 'Periodic days must be 0 or greater']"
/> />
</div> </div>
</q-card-section> </q-card-section>
@@ -482,8 +512,10 @@ export default {
is_active: true, is_active: true,
action: null, action: null,
action_args: [], action_args: [],
action_timeout: 15,
resolved_action: null, resolved_action: null,
resolved_action_args: [], resolved_action_args: [],
resolved_action_timeout: 15,
email_recipients: [], email_recipients: [],
email_from: "", email_from: "",
text_recipients: [], text_recipients: [],

View File

@@ -113,6 +113,24 @@
<q-icon name="flag" size="sm" class="cursor-pointer" @click="resolveAlert(props.row)"> <q-icon name="flag" size="sm" class="cursor-pointer" @click="resolveAlert(props.row)">
<q-tooltip>Resolve alert</q-tooltip> <q-tooltip>Resolve alert</q-tooltip>
</q-icon> </q-icon>
<q-icon
v-if="props.row.action_run"
name="mdi-archive-alert"
size="sm"
class="cursor-pointer"
@click="showScriptOutput(props.row, true)"
>
<q-tooltip>Show failure action run results</q-tooltip>
</q-icon>
<q-icon
v-if="props.row.resolved_action_run"
name="mdi-archive-check"
size="sm"
class="cursor-pointer"
@click="showScriptOutput(props.row, false)"
>
<q-tooltip>Show resolved action run results</q-tooltip>
</q-icon>
</div> </div>
</q-td> </q-td>
</template> </template>
@@ -130,6 +148,7 @@
<script> <script>
import mixins from "@/mixins/mixins"; import mixins from "@/mixins/mixins";
import ScriptOutput from "@/components/modals/checks/ScriptOutput";
export default { export default {
name: "AlertsOverview", name: "AlertsOverview",
@@ -380,6 +399,30 @@ export default {
}); });
}); });
}, },
showScriptOutput(alert, failure = false) {
let results = {};
if (failure) {
results.readable_desc = `${alert.alert_type} failure action results`;
results.execution_time = alert.action_execution_time;
results.retcode = alert.action_retcode;
results.stdout = alert.action_stdout;
results.errout = alert.action_errout;
results.last_run = alert.action_run;
} else {
results.readable_desc = `${alert.alert_type} resolved action results`;
results.execution_time = alert.resolved_action_execution_time;
results.retcode = alert.resolved_action_retcode;
results.stdout = alert.resolved_action_stdout;
results.errout = alert.resolved_action_errout;
results.last_run = alert.resolved_action_run;
}
this.$q.dialog({
component: ScriptOutput,
parent: this,
scriptInfo: results,
});
},
alertColor(severity) { alertColor(severity) {
if (severity === "error") { if (severity === "error") {
return "red"; return "red";

View File

@@ -11,25 +11,19 @@
<q-card-section> <q-card-section>
<q-input <q-input
outlined outlined
type="number"
v-model.number="cpuloadcheck.warning_threshold" v-model.number="cpuloadcheck.warning_threshold"
label="Warning Threshold (%)" label="Warning Threshold (%)"
:rules="[ :rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
val => !!val || '*Required',
val => val >= 1 || 'Minimum threshold is 1',
val => val < 100 || 'Maximum threshold is 99',
]"
/> />
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
<q-input <q-input
outlined outlined
type="number"
v-model.number="cpuloadcheck.error_threshold" v-model.number="cpuloadcheck.error_threshold"
label="Error Threshold (%)" label="Error Threshold (%)"
:rules="[ :rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
val => !!val || '*Required',
val => val >= 1 || 'Minimum threshold is 1',
val => val < 100 || 'Maximum threshold is 99',
]"
/> />
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
@@ -74,22 +68,12 @@ export default {
failOptions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], failOptions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
}; };
}, },
computed: {
thresholdIsValid() {
return (
this.cpuloadcheck.warning_threshold === 0 ||
this.cpuloadcheck.error_threshold === 0 ||
this.cpuloadcheck.warning_threshold < this.cpuloadcheck.error_threshold
);
},
},
methods: { methods: {
getCheck() { getCheck() {
axios.get(`/checks/${this.checkpk}/check/`).then(r => (this.cpuloadcheck = r.data)); axios.get(`/checks/${this.checkpk}/check/`).then(r => (this.cpuloadcheck = r.data));
}, },
addCheck() { addCheck() {
if (!this.thresholdIsValid) { if (!this.isValidThreshold(this.cpuloadcheck.warning_threshold, this.cpuloadcheck.error_threshold)) {
this.notifyError("Warning Threshold needs to be less than Error threshold");
return; return;
} }
@@ -108,8 +92,7 @@ export default {
.catch(e => this.notifyError(e.response.data.non_field_errors)); .catch(e => this.notifyError(e.response.data.non_field_errors));
}, },
editCheck() { editCheck() {
if (!this.thresholdIsValid) { if (!this.isValidThreshold(this.cpuloadcheck.warning_threshold, this.cpuloadcheck.error_threshold)) {
this.notifyError("Warning Threshold needs to be less than Error threshold");
return; return;
} }

View File

@@ -20,25 +20,19 @@
<q-card-section> <q-card-section>
<q-input <q-input
outlined outlined
type="number"
v-model.number="diskcheck.warning_threshold" v-model.number="diskcheck.warning_threshold"
label="Warning Threshold (%)" label="Warning Threshold Remaining (%)"
:rules="[ :rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
val => !!val || '*Required',
val => val >= 1 || 'Minimum threshold is 1',
val => val < 100 || 'Maximum threshold is 99',
]"
/> />
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
<q-input <q-input
outlined outlined
type="number"
v-model.number="diskcheck.error_threshold" v-model.number="diskcheck.error_threshold"
label="Error Threshold (%)" label="Error Threshold Remaining (%)"
:rules="[ :rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
val => !!val || '*Required',
val => val >= 1 || 'Minimum threshold is 1',
val => val < 100 || 'Maximum threshold is 99',
]"
/> />
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
@@ -62,7 +56,7 @@
<script> <script>
import axios from "axios"; import axios from "axios";
import { mapState, mapGetters } from "vuex"; import { mapGetters } from "vuex";
import mixins from "@/mixins/mixins"; import mixins from "@/mixins/mixins";
export default { export default {
name: "DiskSpaceCheck", name: "DiskSpaceCheck",
@@ -102,8 +96,7 @@ export default {
axios.get(`/checks/${this.checkpk}/check/`).then(r => (this.diskcheck = r.data)); axios.get(`/checks/${this.checkpk}/check/`).then(r => (this.diskcheck = r.data));
}, },
addCheck() { addCheck() {
if (!this.thresholdIsValid) { if (!this.isValidThreshold(this.diskcheck.warning_threshold, this.diskcheck.error_threshold, true)) {
this.notifyError("Warning Threshold needs to be greater than Error threshold");
return; return;
} }
@@ -122,8 +115,7 @@ export default {
.catch(e => this.notifyError(e.response.data.non_field_errors)); .catch(e => this.notifyError(e.response.data.non_field_errors));
}, },
editCheck() { editCheck() {
if (!this.thresholdIsValid) { if (!this.isValidThreshold(this.diskcheck.warning_threshold, this.diskcheck.error_threshold, true)) {
this.notifyError("Warning Threshold needs to be greater than Error threshold");
return; return;
} }
@@ -144,13 +136,6 @@ export default {
}, },
computed: { computed: {
...mapGetters(["agentDisks"]), ...mapGetters(["agentDisks"]),
thresholdIsValid() {
return (
!!this.diskcheck.warning_threshold ||
!!this.diskcheck.error_threshold ||
this.diskcheck.warning_threshold > this.diskcheck.error_threshold
);
},
}, },
created() { created() {
if (this.mode === "add") { if (this.mode === "add") {

View File

@@ -11,25 +11,19 @@
<q-card-section> <q-card-section>
<q-input <q-input
outlined outlined
type="number"
v-model.number="memcheck.warning_threshold" v-model.number="memcheck.warning_threshold"
label="Warning Threshold (%)" label="Warning Threshold (%)"
:rules="[ :rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
val => !!val || '*Required',
val => val >= 1 || 'Minimum threshold is 1',
val => val < 100 || 'Maximum threshold is 99',
]"
/> />
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
<q-input <q-input
outlined outlined
type="number"
v-model.number="memcheck.error_threshold" v-model.number="memcheck.error_threshold"
label="Error Threshold (%)" label="Error Threshold (%)"
:rules="[ :rules="[val => val >= 0 || 'Minimum threshold is 0', val => val < 100 || 'Maximum threshold is 99']"
val => !!val || '*Required',
val => val >= 1 || 'Minimum threshold is 1',
val => val < 100 || 'Maximum threshold is 99',
]"
/> />
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
@@ -74,22 +68,12 @@ export default {
failOptions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], failOptions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
}; };
}, },
computed: {
thresholdIsValid() {
return (
this.memcheck.warning_threshold === 0 ||
this.memcheck.error_threshold === 0 ||
this.memcheck.warning_threshold < this.memcheck.error_threshold
);
},
},
methods: { methods: {
getCheck() { getCheck() {
axios.get(`/checks/${this.checkpk}/check/`).then(r => (this.memcheck = r.data)); axios.get(`/checks/${this.checkpk}/check/`).then(r => (this.memcheck = r.data));
}, },
addCheck() { addCheck() {
if (!this.thresholdIsValid) { if (!this.isValidThreshold(this.memcheck.warning_threshold, this.memcheck.error_threshold)) {
this.notifyError("Warning Threshold needs to be less than Error threshold");
return; return;
} }
@@ -108,8 +92,7 @@ export default {
.catch(e => this.notifyError(e.response.data.non_field_errors)); .catch(e => this.notifyError(e.response.data.non_field_errors));
}, },
editCheck() { editCheck() {
if (!this.thresholdIsValid) { if (!this.isValidThreshold(this.memcheck.warning_threshold, this.memcheck.error_threshold)) {
this.notifyError("Warning Threshold needs to be less than Error threshold");
return; return;
} }

View File

@@ -1,44 +1,56 @@
<template> <template>
<q-card style="min-width: 70vw" class="q-pa-xs"> <q-dialog ref="dialog" @hide="onHide">
<q-card-section> <q-card class="q-dialog-plugin q-pa-xs" style="min-width: 70vw">
<div class="row items-center"> <q-bar>
<div class="text-h6">{{ scriptInfo.readable_desc }}</div> {{ scriptInfo.readable_desc }}
<q-space /> <q-space />
<q-btn icon="close" flat round dense v-close-popup /> <q-btn dense flat icon="close" v-close-popup>
</div> <q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
<div> </q-btn>
Last Run: </q-bar>
<code>{{ scriptInfo.last_run }}</code> <q-card-section>
<br />Run Time: <div>
<code>{{ scriptInfo.execution_time}} seconds</code> Last Run:
<br />Return Code: <code>{{ scriptInfo.last_run }}</code>
<code>{{ scriptInfo.retcode }}</code> <br />Run Time:
<code>{{ scriptInfo.execution_time }} seconds</code>
<br />Return Code:
<code>{{ scriptInfo.retcode }}</code>
<br />
</div>
<br /> <br />
</div> <div v-if="scriptInfo.stdout">
<br /> Standard Output
<div v-if="scriptInfo.stdout"> <q-separator />
Standard Output <q-scroll-area style="height: 50vh; max-height: 70vh">
<q-separator /> <pre>{{ scriptInfo.stdout }}</pre>
<q-scroll-area style="height: 50vh; max-height: 70vh;"> </q-scroll-area>
<pre>{{ scriptInfo.stdout }}</pre> </div>
</q-scroll-area> <div v-if="scriptInfo.stderr">
</div> Standard Error:
<div v-if="scriptInfo.stderr"> <q-scroll-area style="height: 50vh; max-height: 70vh">
Standard Error: <pre>{{ scriptInfo.stderr }}</pre>
<q-scroll-area style="height: 50vh; max-height: 70vh;"> </q-scroll-area>
<pre>{{ scriptInfo.stderr }}</pre> </div>
</q-scroll-area> </q-card-section>
</div> </q-card>
</q-card-section> </q-dialog>
</q-card>
</template> </template>
<script> <script>
export default { export default {
name: "ScriptOutput", name: "ScriptOutput",
props: ["scriptInfo"], props: ["scriptInfo"],
beforeDestroy() { methods: {
this.$emit("close"); show() {
this.$refs.dialog.show();
},
hide() {
this.$refs.dialog.hide();
},
onHide() {
this.$emit("hide");
},
}, },
}; };
</script> </script>

View File

@@ -84,6 +84,25 @@ export default {
notifyInfo(msg, timeout = 2000) { notifyInfo(msg, timeout = 2000) {
Notify.create(notifyInfoConfig(msg, timeout)); 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) { 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}$/; 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); return email.test(val);