Merge pull request #422 from sadnub/develop

Policy rework, global keystore, and collector tasks
This commit is contained in:
Dan
2021-04-24 23:57:12 -07:00
committed by GitHub
15 changed files with 429 additions and 34 deletions

View File

@@ -60,6 +60,14 @@
<q-th auto-width :props="props"></q-th>
</template>
<template v-slot:header-cell-collector="props">
<q-th auto-width :props="props">
<q-icon name="mdi-database-arrow-up" size="1.5em">
<q-tooltip>Collector Task</q-tooltip>
</q-icon>
</q-th>
</template>
<template v-slot:header-cell-status="props">
<q-th auto-width :props="props"></q-th>
</template>
@@ -170,6 +178,13 @@
<q-tooltip>This task is managed by a policy</q-tooltip>
</q-icon>
</q-td>
<!-- is collector task -->
<q-td>
<q-icon v-if="!!props.row.custom_field" style="font-size: 1.3rem" name="check">
<q-tooltip>The task updates a custom field on the agent</q-tooltip>
</q-icon>
</q-td>
<!-- status icon -->
<q-td v-if="props.row.status === 'passing'">
<q-icon style="font-size: 1.3rem" color="positive" name="check_circle">
@@ -199,6 +214,8 @@
<q-td v-if="props.row.sync_status === 'notsynced'">Will sync on next agent checkin</q-td>
<q-td v-else-if="props.row.sync_status === 'synced'">Synced with agent</q-td>
<q-td v-else-if="props.row.sync_status === 'pendingdeletion'">Pending deletion on agent</q-td>
<q-td v-else-if="props.row.sync_status === 'initial'">Waiting for task creation on agent</q-td>
<q-td v-else></q-td>
<q-td v-if="props.row.retcode !== null || props.row.stdout || props.row.stderr">
<span
style="cursor: pointer; text-decoration: underline"
@@ -249,36 +266,43 @@ export default {
{ name: "emailalert", field: "email_alert", align: "left" },
{ name: "dashboardalert", field: "dashboard_alert", align: "left" },
{ name: "policystatus", align: "left" },
{ name: "collector", label: "Collector", field: "custom_field", align: "left", sortable: true },
{ name: "status", align: "left" },
{ name: "name", label: "Name", field: "name", align: "left" },
{ name: "sync_status", label: "Sync Status", field: "sync_status", align: "left" },
{ name: "name", label: "Name", field: "name", align: "left", sortable: true },
{ name: "sync_status", label: "Sync Status", field: "sync_status", align: "left", sortable: true },
{
name: "moreinfo",
label: "More Info",
field: "more_info",
align: "left",
sortable: true,
},
{
name: "datetime",
label: "Last Run Time",
field: "last_run",
align: "left",
sortable: true,
},
{
name: "schedule",
label: "Schedule",
field: "schedule",
align: "left",
sortable: true,
},
{
name: "assignedcheck",
label: "Assigned Check",
field: "assigned_check",
align: "left",
sortable: true,
},
],
pagination: {
rowsPerPage: 9999,
sortBy: "name",
descending: false,
},
};
},
@@ -383,7 +407,7 @@ export default {
automatedTasks: state => state.automatedTasks,
}),
tasks() {
return this.automatedTasks.autotasks;
return this.automatedTasks.autotasks.filter(task => task.sync_status !== "pendingdeletion");
},
},
};

View File

