alerts overview work
This commit is contained in:
18
api/tacticalrmm/alerts/migrations/0002_alert_snoozed.py
Normal file
18
api/tacticalrmm/alerts/migrations/0002_alert_snoozed.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.1.4 on 2021-01-24 18:26
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('alerts', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='alert',
|
||||
name='snoozed',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
18
api/tacticalrmm/alerts/migrations/0003_alert_alert_type.py
Normal file
18
api/tacticalrmm/alerts/migrations/0003_alert_alert_type.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.1.4 on 2021-01-24 18:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('alerts', '0002_alert_snoozed'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='alert',
|
||||
name='alert_type',
|
||||
field=models.CharField(choices=[('availability', 'Availability'), ('check', 'Check'), ('task', 'Task'), ('custom', 'Custom')], default='availability', max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -8,6 +8,13 @@ SEVERITY_CHOICES = [
|
||||
("error", "Error"),
|
||||
]
|
||||
|
||||
ALERT_TYPE_CHOICES = [
|
||||
("availability", "Availability"),
|
||||
("check", "Check"),
|
||||
("task", "Task"),
|
||||
("custom", "Custom"),
|
||||
]
|
||||
|
||||
|
||||
class Alert(models.Model):
|
||||
agent = models.ForeignKey(
|
||||
@@ -31,8 +38,12 @@ class Alert(models.Model):
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
alert_type = models.CharField(
|
||||
max_length=20, choices=ALERT_TYPE_CHOICES, default="availability"
|
||||
)
|
||||
message = models.TextField(null=True, blank=True)
|
||||
alert_time = models.DateTimeField(auto_now_add=True, null=True, blank=True)
|
||||
snoozed = models.BooleanField(default=False)
|
||||
snooze_until = models.DateTimeField(null=True, blank=True)
|
||||
resolved = models.BooleanField(default=False)
|
||||
resolved_time = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
@@ -10,8 +10,8 @@ from .models import Alert, AlertTemplate
|
||||
class AlertSerializer(ModelSerializer):
|
||||
|
||||
hostname = ReadOnlyField(source="agent.hostname")
|
||||
client = ReadOnlyField(source="agent.client")
|
||||
site = ReadOnlyField(source="agent.site")
|
||||
client = ReadOnlyField(source="agent.client.name")
|
||||
site = ReadOnlyField(source="agent.site.name")
|
||||
alert_time = DateTimeField(format="iso-8601")
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Q
|
||||
from datetime import datetime as dt
|
||||
from django.utils import timezone as djangotime
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
from .models import Alert, AlertTemplate
|
||||
|
||||
@@ -10,11 +12,84 @@ from .serializers import AlertSerializer, AlertTemplateSerializer
|
||||
|
||||
|
||||
class GetAddAlerts(APIView):
|
||||
def get(self, request):
|
||||
def patch(self, request):
|
||||
|
||||
# top 10 alerts for dashboard icon
|
||||
if "top" in request.data.keys():
|
||||
alerts = Alert.objects.filter(resolved=False, snooze_until=None).order_by(
|
||||
"alert_time"
|
||||
)[: int(request.data["top"])]
|
||||
count = Alert.objects.filter(resolved=False).count()
|
||||
return Response(
|
||||
{
|
||||
"alerts_count": count,
|
||||
"alerts": AlertSerializer(alerts, many=True).data,
|
||||
}
|
||||
)
|
||||
|
||||
elif any(
|
||||
key
|
||||
in [
|
||||
"timeFilter",
|
||||
"clientFilter",
|
||||
"severityFilter",
|
||||
"resolvedFilter",
|
||||
"snoozedFilter",
|
||||
]
|
||||
for key in request.data.keys()
|
||||
):
|
||||
clientFilter = Q()
|
||||
severityFilter = Q()
|
||||
timeFilter = Q()
|
||||
resolvedFilter = Q()
|
||||
snoozedFilter = Q()
|
||||
|
||||
if (
|
||||
"snoozedFilter" in request.data.keys()
|
||||
and not request.data["snoozedFilter"]
|
||||
):
|
||||
snoozedFilter = Q(snoozed=request.data["snoozedFilter"])
|
||||
|
||||
if (
|
||||
"resolvedFilter" in request.data.keys()
|
||||
and not request.data["resolvedFilter"]
|
||||
):
|
||||
resolvedFilter = Q(resolved=request.data["resolvedFilter"])
|
||||
|
||||
if "clientFilter" in request.data.keys():
|
||||
from agents.models import Agent
|
||||
from clients.models import Client
|
||||
|
||||
clients = Client.objects.filter(
|
||||
pk__in=request.data["clientFilter"]
|
||||
).values_list("id")
|
||||
agents = Agent.objects.filter(site__client_id__in=clients).values_list(
|
||||
"id"
|
||||
)
|
||||
|
||||
clientFilter = Q(agent__in=agents)
|
||||
|
||||
if "severityFilter" in request.data.keys():
|
||||
severityFilter = Q(severity__in=request.data["severityFilter"])
|
||||
|
||||
if "timeFilter" in request.data.keys():
|
||||
timeFilter = Q(
|
||||
alert_time__lte=djangotime.make_aware(dt.today()),
|
||||
alert_time__gt=djangotime.make_aware(dt.today())
|
||||
- djangotime.timedelta(days=int(request.data["timeFilter"])),
|
||||
)
|
||||
|
||||
alerts = (
|
||||
Alert.objects.filter(clientFilter)
|
||||
.filter(severityFilter)
|
||||
.filter(resolvedFilter)
|
||||
.filter(snoozedFilter)
|
||||
.filter(timeFilter)
|
||||
)
|
||||
return Response(AlertSerializer(alerts, many=True).data)
|
||||
|
||||
else:
|
||||
alerts = Alert.objects.all()
|
||||
|
||||
# Add time and severity filters
|
||||
|
||||
return Response(AlertSerializer(alerts, many=True).data)
|
||||
|
||||
def post(self, request):
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
<template>
|
||||
<q-btn dense flat icon="notifications">
|
||||
<q-badge v-if="alerts.length !== 0" color="red" floating transparent>{{ alertsLengthText() }}</q-badge>
|
||||
<q-badge v-if="alertsCount > 0" color="red" floating transparent>{{ alertsCountText() }}</q-badge>
|
||||
<q-menu>
|
||||
<q-list separator>
|
||||
<q-item v-if="alerts.length === 0">No New Alerts</q-item>
|
||||
<q-item v-for="alert in alerts" :key="alert.id">
|
||||
<q-item v-if="alertsCount === 0">No New Alerts</q-item>
|
||||
<q-item v-for="alert in topAlerts" :key="alert.id">
|
||||
<q-item-section>
|
||||
<q-item-label overline>{{ alert.client }} - {{ alert.site }} - {{ alert.hostname }}</q-item-label>
|
||||
<q-item-label>
|
||||
<q-icon
|
||||
size="xs"
|
||||
:class="`text-${alertColor(alert.severity)}`"
|
||||
:name="alert.severity"
|
||||
></q-icon>
|
||||
<q-icon size="xs" :class="`text-${alertIconColor(alert.severity)}`" :name="alert.severity"></q-icon>
|
||||
{{ alert.message }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
@@ -20,51 +16,56 @@
|
||||
<q-item-section side top>
|
||||
<q-item-label caption>{{ alertTime(alert.alert_time) }}</q-item-label>
|
||||
<q-item-label>
|
||||
<q-icon name="snooze" size="xs">
|
||||
<q-icon name="snooze" size="xs" class="cursor-pointer">
|
||||
<q-tooltip>Snooze the alert for 24 hours</q-tooltip>
|
||||
</q-icon>
|
||||
<q-icon name="alarm_off" size="xs">
|
||||
<q-icon name="alarm_off" size="xs" class="cursor-pointer">
|
||||
<q-tooltip>Dismiss alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="showAlertsModal = true">View All Alerts ({{ alerts.length }})</q-item>
|
||||
<q-item clickable @click="showOverview">View All Alerts ({{ alertsCount }})</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
|
||||
<q-dialog
|
||||
v-model="showAlertsModal"
|
||||
maximized
|
||||
transition-show="slide-up"
|
||||
transition-hide="slide-down"
|
||||
>
|
||||
<AlertsOverview @close="showAlertsModal = false" />
|
||||
</q-dialog>
|
||||
</q-btn>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import mixins from "@/mixins/mixins";
|
||||
import AlertsOverview from "@/components/modals/alerts/AlertsOverview";
|
||||
|
||||
export default {
|
||||
name: "AlertsIcon",
|
||||
components: { AlertsOverview },
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {
|
||||
showAlertsModal: false,
|
||||
alertsCount: 0,
|
||||
topAlerts: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getAlerts() {
|
||||
this.$store.dispatch("alerts/getAlerts").catch(error => {
|
||||
console.error(error);
|
||||
this.$q.loading.show();
|
||||
this.$axios
|
||||
.patch("alerts/alerts/", { top: 10 })
|
||||
.then(r => {
|
||||
this.alertsCount = r.data.alerts_count;
|
||||
this.topAlerts = r.data.alerts;
|
||||
this.$q.loading.hide();
|
||||
})
|
||||
.catch(error => {
|
||||
this.$q.loading.hide();
|
||||
this.notifyError("Unable to get alerts");
|
||||
});
|
||||
},
|
||||
alertColor(type) {
|
||||
showOverview() {
|
||||
this.$q.dialog({
|
||||
component: AlertsOverview,
|
||||
parent: this,
|
||||
});
|
||||
},
|
||||
alertIconColor(type) {
|
||||
if (type === "error") {
|
||||
return "red";
|
||||
}
|
||||
@@ -72,20 +73,14 @@ export default {
|
||||
return "orange";
|
||||
}
|
||||
},
|
||||
alertsLengthText() {
|
||||
if (this.alerts.length > 9) {
|
||||
return "9+";
|
||||
alertsCountText() {
|
||||
if (this.alertsCount > 99) {
|
||||
return "99+";
|
||||
} else {
|
||||
return this.alerts.length;
|
||||
return this.alertsCount;
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
newAlerts: "alerts/getNewAlerts",
|
||||
alerts: "alerts/getAlerts",
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.getAlerts();
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div style="width: 60vw; max-width: 90vw">
|
||||
<div style="width: 90vw; max-width: 90vw">
|
||||
<q-card>
|
||||
<q-bar>
|
||||
<q-btn @click="getScripts" class="q-mr-sm" dense flat push icon="refresh" />Script Manager
|
||||
|
||||
@@ -311,7 +311,7 @@ export default {
|
||||
this.getPolicies();
|
||||
this.clearRow();
|
||||
},
|
||||
deletePolicy(id) {
|
||||
deletePolicy(policy) {
|
||||
this.$q
|
||||
.dialog({
|
||||
title: "Delete policy?",
|
||||
@@ -319,12 +319,16 @@ export default {
|
||||
ok: { label: "Delete", color: "negative" },
|
||||
})
|
||||
.onOk(() => {
|
||||
this.$q.loading.show();
|
||||
this.$axios
|
||||
.delete(`/automation/policies/${pk}/`)
|
||||
.delete(`/automation/policies/${policy.id}/`)
|
||||
.then(r => {
|
||||
this.refresh();
|
||||
this.$q.loading.hide();
|
||||
this.notifySuccess("Policy was deleted!");
|
||||
})
|
||||
.catch(error => {
|
||||
this.$q.loading.hide();
|
||||
this.notifyError("An Error occured while deleting policy");
|
||||
});
|
||||
});
|
||||
@@ -422,6 +426,7 @@ export default {
|
||||
this.$axios
|
||||
.put(`/automation/policies/${data.id}/`, data)
|
||||
.then(r => {
|
||||
this.refresh();
|
||||
this.$q.loading.hide();
|
||||
this.notifySuccess(text);
|
||||
})
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
:nodes="clientSiteTree"
|
||||
node-key="id"
|
||||
selected-color="primary"
|
||||
@update:selected="setSelectedPolicyId(key)"
|
||||
default-expand-all
|
||||
:selected.sync="selectedPolicyId"
|
||||
></q-tree>
|
||||
</div>
|
||||
</template>
|
||||
@@ -39,10 +38,10 @@
|
||||
</q-tabs>
|
||||
<q-tab-panels v-model="selectedTab" animated transition-prev="jump-up" transition-next="jump-up">
|
||||
<q-tab-panel name="checks">
|
||||
<PolicyChecksTab :selectedPolicy="selectedPolicyId" />
|
||||
<PolicyChecksTab v-if="!!selectedPolicyId" :selectedPolicy="selectedPolicyId" />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="tasks">
|
||||
<PolicyAutomatedTasksTab :selectedPolicy="selectedPolicyId" />
|
||||
<PolicyAutomatedTasksTab v-if="!!selectedPolicyId" :selectedPolicy="selectedPolicyId" />
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</template>
|
||||
@@ -85,12 +84,6 @@ export default {
|
||||
this.notifyError("Error getting policy tree data");
|
||||
});
|
||||
},
|
||||
setSelectedPolicyId(key) {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
this.selectedPolicyId = this.$refs.tree.getNodeByKey(key);
|
||||
},
|
||||
processTreeDataFromApi(data) {
|
||||
/* Structure
|
||||
* [{
|
||||
@@ -114,10 +107,10 @@ export default {
|
||||
// Used by tree for unique identification
|
||||
let unique_id = 0;
|
||||
|
||||
for (let client in data) {
|
||||
for (let client of data) {
|
||||
var client_temp = {};
|
||||
|
||||
client_temp["label"] = data[client].name;
|
||||
client_temp["label"] = client.name;
|
||||
client_temp["id"] = unique_id;
|
||||
client_temp["icon"] = "business";
|
||||
client_temp["selectable"] = false;
|
||||
@@ -126,41 +119,41 @@ export default {
|
||||
unique_id--;
|
||||
|
||||
// Add any server policies assigned to client
|
||||
if (data[client].server_policy !== null) {
|
||||
if (!!client.server_policy) {
|
||||
let disabled = "";
|
||||
|
||||
// Indicate if the policy is active or not
|
||||
if (!data[client].server_policy.active) {
|
||||
if (!client.server_policy.active) {
|
||||
disabled = " (disabled)";
|
||||
}
|
||||
|
||||
client_temp["children"].push({
|
||||
label: data[client].server_policy.name + " (Servers)" + disabled,
|
||||
label: client.server_policy.name + " (Servers)" + disabled,
|
||||
icon: "policy",
|
||||
id: data[client].server_policy.id,
|
||||
id: client.server_policy.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Add any workstation policies assigned to client
|
||||
if (data[client].workstation_policy !== null) {
|
||||
if (!!client.workstation_policy) {
|
||||
let disabled = "";
|
||||
|
||||
// Indicate if the policy is active or not
|
||||
if (!data[client].workstation_policy.active) {
|
||||
if (!client.workstation_policy.active) {
|
||||
disabled = " (disabled)";
|
||||
}
|
||||
|
||||
client_temp["children"].push({
|
||||
label: data[client].workstation_policy.name + " (Workstations)" + disabled,
|
||||
label: client.workstation_policy.name + " (Workstations)" + disabled,
|
||||
icon: "policy",
|
||||
id: data[client].workstation_policy.id,
|
||||
id: client.workstation_policy.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Iterate through Sites
|
||||
for (let site in data[client].sites) {
|
||||
for (let site of client.sites) {
|
||||
var site_temp = {};
|
||||
site_temp["label"] = data[client].sites[site].name;
|
||||
site_temp["label"] = site.name;
|
||||
site_temp["id"] = unique_id;
|
||||
site_temp["icon"] = "apartment";
|
||||
site_temp["selectable"] = false;
|
||||
@@ -168,36 +161,36 @@ export default {
|
||||
unique_id--;
|
||||
|
||||
// Add any server policies assigned to site
|
||||
if (data[client].sites[site].server_policy !== null) {
|
||||
if (!!site.server_policy) {
|
||||
site_temp["children"] = [];
|
||||
|
||||
// Indicate if the policy is active or not
|
||||
let disabled = "";
|
||||
if (!data[client].sites[site].server_policy.active) {
|
||||
if (!site.server_policy.active) {
|
||||
disabled = " (disabled)";
|
||||
}
|
||||
|
||||
site_temp["children"].push({
|
||||
label: data[client].sites[site].server_policy.name + " (Servers)" + disabled,
|
||||
label: site.server_policy.name + " (Servers)" + disabled,
|
||||
icon: "policy",
|
||||
id: data[client].sites[site].server_policy.id,
|
||||
id: site.server_policy.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Add any server policies assigned to site
|
||||
if (data[client].sites[site].workstation_policy !== null) {
|
||||
if (!!site.workstation_policy) {
|
||||
site_temp["children"] = [];
|
||||
|
||||
// Indicate if the policy is active or not
|
||||
let disabled = "";
|
||||
if (!data[client].sites[site].workstation_policy.active) {
|
||||
if (!site.workstation_policy.active) {
|
||||
disabled = " (disabled)";
|
||||
}
|
||||
|
||||
site_temp["children"].push({
|
||||
label: data[client].sites[site].workstation_policy.name + " (Workstations)" + disabled,
|
||||
label: site.workstation_policy.name + " (Workstations)" + disabled,
|
||||
icon: "policy",
|
||||
id: data[client].sites[site].workstation_policy.id,
|
||||
id: site.workstation_policy.id,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<q-form @submit="submit">
|
||||
<q-card-section v-if="options.length > 0">
|
||||
<q-select
|
||||
v-if="type !== 'agent'"
|
||||
v-if="type === 'client' || type === 'site'"
|
||||
class="q-mb-md"
|
||||
v-model="selectedServerPolicy"
|
||||
:options="options"
|
||||
@@ -25,7 +25,7 @@
|
||||
>
|
||||
</q-select>
|
||||
<q-select
|
||||
v-if="type !== 'agent'"
|
||||
v-if="type === 'client' || type === 'site'"
|
||||
v-model="selectedWorkstationPolicy"
|
||||
:options="options"
|
||||
outlined
|
||||
@@ -91,7 +91,7 @@ export default {
|
||||
methods: {
|
||||
submit() {
|
||||
// check if data was changed
|
||||
if (this.type !== "agent") {
|
||||
if (this.type === "client" || this.type === "site") {
|
||||
if (
|
||||
this.object.workstation_policy === this.selectedWorkstationPolicy &&
|
||||
this.object.server_policy === this.selectedServerPolicy
|
||||
@@ -99,11 +99,13 @@ export default {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
} else if (this.type === "agent") {
|
||||
if (this.object.policy === this.selectedAgentPolicy) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
this.$q.loading.show();
|
||||
|
||||
@@ -112,14 +114,13 @@ export default {
|
||||
type: this.type,
|
||||
};
|
||||
|
||||
if (this.type !== "agent") {
|
||||
if (this.type === "client" || this.type === "site") {
|
||||
data.server_policy = this.selectedServerPolicy;
|
||||
data.workstation_policy = this.selectedWorkstationPolicy;
|
||||
} else {
|
||||
} else if (this.type === "agent") {
|
||||
data.policy = this.selectedAgentPolicy;
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
this.$axios
|
||||
.post(`/automation/related/`, data)
|
||||
.then(r => {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<q-dialog ref="dialog" @hide="onHide" maximized transition-show="slide-up" transition-hide="slide-down">
|
||||
<q-card>
|
||||
<q-bar>
|
||||
<q-btn @click="search" class="q-mr-sm" dense flat push icon="refresh" />
|
||||
<q-space />
|
||||
Alerts Overview
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
@@ -8,79 +11,260 @@
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
|
||||
<q-separator />
|
||||
|
||||
<q-card-section class="row">
|
||||
<div class="col-3">
|
||||
<q-input outlined dense v-model="search">
|
||||
<template v-slot:append>
|
||||
<q-icon v-if="search !== ''" name="close" @click="search = ''" class="cursor-pointer" />
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
|
||||
<template v-slot:hint>Type in client, site, or agent name</template>
|
||||
</q-input>
|
||||
<div class="text-h6 q-pl-sm q-pt-sm">Filter</div>
|
||||
<div class="row">
|
||||
<div class="q-pa-sm col-3">
|
||||
<q-select
|
||||
v-model="clientFilter"
|
||||
:options="clientsOptions"
|
||||
label="Clients"
|
||||
multiple
|
||||
outlined
|
||||
dense
|
||||
use-chips
|
||||
map-options
|
||||
emit-value
|
||||
/>
|
||||
</div>
|
||||
<div class="q-pa-sm col-3">
|
||||
<q-select
|
||||
v-model="severityFilter"
|
||||
:options="severityOptions"
|
||||
label="Severity"
|
||||
multiple
|
||||
outlined
|
||||
dense
|
||||
use-chips
|
||||
map-options
|
||||
emit-value
|
||||
/>
|
||||
</div>
|
||||
<div class="q-pa-sm col-2">
|
||||
<q-select outlined dense v-model="timeFilter" label="Time" emit-value map-options :options="timeOptions" />
|
||||
</div>
|
||||
<div class="q-pa-sm col-2">
|
||||
<q-checkbox outlined dense v-model="includeSnoozed" label="Include snoozed" />
|
||||
<q-checkbox outlined dense v-model="includeResolved" label="Include resolved" />
|
||||
</div>
|
||||
<div class="q-pa-sm col-1">
|
||||
<q-btn color="primary" label="Search" @click="search" />
|
||||
</div>
|
||||
|
||||
<div class="col-3">
|
||||
<q-checkbox outlined dense v-model="includeDismissed" label="Include dismissed alerts?" />
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-separator />
|
||||
|
||||
<q-list separator>
|
||||
<q-item v-if="alerts.length === 0">No Alerts!</q-item>
|
||||
<q-item v-for="alert in alerts" :key="alert.id">
|
||||
<q-card-section>
|
||||
<q-table
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="tabs-tbl-sticky"
|
||||
:data="alerts"
|
||||
:columns="columns"
|
||||
:rows-per-page-options="[0]"
|
||||
:pagination.sync="pagination"
|
||||
:no-data-label="noDataText"
|
||||
:visible-columns="visibleColumns"
|
||||
:selected.sync="selectedAlerts"
|
||||
selection="multiple"
|
||||
binary-state-sort
|
||||
row-key="id"
|
||||
dense
|
||||
virtual-scroll
|
||||
>
|
||||
<template v-slot:top="props">
|
||||
<div class="col-2 q-table__title">Treats</div>
|
||||
|
||||
<q-btn-dropdown label="Bulk Actions" v-if="props.selected > 0">
|
||||
<q-list dense>
|
||||
<q-item clickable v-close-popup @click="snoozeAlert(props.selected)">
|
||||
<q-item-section>
|
||||
<q-item-label overline>{{ alert.client }} - {{ alert.site }} - {{ alert.hostname }}</q-item-label>
|
||||
<q-item-label>
|
||||
<q-icon size="sm" :class="`text-${alertColor(alert.severity)}`" :name="alert.severity"></q-icon>
|
||||
{{ alert.message }}
|
||||
</q-item-label>
|
||||
<q-item-label>Snooze alerts</q-item-label>
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section side top>
|
||||
<q-item-label caption>{{ alertTime(alert.alert_time) }}</q-item-label>
|
||||
<q-item-label>
|
||||
<q-icon name="snooze" size="sm">
|
||||
<q-tooltip>Snooze the alert for 24 hours</q-tooltip>
|
||||
</q-icon>
|
||||
<q-icon name="alarm_off" size="sm">
|
||||
<q-tooltip>Dismiss alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-item-label>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="resolveAlert(props.selected)">
|
||||
<q-item-section>
|
||||
<q-item-label>Resolve alert</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-actions="props">
|
||||
<q-td :props="props">
|
||||
<q-icon name="snooze" size="sm" class="cursor-pointer" @click="snoozeAlert(props.row)">
|
||||
<q-tooltip>Snooze alert</q-tooltip>
|
||||
</q-icon>
|
||||
<q-icon name="alarm_off" size="sm" class="cursor-pointer" @click="resolveAlert(props.row)">
|
||||
<q-tooltip>Dismiss alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-severity="props">
|
||||
<q-td :props="props">
|
||||
<q-badge :color="alertColor(props.row.severity)">{{ capitalize(props.row.severity) }}</q-badge>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixins from "@/mixins/mixins";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "AlertsOverview",
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {
|
||||
search: "",
|
||||
includeDismissed: false,
|
||||
alerts: [],
|
||||
selectedAlerts: [],
|
||||
severityFilter: [],
|
||||
clientFilter: [],
|
||||
timeFilter: 30,
|
||||
includeResolved: false,
|
||||
includeSnoozed: false,
|
||||
searched: false,
|
||||
clientsOptions: [],
|
||||
severityOptions: [
|
||||
{ label: "Informational", value: "info" },
|
||||
{ label: "Warning", value: "warning" },
|
||||
{ label: "Error", value: "error" },
|
||||
],
|
||||
timeOptions: [
|
||||
{ value: 1, label: "1 Day Ago" },
|
||||
{ value: 7, label: "1 Week Ago" },
|
||||
{ value: 30, label: "30 Days Ago" },
|
||||
{ value: 90, label: "3 Months Ago" },
|
||||
{ value: 180, label: "6 Months Ago" },
|
||||
{ value: 365, label: "1 Year Ago" },
|
||||
{ value: 0, label: "Everything" },
|
||||
],
|
||||
columns: [
|
||||
{ name: "alert_time", label: "Time", field: "alert_time", align: "left", sortable: true },
|
||||
{ name: "hostname", label: "Agent", field: "hostname", align: "left", sortable: true },
|
||||
{
|
||||
name: "alert_type",
|
||||
label: "Type",
|
||||
field: "alert_type",
|
||||
align: "left",
|
||||
sortable: true,
|
||||
format: a => this.capitalize(a),
|
||||
},
|
||||
{ name: "severity", label: "Severity", field: "severity", align: "left", sortable: true },
|
||||
{ name: "message", label: "Message", field: "message", align: "left", sortable: true },
|
||||
{ name: "resolved_on", label: "Resolved On", field: "resolved_on", align: "left", sortable: true },
|
||||
{ name: "snooze_until", label: "Snoozed Until", field: "snoozed_until", align: "left", sortable: true },
|
||||
{ name: "actions", label: "Actions", align: "left" },
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 0,
|
||||
sortBy: "alert_time",
|
||||
descending: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
noDataText() {
|
||||
return this.searched ? "No data found. Try to refine you search" : "Click search to find alerts";
|
||||
},
|
||||
visibleColumns() {
|
||||
return this.columns.map(column => {
|
||||
if (column.name === "snooze_until") {
|
||||
if (this.includeSnoozed) return column.name;
|
||||
} else if (column.name === "resolved_on") {
|
||||
if (this.includeResolved) return column.name;
|
||||
} else {
|
||||
return column.name;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getAlerts() {
|
||||
this.$q.loading.show();
|
||||
|
||||
this.$store
|
||||
.dispatch("alerts/getAlerts")
|
||||
.then(response => {
|
||||
this.$axios
|
||||
.get("alerts/alerts/")
|
||||
.then(r => {
|
||||
this.alerts = r.data;
|
||||
this.$q.loading.hide();
|
||||
})
|
||||
.catch(error => {
|
||||
this.$q.loading.hide();
|
||||
this.notifyError("Something went wrong");
|
||||
this.notifyError("There was an issue getting alerts");
|
||||
});
|
||||
},
|
||||
getClients() {
|
||||
this.$axios.get("/clients/clients/").then(r => {
|
||||
this.clientsOptions = Object.freeze(r.data.map(client => ({ label: client.name, value: client.id })));
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.$q.loading.show();
|
||||
|
||||
this.searched = true;
|
||||
|
||||
let data = {
|
||||
snoozedFilter: this.includeSnoozed,
|
||||
resolvedFilter: this.includeResolved,
|
||||
};
|
||||
|
||||
if (this.clientFilter.length > 0) data["clientFilter"] = this.clientFilter;
|
||||
if (this.timeFilter) data["timeFilter"] = this.timeFilter;
|
||||
if (this.severityFilter.length > 0) data["severityFilter"] = this.severityFilter;
|
||||
|
||||
this.$axios
|
||||
.patch("/alerts/alerts/", data)
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
this.alerts = Object.freeze(r.data);
|
||||
})
|
||||
.catch(e => {
|
||||
this.notifyError("There was an issue getting alerts");
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
},
|
||||
snoozeAlert(alert) {
|
||||
this.$q.loading.show();
|
||||
|
||||
const data = {
|
||||
id: alert.id,
|
||||
snoozed: true,
|
||||
};
|
||||
|
||||
this.$axios
|
||||
.put(`alerts/alerts/${alert.id}/`, data)
|
||||
.then(r => {
|
||||
this.search();
|
||||
this.$q.loading.hide();
|
||||
this.notifySuccess("The alert has been snoozed for 48 hours");
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
this.notifyError("There was an issue snoozing alert");
|
||||
});
|
||||
},
|
||||
resolveAlert(alert) {
|
||||
this.$q.loading.show();
|
||||
|
||||
const data = {
|
||||
id: alert.id,
|
||||
resolved: true,
|
||||
};
|
||||
|
||||
this.$axios
|
||||
.put(`alerts/alerts/${alert.id}/`, data)
|
||||
.then(r => {
|
||||
this.search();
|
||||
this.$q.loading.hide();
|
||||
this.notifySuccess("The alert has been snoozed for 48 hours");
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
this.notifyError("There was an issue snoozing alert");
|
||||
});
|
||||
},
|
||||
alertColor(severity) {
|
||||
@@ -94,14 +278,18 @@ export default {
|
||||
return "blue";
|
||||
}
|
||||
},
|
||||
show() {
|
||||
this.$refs.dialog.show();
|
||||
},
|
||||
hide() {
|
||||
this.$refs.dialog.hide();
|
||||
},
|
||||
onHide() {
|
||||
this.$emit("hide");
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
alerts: "alerts/getAlerts",
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.getAlerts();
|
||||
this.getClients();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
</q-menu>
|
||||
</q-chip>
|
||||
|
||||
<!--<AlertsIcon />-->
|
||||
<AlertsIcon />
|
||||
|
||||
<q-btn-dropdown flat no-caps stretch :label="user">
|
||||
<q-list>
|
||||
|
||||
Reference in New Issue
Block a user