Added frontrend and backend for alerts to stop error messages. Still WIP. Fixed related_agents function to help with policy check bugs.
This commit is contained in:
parent
111ccad70d
commit
4b366ad53a
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AlertsConfig(AppConfig):
|
||||
name = 'alerts'
|
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 3.1 on 2020-08-15 15:31
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('agents', '0012_auto_20200810_0544'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Alert',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('subject', models.TextField(blank=True, null=True)),
|
||||
('message', models.TextField(blank=True, null=True)),
|
||||
('alert_time', models.DateTimeField(blank=True, null=True)),
|
||||
('snooze_until', models.DateTimeField(blank=True, null=True)),
|
||||
('resolved', models.BooleanField(default=False)),
|
||||
('agent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='agent', to='agents.agent')),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 3.1 on 2020-08-15 16:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('alerts', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='alert',
|
||||
name='subject',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='alert',
|
||||
name='severity',
|
||||
field=models.CharField(choices=[('info', 'Informational'), ('warning', 'Warning'), ('error', 'Error')], default='info', max_length=100),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,27 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
SEVERITY_CHOICES = [
|
||||
("info", "Informational"),
|
||||
("warning", "Warning"),
|
||||
("error", "Error"),
|
||||
]
|
||||
|
||||
class Alert(models.Model):
|
||||
agent = models.ForeignKey(
|
||||
"agents.Agent",
|
||||
related_name="agent",
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
message = models.TextField(null=True, blank=True)
|
||||
alert_time = models.DateTimeField(null=True, blank=True)
|
||||
snooze_until = models.DateTimeField(null=True, blank=True)
|
||||
resolved = models.BooleanField(default=False)
|
||||
severity = models.CharField(
|
||||
max_length=100, choices=SEVERITY_CHOICES, default="info"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
|
@ -0,0 +1,17 @@
|
|||
from rest_framework.serializers import (
|
||||
ModelSerializer,
|
||||
ReadOnlyField,
|
||||
)
|
||||
|
||||
from .models import Alert
|
||||
|
||||
|
||||
class AlertSerializer(ModelSerializer):
|
||||
|
||||
hostname = ReadOnlyField(source="agent.hostname")
|
||||
client = ReadOnlyField(source="agent.client")
|
||||
site = ReadOnlyField(source="agent.site")
|
||||
|
||||
class Meta:
|
||||
model = Alert
|
||||
fields = "__all__"
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -0,0 +1,7 @@
|
|||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("alerts/", views.GetAddAlerts.as_view()),
|
||||
path("alerts/<int:pk>/", views.GetUpdateDeleteAlert.as_view()),
|
||||
]
|
|
@ -0,0 +1,44 @@
|
|||
from django.db import DataError
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
from .models import Alert
|
||||
|
||||
from .serializers import AlertSerializer
|
||||
|
||||
class GetAddAlerts(APIView):
|
||||
def get(self, request):
|
||||
alerts = Alert.objects.all()
|
||||
|
||||
return Response(AlertSerializer(alerts, many=True).data)
|
||||
|
||||
def post(self, request):
|
||||
serializer = AlertSerializer(data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
|
||||
return Response("ok")
|
||||
|
||||
|
||||
class GetUpdateDeleteAlert(APIView):
|
||||
def get(self, request, pk):
|
||||
alert = get_object_or_404(Alert, pk=pk)
|
||||
|
||||
return Response(AlertSerializer(alert).data)
|
||||
|
||||
def put(self, request, pk):
|
||||
alert = get_object_or_404(Alert, pk=pk)
|
||||
|
||||
serializer = AlertSerializer(instance=alert, data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
|
||||
return Response("ok")
|
||||
|
||||
def delete(self, request, pk):
|
||||
Alert.objects.get(pk=pk).delete()
|
||||
|
||||
return Response("ok")
|
|
@ -17,22 +17,22 @@ class Policy(models.Model):
|
|||
explicit_clients = self.clients.all()
|
||||
explicit_sites = self.sites.all()
|
||||
|
||||
filtered_sites_ids = list()
|
||||
client_ids = list()
|
||||
filtered_sites_pks = list()
|
||||
client_pks = list()
|
||||
|
||||
for site in explicit_sites:
|
||||
if site.client not in explicit_clients:
|
||||
filtered_sites_ids.append(site.site)
|
||||
filtered_sites_pks.append(site.pk)
|
||||
|
||||
for client in explicit_clients:
|
||||
client_ids.append(client.client)
|
||||
client_pks.append(client.pk)
|
||||
for site in client.sites.all():
|
||||
filtered_sites_ids.append(site.site)
|
||||
filtered_sites_pks.append(site.pk)
|
||||
|
||||
return Agent.objects.filter(
|
||||
models.Q(pk__in=explicit_agents.only("pk"))
|
||||
| models.Q(site__in=filtered_sites_ids)
|
||||
| models.Q(client__in=client_ids)
|
||||
| models.Q(pk__in=filtered_sites_pks)
|
||||
| models.Q(pk__in=client_pks)
|
||||
).distinct()
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -45,6 +45,7 @@ INSTALLED_APPS = [
|
|||
"autotasks",
|
||||
"logs",
|
||||
"scripts",
|
||||
"alerts",
|
||||
]
|
||||
|
||||
if not "TRAVIS" in os.environ and not "AZPIPELINE" in os.environ:
|
||||
|
|
|
@ -22,4 +22,5 @@ urlpatterns = [
|
|||
path("tasks/", include("autotasks.urls")),
|
||||
path("logs/", include("logs.urls")),
|
||||
path("scripts/", include("scripts.urls")),
|
||||
path("alerts/", include("alerts.urls")),
|
||||
]
|
||||
|
|
|
@ -23,7 +23,7 @@ services:
|
|||
# Container for Django backend
|
||||
api:
|
||||
image: python:3.8
|
||||
command: /bin/bash -c "python manage.py collectstatic --clear --no-input && python manage.py migrate && sleep 10s && python manage.py initial_db_setup && python manage.py initial_mesh_setup && python manage.py load_chocos && python manage.py fix_salt_key && python manage.py runserver 0.0.0.0:80"
|
||||
command: /bin/bash -c "python manage.py collectstatic --clear --no-input && python manage.py migrate && sleep 10s && python manage.py initial_db_setup && python manage.py initial_mesh_setup && python manage.py load_chocos && python manage.py runserver 0.0.0.0:80"
|
||||
working_dir: /app
|
||||
environment:
|
||||
VIRTUAL_ENV: /app/env
|
||||
|
|
|
@ -98,7 +98,7 @@ For HMR to work with vue you can copy .env.example and modify the setting to fit
|
|||
|
||||
Each python container shares the same virtual env to make spinning up faster. It is located in api/tacticalrmm/env.
|
||||
|
||||
There is a container dedicated to creating and keeping this up to date. Prior to spinning up the environment you can run `docker-compose -f docker-compose.dev.yml up venv` to make sure the virtual env is ready. Otherwise the api and celery containers will fail to start.
|
||||
There is a container dedicated to creating and keeping this up to date. Prior to spinning up the environment you can run `docker-compose -f docker-compose.yml -f docker-compose.dev.yml up venv` to make sure the virtual env is ready. Otherwise the api and celery containers will fail to start.
|
||||
|
||||
### Spinup the environment
|
||||
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
<template>
|
||||
<q-btn dense flat icon="notifications">
|
||||
<q-badge color="red" floating transparent>{{ test_alerts.length }}</q-badge>
|
||||
<q-badge v-if="alerts.length !== 0" color="red" floating transparent>{{ alertsLengthText() }}</q-badge>
|
||||
<q-menu>
|
||||
<q-list separator>
|
||||
<q-item v-for="alert in test_alerts" :key="alert.id">
|
||||
<q-item v-if="alerts.length === 0">No Alerts</q-item>
|
||||
<q-item v-for="alert in alerts" :key="alert.id">
|
||||
<q-item-section>
|
||||
<q-item-label>{{ alert.client }} - {{ alert.hostname }}</q-item-label>
|
||||
<q-item-label caption>
|
||||
<q-icon :class="`text-${alertColor(alert.type)}`" :name="alert.type"></q-icon>
|
||||
<q-icon :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>{{ alert.timestamp }}</q-item-label>
|
||||
<q-item-label caption>{{ alert.alert_time }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="showAlertsModal = true">View All Alerts ({{test_alerts.length}})</q-item>
|
||||
<q-item clickable @click="showAlertsModal = true">View All Alerts</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
|
||||
|
@ -32,38 +33,27 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
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,
|
||||
test_alerts: [
|
||||
{
|
||||
id: 1,
|
||||
client: "NMHSI",
|
||||
site: "Default",
|
||||
hostname: "NMSC-BACK01",
|
||||
message: "HDD error. Stuff ain't working",
|
||||
type: "error",
|
||||
timestamp: "2 min ago"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
client: "Dove IT",
|
||||
site: "Default",
|
||||
hostname: "NMSC-ANOTHER",
|
||||
message: "Big error. Stuff still ain't working",
|
||||
type: "warning",
|
||||
timestamp: "5 hours ago"
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getAlerts() {
|
||||
this.$store
|
||||
.dispatch("alerts/getAlerts")
|
||||
.catch(error => {
|
||||
console.error(error)
|
||||
});
|
||||
},
|
||||
alertColor(type) {
|
||||
if (type === "error") {
|
||||
return "red";
|
||||
|
@ -71,12 +61,22 @@ export default {
|
|||
if (type === "warning") {
|
||||
return "orange";
|
||||
}
|
||||
},
|
||||
alertsLengthText() {
|
||||
if (this.alerts.length > 9) {
|
||||
return "9+";
|
||||
} else {
|
||||
return this.alerts.length;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState("alerts/", {
|
||||
alerts: state => state.alerts
|
||||
...mapGetters({
|
||||
alerts: "alerts/getNewAlerts"
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
this.getAlerts()
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -10,40 +10,70 @@
|
|||
<q-separator />
|
||||
<q-card-section>All Alerts</q-card-section>
|
||||
<q-card-section>
|
||||
<q-btn label="Update" color="primary" />
|
||||
<q-list separator>
|
||||
<q-item v-for="alert in alerts" :key="alert.id">
|
||||
<q-item-section>
|
||||
<q-item-label>{{ alert.client }} - {{ alert.hostname }}</q-item-label>
|
||||
<q-item-label caption>
|
||||
<q-icon :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>{{ alert.timestamp }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import mixins from "@/mixins/mixins";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "AlertsOverview",
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {
|
||||
alerts: []
|
||||
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getAlerts() {
|
||||
this.$q.loading.show();
|
||||
axios
|
||||
.get("/alerts/")
|
||||
.then(r => {
|
||||
this.alerts = r.data.alerts;
|
||||
|
||||
this.$store
|
||||
.dispatch("alerts/getAlerts")
|
||||
.then(response => {
|
||||
this.$q.loading.hide();
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(error => {
|
||||
this.$q.loading.hide();
|
||||
this.notifyError("Something went wrong");
|
||||
});
|
||||
}
|
||||
},
|
||||
alertColor(severity) {
|
||||
if (severity === "error") {
|
||||
return "red";
|
||||
}
|
||||
if (severity === "warning") {
|
||||
return "orange";
|
||||
}
|
||||
if (severity === "info") {
|
||||
return "blue";
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {},
|
||||
created() {
|
||||
this.getAlerts();
|
||||
computed: {
|
||||
...mapGetters({
|
||||
alerts: "alerts/getAlerts"
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
this.getAlerts()
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,15 +3,15 @@ import axios from 'axios'
|
|||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
alerts: [],
|
||||
alerts: []
|
||||
},
|
||||
|
||||
getters: {
|
||||
getAlerts(state) {
|
||||
return state.alerts;
|
||||
},
|
||||
getUncheckedAlerts(state) {
|
||||
//filter for non-dismissed active alerts
|
||||
getNewAlerts(state) {
|
||||
return state.alerts.filter(alert => !alert.resolved || alert.snoozed_until == undefined)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -23,9 +23,12 @@ export default {
|
|||
|
||||
actions: {
|
||||
getAlerts(context) {
|
||||
axios.get(`/alerts/getAlerts/`).then(r => {
|
||||
axios.get("/alerts/alerts/").then(r => {
|
||||
context.commit("SET_ALERTS", r.data);
|
||||
});
|
||||
},
|
||||
editAlert(context, pk) {
|
||||
return axios.put(`/alerts/alerts/${pk}`);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue