Merge branch 'tasks' into develop

This commit is contained in:
wh1te909
2020-04-16 05:56:00 +00:00
9 changed files with 482 additions and 22 deletions

View File

@@ -431,6 +431,7 @@ export default {
this.$store.commit("setActiveRow", pk);
this.$store.dispatch("loadSummary", pk);
this.$store.dispatch("loadChecks", pk);
this.$store.dispatch("loadAutomatedTasks", pk);
this.$store.dispatch("loadWinUpdates", pk);
this.$store.dispatch("loadInstalledSoftware", pk);
},

View File

@@ -0,0 +1,235 @@
<template>
<div v-if="!this.selectedAgentPk">No agent selected</div>
<div v-else-if="Object.keys(automatedTasks).length === 0">No Tasks</div>
<div class="row" v-else>
<div class="col-12">
<q-btn size="sm" color="grey-5" icon="fas fa-plus" label="Add Task" text-color="black">
<q-menu>
<q-list dense style="min-width: 200px">
<q-item clickable v-close-popup @click="showAddAutomatedTask = true">
<q-item-section side>
<q-icon size="xs" name="fas fa-tasks" />
</q-item-section>
<q-item-section>Automated Task</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
<q-btn dense flat push @click="refreshTasks(automatedTasks.pk)" icon="refresh" />
<template v-if="tasks === undefined || tasks.length === 0">
<p>No Tasks</p>
</template>
<template v-else>
<q-table
dense
class="autotasks-tbl-sticky"
:data="tasks"
:columns="columns"
:row-key="row => row.id"
binary-state-sort
:pagination.sync="pagination"
hide-bottom
>
<!-- header slots -->
<template v-slot:header-cell-enabled="props">
<q-th auto-width :props="props">
<small>Enabled</small>
</q-th>
</template>
<!-- body slots -->
<template slot="body" slot-scope="props" :props="props">
<q-tr @contextmenu="editTaskPk = props.row.id">
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
<q-item clickable v-close-popup @click="runTask(props.row.id)">
<q-item-section side>
<q-icon name="play_arrow" />
</q-item-section>
<q-item-section>Run task now</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showEditAutomatedTask = true">
<q-item-section side>
<q-icon name="edit" />
</q-item-section>
<q-item-section>Edit</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="deleteTask(props.row.name, props.row.id)">
<q-item-section side>
<q-icon name="delete" />
</q-item-section>
<q-item-section>Delete</q-item-section>
</q-item>
<q-separator></q-separator>
<q-item clickable v-close-popup>
<q-item-section>Close</q-item-section>
</q-item>
</q-list>
</q-menu>
<!-- tds -->
<q-td>
<q-checkbox
dense
@input="taskEnableorDisable(props.row.id, props.row.enabled)"
v-model="props.row.enabled"
/>
</q-td>
<q-td>{{ props.row.name }}</q-td>
<q-td v-if="props.row.retcode || props.row.stdout || props.row.stderr">
<span
style="cursor:pointer;color:blue;text-decoration:underline"
@click="scriptMoreInfo(props.row)"
>output</span>
</q-td>
<q-td v-else>Awaiting output</q-td>
<q-td v-if="props.row.last_run">{{ props.row.last_run }}</q-td>
<q-td v-else>Has not run yet</q-td>
<q-td>{{ props.row.schedule }}</q-td>
<q-td>{{ props.row.assigned_check }}</q-td>
</q-tr>
</template>
</q-table>
</template>
</div>
<!-- modals -->
<q-dialog v-model="showAddAutomatedTask" position="top">
<AddAutomatedTask @close="showAddAutomatedTask = false" />
</q-dialog>
<q-dialog v-model="showScriptOutput">
<ScriptOutput @close="showScriptOutput = false; scriptInfo = {}" :scriptInfo="scriptInfo" />
</q-dialog>
</div>
</template>
<script>
import axios from "axios";
import { mapState } from "vuex";
import { mapGetters } from "vuex";
import mixins from "@/mixins/mixins";
import AddAutomatedTask from "@/components/modals/automation/AddAutomatedTask";
import ScriptOutput from "@/components/modals/checks/ScriptOutput";
export default {
name: "AutomatedTasksTab",
components: { AddAutomatedTask, ScriptOutput },
mixins: [mixins],
data() {
return {
showAddAutomatedTask: false,
showEditAutomatedTask: false,
showScriptOutput: false,
editTaskPk: null,
showScriptOutput: false,
scriptInfo: {},
columns: [
{ name: "enabled", align: "left", field: "enabled" },
{ name: "name", label: "Name", field: "name", align: "left" },
{
name: "moreinfo",
label: "More Info",
field: "more_info",
align: "left"
},
{
name: "datetime",
label: "Last Run Time",
field: "last_run",
align: "left"
},
{
name: "schedule",
label: "Schedule",
field: "schedule",
align: "left"
},
{
name: "assignedcheck",
label: "Assigned Check",
field: "assigned_check",
align: "left"
}
],
pagination: {
rowsPerPage: 9999
}
};
},
methods: {
taskEnableorDisable(pk, action) {
const data = { enableordisable: action };
axios
.patch(`/automation/${pk}/automatedtasks/`, data)
.then(r => {
this.$store.dispatch("loadAutomatedTasks", this.automatedTasks.pk);
this.notifySuccess(r.data);
})
.catch(e => this.notifyError("Something went wrong"));
},
refreshTasks(id) {
this.$store.dispatch("loadAutomatedTasks", id);
},
scriptMoreInfo(props) {
this.scriptInfo = props;
this.showScriptOutput = true;
},
runTask(pk) {
axios
.get(`/automation/runwintask/${pk}/`)
.then(r => this.notifySuccess(r.data))
.catch(() => this.notifyError("Something went wrong"));
},
deleteTask(name, pk) {
this.$q
.dialog({
title: "Are you sure?",
message: `Delete ${name} task`,
cancel: true,
persistent: true
})
.onOk(() => {
axios
.delete(`/automation/${pk}/automatedtasks/`)
.then(r => {
this.$store.dispatch(
"loadAutomatedTasks",
this.automatedTasks.pk
);
this.$store.dispatch("loadChecks", this.automatedTasks.pk);
this.notifySuccess(r.data);
})
.catch(e => this.notifyError("Something went wrong"));
});
}
},
computed: {
...mapGetters(["selectedAgentPk"]),
...mapState({
automatedTasks: state => state.automatedTasks
}),
tasks() {
return this.automatedTasks.autotasks;
}
}
};
</script>
<style lang="stylus">
.autotasks-tbl-sticky {
.q-table__middle {
max-height: 25vh;
}
.q-table__top, .q-table__bottom, thead tr:first-child th {
background-color: #f5f4f2;
}
thead tr:first-child th {
position: sticky;
top: 0;
opacity: 1;
z-index: 1;
}
}
</style>

