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)
# 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(

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

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.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)

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

View File

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

View File

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

View File

@@ -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(

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
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()

View File

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

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

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

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
style="cursor: pointer; text-decoration: underline"
class="text-primary"
@click="scriptMoreInfo(props.row)"
@click="showScriptOutput(props.row)"
>output</span
>
</q-td>
@@ -191,16 +191,6 @@
<q-dialog v-model="showAddAutomatedTask" position="top">
<AddAutomatedTask @close="showAddAutomatedTask = false" />
</q-dialog>
<q-dialog v-model="showScriptOutput">
<ScriptOutput
@close="
showScriptOutput = false;
scriptInfo = {};
"
:scriptInfo="scriptInfo"
/>
</q-dialog>
</div>
</template>
@@ -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

View File

@@ -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</span
>
<span
@@ -225,7 +225,7 @@
>Last Output</span
>
</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"
>{{ props.row.assigned_task.length }} Tasks</q-td
>
@@ -258,15 +258,6 @@
<q-dialog v-model="showScriptCheck">
<ScriptCheck @close="showScriptCheck = false" :agentpk="selectedAgentPk" :mode="mode" :checkpk="checkpk" />
</q-dialog>
<q-dialog v-model="showScriptOutput">
<ScriptOutput
@close="
showScriptOutput = false;
scriptInfo = {};
"
:scriptInfo="scriptInfo"
/>
</q-dialog>
<q-dialog v-model="showEventLogOutput">
<EventLogCheckOutput
@close="
@@ -304,7 +295,6 @@ export default {
WinSvcCheck,
EventLogCheck,
ScriptCheck,
ScriptOutput,
EventLogCheckOutput,
},
mixins: [mixins],
@@ -319,9 +309,7 @@ export default {
showWinSvcCheck: false,
showEventLogCheck: false,
showScriptCheck: false,
showScriptOutput: false,
showEventLogOutput: false,
scriptInfo: {},
evtlogdata: {},
columns: [
{ name: "smsalert", field: "text_alert", align: "left" },
@@ -425,10 +413,6 @@ export default {
html: true,
});
},
scriptMoreInfo(props) {
this.scriptInfo = props;
this.showScriptOutput = true;
},
eventLogMoreInfo(props) {
this.evtlogdata = props;
this.showEventLogOutput = true;
@@ -460,6 +444,13 @@ export default {
check: check,
});
},
showScriptOutput(script) {
this.$q.dialog({
component: ScriptOutput,
parent: this,
scriptInfo: script,
});
},
},
computed: {
...mapGetters(["selectedAgentPk", "checks", "tabsTableHeight"]),

View File

@@ -78,7 +78,7 @@
>
<span
style="cursor: pointer; text-decoration: underline"
@click="scriptMoreInfo(props.row)"
@click="showScriptOutput(props.row)"
class="script-cell text-primary"
>output</span
>
@@ -103,9 +103,6 @@
</q-table>
</q-card-section>
<q-dialog v-model="showScriptOutput" @hide="closeScriptOutput">
<ScriptOutput @close="closeScriptOutput" :scriptInfo="scriptInfo" />
</q-dialog>
<q-dialog v-model="showEventLogOutput" @hide="closeEventLogOutput">
<EventLogCheckOutput @close="closeEventLogOutput" :evtlogdata="evtLogData" />
</q-dialog>
@@ -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();
},

View File

@@ -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"] = {

View File

@@ -56,6 +56,21 @@
/>
</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">
<span style="text-decoration: underline; cursor: help"
>Resolved action
@@ -92,6 +107,21 @@
new-value-mode="add"
/>
</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>
<div class="q-pl-md text-subtitle1">Email Settings (Overrides global email settings)</div>
@@ -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']"
/>
</div>
</q-card-section>
@@ -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']"
/>
</div>
</q-card-section>
@@ -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: [],

View File

@@ -113,6 +113,24 @@
<q-icon name="flag" size="sm" class="cursor-pointer" @click="resolveAlert(props.row)">
<q-tooltip>Resolve alert</q-tooltip>
</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>
</q-td>
</template>
@@ -130,6 +148,7 @@
<script>
import mixins from "@/mixins/mixins";
import ScriptOutput from "@/components/modals/checks/ScriptOutput";
export default {
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) {
if (severity === "error") {
return "red";

View File

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

View File

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

View File

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

View File

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

View File

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