@@ -61,6 +61,15 @@
</q-icon>
</q-th>
</template>
<template v-slot:header-cell-collector="props">
<q-th auto-width :props="props">
<q-icon name="mdi-database-arrow-up" size="1.5em">
<q-tooltip>Collector Task</q-tooltip>
</q-icon>
</q-th>
</template>
<!-- body slots -->
<template v-slot:body="props" :props="props">
<q-tr class="cursor-pointer" @dblclick="showEditTask(props.row)">
@@ -130,6 +139,12 @@
v-model="props.row.dashboard_alert"
/>
</q-td>
<!-- is collector task -->
<q-td>
<q-icon v-if="!!props.row.custom_field" style="font-size: 1.3rem" name="check">
<q-tooltip>The task updates a custom field on the agent</q-tooltip>
</q-icon>
</q-td>
<q-td>{{ props.row.name }}</q-td>
<q-td>{{ props.row.schedule }}</q-td>
<q-td>
@@ -182,24 +197,28 @@ export default {
{ name: "smsalert", field: "text_alert", align: "left" },
{ name: "emailalert", field: "email_alert", align: "left" },
{ name: "dashboardalert", field: "dashboard_alert", align: "left" },
{ name: "name", label: "Name", field: "name", align: "left" },
{ name: "collector", label: "Collector", field: "custom_field", align: "left", sortable: true },
{ name: "name", label: "Name", field: "name", align: "left", sortable: true },
{
name: "schedule",
label: "Schedule",
field: "schedule",
align: "left",
sortable: true,
},
{
name: "status",
label: "More Info",
field: "more_info",
align: "left",
sortable: true,
},
{
name: "assignedcheck",
label: "Assigned Check",
field: "assigned_check",
align: "left",
sortable: true,
},
],
pagination: {

View File

@@ -50,6 +50,10 @@
label="Policy"
>
</q-select>
<q-checkbox label="Block policy inheritance" v-model="blockInheritance">
<q-tooltip>This {{ type }} will not inherit from higher policies</q-tooltip>
</q-checkbox>
</q-card-section>
<q-card-section v-else>
No Automation Policies have been setup. Go to Settings > Automation Manager
@@ -85,6 +89,7 @@ export default {
selectedWorkstationPolicy: null,
selectedServerPolicy: null,
selectedAgentPolicy: null,
blockInheritance: false,
options: [],
};
},
@@ -94,13 +99,17 @@ export default {
if (this.type === "client" || this.type === "site") {
if (
this.object.workstation_policy === this.selectedWorkstationPolicy &&
this.object.server_policy === this.selectedServerPolicy
this.object.server_policy === this.selectedServerPolicy &&
this.object.blockInheritance === this.blockInheritance
) {
this.hide();
return;
}
} else if (this.type === "agent") {
if (this.object.policy === this.selectedAgentPolicy) {
if (
this.object.policy === this.selectedAgentPolicy &&
this.object.block_policy_inheritance === this.blockInheritance
) {
this.hide();
return;
}
@@ -118,6 +127,7 @@ export default {
pk: this.object.id,
server_policy: this.selectedServerPolicy,
workstation_policy: this.selectedWorkstationPolicy,
block_policy_inheritance: this.blockInheritance,
},
};
} else if (this.type === "site") {
@@ -127,6 +137,7 @@ export default {
pk: this.object.id,
server_policy: this.selectedServerPolicy,
workstation_policy: this.selectedWorkstationPolicy,
block_policy_inheritance: this.blockInheritance,
},
};
} else if (this.type === "agent") {
@@ -134,6 +145,7 @@ export default {
data = {
id: this.object.id,
policy: this.selectedAgentPolicy,
block_policy_inheritance: this.blockInheritance,
};
}
@@ -186,8 +198,10 @@ export default {
if (this.type !== "agent") {
this.selectedServerPolicy = this.object.server_policy;
this.selectedWorkstationPolicy = this.object.workstation_policy;
this.blockInheritance = this.object.blockInheritance;
} else {
this.selectedAgentPolicy = this.object.policy;
this.blockInheritance = this.object.block_policy_inheritance;
}
},
};

View File

@@ -62,6 +62,8 @@
<q-td v-else-if="props.row.sync_status === 'notsynced'">Will sync on next agent checkin</q-td>
<q-td v-else-if="props.row.sync_status === 'synced'">Synced with agent</q-td>
<q-td v-else-if="props.row.sync_status === 'pendingdeletion'">Pending deletion on agent</q-td>
<q-td v-else-if="props.row.sync_status === 'initial'">Waiting for task creation on agent</q-td>
<q-td v-else></q-td>
<!-- more info -->
<q-td v-if="props.row.check_type === 'ping'">
<span

View File