View File

@@ -166,6 +166,7 @@
</q-td>
<q-td v-else>{{ props.row.more_info }}</q-td>
<q-td>{{ props.row.last_run }}</q-td>
<q-td>{{ props.row.assigned_task }}</q-td>
</q-tr>
</template>
</q-table>
@@ -311,7 +312,8 @@ export default {
label: "Date / Time",
field: "last_run",
align: "left"
}
},
{ name: "assignedtasks", label: "Assigned Tasks", field: "assigned_task", align: "left" },
],
pagination: {
rowsPerPage: 9999
@@ -338,6 +340,7 @@ export default {
},
onRefresh(id) {
this.$store.dispatch("loadChecks", id);
this.$store.dispatch("loadAutomatedTasks", id);
},
moreInfo(name, output) {
this.$q.dialog({
@@ -390,6 +393,7 @@ export default {
.delete("checks/deletestandardcheck/", { data: data })
.then(r => {
this.$store.dispatch("loadChecks", this.checks.pk);
this.$store.dispatch("loadAutomatedTasks", this.checks.pk);
this.notifySuccess("Check was deleted!");
})
.catch(e => this.notifyError(e.response.data.error));

View File

@@ -1,5 +1,6 @@
<template>
<div v-if="!Array.isArray(software) || !software.length">No software</div>
<div v-if="!this.selectedAgentPk">No agent selected</div>
<div v-else-if="!Array.isArray(software) || !software.length">No software</div>
<div v-else>
<q-btn
size="sm"

View File

@@ -13,6 +13,7 @@
>
<q-tab name="summary" icon="fas fa-info-circle" size="xs" label="Summary" />
<q-tab name="checks" icon="fas fa-check-double" label="Checks" />
<q-tab name="tasks" icon="fas fa-tasks" label="Tasks" />
<q-tab name="patches" icon="system_update" label="Patches" />
<q-tab name="software" icon="fab fa-windows" label="Software" />
</q-tabs>
@@ -24,6 +25,9 @@
<q-tab-panel name="checks">
<ChecksTab />
</q-tab-panel>
<q-tab-panel name="tasks">
<AutomatedTasksTab />
</q-tab-panel>
<q-tab-panel name="patches">
<WindowsUpdates />
</q-tab-panel>
@@ -37,6 +41,7 @@
<script>
import SummaryTab from '@/components/SummaryTab';
import ChecksTab from '@/components/ChecksTab';
import AutomatedTasksTab from '@/components/AutomatedTasksTab';
import WindowsUpdates from '@/components/WindowsUpdates';
import SoftwareTab from '@/components/SoftwareTab';
export default {
@@ -44,6 +49,7 @@ export default {
components: {
SummaryTab,
ChecksTab,
AutomatedTasksTab,
WindowsUpdates,
SoftwareTab,
},

View File

@@ -0,0 +1,214 @@
<template>
<q-card v-if="scripts.length === 0" class="q-pa-xs" style="min-width: 400px">
<q-card-section class="row items-center">
<div class="text-h6">Add Automated Task</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-card-section>
<p>You need to upload a script/task first</p>
<p>Settings -> Script Manager</p>
</q-card-section>
</q-card>
<q-card v-else class="q-pa-xs" style="min-width: 550px">
<q-card-section class="row items-center">
<div class="text-h6">Add Automated Task</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-stepper v-model="step" ref="stepper" color="primary" animated>
<q-step :name="1" title="Select Task" :done="step1Done" :error="!step1Done">
<q-card-section>
<q-select
:rules="[val => !!val || '*Required']"
dense
outlined
v-model="scriptPk"
:options="scriptOptions"
label="Select task"
map-options
emit-value
/>
</q-card-section>
<q-card-section>
<q-input
:rules="[val => !!val || '*Required']"
outlined
dense
v-model="taskName"
label="Descriptive name of task"
/>
</q-card-section>
</q-step>
<q-step :name="2" title="Choose Schedule" :done="step2Done" :error="!step2Done">
<q-radio v-model="trigger" val="daily" label="Scheduled" />
<q-radio v-model="trigger" val="checkfailure" label="On check failure" />
<q-radio v-model="trigger" val="manual" label="Manual" />
<div v-if="trigger === 'daily'" class="row q-pa-lg">
<div class="col-3">
Run on Days:
<q-option-group :options="dayOptions" label="Days" type="checkbox" v-model="days" />
</div>
<div class="col-2"></div>
<div class="col-6">
At time:
<q-time v-model="time" />
</div>
<div class="col-1"></div>
</div>
<div v-else-if="trigger === 'checkfailure'" class="q-pa-lg">
When Check Fails:
<q-select
:rules="[val => !!val || '*Required']"
dense
outlined
v-model="assignedCheck"
:options="checksOptions"
label="Select Check"
map-options
emit-value
/>
</div>
</q-step>
<template v-slot:navigation>
<q-stepper-navigation>
<q-btn
v-if="step === 2"
:disable="!step1Done || !step2Done"
color="primary"
@click="addTask"
label="Add Task"
/>
<q-btn v-else @click="$refs.stepper.next()" color="primary" label="Next" />
<q-btn
v-if="step > 1"
flat
color="primary"
@click="$refs.stepper.previous()"
label="Back"
class="q-ml-sm"
/>
</q-stepper-navigation>
</template>
</q-stepper>
</q-card>
</template>
<script>
import axios from "axios";
import { mapState } from "vuex";
import { mapGetters } from "vuex";
import mixins from "@/mixins/mixins";
export default {
name: "AddAutomatedTask",
mixins: [mixins],
data() {
return {
step: 1,
trigger: "daily",
time: null,
taskName: null,
scriptPk: null,
assignedCheck: null,
days: [],
dayOptions: [
{ label: "Monday", value: 0 },
{ label: "Tuesday", value: 1 },
{ label: "Wednesday", value: 2 },
{ label: "Thursday", value: 3 },
{ label: "Friday", value: 4 },
{ label: "Saturday", value: 5 },
{ label: "Sunday", value: 6 }
]
};
},
methods: {
addTask() {
if (!this.step1Done || !this.step2Done) {
this.notifyError("Some steps incomplete");
} else {
const data = {
agent: this.selectedAgentPk,
name: this.taskName,
script: this.scriptPk,
trigger: this.trigger,
check: this.assignedCheck,
time: this.time,
days: this.days
};
axios
.post(`/automation/${this.selectedAgentPk}/automatedtasks/`, data)
.then(r => {
this.$emit("close");
this.$store.dispatch("loadAutomatedTasks", this.selectedAgentPk);
this.$store.dispatch("loadChecks", this.selectedAgentPk);
this.notifySuccess(r.data);
})
.catch(e => this.notifyError(e.response.data));
}
},
getScripts() {
this.$store.dispatch("getScripts");
}
},
computed: {
...mapGetters(["selectedAgentPk", "scripts"]),
...mapState({
checks: state => state.agentChecks
}),
allChecks() {
return [
...this.checks.diskchecks,
...this.checks.cpuloadchecks,
...this.checks.memchecks,
...this.checks.scriptchecks,
...this.checks.winservicechecks,
...this.checks.pingchecks
];
},
checksOptions() {
const r = [];
this.allChecks.forEach(k => {
// some checks may have the same primary key so add the check type to make them unique
r.push({ label: k.readable_desc, value: `${k.id}|${k.check_type}` });
});
return r;
},
scriptOptions() {
const r = [];
this.scripts.forEach(k => {
r.push({ label: k.name, value: k.id });
});
return r;
},
step1Done() {
return this.step > 1 && this.scriptPk !== null && this.taskName !== null
? true
: false;
},
step2Done() {
if (this.trigger === "daily") {
return this.days !== null &&
this.days.length !== 0 &&
this.time !== null
? true
: false;
} else if (this.trigger === "checkfailure") {
return this.assignedCheck !== null && this.assignedCheck.length !== 0
? true
: false;
} else if (this.trigger === "manual") {
return true;
} else {
return false;
}
}
},
created() {
this.getScripts();
}
};
</script>

View File

@@ -1,13 +1,5 @@
import Vue from "vue";
import Router from "vue-router";
import Dashboard from "@/views/Dashboard";
import Login from "@/views/Login";
import Logout from "@/views/Logout";
import SessionExpired from "@/views/SessionExpired";
import NotFound from "@/views/NotFound";
import TakeControl from "@/views/TakeControl";
import InitialSetup from "@/views/InitialSetup";
import RemoteBackground from "@/views/RemoteBackground";
Vue.use(Router);
@@ -18,7 +10,7 @@ export default new Router({
{
path: "/",
name: "Dashboard",
component: Dashboard,
component: () => import ("@/views/Dashboard"),
meta: {
requireAuth: true
}
@@ -26,7 +18,7 @@ export default new Router({
{
path: "/setup",
name: "InitialSetup",
component: InitialSetup,
component: () => import ("@/views/InitialSetup"),
meta: {
requireAuth: true
}
@@ -34,7 +26,7 @@ export default new Router({
{
path: "/takecontrol/:pk",
name: "TakeControl",
component: TakeControl,
component: () => import ("@/views/TakeControl"),
meta: {
requireAuth: true
}
@@ -42,7 +34,7 @@ export default new Router({
{
path: "/remotebackground/:pk",
name: "RemoteBackground",
component: RemoteBackground,
component: () => import ("@/views/RemoteBackground"),
meta: {
requireAuth: true
}
@@ -50,7 +42,7 @@ export default new Router({
{
path: "/login",
name: "Login",
component: Login,
component: () => import ("@/views/Login"),
meta: {
requiresVisitor: true
}
@@ -58,16 +50,16 @@ export default new Router({
{
path: "/logout",
name: "Logout",
component: Logout
component: () => import ("@/views/Logout")
},
{
path: "/expired",
name: "SessionExpired",
component: SessionExpired,
component: () => import ("@/views/SessionExpired"),
meta: {
requireAuth: true
}
},
{ path: "*", component: NotFound }
{ path: "*", component: () => import ("@/views/NotFound") }
]
});

View File

@@ -23,6 +23,7 @@ export const store = new Vuex.Store({
agentSummary: {},
winUpdates: {},
agentChecks: {},
automatedTasks: {},
agentTableLoading: false,
treeLoading: false,
installedSoftware: [],
@@ -104,6 +105,9 @@ export const store = new Vuex.Store({
setChecks(state, checks) {
state.agentChecks = checks;
},
SET_AUTOMATED_TASKS(state, tasks) {
state.automatedTasks = tasks;
},
destroySubTable(state) {
(state.agentSummary = {}),
(state.agentChecks = {}),
@@ -124,6 +128,11 @@ export const store = new Vuex.Store({
context.commit("SET_POLICIES", r.data);
})
},
loadAutomatedTasks(context, pk) {
axios.get(`/automation/${pk}/automatedtasks/`).then(r => {
context.commit("SET_AUTOMATED_TASKS", r.data);
})
},
getScripts(context) {
axios.get("/checks/getscripts/").then(r => {
context.commit("SET_SCRIPTS", r.data);
@@ -211,11 +220,8 @@ export const store = new Vuex.Store({
})
.catch(error => {
Notify.create({
color: "red",
position: "top",
type: "negative",
timeout: 1000,
textColor: "white",
icon: "fas fa-times-circle",
message: "Bad token"
});
reject(error);

View File

@@ -193,6 +193,7 @@ export default {
const pk = this.selectedAgentPk;
this.$store.dispatch("loadSummary", pk);
this.$store.dispatch("loadChecks", pk);
this.$store.dispatch("loadAutomatedTasks", pk);
this.$store.dispatch("loadWinUpdates", pk);
this.$store.dispatch("loadInstalledSoftware", pk);
}