548 lines
15 KiB
Vue
548 lines
15 KiB
Vue
<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>
|
|
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
|
|
</q-btn>
|
|
</q-bar>
|
|
|
|
<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-2">
|
|
<q-btn color="primary" label="Search" @click="search" />
|
|
</div>
|
|
</div>
|
|
|
|
<q-separator />
|
|
|
|
<q-card-section>
|
|
<q-table
|
|
:table-class="{
|
|
'table-bgcolor': !$q.dark.isActive,
|
|
'table-bgcolor-dark': $q.dark.isActive,
|
|
}"
|
|
class="audit-mgr-tbl-sticky"
|
|
:rows="alerts"
|
|
:columns="columns"
|
|
:rows-per-page-options="[25, 50, 100, 500, 1000]"
|
|
v-model:pagination="pagination"
|
|
:no-data-label="noDataText"
|
|
:visible-columns="visibleColumns"
|
|
v-model:selected="selectedAlerts"
|
|
selection="multiple"
|
|
binary-state-sort
|
|
row-key="id"
|
|
dense
|
|
virtual-scroll
|
|
>
|
|
<template v-slot:top>
|
|
<div class="col-1 q-table__title">Alerts</div>
|
|
|
|
<q-btn-dropdown
|
|
flat
|
|
label="Bulk Actions"
|
|
:disable="selectedAlerts.length === 0 || includeResolved"
|
|
>
|
|
<q-list dense>
|
|
<q-item
|
|
clickable
|
|
v-close-popup
|
|
@click="snoozeAlertBulk(selectedAlerts)"
|
|
>
|
|
<q-item-section avatar>
|
|
<q-icon name="alarm_off" />
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>Snooze alerts</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item
|
|
clickable
|
|
v-close-popup
|
|
@click="resolveAlertBulk(selectedAlerts)"
|
|
>
|
|
<q-item-section avatar>
|
|
<q-icon name="flag" />
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>Resolve alerts</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
|
|
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>
|
|
<q-icon
|
|
v-if="!props.row.resolved && !props.row.snoozed"
|
|
name="snooze"
|
|
size="sm"
|
|
class="cursor-pointer"
|
|
@click="snoozeAlert(props.row)"
|
|
>
|
|
<q-tooltip>Snooze alert</q-tooltip>
|
|
</q-icon>
|
|
<q-icon
|
|
v-else-if="!props.row.resolved && props.row.snoozed"
|
|
name="alarm_off"
|
|
size="sm"
|
|
class="cursor-pointer"
|
|
@click="unsnoozeAlert(props.row)"
|
|
>
|
|
<q-tooltip>Unsnooze alert</q-tooltip>
|
|
</q-icon>
|
|
<q-icon
|
|
v-if="!props.row.resolved"
|
|
name="flag"
|
|
size="sm"
|
|
class="cursor-pointer"
|
|
@click="resolveAlert(props.row)"
|
|
>
|
|
<q-tooltip>Resolve 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>
|
|
|
|
<template v-slot:body-cell-alert_time="props">
|
|
<q-td :props="props">
|
|
{{ formatDate(props.value) }}
|
|
</q-td>
|
|
</template>
|
|
|
|
<template v-slot:body-cell-resolve_on="props">
|
|
<q-td :props="props">
|
|
{{ formatDate(props.value) }}
|
|
</q-td>
|
|
</template>
|
|
|
|
<template v-slot:body-cell-snoozed_until="props">
|
|
<q-td :props="props">
|
|
{{ formatDate(props.value) }}
|
|
</q-td>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
</q-dialog>
|
|
</template>
|
|
|
|
<script>
|
|
import mixins from "@/mixins/mixins";
|
|
import ScriptOutput from "@/components/checks/ScriptOutput";
|
|
import { computed } from "vue";
|
|
import { useStore } from "vuex";
|
|
|
|
export default {
|
|
name: "AlertsOverview",
|
|
emits: ["hide"],
|
|
mixins: [mixins],
|
|
setup() {
|
|
// setup vuex store
|
|
const store = useStore();
|
|
const formatDate = computed(() => store.getters.formatDate);
|
|
return {
|
|
formatDate,
|
|
};
|
|
},
|
|
data() {
|
|
return {
|
|
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, true),
|
|
},
|
|
{
|
|
name: "severity",
|
|
label: "Severity",
|
|
field: "severity",
|
|
align: "left",
|
|
sortable: true,
|
|
},
|
|
{
|
|
name: "message",
|
|
label: "Message",
|
|
field: "message",
|
|
align: "left",
|
|
sortable: true,
|
|
},
|
|
{
|
|
name: "resolve_on",
|
|
label: "Resolved On",
|
|
field: "resolve_on",
|
|
align: "left",
|
|
sortable: true,
|
|
},
|
|
{
|
|
name: "snoozed_until",
|
|
label: "Snoozed Until",
|
|
field: "snoozed_until",
|
|
align: "left",
|
|
sortable: true,
|
|
},
|
|
{ name: "actions", label: "Actions", align: "left" },
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 50,
|
|
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 === "snoozed_until") {
|
|
if (this.includeSnoozed) return column.name;
|
|
} else if (column.name === "resolve_on") {
|
|
if (this.includeResolved) return column.name;
|
|
} else {
|
|
return column.name;
|
|
}
|
|
});
|
|
},
|
|
},
|
|
methods: {
|
|
getClients() {
|
|
this.$axios.get("/clients/").then((r) => {
|
|
this.clientsOptions = Object.freeze(
|
|
r.data.map((client) => ({ label: client.name, value: client.id }))
|
|
);
|
|
});
|
|
},
|
|
search() {
|
|
this.$q.loading.show();
|
|
|
|
this.selectedAlerts = [];
|
|
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/", data)
|
|
.then((r) => {
|
|
this.$q.loading.hide();
|
|
this.alerts = Object.freeze(r.data);
|
|
})
|
|
.catch(() => {
|
|
this.$q.loading.hide();
|
|
});
|
|
},
|
|
snoozeAlert(alert) {
|
|
this.$q
|
|
.dialog({
|
|
title: "Snooze Alert",
|
|
message: "How many days to snooze alert?",
|
|
prompt: {
|
|
model: "",
|
|
type: "number",
|
|
isValid: (val) => !!val && val > 0 && val < 9999,
|
|
},
|
|
cancel: true,
|
|
})
|
|
.onOk((days) => {
|
|
this.$q.loading.show();
|
|
|
|
const data = {
|
|
id: alert.id,
|
|
type: "snooze",
|
|
snooze_days: days,
|
|
};
|
|
|
|
this.$axios
|
|
.put(`alerts/${alert.id}/`, data)
|
|
.then(() => {
|
|
this.search();
|
|
this.$q.loading.hide();
|
|
this.notifySuccess(`The alert has been snoozed for ${days} days`);
|
|
})
|
|
.catch(() => {
|
|
this.$q.loading.hide();
|
|
});
|
|
});
|
|
},
|
|
unsnoozeAlert(alert) {
|
|
this.$q.loading.show();
|
|
|
|
const data = {
|
|
id: alert.id,
|
|
type: "unsnooze",
|
|
};
|
|
|
|
this.$axios
|
|
.put(`alerts/${alert.id}/`, data)
|
|
.then(() => {
|
|
this.search();
|
|
this.$q.loading.hide();
|
|
this.notifySuccess("The alert has been unsnoozed");
|
|
})
|
|
.catch(() => {
|
|
this.$q.loading.hide();
|
|
});
|
|
},
|
|
resolveAlert(alert) {
|
|
this.$q.loading.show();
|
|
|
|
const data = {
|
|
id: alert.id,
|
|
type: "resolve",
|
|
};
|
|
|
|
this.$axios
|
|
.put(`alerts/${alert.id}/`, data)
|
|
.then(() => {
|
|
this.search();
|
|
this.$q.loading.hide();
|
|
this.notifySuccess("The alert has been resolved");
|
|
})
|
|
.catch(() => {
|
|
this.$q.loading.hide();
|
|
});
|
|
},
|
|
resolveAlertBulk(alerts) {
|
|
this.$q.loading.show();
|
|
|
|
const data = {
|
|
alerts: alerts.map((alert) => alert.id),
|
|
bulk_action: "resolve",
|
|
};
|
|
|
|
this.$axios
|
|
.post("alerts/bulk/", data)
|
|
.then(() => {
|
|
this.search();
|
|
this.$q.loading.hide();
|
|
this.notifySuccess("Alerts were resolved");
|
|
})
|
|
.catch(() => {
|
|
this.$q.loading.hide();
|
|
});
|
|
},
|
|
snoozeAlertBulk(alerts) {
|
|
this.$q
|
|
.dialog({
|
|
title: "Snooze Alert",
|
|
message: "How many days to snooze alert?",
|
|
prompt: {
|
|
model: "",
|
|
type: "number",
|
|
isValid: (val) => !!val && val > 0 && val < 9999,
|
|
},
|
|
cancel: true,
|
|
})
|
|
.onOk((days) => {
|
|
this.$q.loading.show();
|
|
|
|
const data = {
|
|
alerts: alerts.map((alert) => alert.id),
|
|
bulk_action: "snooze",
|
|
snooze_days: days,
|
|
};
|
|
|
|
this.$axios
|
|
.post("alerts/bulk/", data)
|
|
.then(() => {
|
|
this.search();
|
|
this.$q.loading.hide();
|
|
this.notifySuccess(`Alerts were snoozed for ${days} days`);
|
|
})
|
|
.catch(() => {
|
|
this.$q.loading.hide();
|
|
});
|
|
});
|
|
},
|
|
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,
|
|
componentProps: {
|
|
scriptInfo: results,
|
|
},
|
|
});
|
|
},
|
|
alertColor(severity) {
|
|
if (severity === "error") {
|
|
return "red";
|
|
}
|
|
if (severity === "warning") {
|
|
return "orange";
|
|
}
|
|
if (severity === "info") {
|
|
return "info";
|
|
}
|
|
},
|
|
show() {
|
|
this.$refs.dialog.show();
|
|
},
|
|
hide() {
|
|
this.$refs.dialog.hide();
|
|
},
|
|
onHide() {
|
|
this.$emit("hide");
|
|
},
|
|
},
|
|
mounted() {
|
|
this.getClients();
|
|
},
|
|
};
|
|
</script>
|