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"),
|
("error", "Error"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ALERT_TYPE_CHOICES = [
|
||||||
|
("availability", "Availability"),
|
||||||
|
("check", "Check"),
|
||||||
|
("task", "Task"),
|
||||||
|
("custom", "Custom"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Alert(models.Model):
|
class Alert(models.Model):
|
||||||
agent = models.ForeignKey(
|
agent = models.ForeignKey(
|
||||||
@@ -31,8 +38,12 @@ class Alert(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
alert_type = models.CharField(
|
||||||
|
max_length=20, choices=ALERT_TYPE_CHOICES, default="availability"
|
||||||
|
)
|
||||||
message = models.TextField(null=True, blank=True)
|
message = models.TextField(null=True, blank=True)
|
||||||
alert_time = models.DateTimeField(auto_now_add=True, 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)
|
snooze_until = models.DateTimeField(null=True, blank=True)
|
||||||
resolved = models.BooleanField(default=False)
|
resolved = models.BooleanField(default=False)
|
||||||
resolved_time = models.DateTimeField(null=True, blank=True)
|
resolved_time = models.DateTimeField(null=True, blank=True)
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ from .models import Alert, AlertTemplate
|
|||||||
class AlertSerializer(ModelSerializer):
|
class AlertSerializer(ModelSerializer):
|
||||||
|
|
||||||
hostname = ReadOnlyField(source="agent.hostname")
|
hostname = ReadOnlyField(source="agent.hostname")
|
||||||
client = ReadOnlyField(source="agent.client")
|
client = ReadOnlyField(source="agent.client.name")
|
||||||
site = ReadOnlyField(source="agent.site")
|
site = ReadOnlyField(source="agent.site.name")
|
||||||
alert_time = DateTimeField(format="iso-8601")
|
alert_time = DateTimeField(format="iso-8601")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from django.shortcuts import get_object_or_404
|
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.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from .models import Alert, AlertTemplate
|
from .models import Alert, AlertTemplate
|
||||||
|
|
||||||
@@ -10,12 +12,85 @@ from .serializers import AlertSerializer, AlertTemplateSerializer
|
|||||||
|
|
||||||
|
|
||||||
class GetAddAlerts(APIView):
|
class GetAddAlerts(APIView):
|
||||||
def get(self, request):
|
def patch(self, request):
|
||||||
alerts = Alert.objects.all()
|
|
||||||
|
|
||||||
# Add time and severity filters
|
# 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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return Response(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()
|
||||||
|
return Response(AlertSerializer(alerts, many=True).data)
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = AlertSerializer(data=request.data, partial=True)
|
serializer = AlertSerializer(data=request.data, partial=True)
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-btn dense flat icon="notifications">
|
<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-menu>
|
||||||
<q-list separator>
|
<q-list separator>
|
||||||
<q-item v-if="alerts.length === 0">No New Alerts</q-item>
|
<q-item v-if="alertsCount === 0">No New Alerts</q-item>
|
||||||
<q-item v-for="alert in alerts" :key="alert.id">
|
<q-item v-for="alert in topAlerts" :key="alert.id">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label overline>{{ alert.client }} - {{ alert.site }} - {{ alert.hostname }}</q-item-label>
|
<q-item-label overline>{{ alert.client }} - {{ alert.site }} - {{ alert.hostname }}</q-item-label>
|
||||||
<q-item-label>
|
<q-item-label>
|
||||||
<q-icon
|
<q-icon size="xs" :class="`text-${alertIconColor(alert.severity)}`" :name="alert.severity"></q-icon>
|
||||||
size="xs"
|
|
||||||
:class="`text-${alertColor(alert.severity)}`"
|
|
||||||
:name="alert.severity"
|
|
||||||
></q-icon>
|
|
||||||
{{ alert.message }}
|
{{ alert.message }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
@@ -20,51 +16,56 @@
|
|||||||
<q-item-section side top>
|
<q-item-section side top>
|
||||||
<q-item-label caption>{{ alertTime(alert.alert_time) }}</q-item-label>
|
<q-item-label caption>{{ alertTime(alert.alert_time) }}</q-item-label>
|
||||||
<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-tooltip>Snooze the alert for 24 hours</q-tooltip>
|
||||||
</q-icon>
|
</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-tooltip>Dismiss alert</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</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-list>
|
||||||
</q-menu>
|
</q-menu>
|
||||||
|
|
||||||
<q-dialog
|
|
||||||
v-model="showAlertsModal"
|
|
||||||
maximized
|
|
||||||
transition-show="slide-up"
|
|
||||||
transition-hide="slide-down"
|
|
||||||
>
|
|
||||||
<AlertsOverview @close="showAlertsModal = false" />
|
|
||||||
</q-dialog>
|
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
|
||||||
import mixins from "@/mixins/mixins";
|
import mixins from "@/mixins/mixins";
|
||||||
import AlertsOverview from "@/components/modals/alerts/AlertsOverview";
|
import AlertsOverview from "@/components/modals/alerts/AlertsOverview";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "AlertsIcon",
|
name: "AlertsIcon",
|
||||||
components: { AlertsOverview },
|
|
||||||
mixins: [mixins],
|
mixins: [mixins],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showAlertsModal: false,
|
alertsCount: 0,
|
||||||
|
topAlerts: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getAlerts() {
|
getAlerts() {
|
||||||
this.$store.dispatch("alerts/getAlerts").catch(error => {
|
this.$q.loading.show();
|
||||||
console.error(error);
|
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");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showOverview() {
|
||||||
|
this.$q.dialog({
|
||||||
|
component: AlertsOverview,
|
||||||
|
parent: this,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
alertColor(type) {
|
alertIconColor(type) {
|
||||||
if (type === "error") {
|
if (type === "error") {
|
||||||
return "red";
|
return "red";
|
||||||
}
|
}
|
||||||
@@ -72,20 +73,14 @@ export default {
|
|||||||
return "orange";
|
return "orange";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
alertsLengthText() {
|
alertsCountText() {
|
||||||
if (this.alerts.length > 9) {
|
if (this.alertsCount > 99) {
|
||||||
return "9+";
|
return "99+";
|
||||||
} else {
|
} else {
|
||||||
return this.alerts.length;
|
return this.alertsCount;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
...mapGetters({
|
|
||||||
newAlerts: "alerts/getNewAlerts",
|
|
||||||
alerts: "alerts/getAlerts",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getAlerts();
|
this.getAlerts();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="width: 60vw; max-width: 90vw">
|
<div style="width: 90vw; max-width: 90vw">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-bar>
|
<q-bar>
|
||||||
<q-btn @click="getScripts" class="q-mr-sm" dense flat push icon="refresh" />Script Manager
|
<q-btn @click="getScripts" class="q-mr-sm" dense flat push icon="refresh" />Script Manager
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ export default {
|
|||||||
this.getPolicies();
|
this.getPolicies();
|
||||||
this.clearRow();
|
this.clearRow();
|
||||||
},
|
},
|
||||||
deletePolicy(id) {
|
deletePolicy(policy) {
|
||||||
this.$q
|
this.$q
|
||||||
.dialog({
|
.dialog({
|
||||||
title: "Delete policy?",
|
title: "Delete policy?",
|
||||||
@@ -319,12 +319,16 @@ export default {
|
|||||||
ok: { label: "Delete", color: "negative" },
|
ok: { label: "Delete", color: "negative" },
|
||||||
})
|
})
|
||||||
.onOk(() => {
|
.onOk(() => {
|
||||||
|
this.$q.loading.show();
|
||||||
this.$axios
|
this.$axios
|
||||||
.delete(`/automation/policies/${pk}/`)
|
.delete(`/automation/policies/${policy.id}/`)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
|
this.refresh();
|
||||||
|
this.$q.loading.hide();
|
||||||
this.notifySuccess("Policy was deleted!");
|
this.notifySuccess("Policy was deleted!");
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
this.$q.loading.hide();
|
||||||
this.notifyError("An Error occured while deleting policy");
|
this.notifyError("An Error occured while deleting policy");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -422,6 +426,7 @@ export default {
|
|||||||
this.$axios
|
this.$axios
|
||||||
.put(`/automation/policies/${data.id}/`, data)
|
.put(`/automation/policies/${data.id}/`, data)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
|
this.refresh();
|
||||||
this.$q.loading.hide();
|
this.$q.loading.hide();
|
||||||
this.notifySuccess(text);
|
this.notifySuccess(text);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,8 +16,7 @@
|
|||||||
:nodes="clientSiteTree"
|
:nodes="clientSiteTree"
|
||||||
node-key="id"
|
node-key="id"
|
||||||
selected-color="primary"
|
selected-color="primary"
|
||||||
@update:selected="setSelectedPolicyId(key)"
|
:selected.sync="selectedPolicyId"
|
||||||
default-expand-all
|
|
||||||
></q-tree>
|
></q-tree>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -39,10 +38,10 @@
|
|||||||
</q-tabs>
|
</q-tabs>
|
||||||
<q-tab-panels v-model="selectedTab" animated transition-prev="jump-up" transition-next="jump-up">
|
<q-tab-panels v-model="selectedTab" animated transition-prev="jump-up" transition-next="jump-up">
|
||||||
<q-tab-panel name="checks">
|
<q-tab-panel name="checks">
|
||||||
<PolicyChecksTab :selectedPolicy="selectedPolicyId" />
|
<PolicyChecksTab v-if="!!selectedPolicyId" :selectedPolicy="selectedPolicyId" />
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
<q-tab-panel name="tasks">
|
<q-tab-panel name="tasks">
|
||||||
<PolicyAutomatedTasksTab :selectedPolicy="selectedPolicyId" />
|
<PolicyAutomatedTasksTab v-if="!!selectedPolicyId" :selectedPolicy="selectedPolicyId" />
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
</q-tab-panels>
|
</q-tab-panels>
|
||||||
</template>
|
</template>
|
||||||
@@ -85,12 +84,6 @@ export default {
|
|||||||
this.notifyError("Error getting policy tree data");
|
this.notifyError("Error getting policy tree data");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setSelectedPolicyId(key) {
|
|
||||||
if (!key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.selectedPolicyId = this.$refs.tree.getNodeByKey(key);
|
|
||||||
},
|
|
||||||
processTreeDataFromApi(data) {
|
processTreeDataFromApi(data) {
|
||||||
/* Structure
|
/* Structure
|
||||||
* [{
|
* [{
|
||||||
@@ -114,10 +107,10 @@ export default {
|
|||||||
// Used by tree for unique identification
|
// Used by tree for unique identification
|
||||||
let unique_id = 0;
|
let unique_id = 0;
|
||||||
|
|
||||||
for (let client in data) {
|
for (let client of data) {
|
||||||
var client_temp = {};
|
var client_temp = {};
|
||||||
|
|
||||||
client_temp["label"] = data[client].name;
|
client_temp["label"] = client.name;
|
||||||
client_temp["id"] = unique_id;
|
client_temp["id"] = unique_id;
|
||||||
client_temp["icon"] = "business";
|
client_temp["icon"] = "business";
|
||||||
client_temp["selectable"] = false;
|
client_temp["selectable"] = false;
|
||||||
@@ -126,41 +119,41 @@ export default {
|
|||||||
unique_id--;
|
unique_id--;
|
||||||
|
|
||||||
// Add any server policies assigned to client
|
// Add any server policies assigned to client
|
||||||
if (data[client].server_policy !== null) {
|
if (!!client.server_policy) {
|
||||||
let disabled = "";
|
let disabled = "";
|
||||||
|
|
||||||
// Indicate if the policy is active or not
|
// Indicate if the policy is active or not
|
||||||
if (!data[client].server_policy.active) {
|
if (!client.server_policy.active) {
|
||||||
disabled = " (disabled)";
|
disabled = " (disabled)";
|
||||||
}
|
}
|
||||||
|
|
||||||
client_temp["children"].push({
|
client_temp["children"].push({
|
||||||
label: data[client].server_policy.name + " (Servers)" + disabled,
|
label: client.server_policy.name + " (Servers)" + disabled,
|
||||||
icon: "policy",
|
icon: "policy",
|
||||||
id: data[client].server_policy.id,
|
id: client.server_policy.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add any workstation policies assigned to client
|
// Add any workstation policies assigned to client
|
||||||
if (data[client].workstation_policy !== null) {
|
if (!!client.workstation_policy) {
|
||||||
let disabled = "";
|
let disabled = "";
|
||||||
|
|
||||||
// Indicate if the policy is active or not
|
// Indicate if the policy is active or not
|
||||||
if (!data[client].workstation_policy.active) {
|
if (!client.workstation_policy.active) {
|
||||||
disabled = " (disabled)";
|
disabled = " (disabled)";
|
||||||
}
|
}
|
||||||
|
|
||||||
client_temp["children"].push({
|
client_temp["children"].push({
|
||||||
label: data[client].workstation_policy.name + " (Workstations)" + disabled,
|
label: client.workstation_policy.name + " (Workstations)" + disabled,
|
||||||
icon: "policy",
|
icon: "policy",
|
||||||
id: data[client].workstation_policy.id,
|
id: client.workstation_policy.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate through Sites
|
// Iterate through Sites
|
||||||
for (let site in data[client].sites) {
|
for (let site of client.sites) {
|
||||||
var site_temp = {};
|
var site_temp = {};
|
||||||
site_temp["label"] = data[client].sites[site].name;
|
site_temp["label"] = site.name;
|
||||||
site_temp["id"] = unique_id;
|
site_temp["id"] = unique_id;
|
||||||
site_temp["icon"] = "apartment";
|
site_temp["icon"] = "apartment";
|
||||||
site_temp["selectable"] = false;
|
site_temp["selectable"] = false;
|
||||||
@@ -168,36 +161,36 @@ export default {
|
|||||||
unique_id--;
|
unique_id--;
|
||||||
|
|
||||||
// Add any server policies assigned to site
|
// Add any server policies assigned to site
|
||||||
if (data[client].sites[site].server_policy !== null) {
|
if (!!site.server_policy) {
|
||||||
site_temp["children"] = [];
|
site_temp["children"] = [];
|
||||||
|
|
||||||
// Indicate if the policy is active or not
|
// Indicate if the policy is active or not
|
||||||
let disabled = "";
|
let disabled = "";
|
||||||
if (!data[client].sites[site].server_policy.active) {
|
if (!site.server_policy.active) {
|
||||||
disabled = " (disabled)";
|
disabled = " (disabled)";
|
||||||
}
|
}
|
||||||
|
|
||||||
site_temp["children"].push({
|
site_temp["children"].push({
|
||||||
label: data[client].sites[site].server_policy.name + " (Servers)" + disabled,
|
label: site.server_policy.name + " (Servers)" + disabled,
|
||||||
icon: "policy",
|
icon: "policy",
|
||||||
id: data[client].sites[site].server_policy.id,
|
id: site.server_policy.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add any server policies assigned to site
|
// Add any server policies assigned to site
|
||||||
if (data[client].sites[site].workstation_policy !== null) {
|
if (!!site.workstation_policy) {
|
||||||
site_temp["children"] = [];
|
site_temp["children"] = [];
|
||||||
|
|
||||||
// Indicate if the policy is active or not
|
// Indicate if the policy is active or not
|
||||||
let disabled = "";
|
let disabled = "";
|
||||||
if (!data[client].sites[site].workstation_policy.active) {
|
if (!site.workstation_policy.active) {
|
||||||
disabled = " (disabled)";
|
disabled = " (disabled)";
|
||||||
}
|
}
|
||||||
|
|
||||||
site_temp["children"].push({
|
site_temp["children"].push({
|
||||||
label: data[client].sites[site].workstation_policy.name + " (Workstations)" + disabled,
|
label: site.workstation_policy.name + " (Workstations)" + disabled,
|
||||||
icon: "policy",
|
icon: "policy",
|
||||||
id: data[client].sites[site].workstation_policy.id,
|
id: site.workstation_policy.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<q-form @submit="submit">
|
<q-form @submit="submit">
|
||||||
<q-card-section v-if="options.length > 0">
|
<q-card-section v-if="options.length > 0">
|
||||||
<q-select
|
<q-select
|
||||||
v-if="type !== 'agent'"
|
v-if="type === 'client' || type === 'site'"
|
||||||
class="q-mb-md"
|
class="q-mb-md"
|
||||||
v-model="selectedServerPolicy"
|
v-model="selectedServerPolicy"
|
||||||
:options="options"
|
:options="options"
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
>
|
>
|
||||||
</q-select>
|
</q-select>
|
||||||
<q-select
|
<q-select
|
||||||
v-if="type !== 'agent'"
|
v-if="type === 'client' || type === 'site'"
|
||||||
v-model="selectedWorkstationPolicy"
|
v-model="selectedWorkstationPolicy"
|
||||||
:options="options"
|
:options="options"
|
||||||
outlined
|
outlined
|
||||||
@@ -91,7 +91,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
submit() {
|
submit() {
|
||||||
// check if data was changed
|
// check if data was changed
|
||||||
if (this.type !== "agent") {
|
if (this.type === "client" || this.type === "site") {
|
||||||
if (
|
if (
|
||||||
this.object.workstation_policy === this.selectedWorkstationPolicy &&
|
this.object.workstation_policy === this.selectedWorkstationPolicy &&
|
||||||
this.object.server_policy === this.selectedServerPolicy
|
this.object.server_policy === this.selectedServerPolicy
|
||||||
@@ -99,11 +99,13 @@ export default {
|
|||||||
this.hide();
|
this.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (this.type === "agent") {
|
||||||
if (this.object.policy === this.selectedAgentPolicy) {
|
if (this.object.policy === this.selectedAgentPolicy) {
|
||||||
this.hide();
|
this.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
this.$q.loading.show();
|
this.$q.loading.show();
|
||||||
|
|
||||||
@@ -112,14 +114,13 @@ export default {
|
|||||||
type: this.type,
|
type: this.type,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.type !== "agent") {
|
if (this.type === "client" || this.type === "site") {
|
||||||
data.server_policy = this.selectedServerPolicy;
|
data.server_policy = this.selectedServerPolicy;
|
||||||
data.workstation_policy = this.selectedWorkstationPolicy;
|
data.workstation_policy = this.selectedWorkstationPolicy;
|
||||||
} else {
|
} else if (this.type === "agent") {
|
||||||
data.policy = this.selectedAgentPolicy;
|
data.policy = this.selectedAgentPolicy;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
this.$axios
|
this.$axios
|
||||||
.post(`/automation/related/`, data)
|
.post(`/automation/related/`, data)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
|
|||||||
@@ -1,86 +1,270 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-card>
|
<q-dialog ref="dialog" @hide="onHide" maximized transition-show="slide-up" transition-hide="slide-down">
|
||||||
<q-bar>
|
<q-card>
|
||||||
Alerts Overview
|
<q-bar>
|
||||||
<q-space />
|
<q-btn @click="search" class="q-mr-sm" dense flat push icon="refresh" />
|
||||||
<q-btn dense flat icon="close" v-close-popup>
|
<q-space />
|
||||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
Alerts Overview
|
||||||
</q-btn>
|
<q-space />
|
||||||
</q-bar>
|
<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-separator />
|
<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>
|
||||||
|
|
||||||
<q-card-section class="row">
|
<q-separator />
|
||||||
<div class="col-3">
|
|
||||||
<q-input outlined dense v-model="search">
|
<q-card-section>
|
||||||
<template v-slot:append>
|
<q-table
|
||||||
<q-icon v-if="search !== ''" name="close" @click="search = ''" class="cursor-pointer" />
|
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||||
<q-icon name="search" />
|
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>Snooze alerts</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</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>
|
||||||
|
|
||||||
<template v-slot:hint>Type in client, site, or agent name</template>
|
<template v-slot:body-cell-actions="props">
|
||||||
</q-input>
|
<q-td :props="props">
|
||||||
</div>
|
<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>
|
||||||
|
|
||||||
<div class="col-3">
|
<template v-slot:body-cell-severity="props">
|
||||||
<q-checkbox outlined dense v-model="includeDismissed" label="Include dismissed alerts?" />
|
<q-td :props="props">
|
||||||
</div>
|
<q-badge :color="alertColor(props.row.severity)">{{ capitalize(props.row.severity) }}</q-badge>
|
||||||
</q-card-section>
|
</q-td>
|
||||||
|
</template>
|
||||||
<q-separator />
|
</q-table>
|
||||||
|
</q-card-section>
|
||||||
<q-list separator>
|
</q-card>
|
||||||
<q-item v-if="alerts.length === 0">No Alerts!</q-item>
|
</q-dialog>
|
||||||
<q-item v-for="alert in alerts" :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="sm" :class="`text-${alertColor(alert.severity)}`" :name="alert.severity"></q-icon>
|
|
||||||
{{ alert.message }}
|
|
||||||
</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-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import mixins from "@/mixins/mixins";
|
import mixins from "@/mixins/mixins";
|
||||||
import { mapGetters } from "vuex";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "AlertsOverview",
|
name: "AlertsOverview",
|
||||||
mixins: [mixins],
|
mixins: [mixins],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
search: "",
|
alerts: [],
|
||||||
includeDismissed: false,
|
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: {
|
methods: {
|
||||||
getAlerts() {
|
getAlerts() {
|
||||||
this.$q.loading.show();
|
this.$q.loading.show();
|
||||||
|
|
||||||
this.$store
|
this.$axios
|
||||||
.dispatch("alerts/getAlerts")
|
.get("alerts/alerts/")
|
||||||
.then(response => {
|
.then(r => {
|
||||||
|
this.alerts = r.data;
|
||||||
this.$q.loading.hide();
|
this.$q.loading.hide();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.$q.loading.hide();
|
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) {
|
alertColor(severity) {
|
||||||
@@ -94,14 +278,18 @@ export default {
|
|||||||
return "blue";
|
return "blue";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
show() {
|
||||||
computed: {
|
this.$refs.dialog.show();
|
||||||
...mapGetters({
|
},
|
||||||
alerts: "alerts/getAlerts",
|
hide() {
|
||||||
}),
|
this.$refs.dialog.hide();
|
||||||
|
},
|
||||||
|
onHide() {
|
||||||
|
this.$emit("hide");
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getAlerts();
|
this.getClients();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
</q-menu>
|
</q-menu>
|
||||||
</q-chip>
|
</q-chip>
|
||||||
|
|
||||||
<!--<AlertsIcon />-->
|
<AlertsIcon />
|
||||||
|
|
||||||
<q-btn-dropdown flat no-caps stretch :label="user">
|
<q-btn-dropdown flat no-caps stretch :label="user">
|
||||||
<q-list>
|
<q-list>
|
||||||
|
|||||||
Reference in New Issue
Block a user