Merge pull request #7 from sadnub/feature-automation-alerts

Automation and Alerts Overview
This commit is contained in:
wh1te909
2020-04-09 17:59:39 -07:00
committed by GitHub
10 changed files with 579 additions and 6 deletions

View File

@@ -0,0 +1,86 @@
<template>
<q-btn dense flat icon="notifications">
<q-badge color="red" floating transparent>
{{ test_alerts.length }}
</q-badge>
<q-menu>
<q-list separator>
<q-item v-for="alert in test_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>
{{ 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-item clickable @click="showAlertsModal = true">
View All Alerts ({{test_alerts.length}})
</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 { mapState } from 'vuex';
import AlertsOverview from '@/components/modals/alerts/AlertsOverview'
export default {
name: "AlertsIcon",
components: {AlertsOverview},
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: {
alertColor (type) {
if (type === "error"){
return "red";
}
if (type === "warning"){
return "orange"
}
}
},
computed: {
...mapState('alerts/', {
alerts: state => state.alerts
})
}
}
</script>

View File

@@ -0,0 +1,217 @@
<template>
<div class="q-pa-md q-gutter-sm">
<q-dialog :value="toggleAutomationManager" @hide="hideAutomationManager" @show="getPolicies">
<q-card style="width: 900px; max-width: 90vw;">
<q-bar>
<q-btn @click="getPolicies" class="q-mr-sm" dense flat push icon="refresh" />Automation Manager
<q-space />
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
</q-btn>
</q-bar>
<div class="q-pa-md">
<div class="q-gutter-sm">
<q-btn
label="New"
dense
flat
push
unelevated
no-caps
icon="add"
@click="showAddPolicyModal = true; clearRow"
/>
<q-btn
label="Edit"
:disable="selectedRow === null"
dense
flat
push
unelevated
no-caps
icon="edit"
@click="showEditPolicyModal = true"
/>
<q-btn
label="Delete"
:disable="selectedRow === null"
dense
flat
push
unelevated
no-caps
icon="delete"
@click="deletePolicy"
/>
<q-btn
label="Policy Overview"
dense
flat
push
unelevated
no-caps
icon="remove_red_eye"
@click="showPolicyOverviewModal = true"
/>
</div>
<q-table
dense
class="automation-sticky-header-table"
:data="policies"
:columns="columns"
:visible-columns="visibleColumns"
:pagination.sync="pagination"
row-key="id"
binary-state-sort
hide-bottom
virtual-scroll
flat
:rows-per-page-options="[0]"
>
<template slot="body" slot-scope="props" :props="props">
<q-tr
:class="{highlight: selectedRow === props.row.id}"
@click="policyRowSelected(props.row.id)"
>
<q-td>{{ props.row.name }}</q-td>
<q-td>{{ props.row.desc }}</q-td>
<q-td>{{ props.row.applied_to }}</q-td>
<q-td>{{ props.row.actions }}</q-td>
</q-tr>
</template>
</q-table>
</div>
<q-card-section></q-card-section>
<q-separator />
<q-card-section></q-card-section>
</q-card>
</q-dialog>
<q-dialog v-model="showAddPolicyModal">
<AddPolicy @close="showAddPolicyModal = false" @added="getPolicies" />
</q-dialog>
<q-dialog v-model="showEditPolicyModal">
<EditPolicy :pk="selectedRow" @close="showEditPolicyModal = false" @edited="getPolicies" />
</q-dialog>
<q-dialog v-model="showPolicyOverviewModal">
<PolicyOverview @close="showPolicyOverviewModal = false" />
</q-dialog>
</div>
</template>
<script>
import axios from "axios";
import mixins from "@/mixins/mixins";
import { mapState } from "vuex";
import AddPolicy from "@/components/modals/automation/AddPolicy";
import EditPolicy from "@/components/modals/automation/EditPolicy";
import PolicyOverview from "@/components/modals/automation/PolicyOverview";
export default {
name: "AutomationManager",
components: { AddPolicy, EditPolicy, PolicyOverview },
mixins: [mixins],
data() {
return {
selectedRow: null,
showAddPolicyModal: false,
showEditPolicyModal: false,
showPolicyOverviewModal: false,
pagination: {
rowsPerPage: 0,
sortBy: "id",
descending: false
},
columns: [
{ name: "id", label: "ID", field: "id" },
{
name: "name",
label: "Name",
field: "name",
align: "left",
sortable: true
},
{
name: "desc",
label: "Description",
field: "desc",
align: "left",
sortable: false
},
{
name: "active",
label: "Active",
field: "active",
align: "left",
sortable: true
},
{
name: "applied_to",
label: "Applied To",
field: "applied_to",
align: "left",
sortable: false
}
],
visibleColumns: ["name", "desc", "active", "applied_to"]
};
},
methods: {
getPolicies() {
this.clearRow();
this.$store.dispatch("getPolicies");
},
hideAutomationManager() {
this.$store.commit("TOGGLE_AUTOMATION_MANAGER", false);
},
policyRowSelected(pk) {
this.selectedRow = pk;
},
clearRow() {
this.selectedRow = null;
},
deletePolicy() {
this.$q
.dialog({
title: "Delete policy?",
cancel: true,
ok: { label: "Delete", color: "negative" }
})
.onOk(() => {
axios.delete(`/automation/policies/${this.selectedRow}`).then(r => {
this.getPolicies();
this.notifySuccess(`Policy ${r.data} was deleted!`);
});
});
}
},
computed: {
...mapState({
toggleAutomationManager: state => state.toggleAutomationManager,
policies: state => state.policies
})
},
mounted() {
this.getPolicies();
}
};
</script>
<style lang="stylus">
.automation-sticky-header-table {
/* max height is important */
.q-table__middle {
max-height: 500px;
}
.q-table__top, .q-table__bottom, thead tr:first-child th {
background-color: #CBCBCB;
}
thead tr:first-child th {
position: sticky;
top: 0;
opacity: 1;
z-index: 1;
}
}
</style>

View File

@@ -40,6 +40,10 @@
<q-item clickable v-close-popup @click="showScriptManager">
<q-item-section>Script Manager</q-item-section>
</q-item>
<!-- automation manager -->
<q-item clickable v-close-popup @click="showAutomationManager">
<q-item-section>Automation Manager</q-item-section>
</q-item>
<!-- core settings -->
<q-item clickable v-close-popup @click="showEditCoreSettingsModal = true">
<q-item-section>Global Settings</q-item-section>
@@ -75,6 +79,9 @@
</div>
<!-- Script Manager -->
<ScriptManager />
<!-- Automation Manager -->
<AutomationManager />
</q-bar>
</div>
</template>
@@ -86,6 +93,8 @@ import AddSite from "@/components/modals/clients/AddSite";
import UpdateAgents from "@/components/modals/agents/UpdateAgents";
import ScriptManager from "@/components/ScriptManager";
import EditCoreSettings from "@/components/modals/coresettings/EditCoreSettings";
import AutomationManager from "@/components/AutomationManager";
export default {
name: "FileBar",
components: {
@@ -94,7 +103,8 @@ export default {
AddSite,
UpdateAgents,
ScriptManager,
EditCoreSettings
EditCoreSettings,
AutomationManager
},
props: ["clients"],
data() {
@@ -111,6 +121,9 @@ export default {
},
showScriptManager() {
this.$store.commit("TOGGLE_SCRIPT_MANAGER", true);
},
showAutomationManager() {
this.$store.commit("TOGGLE_AUTOMATION_MANAGER", true);
}
}
};

View File

@@ -0,0 +1,53 @@
<template>
<q-card>
<q-bar>
Alerts Overview
<q-space />
<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 />
<q-card-section>
All Alerts
</q-card-section>
<q-card-section>
<q-btn
label="Update"
color="primary"
/>
</q-card-section>
</q-card>
</template>
<script>
import axios from "axios";
export default {
name: "AlertsOverview",
data() {
return {
alerts: [],
};
},
methods: {
getAlerts() {
this.$q.loading.show();
axios
.get("/alerts/")
.then(r => {
this.alerts = r.data.alerts
})
.catch(() => {
this.$q.loading.hide();
this.notifyError("Something went wrong");
});
},
},
computed: {
},
created() {
this.getAlerts();
}
};
</script>

View File

@@ -0,0 +1,66 @@
<template>
<q-card style="width: 40vw">
<q-form @submit.prevent="addPolicy">
<q-card-section class="row items-center">
<div class="text-h6">Add Policy</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-card-section class="row">
<div class="col-2">Name:</div>
<div class="col-10">
<q-input outlined dense v-model="name" :rules="[ val => !!val || '*Required']" />
</div>
</q-card-section>
<q-card-section class="row">
<div class="col-2">Description:</div>
<div class="col-10">
<q-input outlined dense v-model="desc" type="textarea" />
</div>
</q-card-section>
<q-card-section class="row items-center">
<q-btn label="Add" color="primary" type="submit" />
</q-card-section>
</q-form>
</q-card>
</template>
<script>
import axios from "axios";
import mixins from "@/mixins/mixins";
export default {
name: "AddPolicy",
mixins: [mixins],
data() {
return {
name: "",
desc: ""
};
},
methods: {
addPolicy() {
if (!this.name) {
this.notifyError("Name is required!");
return false;
}
this.$q.loading.show();
let formData = new FormData();
formData.append("name", this.name);
formData.append("desc", this.desc);
axios
.post("/automation/policies/", formData)
.then(r => {
this.$q.loading.hide();
this.$emit("close");
this.$emit("added");
this.notifySuccess("Policy added! Edit the policy to add Checks!");
})
.catch(e => {
this.$q.loading.hide();
this.notifyError(e.response.data);
});
}
}
};
</script>

View File

@@ -0,0 +1,79 @@
<template>
<q-card style="width: 40vw">
<q-form @submit.prevent="editPolicy">
<q-card-section class="row items-center">
<div class="text-h6">Edit Policy</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-card-section class="row">
<div class="col-2">Name:</div>
<div class="col-10">
<q-input outlined dense v-model="name" :rules="[ val => !!val || '*Required']" />
</div>
</q-card-section>
<q-card-section class="row">
<div class="col-2">Description:</div>
<div class="col-10">
<q-input outlined dense v-model="desc" type="textarea" />
</div>
</q-card-section>
<q-card-section class="row items-center">
<q-btn label="Edit" color="primary" type="submit" />
</q-card-section>
</q-form>
</q-card>
</template>
<script>
import axios from "axios";
import mixins from "@/mixins/mixins";
export default {
name: "EditPolicy",
mixins: [mixins],
props: ["pk"],
data() {
return {
name: "",
desc: "",
associations: []
};
},
methods: {
getPolicy() {
axios.get(`/automation/policies/${this.pk}/`).then(r => {
this.name = r.data.name;
this.desc = r.data.desc;
})
},
editPolicy() {
if (!this.name) {
this.notifyError("Name is required!");
return false;
}
this.$q.loading.show();
let formData = new FormData();
formData.append("name", this.name);
formData.append("desc", this.desc);
axios.put(`/automation/policies/${this.pk}/`, formData)
.then(r => {
this.$q.loading.hide();
this.$emit("close");
this.$emit("edited");
this.notifySuccess("Policy edited!");
})
.catch(e => {
this.$q.loading.hide();
this.notifyError(e.response.data);
});
}
},
created() {
this.getPolicy();
}
};
</script>

View File

@@ -0,0 +1,7 @@
<template>
<div>Policy Overview</div>
</template>
<script>
</script>

29
src/store/alerts.js Normal file
View File

@@ -0,0 +1,29 @@
export default {
namespaced: true,
state: {
alerts: [],
},
getters:{
getAlerts(state) {
return state.alerts;
},
getUncheckedAlerts(state) {
//filter for non-dismissed active alerts
}
},
mutation: {
SET_ALERTS(state, alerts) {
state.alerts = alerts;
},
},
actions: {
getAlerts(context) {
axios.get(`/alerts/getAlerts/`).then(r => {
context.commit("SET_ALERTS", r.data);
});
}
}
}

View File

@@ -4,12 +4,14 @@ import axios from "axios";
import { Notify } from "quasar";
import router from "../router";
import logModule from "./logs";
import alertsModule from "./alerts";
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
logs: logModule
logs: logModule,
alerts: alertsModule
},
state: {
username: localStorage.getItem("user_name") || null,
@@ -25,7 +27,9 @@ export const store = new Vuex.Store({
treeLoading: false,
installedSoftware: [],
scripts: [],
toggleScriptManager: false
toggleScriptManager: false,
policies: [],
toggleAutomationManager: false
},
getters: {
loggedIn(state) {
@@ -55,9 +59,15 @@ export const store = new Vuex.Store({
},
scripts(state) {
return state.scripts;
},
policies(state) {
return state.policies;
}
},
mutations: {
TOGGLE_AUTOMATION_MANAGER(state, action) {
state.toggleAutomationManager = action;
},
TOGGLE_SCRIPT_MANAGER(state, action) {
state.toggleScriptManager = action;
},
@@ -103,9 +113,17 @@ export const store = new Vuex.Store({
},
SET_SCRIPTS(state, scripts) {
state.scripts = scripts;
},
SET_POLICIES(state, policies) {
state.policies = policies;
}
},
actions: {
getPolicies(context) {
axios.get("/automation/policies/").then(r => {
context.commit("SET_POLICIES", r.data);
})
},
getScripts(context) {
axios.get("/checks/getscripts/").then(r => {
context.commit("SET_SCRIPTS", r.data);

View File

@@ -6,6 +6,9 @@
<q-toolbar-title>
Tactical RMM
</q-toolbar-title>
<AlertsIcon />
<q-btn-dropdown flat no-caps stretch :label="user">
<q-list>
<q-item to="/logout" exact>
@@ -88,11 +91,13 @@ import { mapState, mapGetters } from 'vuex';
import FileBar from "@/components/FileBar";
import AgentTable from "@/components/AgentTable";
import SubTableTabs from "@/components/SubTableTabs";
import AlertsIcon from "@/components/AlertsIcon";
export default {
components: {
FileBar,
AgentTable,
SubTableTabs
SubTableTabs,
AlertsIcon
},
data() {
return {
@@ -171,7 +176,7 @@ export default {
sortable: true,
align: "left"
}
]
],
};
},
methods: {
@@ -242,7 +247,7 @@ export default {
//this.$store.commit("destroySubTable");
this.$store.commit("AGENT_TABLE_LOADING", false);
});
},
}
},
computed: {
...mapState({