@@ -266,7 +266,7 @@ export default {
created() {
// Get custom fields
this.getCustomFields("agent").then(r => {
this.customFields = r.data;
this.customFields = r.data.filter(field => !field.hide_in_ui);
});
this.getAgentInfo();
this.getClientsSites();

View File

@@ -29,6 +29,7 @@
/>
</q-card-section>
<div class="text-h6">Custom Fields</div>
<q-card-section v-for="field in customFields" :key="field.id">
<CustomField v-model="custom_fields[field.name]" :field="field" />
</q-card-section>
@@ -175,7 +176,7 @@ export default {
created() {
// Get custom fields
this.getCustomFields("client").then(r => {
this.customFields = r.data;
this.customFields = r.data.filter(field => !field.hide_in_ui);
});
// Copy client prop locally

View File

@@ -33,6 +33,7 @@
/>
</q-card-section>
<div class="text-h6">Custom Fields</div>
<q-card-section v-for="field in customFields" :key="field.id">
<CustomField v-model="custom_fields[field.name]" :field="field" />
</q-card-section>
@@ -199,7 +200,7 @@ export default {
// Get custom fields
this.getCustomFields("site").then(r => {
this.customFields = r.data;
this.customFields = r.data.filter(field => !field.hide_in_ui);
});
// Copy site prop locally

View File

@@ -147,6 +147,7 @@
v-model="localField.required"
color="green"
/>
<q-toggle label="Hide in Dashboard" v-model="localField.hide_in_ui" color="green" />
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" v-close-popup />
@@ -175,6 +176,7 @@ export default {
default_value_string: "",
default_value_bool: false,
default_values_multiple: [],
hide_in_ui: false,
},
modelOptions: [
{ label: "Client", value: "client" },
@@ -222,7 +224,6 @@ export default {
})
.catch(e => {
this.$q.loading.hide();
this.onOk();
this.notifyError("There was an error editing the custom field");
});
} else {
@@ -261,7 +262,7 @@ export default {
},
},
mounted() {
// If pk prop is set that means we are editting
// If pk prop is set that means we are editing
if (this.field) Object.assign(this.localField, this.field);
// Set model to current tab

View File

@@ -13,12 +13,7 @@
>
<!-- body slots -->
<template v-slot:body="props">
<q-tr
:props="props"
class="cursor-pointer"
@contextmenu="selectedTemplate = props.row"
@dblclick="editCustomField(props.row)"
>
<q-tr :props="props" class="cursor-pointer" @dblclick="editCustomField(props.row)">
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
@@ -50,6 +45,10 @@
<q-td>
{{ capitalize(props.row.type) }}
</q-td>
<!-- hide in ui -->
<q-td>
<q-icon v-if="props.row.hide_in_ui" name="check" />
</q-td>
<!-- default value -->
<q-td v-if="props.row.type === 'checkbox'">
{{ props.row.default_value_bool }}
@@ -101,6 +100,7 @@ export default {
align: "left",
sortable: true,
},
{ name: "hide_in_ui", label: "Hide in UI", field: "hide_in_ui", align: "left", sortable: true },
{ name: "default_value", label: "Default Value", field: "default_value", align: "left", sortable: true },
{ name: "required", label: "Required", field: "required", align: "left", sortable: true },
],
@@ -128,7 +128,7 @@ export default {
.onOk(() => {
this.$q.loading.show();
this.$axios
.delete(`core/customfields/${field.id}/`)
.delete(`/core/customfields/${field.id}/`)
.then(r => {
this.refresh();
this.$q.loading.hide();

View File

@@ -8,6 +8,7 @@
<q-tab name="smsalerts" label="SMS Alerts" />
<q-tab name="meshcentral" label="MeshCentral" />
<q-tab name="customfields" label="Custom Fields" />
<q-tab name="keystore" label="Key Store" />
</q-tabs>
</template>
<template v-slot:after>
@@ -293,10 +294,14 @@
<q-tab-panel name="customfields">
<CustomFields />
</q-tab-panel>
<q-tab-panel name="keystore">
<KeyStoreTable />
</q-tab-panel>
</q-tab-panels>
</q-scroll-area>
<q-card-section class="row items-center">
<q-btn v-show="tab !== 'customfields'" label="Save" color="primary" type="submit" />
<q-btn v-show="tab !== 'customfields' || tab !== 'keystore'" label="Save" color="primary" type="submit" />
<q-btn
v-show="tab === 'emailalerts'"
label="Save and Test"
@@ -316,11 +321,13 @@
import mixins from "@/mixins/mixins";
import ResetPatchPolicy from "@/components/modals/coresettings/ResetPatchPolicy";
import CustomFields from "@/components/modals/coresettings/CustomFields";
import KeyStoreTable from "@/components/modals/coresettings/KeyStoreTable";
export default {
name: "EditCoreSettings",
components: {
CustomFields,
KeyStoreTable,
},
mixins: [mixins],
data() {

View File

@@ -0,0 +1,107 @@
<template>
<q-dialog ref="dialog" @hide="onHide">
<q-card class="q-dialog-plugin" style="width: 60vw">
<q-bar>
{{ title }}
<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-form @submit="submit">
<!-- name -->
<q-card-section>
<q-input label="Name" outlined dense v-model="localKey.name" :rules="[val => !!val || '*Required']" />
</q-card-section>
<!-- name -->
<q-card-section>
<q-input label="Value" outlined dense v-model="localKey.value" :rules="[val => !!val || '*Required']" />
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" v-close-popup />
<q-btn flat label="Submit" color="primary" type="submit" />
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>
<script>
import mixins from "@/mixins/mixins";
export default {
name: "KeyStoreForm",
mixins: [mixins],
props: { globalKey: Object },
data() {
return {
localKey: {
name: "",
value: "",
},
};
},
computed: {
title() {
return this.editing ? "Edit Global Key" : "Add Global Key";
},
editing() {
return !!this.globalKey;
},
},
methods: {
submit() {
this.$q.loading.show();
let data = {
...this.localKey,
};
if (this.editing) {
this.$axios
.put(`/core/keystore/${data.id}/`, data)
.then(r => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess("Key was edited!");
})
.catch(e => {
this.$q.loading.hide();
this.notifyError("There was an error editing the key");
});
} else {
this.$axios
.post("/core/keystore/", data)
.then(r => {
this.$q.loading.hide();
this.onOk();
this.notifySuccess("Key was added!");
})
.catch(e => {
this.$q.loading.hide();
this.notifyError("There was an error adding the key");
});
}
},
show() {
this.$refs.dialog.show();
},
hide() {
this.$refs.dialog.hide();
},
onHide() {
this.$emit("hide");
},
onOk() {
this.$emit("ok");
this.hide();
},
},
mounted() {
// If pk prop is set that means we are editing
if (this.globalKey) Object.assign(this.localKey, this.globalKey);
},
};
</script>

View File

@@ -0,0 +1,156 @@
<template>
<div>
<div class="row">
<div class="text-subtitle2">Global Key Store</div>
<q-space />
<q-btn size="sm" color="grey-5" icon="fas fa-plus" text-color="black" label="Add key" @click="addKey" />
</div>
<hr />
<q-table
dense
:data="keystore"
:columns="columns"
:pagination.sync="pagination"
row-key="id"
binary-state-sort
hide-pagination
virtual-scroll
:rows-per-page-options="[0]"
no-data-label="No Keys added yet"
>
<!-- body slots -->
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @dblclick="editKey(props.row)">
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
<q-item clickable v-close-popup @click="editKey(props.row)">
<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="deleteKey(props.row)">
<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>
<!-- name -->
<q-td>
{{ props.row.name }}
</q-td>
<!-- value -->
<q-td>
{{ props.row.value }}
</q-td>
</q-tr>
</template>
</q-table>
</div>
</template>
<script>
import KeyStoreForm from "@/components/modals/coresettings/KeyStoreForm";
import mixins from "@/mixins/mixins";
export default {
name: "KeyStoreTable",
mixins: [mixins],
data() {
return {
keystore: [],
pagination: {
rowsPerPage: 0,
sortBy: "name",
descending: true,
},
columns: [
{
name: "name",
label: "Name",
field: "name",
align: "left",
sortable: true,
},
{
name: "value",
label: "Value",
field: "value",
align: "left",
sortable: true,
},
],
};
},
methods: {
getKeyStore() {
this.$q.loading.show();
this.$axios
.get("/core/keystore/")
.then(r => {
this.$q.loading.hide();
this.keystore = r.data;
})
.catch(e => {
this.$q.loading.hide();
});
},
addKey() {
this.$q
.dialog({
component: KeyStoreForm,
parent: this,
})
.onOk(() => {
this.getKeyStore();
});
},
editKey(key) {
this.$q
.dialog({
component: KeyStoreForm,
parent: this,
globalKey: key,
})
.onOk(() => {
this.getKeyStore();
});
},
deleteKey(key) {
this.$q
.dialog({
title: `Delete key: ${key.name}?`,
cancel: true,
ok: { label: "Delete", color: "negative" },
})
.onOk(() => {
this.$q.loading.show();
this.$axios
.delete(`/core/keystore/${key.id}/`)
.then(r => {
this.getKeyStore();
this.$q.loading.hide();
this.notifySuccess(`key: ${key.name} was deleted!`);
})
.catch(error => {
this.$q.loading.hide();
this.notifyError(`An Error occured while deleting key: ${key.name}`);
});
});
},
},
mounted() {
this.getKeyStore();
},
};
</script>

View File

@@ -64,6 +64,28 @@
dense
v-model="autotask.name"
label="Descriptive name of task"
class="q-pb-none"
/>
</q-card-section>
<q-card-section>
<q-checkbox
dense
label="Collector Task"
v-model="collector"
class="q-pb-sm"
@input="autotask.custom_field = null"
/>
<q-select
v-if="collector"
v-model="autotask.custom_field"
:options="customFieldOptions"
dense
label="Custom Field to update"
outlined
map-options
emit-value
options-dense
hint="The last line of script output will be saved to custom field selected"
/>
</q-card-section>
<q-card-section>
@@ -86,6 +108,7 @@
v-model.number="autotask.timeout"
type="number"
label="Maximum permitted execution time (seconds)"
class="q-pb-none"
/>
</q-card-section>
</q-step>
@@ -190,10 +213,13 @@ export default {
return {
step: 1,
scriptOptions: [],
customFieldOptions: [],
collector: false,
autotask: {
script: null,
script_args: [],
assigned_check: null,
custom_field: null,
name: null,
run_time_days: [],
run_time_minute: null,
@@ -319,6 +345,10 @@ export default {
if (this.policypk) {
this.getPolicyChecks();
}
this.getCustomFields("agent").then(r => {
this.customFieldOptions = r.data.map(field => ({ label: field.name, value: field.id }));
});
},
};
</script>

View File

@@ -15,11 +15,12 @@
dense
options-dense
outlined
v-model="localTask.script"
v-model="autotask.script"
:options="scriptOptions"
label="Select script"
map-options
emit-value
@input="setScriptDefaults"
>
<template v-slot:option="scope">
<q-item v-if="!scope.opt.category" v-bind="scope.itemProps" v-on="scope.itemEvents" class="q-pl-lg">
@@ -38,14 +39,13 @@
dense
label="Script Arguments (press Enter after typing each argument)"
filled
v-model="localTask.script_args"
v-model="autotask.script_args"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
@input="setScriptDefaults"
/>
</q-card-section>
<q-card-section>
@@ -53,14 +53,14 @@
:rules="[val => !!val || '*Required']"
outlined
dense
v-model="localTask.name"
v-model="autotask.name"
label="Descriptive name of task"
class="q-pb-none"
/>
</q-card-section>
<q-card-section>
<q-select
v-model="localTask.alert_severity"
v-model="autotask.alert_severity"
:options="severityOptions"
dense
label="Alert Severity"
@@ -70,12 +70,33 @@
options-dense
/>
</q-card-section>
<q-card-section>
<q-checkbox
dense
label="Collector Task"
v-model="collector"
class="q-pb-sm"
@input="autotask.custom_field = null"
/>
<q-select
v-if="collector"
v-model="autotask.custom_field"
:options="customFieldOptions"
dense
label="Custom Field to update"
outlined
map-options
emit-value
options-dense
hint="The return value of script will be saved to custom field selected"
/>
</q-card-section>
<q-card-section>
<q-input
:rules="[val => !!val || '*Required']"
outlined
dense
v-model.number="localTask.timeout"
v-model.number="autotask.timeout"
type="number"
label="Maximum permitted execution time (seconds)"
class="q-pb-none"
@@ -102,14 +123,17 @@ export default {
},
data() {
return {
localTask: {
autotask: {
id: null,
name: "",
script: null,
script_args: [],
alert_severity: null,
timeout: 120,
custom_field: null,
},
collector: false,
customFieldOptions: [],
scriptOptions: [],
severityOptions: [
{ label: "Informational", value: "info" },
@@ -132,7 +156,7 @@ export default {
this.$q.loading.show();
this.$axios
.put(`/tasks/${this.localTask.id}/automatedtasks/`, this.localTask)
.put(`/tasks/${this.autotask.id}/automatedtasks/`, this.autotask)
.then(r => {
this.$q.loading.hide();
this.onOk();
@@ -160,13 +184,20 @@ export default {
mounted() {
this.scriptOptions = this.getScriptOptions(this.showCommunityScripts);
this.getCustomFields("agent").then(r => {
this.customFieldOptions = r.data.map(field => ({ label: field.name, value: field.id }));
});
this.collector = !!this.task.custom_field;
// copy only certain task props locally
this.localTask.id = this.task.id;
this.localTask.name = this.task.name;
this.localTask.script = this.task.script;
this.localTask.script_args = this.task.script_args;
this.localTask.alert_severity = this.task.alert_severity;
this.localTask.timeout = this.task.timeout;
this.autotask.id = this.task.id;
this.autotask.name = this.task.name;
this.autotask.script = this.task.script;
this.autotask.script_args = this.task.script_args;
this.autotask.alert_severity = this.task.alert_severity;
this.autotask.timeout = this.task.timeout;
this.autotask.custom_field = this.task.custom_field;
},
};
</script>

View File

@@ -257,7 +257,8 @@ export default function () {
client: client.id,
server_policy: site.server_policy,
workstation_policy: site.workstation_policy,
alert_template: site.alert_template
alert_template: site.alert_template,
blockInheritance: site.block_policy_inheritance
}
if (site.maintenance_mode) { siteNode["color"] = "green" }
@@ -276,6 +277,7 @@ export default function () {
server_policy: client.server_policy,
workstation_policy: client.workstation_policy,
alert_template: client.alert_template,
blockInheritance: client.block_policy_inheritance,
children: childSites
}