mirror of
https://github.com/jpros/tacticalrmm-web.git
synced 2026-01-19 19:40:50 +00:00
automated task rework
This commit is contained in:
30
package-lock.json
generated
30
package-lock.json
generated
@@ -16,6 +16,7 @@
|
||||
"quasar": "^2.3.4",
|
||||
"vue3-ace-editor": "^2.2.1",
|
||||
"vue3-apexcharts": "^1.4.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -10962,6 +10963,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
|
||||
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
|
||||
},
|
||||
"node_modules/source-list-map": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
||||
@@ -12080,6 +12086,17 @@
|
||||
"vue": "> 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vuedraggable": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
|
||||
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
|
||||
"dependencies": {
|
||||
"sortablejs": "1.14.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/vuex": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
|
||||
@@ -21101,6 +21118,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sortablejs": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
|
||||
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
|
||||
},
|
||||
"source-list-map": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
||||
@@ -21952,6 +21974,14 @@
|
||||
"integrity": "sha512-96qP8JDqB9vwU7bkG5nVU+E0UGQn7yYQVqUUCLQMYWDuQyu2vE77H/UFZ1yI+hwzlSTBKT9BqnNG8JsFegB3eg==",
|
||||
"requires": {}
|
||||
},
|
||||
"vuedraggable": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
|
||||
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
|
||||
"requires": {
|
||||
"sortablejs": "1.14.0"
|
||||
}
|
||||
},
|
||||
"vuex": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"quasar": "^2.3.4",
|
||||
"vue3-ace-editor": "^2.2.1",
|
||||
"vue3-apexcharts": "^1.4.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -249,8 +249,7 @@ import { notifySuccess, notifyError } from "@/utils/notify";
|
||||
import { truncateText } from "@/utils/format";
|
||||
|
||||
// ui imports
|
||||
import AddAutomatedTask from "@/components/tasks/AddAutomatedTask";
|
||||
import EditAutomatedTask from "@/components/tasks/EditAutomatedTask";
|
||||
import AutomatedTaskForm from "@/components/tasks/AutomatedTaskForm";
|
||||
import ScriptOutput from "@/components/checks/ScriptOutput";
|
||||
|
||||
// static data
|
||||
@@ -380,7 +379,7 @@ export default {
|
||||
|
||||
function showAddTask() {
|
||||
$q.dialog({
|
||||
component: AddAutomatedTask,
|
||||
component: AutomatedTaskForm,
|
||||
componentProps: {
|
||||
parent: { agent: selectedAgent.value },
|
||||
},
|
||||
@@ -393,9 +392,10 @@ export default {
|
||||
if (task.managed_by_policy) return;
|
||||
|
||||
$q.dialog({
|
||||
component: EditAutomatedTask,
|
||||
component: AutomatedTaskForm,
|
||||
componentProps: {
|
||||
task: task,
|
||||
parent: { agent: selectedAgent.value },
|
||||
},
|
||||
}).onOk(() => {
|
||||
getTasks();
|
||||
|
||||
@@ -164,8 +164,7 @@
|
||||
|
||||
<script>
|
||||
import mixins from "@/mixins/mixins";
|
||||
import AddAutomatedTask from "@/components/tasks/AddAutomatedTask";
|
||||
import EditAutomatedTask from "@/components/tasks/EditAutomatedTask";
|
||||
import AutomatedTaskForm from "@/components/tasks/AutomatedTaskForm";
|
||||
import PolicyStatus from "@/components/automation/modals/PolicyStatus";
|
||||
|
||||
export default {
|
||||
@@ -246,7 +245,7 @@ export default {
|
||||
showAddTask() {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: AddAutomatedTask,
|
||||
component: AutomatedTaskForm,
|
||||
componentProps: {
|
||||
parent: { policy: this.selectedPolicy },
|
||||
},
|
||||
@@ -256,9 +255,10 @@ export default {
|
||||
showEditTask(task) {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: EditAutomatedTask,
|
||||
component: AutomatedTaskForm,
|
||||
componentProps: {
|
||||
task: task,
|
||||
parent: { policy: this.selectedPolicy },
|
||||
},
|
||||
})
|
||||
.onOk(this.getTasks);
|
||||
|
||||
@@ -1,351 +0,0 @@
|
||||
<template>
|
||||
<q-dialog ref="dialogRef" @hide="onDialogHide" persistent>
|
||||
<q-card class="q-dialog-plugin" style="width: 60vw">
|
||||
<q-bar>
|
||||
Add Automated Task
|
||||
<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>
|
||||
|
||||
<q-card-section v-if="scriptOptions.length === 0">
|
||||
<p>You need to upload a script first</p>
|
||||
<p>Settings -> Script Manager</p>
|
||||
</q-card-section>
|
||||
<q-stepper v-else v-model="step" ref="stepper" color="primary" animated>
|
||||
<q-step :name="1" title="Select Task" :done="step1Done" :error="!step1Done">
|
||||
<q-card-section>
|
||||
<tactical-dropdown
|
||||
:rules="[val => !!val || '*Required']"
|
||||
label="Select script"
|
||||
v-model="state.script"
|
||||
:options="scriptOptions"
|
||||
outlined
|
||||
mapOptions
|
||||
filterable
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-select
|
||||
dense
|
||||
label="Script Arguments (press Enter after typing each argument)"
|
||||
filled
|
||||
v-model="state.script_args"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input
|
||||
:rules="[val => !!val || '*Required']"
|
||||
outlined
|
||||
dense
|
||||
v-model="state.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" />
|
||||
<tactical-dropdown
|
||||
v-if="collector"
|
||||
v-model="state.custom_field"
|
||||
:options="customFieldOptions"
|
||||
label="Custom Field to update"
|
||||
outlined
|
||||
mapOptions
|
||||
:hint="
|
||||
state.collector_all_output
|
||||
? 'All script output will be saved to custom field selected'
|
||||
: 'The last line of script output will be saved to custom field selected'
|
||||
"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-if="collector"
|
||||
dense
|
||||
label="Save all output"
|
||||
v-model="state.collector_all_output"
|
||||
class="q-py-sm"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<tactical-dropdown
|
||||
v-model="state.alert_severity"
|
||||
:options="severityOptions"
|
||||
label="Alert Severity"
|
||||
outlined
|
||||
mapOptions
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input
|
||||
:rules="[val => !!val || '*Required']"
|
||||
outlined
|
||||
dense
|
||||
v-model.number="state.timeout"
|
||||
type="number"
|
||||
label="Maximum permitted execution time (seconds)"
|
||||
class="q-pb-none"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-step>
|
||||
|
||||
<q-step :name="2" title="Choose Schedule" :done="step2Done" :error="step > 1 && !step2Done">
|
||||
<q-radio v-model="state.task_type" val="scheduled" label="Scheduled" />
|
||||
<q-radio v-model="state.task_type" val="runonce" label="Run Once" />
|
||||
<q-radio v-model="state.task_type" val="checkfailure" label="On check failure" />
|
||||
<q-radio v-model="state.task_type" val="manual" label="Manual" />
|
||||
<div v-if="state.task_type === 'scheduled'" class="row q-pa-lg">
|
||||
<div class="col-3">
|
||||
Run on Days:
|
||||
<q-option-group :options="dayOptions" label="Days" type="checkbox" v-model="state.run_time_days" />
|
||||
</div>
|
||||
<div class="col-2"></div>
|
||||
<div class="col-6">
|
||||
At time:
|
||||
<q-time v-model="state.run_time_minute" />
|
||||
</div>
|
||||
<div class="col-1"></div>
|
||||
</div>
|
||||
<div v-if="state.task_type === 'runonce'" class="row q-pa-lg">
|
||||
<div class="col-11">
|
||||
<q-input filled v-model="state.run_time_date" hint="Agent timezone will be used">
|
||||
<template v-slot:append>
|
||||
<q-icon name="event" class="cursor-pointer">
|
||||
<q-popup-proxy transition-show="scale" transition-hide="scale">
|
||||
<q-date v-model="state.run_time_date" mask="YYYY-MM-DD HH:mm">
|
||||
<div class="row items-center justify-end">
|
||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
||||
</div>
|
||||
</q-date>
|
||||
</q-popup-proxy>
|
||||
</q-icon>
|
||||
<q-icon name="access_time" class="cursor-pointer">
|
||||
<q-popup-proxy transition-show="scale" transition-hide="scale">
|
||||
<q-time v-model="state.run_time_date" mask="YYYY-MM-DD HH:mm">
|
||||
<div class="row items-center justify-end">
|
||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
||||
</div>
|
||||
</q-time>
|
||||
</q-popup-proxy>
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
<div class="q-gutter-sm">
|
||||
<q-checkbox v-model="state.remove_if_not_scheduled" label="Delete task after scheduled date" />
|
||||
</div>
|
||||
<div class="q-gutter-sm">
|
||||
<q-checkbox
|
||||
v-model="state.run_asap_after_missed"
|
||||
label="Run task ASAP after a scheduled start is missed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1"></div>
|
||||
</div>
|
||||
<div v-else-if="state.task_type === 'checkfailure'" class="q-pa-lg">
|
||||
When Check Fails:
|
||||
<tactical-dropdown
|
||||
:rules="[val => !!val || '*Required']"
|
||||
v-model="state.assigned_check"
|
||||
outlined
|
||||
:options="checkOptions"
|
||||
label="Select Check"
|
||||
mapOptions
|
||||
/>
|
||||
</div>
|
||||
</q-step>
|
||||
</q-stepper>
|
||||
<q-card-actions align="right">
|
||||
<q-btn dense flat push label="Cancel" v-close-popup />
|
||||
<q-btn v-if="step > 1" label="Back" @click="$refs.stepper.previous()" color="primary" flat dense push />
|
||||
<q-btn v-if="step !== 2" @click="step2($refs.stepper)" color="primary" label="Next" flat dense push />
|
||||
<q-btn
|
||||
v-else
|
||||
label="Add Task"
|
||||
:disable="!step1Done || !step2Done"
|
||||
color="primary"
|
||||
@click="submit"
|
||||
:loading="loading"
|
||||
flat
|
||||
dense
|
||||
push
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition imports
|
||||
import { ref, computed, watch, onMounted } from "vue";
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
import { saveTask } from "@/api/tasks";
|
||||
import { useScriptDropdown } from "@/composables/scripts";
|
||||
import { useCheckDropdown } from "@/composables/checks";
|
||||
import { useCustomFieldDropdown } from "@/composables/core";
|
||||
import { notifyError, notifySuccess } from "@/utils/notify";
|
||||
|
||||
// ui imports
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||
|
||||
// static data
|
||||
const severityOptions = [
|
||||
{ label: "Informational", value: "info" },
|
||||
{ label: "Warning", value: "warning" },
|
||||
{ label: "Error", value: "error" },
|
||||
];
|
||||
|
||||
const dayOptions = [
|
||||
{ label: "Monday", value: "Monday" },
|
||||
{ label: "Tuesday", value: "Tuesday" },
|
||||
{ label: "Wednesday", value: "Wednesday" },
|
||||
{ label: "Thursday", value: "Thursday" },
|
||||
{ label: "Friday", value: "Friday" },
|
||||
{ label: "Saturday", value: "Saturday" },
|
||||
{ label: "Sunday", value: "Sunday" },
|
||||
];
|
||||
|
||||
export default {
|
||||
components: { TacticalDropdown },
|
||||
name: "AddAutomatedTask",
|
||||
emits: [...useDialogPluginComponent.emits],
|
||||
props: {
|
||||
parent: !Object,
|
||||
},
|
||||
setup(props) {
|
||||
// setup quasar dialog
|
||||
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
|
||||
|
||||
// setup dropdowns
|
||||
const { script, scriptOptions, defaultTimeout, defaultArgs } = useScriptDropdown(undefined, {
|
||||
onMount: true,
|
||||
});
|
||||
const { checkOptions, getCheckOptions } = useCheckDropdown();
|
||||
const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
|
||||
|
||||
// add task logic
|
||||
const task = ref({
|
||||
...props.parent,
|
||||
script,
|
||||
script_args: defaultArgs,
|
||||
timeout: defaultTimeout,
|
||||
assigned_check: null,
|
||||
custom_field: null,
|
||||
name: null,
|
||||
run_time_days: [],
|
||||
run_time_minute: null,
|
||||
run_time_date: null,
|
||||
remove_if_not_scheduled: false,
|
||||
run_asap_after_missed: true,
|
||||
task_type: "scheduled",
|
||||
alert_severity: "info",
|
||||
collector_all_output: false,
|
||||
});
|
||||
|
||||
const collector = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
async function submit() {
|
||||
if (!step1Done.value || !step2Done.value) {
|
||||
notifyError("Some steps are incomplete");
|
||||
return;
|
||||
} else {
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await saveTask(task.value);
|
||||
notifySuccess(result);
|
||||
onDialogOK();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => task.value.task_type,
|
||||
(newValue, oldValue) => {
|
||||
task.value.assigned_check = null;
|
||||
task.value.run_time_days = [];
|
||||
task.value.run_time_minute = null;
|
||||
task.value.run_time_date = null;
|
||||
task.value.remove_if_not_scheduled = false;
|
||||
}
|
||||
);
|
||||
|
||||
watch(collector, (newValue, oldValue) => {
|
||||
task.value.custom_field = null;
|
||||
task.value.collector_all_ouput = false;
|
||||
});
|
||||
|
||||
// stepper logic
|
||||
const step = ref(1);
|
||||
const step1Done = computed(() => {
|
||||
return (
|
||||
(!!script.value && !!task.value.name && !!defaultTimeout.value && !collector.value) ||
|
||||
(!!script.value && !!task.value.name && !!defaultTimeout.value && collector.value && !!task.value.custom_field)
|
||||
);
|
||||
});
|
||||
|
||||
const step2Done = computed(() => {
|
||||
if (task.value.task_type === "scheduled") {
|
||||
return task.value.run_time_days.length !== 0 && !!task.value.run_time_minute;
|
||||
} else if (task.value.task_type === "checkfailure") {
|
||||
return !!task.value.assigned_check;
|
||||
} else if (task.value.task_type === "manual") {
|
||||
return true;
|
||||
} else if (task.value.task_type === "runonce") {
|
||||
return !!task.value.run_time_date;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
function step2(stepper) {
|
||||
if (step1Done.value) {
|
||||
stepper.next();
|
||||
} else {
|
||||
if (!script.value) notifyError("Script field is required");
|
||||
else if (!task.value.name) notifyError("Name field is required");
|
||||
else if (!defaultTimeout.value) notifyError("Timeout field is required");
|
||||
else if (collector.value && !task.value.custom_field) notifyError("You must select a custom field");
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCheckOptions(props.parent);
|
||||
});
|
||||
|
||||
return {
|
||||
// reactive data
|
||||
state: task,
|
||||
collector,
|
||||
loading,
|
||||
step,
|
||||
scriptOptions,
|
||||
checkOptions,
|
||||
customFieldOptions,
|
||||
step2,
|
||||
step1Done,
|
||||
step2Done,
|
||||
|
||||
// non-reactive data
|
||||
severityOptions,
|
||||
dayOptions,
|
||||
|
||||
// methods
|
||||
submit,
|
||||
|
||||
// quasar dialog
|
||||
dialogRef,
|
||||
onDialogHide,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
968
src/components/tasks/AutomatedTaskForm.vue
Normal file
968
src/components/tasks/AutomatedTaskForm.vue
Normal file
@@ -0,0 +1,968 @@
|
||||
<template>
|
||||
<q-dialog ref="dialogRef" @hide="onDialogHide" persistent>
|
||||
<q-card class="q-dialog-plugin" style="width: 65vw; min-width: 65vw">
|
||||
<q-bar>
|
||||
{{ task ? `Editing Automated Task: ${task.name}` : "Adding Automated Task" }}
|
||||
<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>
|
||||
<q-card-section v-if="scriptOptions.length === 0">
|
||||
<p>You need to upload a script first</p>
|
||||
<p>Settings -> Script Manager</p>
|
||||
</q-card-section>
|
||||
<q-stepper v-else v-model="step" ref="stepper" color="primary" animated>
|
||||
<q-step :name="1" title="Select Task" :done="step > 1" :error="!isValidStep1">
|
||||
<q-form @submit.prevent ref="taskGeneralForm">
|
||||
<q-card-section>
|
||||
<q-input
|
||||
:rules="[val => !!val || '*Required']"
|
||||
filled
|
||||
dense
|
||||
v-model="state.name"
|
||||
label="Descriptive name of task"
|
||||
hide-bottom-space
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-checkbox dense label="Collector Task" v-model="collector" class="q-pb-sm" />
|
||||
<tactical-dropdown
|
||||
v-if="collector"
|
||||
:rules="[val => !!val || '*Required']"
|
||||
v-model="state.custom_field"
|
||||
:options="customFieldOptions"
|
||||
label="Custom Field to update"
|
||||
filled
|
||||
mapOptions
|
||||
:hint="
|
||||
state.collector_all_output
|
||||
? 'All script output will be saved to custom field selected'
|
||||
: 'The last line of script output will be saved to custom field selected'
|
||||
"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-if="collector"
|
||||
dense
|
||||
label="Save all output"
|
||||
v-model="state.collector_all_output"
|
||||
class="q-py-sm"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<tactical-dropdown
|
||||
v-model="state.alert_severity"
|
||||
:options="severityOptions"
|
||||
label="Alert Severity"
|
||||
filled
|
||||
mapOptions
|
||||
:rules="[val => !!val || '*Required']"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
</q-step>
|
||||
|
||||
<q-step :name="2" title="Configure Actions" :done="step > 2" :error="!isValidStep2">
|
||||
<q-form @submit.prevent="addAction">
|
||||
<div class="row q-pa-sm q-gutter-x-xs items-center">
|
||||
<div class="text-subtitle2 col-12">Action Type:</div>
|
||||
<q-option-group
|
||||
class="col-12"
|
||||
inline
|
||||
v-model="actionType"
|
||||
:options="[
|
||||
{ label: 'Script', value: 'script' },
|
||||
{ label: 'Command', value: 'cmd' },
|
||||
]"
|
||||
/>
|
||||
|
||||
<tactical-dropdown
|
||||
v-if="actionType === 'script'"
|
||||
class="col-4"
|
||||
label="Select script"
|
||||
v-model="script"
|
||||
:options="scriptOptions"
|
||||
filled
|
||||
mapOptions
|
||||
filterable
|
||||
/>
|
||||
|
||||
<q-select
|
||||
v-if="actionType === 'script'"
|
||||
class="col-5"
|
||||
dense
|
||||
label="Script Arguments (press Enter after typing each argument)"
|
||||
filled
|
||||
v-model="defaultArgs"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
v-if="actionType === 'script'"
|
||||
class="col-2"
|
||||
filled
|
||||
dense
|
||||
v-model.number="defaultTimeout"
|
||||
type="number"
|
||||
label="Timeout (seconds)"
|
||||
/>
|
||||
|
||||
<q-input v-if="actionType === 'cmd'" label="Command" v-model="command" dense filled class="col-7" />
|
||||
<q-input
|
||||
v-if="actionType === 'cmd'"
|
||||
class="col-2"
|
||||
filled
|
||||
dense
|
||||
v-model.number="defaultTimeout"
|
||||
type="number"
|
||||
label="Timeout (seconds)"
|
||||
/>
|
||||
<q-option-group
|
||||
v-if="actionType === 'cmd'"
|
||||
class="col-2 q-pl-sm"
|
||||
inline
|
||||
v-model="shell"
|
||||
:options="[
|
||||
{ label: 'Batch', value: 'cmd' },
|
||||
{ label: 'Powershell', value: 'powershell' },
|
||||
]"
|
||||
/>
|
||||
<q-btn class="col-1" type="submit" style="width: 50px" flat dense icon="add" color="primary" />
|
||||
</div>
|
||||
</q-form>
|
||||
<div class="text-subtitle2 q-pa-sm">
|
||||
Actions:
|
||||
<q-checkbox class="float-right" label="Continue on Errors" v-model="state.continue_on_error" dense>
|
||||
<q-tooltip>Continue if task if an action fails</q-tooltip>
|
||||
</q-checkbox>
|
||||
</div>
|
||||
<div class="scroll q-pt-sm" style="height: 40vh; max-height: 40vh">
|
||||
<draggable class="q-list" handle=".handle" ghost-class="ghost" v-model="state.actions" item-key="index">
|
||||
<template v-slot:item="{ index, element }">
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon class="handle" style="cursor: move" name="drag_handle" />
|
||||
</q-item-section>
|
||||
<q-item-section v-if="element.type === 'script'">
|
||||
<q-item-label>
|
||||
<q-icon size="sm" name="description" color="primary" /> {{ element.name }}
|
||||
</q-item-label>
|
||||
<q-item-label caption> Arguments: {{ element.script_args }} </q-item-label>
|
||||
<q-item-label caption> Timeout: {{ element.timeout }} </q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section v-else>
|
||||
<q-item-label>
|
||||
<q-icon size="sm" name="terminal" color="primary" />
|
||||
<q-icon
|
||||
size="sm"
|
||||
:name="element.shell === 'cmd' ? 'mdi-microsoft-windows' : 'mdi-powershell'"
|
||||
color="primary"
|
||||
/>
|
||||
{{ element.command }}
|
||||
</q-item-label>
|
||||
<q-item-label caption> Timeout: {{ element.timeout }} </q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon class="cursor-pointer" color="negative" name="close" @click="removeAction(index)" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</q-step>
|
||||
|
||||
<q-step :name="3" title="Choose Schedule" :error="!isValidStep3">
|
||||
<div class="scroll" style="height: 60vh; max-height: 60vh">
|
||||
<q-form @submit.prevent ref="taskDetailForm">
|
||||
<q-card-section>
|
||||
<q-option-group
|
||||
v-model="state.task_type"
|
||||
label="Task run type"
|
||||
:options="taskTypeOptions"
|
||||
dense
|
||||
inline
|
||||
@update:model-value="$refs.taskDetailForm.resetValidation()"
|
||||
/>
|
||||
</q-card-section>
|
||||
|
||||
<!-- task start/expire time fields -->
|
||||
<q-card-section v-if="['runonce', 'daily', 'weekly', 'monthly'].includes(state.task_type)" class="row">
|
||||
<!-- start time input -->
|
||||
<q-input
|
||||
class="col-6 q-pa-sm"
|
||||
type="datetime-local"
|
||||
dense
|
||||
label="Start time"
|
||||
stack-label
|
||||
filled
|
||||
v-model="state.run_time_date"
|
||||
hint="Agent timezone will be used"
|
||||
:rules="[val => !!val || '*Required']"
|
||||
/>
|
||||
|
||||
<!-- expires on input -->
|
||||
<q-input
|
||||
class="col-6 q-pa-sm"
|
||||
type="datetime-local"
|
||||
dense
|
||||
stack-label
|
||||
label="Expires on"
|
||||
filled
|
||||
v-model="state.expire_date"
|
||||
hint="Agent timezone will be used"
|
||||
/>
|
||||
</q-card-section>
|
||||
|
||||
<!-- daily options -->
|
||||
<q-card-section v-if="state.task_type === 'daily'" class="row">
|
||||
<!-- daily interval -->
|
||||
<q-input
|
||||
:rules="[
|
||||
val => !!val || '*Required',
|
||||
val => (val > 0 && val < 256) || 'Daily interval must be greater than 0 and less than 3',
|
||||
]"
|
||||
dense
|
||||
type="number"
|
||||
label="Run every"
|
||||
v-model.number="state.daily_interval"
|
||||
filled
|
||||
class="col-6 q-pa-sm"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<span class="text-subtitle2">days</span>
|
||||
</template>
|
||||
</q-input>
|
||||
<div class="col-6 q-pa-sm"></div>
|
||||
</q-card-section>
|
||||
|
||||
<!-- weekly options -->
|
||||
<q-card-section v-if="state.task_type === 'weekly'" class="row">
|
||||
<!-- weekly interval -->
|
||||
<q-input
|
||||
:rules="[
|
||||
val => !!val || '*Required',
|
||||
val => (val > 0 && val < 53) || 'Weekly interval must be greater than 0 and less than 3',
|
||||
]"
|
||||
class="col-6 q-pa-sm"
|
||||
dense
|
||||
label="Run every"
|
||||
v-model="state.weekly_interval"
|
||||
filled
|
||||
>
|
||||
<template v-slot:append>
|
||||
<span class="text-subtitle2">weeks</span>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="col-6 q-pa-sm"></div>
|
||||
|
||||
<div class="col-12 q-pa-sm">
|
||||
<!-- day of week input -->
|
||||
Run on Days:
|
||||
<q-option-group
|
||||
:rules="[val => val.length > 0 || '*Required']"
|
||||
inline
|
||||
dense
|
||||
:options="dayOfWeekOptions"
|
||||
type="checkbox"
|
||||
v-model="state.run_time_bit_weekdays"
|
||||
/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<!-- monthly options -->
|
||||
<q-card-section v-if="state.task_type === 'monthly'" class="row">
|
||||
<!-- type of monthly schedule -->
|
||||
<q-option-group
|
||||
class="col-12 q-pa-sm"
|
||||
v-model="monthlyType"
|
||||
inline
|
||||
:options="[
|
||||
{ label: 'On Days', value: 'days' },
|
||||
{ label: 'On Weeks', value: 'weeks' },
|
||||
]"
|
||||
/>
|
||||
|
||||
<!-- month select input -->
|
||||
<q-select
|
||||
:rules="[val => val.length > 0 || '*Required']"
|
||||
class="col-4 q-pa-sm"
|
||||
filled
|
||||
dense
|
||||
options-dense
|
||||
v-model="state.monthly_months_of_year"
|
||||
:options="monthOptions"
|
||||
label="Run on Months"
|
||||
multiple
|
||||
emit-value
|
||||
map-options
|
||||
>
|
||||
<template v-slot:before-options>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-item-label>All months</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-checkbox dense v-model="allMonthsCheckbox" @update:model-value="toggleMonths" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<template v-slot:option="{ itemProps, opt, selected, toggleOption }">
|
||||
<q-item v-bind="itemProps">
|
||||
<q-item-section>
|
||||
<q-item-label v-html="opt.label" />
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-checkbox
|
||||
dense
|
||||
:model-value="selected"
|
||||
@update:model-value="
|
||||
toggleOption(opt);
|
||||
allMonthsCheckbox = false;
|
||||
"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<!-- days of month select input -->
|
||||
<q-select
|
||||
v-if="monthlyType === 'days'"
|
||||
:rules="[val => val.length > 0 || '*Required']"
|
||||
class="col-4 q-pa-sm"
|
||||
filled
|
||||
dense
|
||||
options-dense
|
||||
v-model="state.monthly_days_of_month"
|
||||
:options="dayOfMonthOptions"
|
||||
label="Run on Days"
|
||||
multiple
|
||||
emit-value
|
||||
map-options
|
||||
>
|
||||
<template v-slot:before-options>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-item-label>All days</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-checkbox dense v-model="allMonthDaysCheckbox" @update:model-value="toggleMonthDays" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<template v-slot:option="{ itemProps, opt, selected, toggleOption }">
|
||||
<q-item v-bind="itemProps">
|
||||
<q-item-section>
|
||||
<q-item-label v-html="opt.label" />
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-checkbox
|
||||
dense
|
||||
:model-value="selected"
|
||||
@update:model-value="
|
||||
toggleOption(opt);
|
||||
allMonthDaysCheckbox = false;
|
||||
"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<div v-if="monthlyType === 'days'" class="col-4"></div>
|
||||
|
||||
<!-- week of month select input -->
|
||||
<q-select
|
||||
v-if="monthlyType === 'weeks'"
|
||||
:rules="[val => val.length > 0 || '*Required']"
|
||||
class="col-4 q-pa-sm"
|
||||
filled
|
||||
dense
|
||||
options-dense
|
||||
v-model="state.monthly_weeks_of_month"
|
||||
:options="weekOptions"
|
||||
label="Run on weeks"
|
||||
multiple
|
||||
emit-value
|
||||
map-options
|
||||
>
|
||||
<template v-slot:option="{ itemProps, opt, selected, toggleOption }">
|
||||
<q-item v-bind="itemProps">
|
||||
<q-item-section>
|
||||
<q-item-label v-html="opt.label" />
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-checkbox dense :model-value="selected" @update:model-value="toggleOption(opt)" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<!-- day of week select input -->
|
||||
<q-select
|
||||
v-if="monthlyType === 'weeks'"
|
||||
:rules="[val => val.length > 0 || '*Required']"
|
||||
class="col-4 q-pa-sm"
|
||||
filled
|
||||
dense
|
||||
options-dense
|
||||
v-model="state.run_time_bit_weekdays"
|
||||
:options="dayOfWeekOptions"
|
||||
label="Run on days"
|
||||
multiple
|
||||
emit-value
|
||||
map-options
|
||||
>
|
||||
<template v-slot:before-options>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-item-label>All days</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-checkbox dense v-model="allWeekDaysCheckbox" @update:model-value="toggleWeekDays" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<template v-slot:option="{ itemProps, opt, selected, toggleOption }">
|
||||
<q-item v-bind="itemProps">
|
||||
<q-item-section>
|
||||
<q-item-label v-html="opt.label" />
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-checkbox
|
||||
dense
|
||||
:model-value="selected"
|
||||
@update:model-value="
|
||||
toggleOption(opt);
|
||||
allWeekDaysCheckbox = false;
|
||||
"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section v-if="state.task_type !== 'checkfailure' && state.task_type !== 'manual'" class="row">
|
||||
<div class="col-12 text-h6">Advanced Settings</div>
|
||||
<q-input
|
||||
class="col-6 q-pa-sm"
|
||||
dense
|
||||
label="Repeat task every"
|
||||
filled
|
||||
v-model="state.task_repetition_interval"
|
||||
placeholder="e.g. 30m (30 minutes) or 1h (1 hour)"
|
||||
lazy-rules
|
||||
:rules="[
|
||||
val =>
|
||||
!val || validateTimePeriod(val) || 'Valid values are 1-3 digits followed by (D|d|H|h|M|m|S|s)',
|
||||
]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
:disable="!state.task_repetition_interval"
|
||||
class="col-6 q-pa-sm"
|
||||
dense
|
||||
label="Task repeat duration"
|
||||
filled
|
||||
v-model="state.task_repetition_duration"
|
||||
placeholder="e.g. 6h (6 hours) or 1d (1 day)"
|
||||
lazy-rules
|
||||
:rules="[
|
||||
val => validateTimePeriod(val) || 'Valid values are 1-3 digits followed by (D|d|H|h|M|m|S|s)',
|
||||
val => (state.task_repetition_interval ? !!val : true), // field is required if repetition interval is set
|
||||
val =>
|
||||
convertPeriodToSeconds(val) >= convertPeriodToSeconds(state.task_repetition_interval) ||
|
||||
'Repetition duration must be greater than repetition interval',
|
||||
]"
|
||||
/>
|
||||
|
||||
<q-checkbox
|
||||
:disable="!state.task_repetition_interval"
|
||||
class="col-6 q-pa-sm"
|
||||
dense
|
||||
v-model="state.stop_task_at_duration_end"
|
||||
label="Stop all tasks at the end of duration"
|
||||
/>
|
||||
<div class="col-6"></div>
|
||||
|
||||
<q-input
|
||||
class="col-6 q-pa-sm"
|
||||
dense
|
||||
label="Random task delay"
|
||||
filled
|
||||
v-model="state.random_task_delay"
|
||||
placeholder="e.g. 2m (2 minutes) or 1h (1 hour)"
|
||||
lazy-rules
|
||||
:rules="[
|
||||
val =>
|
||||
!val || validateTimePeriod(val) || 'Valid values are 1-3 digits followed by (D|d|H|h|M|m|S|s)',
|
||||
]"
|
||||
/>
|
||||
<div class="col-6"></div>
|
||||
<q-checkbox
|
||||
class="col-6 q-pa-sm"
|
||||
dense
|
||||
v-model="state.remove_if_not_scheduled"
|
||||
label="Delete task if not scheduled for 30 days"
|
||||
/>
|
||||
<div class="col-6"></div>
|
||||
<q-checkbox
|
||||
class="col-6 q-pa-sm"
|
||||
dense
|
||||
v-model="state.run_asap_after_missed"
|
||||
label="Run task ASAP after a scheduled start is missed"
|
||||
/>
|
||||
|
||||
<div class="col-6"></div>
|
||||
|
||||
<tactical-dropdown
|
||||
class="col-6 q-pa-sm"
|
||||
label="Task instance policy"
|
||||
:options="taskInstancePolicyOptions"
|
||||
v-model="state.task_instance_policy"
|
||||
filled
|
||||
mapOptions
|
||||
/>
|
||||
</q-card-section>
|
||||
|
||||
<!-- check failure options -->
|
||||
<q-card-section v-else-if="state.task_type === 'checkfailure'" class="row">
|
||||
<tactical-dropdown
|
||||
class="col-6 q-pa-sm"
|
||||
:rules="[val => !!val || '*Required']"
|
||||
v-model="state.assigned_check"
|
||||
filled
|
||||
:options="checkOptions"
|
||||
label="Select Check"
|
||||
mapOptions
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
</div>
|
||||
</q-step>
|
||||
</q-stepper>
|
||||
<q-card-actions align="right">
|
||||
<q-btn flat label="Cancel" v-close-popup />
|
||||
<q-btn v-if="step > 1" label="Back" @click="$refs.stepper.previous()" color="primary" flat />
|
||||
<q-btn
|
||||
v-if="step < 3"
|
||||
@click="validateStep(step === 1 ? $refs.taskGeneralForm : undefined, $refs.stepper)"
|
||||
color="primary"
|
||||
label="Next"
|
||||
flat
|
||||
/>
|
||||
<q-btn
|
||||
v-else
|
||||
label="Add Task"
|
||||
color="primary"
|
||||
@click="validateStep($refs.taskDetailForm, $refs.stepper)"
|
||||
:loading="loading"
|
||||
flat
|
||||
dense
|
||||
push
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition imports
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import { useDialogPluginComponent, date } from "quasar";
|
||||
import draggable from "vuedraggable";
|
||||
import { saveTask, updateTask } from "@/api/tasks";
|
||||
import { useScriptDropdown } from "@/composables/scripts";
|
||||
import { useCheckDropdown } from "@/composables/checks";
|
||||
import { useCustomFieldDropdown } from "@/composables/core";
|
||||
import { notifySuccess, notifyError } from "@/utils/notify";
|
||||
import { validateTimePeriod } from "@/utils/validation";
|
||||
import { convertPeriodToSeconds, convertToBitArray, convertFromBitArray } from "@/utils/format";
|
||||
|
||||
// ui imports
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||
|
||||
// static data
|
||||
const severityOptions = [
|
||||
{ label: "Informational", value: "info" },
|
||||
{ label: "Warning", value: "warning" },
|
||||
{ label: "Error", value: "error" },
|
||||
];
|
||||
|
||||
const taskTypeOptions = [
|
||||
{ label: "Daily", value: "daily" },
|
||||
{ label: "Weekly", value: "weekly" },
|
||||
{ label: "Monthly", value: "monthly" },
|
||||
{ label: "Run Once", value: "runonce" },
|
||||
{ label: "On check failure", value: "checkfailure" },
|
||||
{ label: "Manual", value: "manual" },
|
||||
];
|
||||
|
||||
const dayOfWeekOptions = [
|
||||
{ label: "Monday", value: 0x2 },
|
||||
{ label: "Tuesday", value: 0x4 },
|
||||
{ label: "Wednesday", value: 0x8 },
|
||||
{ label: "Thursday", value: 0x10 },
|
||||
{ label: "Friday", value: 0x20 },
|
||||
{ label: "Saturday", value: 0x40 },
|
||||
{ label: "Sunday", value: 0x1 },
|
||||
];
|
||||
|
||||
const dayOfMonthOptions = (() => {
|
||||
let result = [];
|
||||
let day = 0x1;
|
||||
for (let i = 1; i <= 31; i++) {
|
||||
result.push({ label: `${i}`, value: day });
|
||||
day = day << 1;
|
||||
}
|
||||
result.push({ label: "Last Day", value: 0x80000000 });
|
||||
return result;
|
||||
})();
|
||||
|
||||
const monthOptions = [
|
||||
{ label: "January", value: 0x1 },
|
||||
{ label: "February", value: 0x2 },
|
||||
{ label: "March", value: 0x4 },
|
||||
{ label: "April", value: 0x8 },
|
||||
{ label: "May", value: 0x10 },
|
||||
{ label: "June", value: 0x20 },
|
||||
{ label: "July", value: 0x40 },
|
||||
{ label: "August", value: 0x80 },
|
||||
{ label: "September", value: 0x100 },
|
||||
{ label: "October", value: 0x200 },
|
||||
{ label: "November", value: 0x400 },
|
||||
{ label: "December", value: 0x800 },
|
||||
];
|
||||
|
||||
const weekOptions = [
|
||||
{ label: "First Week", value: 0x1 },
|
||||
{ label: "Second Week", value: 0x2 },
|
||||
{ label: "Third Week", value: 0x4 },
|
||||
{ label: "Fourth Week", value: 0x8 },
|
||||
{ label: "Last Week", value: 0x10 },
|
||||
];
|
||||
|
||||
const taskInstancePolicyOptions = [
|
||||
{ label: "Run in Parallel", value: 0 },
|
||||
{ label: "Queue Task", value: 1 },
|
||||
{ label: "Ignore", value: 2 },
|
||||
{ label: "Stop Existing", value: 3 },
|
||||
];
|
||||
|
||||
export default {
|
||||
components: { TacticalDropdown, draggable },
|
||||
name: "AddAutomatedTask",
|
||||
emits: [...useDialogPluginComponent.emits],
|
||||
props: {
|
||||
parent: Object, // parent policy or agent for task
|
||||
task: Object, // only for editing
|
||||
},
|
||||
setup(props) {
|
||||
// setup quasar dialog
|
||||
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
|
||||
|
||||
// setup dropdowns
|
||||
const { script, scriptOptions, defaultTimeout, defaultArgs } = useScriptDropdown(undefined, {
|
||||
onMount: true,
|
||||
});
|
||||
|
||||
// set defaultTimeout to 30
|
||||
defaultTimeout.value = 30;
|
||||
|
||||
const { checkOptions, getCheckOptions } = useCheckDropdown();
|
||||
const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
|
||||
|
||||
// add task logic
|
||||
const task = props.task
|
||||
? ref(Object.assign({}, props.task))
|
||||
: ref({
|
||||
...props.parent,
|
||||
actions: [],
|
||||
assigned_check: null,
|
||||
custom_field: null,
|
||||
name: null,
|
||||
expire_date: null,
|
||||
run_time_date: date.formatDate(Date.now(), "YYYY-MM-DDTHH:mm:ss"),
|
||||
run_time_bit_weekdays: [],
|
||||
weekly_interval: 1,
|
||||
daily_interval: 1,
|
||||
monthly_months_of_year: [],
|
||||
monthly_days_of_month: [],
|
||||
monthly_weeks_of_month: [],
|
||||
task_instance_policy: 0,
|
||||
task_repetition_interval: null,
|
||||
task_repetition_duration: null,
|
||||
stop_task_at_duration_end: false,
|
||||
random_task_delay: null,
|
||||
remove_if_not_scheduled: false,
|
||||
run_asap_after_missed: true,
|
||||
task_type: "daily",
|
||||
alert_severity: "info",
|
||||
collector_all_output: false,
|
||||
continue_on_error: true,
|
||||
});
|
||||
|
||||
const actionType = ref("script");
|
||||
const command = ref("");
|
||||
const shell = ref("cmd");
|
||||
const monthlyType = ref("days");
|
||||
const collector = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// before-options check boxes that will select all options
|
||||
|
||||
// if all months is selected or cleared it will either clear the monthly_months_of_year array or add all options to it.
|
||||
const allMonthsCheckbox = ref(false);
|
||||
function toggleMonths() {
|
||||
task.value.monthly_months_of_year = allMonthsCheckbox.value ? monthOptions.map(month => month.value) : [];
|
||||
}
|
||||
|
||||
const allMonthDaysCheckbox = ref(false);
|
||||
function toggleMonthDays() {
|
||||
task.value.monthly_days_of_month = allMonthDaysCheckbox.value ? dayOfMonthOptions.map(day => day.value) : [];
|
||||
}
|
||||
|
||||
const allWeekDaysCheckbox = ref(false);
|
||||
function toggleWeekDays() {
|
||||
task.value.run_time_bit_weekdays = allWeekDaysCheckbox.value ? dayOfWeekOptions.map(day => day.value) : [];
|
||||
}
|
||||
|
||||
// function for adding script and commands to be run from task
|
||||
function addAction() {
|
||||
if (actionType.value === "script" && (!script.value || !defaultTimeout.value)) {
|
||||
notifyError("Script and timeout must be set");
|
||||
return;
|
||||
} else if (actionType.value === "cmd" && (!command.value || !defaultTimeout.value)) {
|
||||
notifyError("A command and timeout must be set");
|
||||
return;
|
||||
}
|
||||
|
||||
if (actionType.value === "script") {
|
||||
task.value.actions.push({
|
||||
type: "script",
|
||||
name: scriptOptions.value.find(option => option.value === script.value).label,
|
||||
script: script.value,
|
||||
timeout: defaultTimeout.value,
|
||||
script_args: defaultArgs.value,
|
||||
});
|
||||
} else if (actionType.value === "cmd") {
|
||||
task.value.actions.push({
|
||||
type: "cmd",
|
||||
command: command.value,
|
||||
shell: shell.value,
|
||||
timeout: defaultTimeout.value,
|
||||
});
|
||||
}
|
||||
|
||||
// clear fields after add
|
||||
script.value = null;
|
||||
defaultArgs.value = [];
|
||||
defaultTimeout.value = 30;
|
||||
command.value = "";
|
||||
}
|
||||
|
||||
function removeAction(index) {
|
||||
task.value.actions.splice(index, 1);
|
||||
}
|
||||
|
||||
// runs whenever task data is saved
|
||||
function processTaskDataforDB(taskData) {
|
||||
// copy data
|
||||
let data = Object.assign({}, taskData);
|
||||
|
||||
// converts fields from arrays to integers
|
||||
data.run_time_bit_weekdays =
|
||||
taskData.run_time_bit_weekdays.length > 0 ? convertFromBitArray(taskData.run_time_bit_weekdays) : null;
|
||||
|
||||
data.monthly_months_of_year =
|
||||
taskData.monthly_months_of_year.length > 0 ? convertFromBitArray(taskData.monthly_months_of_year) : null;
|
||||
|
||||
data.monthly_days_of_month =
|
||||
taskData.monthly_days_of_month.length > 0 ? convertFromBitArray(taskData.monthly_days_of_month) : null;
|
||||
|
||||
data.monthly_weeks_of_month =
|
||||
taskData.monthly_weeks_of_month.length > 0 ? convertFromBitArray(taskData.monthly_weeks_of_month) : null;
|
||||
|
||||
// Add Z back to run_time_date and expires_date
|
||||
data.run_time_date += "Z";
|
||||
|
||||
if (taskData.expire_date) data.expire_date += "Z";
|
||||
|
||||
// change task type if monthly day of week is set
|
||||
if (task.value.task_type === "monthly" && monthlyType.value === "weeks") {
|
||||
data.task_type = "monthlydow";
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// runs when editing a task to convert values to be compatible with quasar
|
||||
function processTaskDatafromDB() {
|
||||
// converts fields from integers to arrays
|
||||
task.value.run_time_bit_weekdays = task.value.run_time_bit_weekdays
|
||||
? convertToBitArray(task.value.run_time_bit_weekdays)
|
||||
: [];
|
||||
task.value.monthly_months_of_year = task.value.monthly_months_of_year
|
||||
? convertToBitArray(task.value.monthly_months_of_year)
|
||||
: [];
|
||||
task.value.monthly_days_of_month = task.value.monthly_days_of_month
|
||||
? convertToBitArray(task.value.monthly_days_of_month)
|
||||
: [];
|
||||
task.value.monthly_weeks_of_month = task.value.monthly_weeks_of_month
|
||||
? convertToBitArray(task.value.monthly_weeks_of_month)
|
||||
: [];
|
||||
|
||||
// remove milliseconds and Z to work with native date input
|
||||
task.value.run_time_date = task.value.run_time_date.split(".")[0].replace("Z", "");
|
||||
|
||||
if (task.value.expire_date) task.value.expire_date = task.value.expire_date.split(".")[0].replace("Z", "");
|
||||
|
||||
// set task type if monthlydow is being used
|
||||
if (task.value.task_type === "monthlydow") {
|
||||
task.value.task_type = "monthly";
|
||||
monthlyType.value = "weeks";
|
||||
}
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = props.task
|
||||
? await updateTask(task.value.id, processTaskDataforDB(task.value))
|
||||
: await saveTask(processTaskDataforDB(task.value));
|
||||
notifySuccess(result);
|
||||
onDialogOK();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// format task data to match what quasar expects if editing
|
||||
if (props.task) processTaskDatafromDB();
|
||||
|
||||
watch(
|
||||
() => task.value.task_type,
|
||||
(newValue, oldValue) => {
|
||||
task.value.assigned_check = null;
|
||||
task.value.run_time_bit_weekdays = [];
|
||||
task.value.remove_if_not_scheduled = false;
|
||||
task.value.task_repetition_interval = null;
|
||||
task.value.task_repetition_duration = null;
|
||||
task.value.stop_task_at_duration_end = false;
|
||||
task.value.random_task_delay = null;
|
||||
task.value.weekly_interval = 1;
|
||||
task.value.daily_interval = 1;
|
||||
task.value.monthly_months_of_year = [];
|
||||
task.value.monthly_days_of_month = [];
|
||||
task.value.monthly_weeks_of_month = [];
|
||||
task.value.task_instance_policy = 0;
|
||||
task.value.expire_date = null;
|
||||
}
|
||||
);
|
||||
|
||||
watch(collector, (newValue, oldValue) => {
|
||||
task.value.custom_field = null;
|
||||
task.value.collector_all_ouput = false;
|
||||
});
|
||||
|
||||
// stepper logic
|
||||
const step = ref(1);
|
||||
const isValidStep1 = ref(true);
|
||||
const isValidStep2 = ref(true);
|
||||
const isValidStep3 = ref(true);
|
||||
|
||||
function validateStep(form, stepper) {
|
||||
if (step.value === 2) {
|
||||
if (task.value.actions.length > 0) {
|
||||
isValidStep2.value = true;
|
||||
stepper.next();
|
||||
return;
|
||||
} else {
|
||||
notifyError("There must be at least one action");
|
||||
}
|
||||
|
||||
// steps 1 or 3
|
||||
} else {
|
||||
form.validate().then(result => {
|
||||
if (step.value === 1) {
|
||||
isValidStep1.value = result;
|
||||
if (result) stepper.next();
|
||||
} else if (step.value === 3) {
|
||||
isValidStep3.value = result;
|
||||
if (result) submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCheckOptions(props.parent);
|
||||
});
|
||||
|
||||
return {
|
||||
// reactive data
|
||||
state: task,
|
||||
script,
|
||||
defaultTimeout,
|
||||
defaultArgs,
|
||||
actionType,
|
||||
command,
|
||||
shell,
|
||||
allMonthsCheckbox,
|
||||
allMonthDaysCheckbox,
|
||||
allWeekDaysCheckbox,
|
||||
collector,
|
||||
monthlyType,
|
||||
loading,
|
||||
step,
|
||||
isValidStep1,
|
||||
isValidStep2,
|
||||
isValidStep3,
|
||||
scriptOptions,
|
||||
checkOptions,
|
||||
customFieldOptions,
|
||||
|
||||
// non-reactive data
|
||||
validateTimePeriod,
|
||||
convertPeriodToSeconds,
|
||||
severityOptions,
|
||||
dayOfWeekOptions,
|
||||
dayOfMonthOptions,
|
||||
weekOptions,
|
||||
monthOptions,
|
||||
taskTypeOptions,
|
||||
taskInstancePolicyOptions,
|
||||
|
||||
// methods
|
||||
submit,
|
||||
validateStep,
|
||||
addAction,
|
||||
removeAction,
|
||||
toggleMonths,
|
||||
toggleMonthDays,
|
||||
toggleWeekDays,
|
||||
|
||||
// quasar dialog
|
||||
dialogRef,
|
||||
onDialogHide,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ghost {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
@@ -1,181 +0,0 @@
|
||||
<template>
|
||||
<q-dialog ref="dialogRef" @hide="onDialogHide">
|
||||
<q-card class="q-dialog-plugin" style="width: 60vw">
|
||||
<q-bar>
|
||||
Edit {{ task.name }}
|
||||
<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>
|
||||
<q-form @submit="submit">
|
||||
<q-card-section>
|
||||
<tactical-dropdown
|
||||
:rules="[val => !!val || '*Required']"
|
||||
v-model="state.script"
|
||||
label="Select script"
|
||||
:options="scriptOptions"
|
||||
outlined
|
||||
mapOptions
|
||||
disable
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-select
|
||||
dense
|
||||
label="Script Arguments (press Enter after typing each argument)"
|
||||
filled
|
||||
v-model="state.script_args"
|
||||
use-input
|
||||
use-chips
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input
|
||||
:rules="[val => !!val || '*Required']"
|
||||
outlined
|
||||
dense
|
||||
v-model="state.name"
|
||||
label="Descriptive name of task"
|
||||
class="q-pb-none"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<tactical-dropdown
|
||||
v-model="state.alert_severity"
|
||||
:options="severityOptions"
|
||||
label="Alert Severity"
|
||||
outlined
|
||||
mapOptions
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-checkbox dense label="Collector Task" v-model="collector" class="q-pb-sm" />
|
||||
<tactical-dropdown
|
||||
v-if="collector"
|
||||
:rules="[val => (collector && !!val) || '*Required']"
|
||||
v-model="state.custom_field"
|
||||
:options="customFieldOptions"
|
||||
label="Custom Field to update"
|
||||
outlined
|
||||
mapOptions
|
||||
:hint="
|
||||
state.collector_all_output
|
||||
? 'All script output will be saved to custom field selected'
|
||||
: 'The last line of script output will be saved to custom field selected'
|
||||
"
|
||||
/>
|
||||
<q-checkbox
|
||||
v-if="collector"
|
||||
dense
|
||||
label="Save all output"
|
||||
v-model="state.collector_all_output"
|
||||
class="q-py-sm"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-input
|
||||
:rules="[val => !!val || '*Required']"
|
||||
outlined
|
||||
dense
|
||||
v-model.number="state.timeout"
|
||||
type="number"
|
||||
label="Maximum permitted execution time (seconds)"
|
||||
class="q-pb-none"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn dense flat push label="Cancel" v-close-popup />
|
||||
<q-btn flat dense push label="Submit" color="primary" type="submit" />
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition imports
|
||||
import { ref, watch } from "vue";
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
import { updateTask } from "@/api/tasks";
|
||||
import { useScriptDropdown } from "@/composables/scripts";
|
||||
import { useCustomFieldDropdown } from "@/composables/core";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
|
||||
// ui imports
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||
|
||||
// static data
|
||||
const severityOptions = [
|
||||
{ label: "Informational", value: "info" },
|
||||
{ label: "Warning", value: "warning" },
|
||||
{ label: "Error", value: "error" },
|
||||
];
|
||||
|
||||
export default {
|
||||
name: "EditAutomatedTask",
|
||||
emits: [...useDialogPluginComponent.emits],
|
||||
components: { TacticalDropdown },
|
||||
props: {
|
||||
task: !Object,
|
||||
},
|
||||
setup(props) {
|
||||
// setup quasar dialog
|
||||
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
|
||||
|
||||
// setup dropdowns
|
||||
const { scriptOptions } = useScriptDropdown(null, {
|
||||
onMount: true,
|
||||
});
|
||||
const { customFieldOptions } = useCustomFieldDropdown({ onMount: true });
|
||||
|
||||
// edit automated task logic
|
||||
const task = ref(Object.assign({}, props.task));
|
||||
const collector = ref(!!task.value.custom_field);
|
||||
const loading = ref(false);
|
||||
|
||||
watch(collector, (newValue, oldValue) => {
|
||||
task.value.custom_field = null;
|
||||
task.value.collector_all_output = false;
|
||||
});
|
||||
|
||||
async function submit() {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
// remove run_date_time
|
||||
delete task.value.run_date_time;
|
||||
const result = await updateTask(task.value.id, task.value);
|
||||
notifySuccess(result);
|
||||
onDialogOK();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
// reactive data
|
||||
state: task,
|
||||
collector,
|
||||
scriptOptions,
|
||||
customFieldOptions,
|
||||
|
||||
// non reactive data
|
||||
severityOptions,
|
||||
|
||||
// methods
|
||||
submit,
|
||||
|
||||
// quasar dialog
|
||||
dialogRef,
|
||||
onDialogHide,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -104,6 +104,10 @@ export function useCheckDropdown() {
|
||||
const checkOptions = ref([])
|
||||
|
||||
async function getCheckOptions({ agent, policy }, flat = false) {
|
||||
if (!agent && !policy) {
|
||||
console.error("Need to specify agent or policy object when calling getCheckOptions")
|
||||
return
|
||||
}
|
||||
checkOptions.value = formatCheckOptions(agent ? await fetchAgentChecks(agent) : await fetchPolicyChecks(policy), flat)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { date } from "quasar";
|
||||
import { validateTimePeriod } from "@/utils/validation"
|
||||
|
||||
// dropdown options formatting
|
||||
|
||||
@@ -198,6 +199,11 @@ export function dateStringToUnix(drfString) {
|
||||
return parseInt(date.formatDate(d, "X"));
|
||||
}
|
||||
|
||||
// takes a unix timestamp and converts it to quasar datetime field value YYYY-MM-DD HH:mm:ss
|
||||
export function formatDateInputField(unixtimestamp) {
|
||||
return date.formatDate(unixtimestamp, "YYYY-MM-DD HH:mm:ss")
|
||||
}
|
||||
|
||||
// string formatting
|
||||
|
||||
export function capitalize(string) {
|
||||
@@ -232,3 +238,51 @@ export function convertMemoryToPercent(percent, memory) {
|
||||
const mb = memory * 1024;
|
||||
return Math.ceil((percent * mb) / 100).toLocaleString();
|
||||
}
|
||||
|
||||
// convert time period(str) to seconds(int) (3h -> 10800) used for comparing time intervals
|
||||
export function convertPeriodToSeconds(period) {
|
||||
if (!validateTimePeriod(period)) {
|
||||
console.error("Time Period is invalid")
|
||||
return NaN
|
||||
}
|
||||
|
||||
if (period.toUpperCase().includes("S"))
|
||||
// remove last letter from string and return since already in seconds
|
||||
return parseInt(period.slice(0, -1))
|
||||
else if (period.toUpperCase().includes("M"))
|
||||
// remove last letter from string and multiple by 60 to get seconds
|
||||
return parseInt(period.slice(0, -1)) * 60
|
||||
else if (period.toUpperCase().includes("H"))
|
||||
// remove last letter from string and multiple by 60 twice to get seconds
|
||||
return parseInt(period.slice(0, -1)) * 60 * 60
|
||||
else if (period.toUpperCase().includes("D"))
|
||||
// remove last letter from string and multiply by 24 and 60 twice to get seconds
|
||||
return parseInt(period.slice(0, -1)) * 24 * 60 * 60
|
||||
}
|
||||
|
||||
// takes an integer and converts it to an array in binary format. i.e: 13 -> [8, 4, 1]
|
||||
// Needed to work with multi-select fields in tasks form
|
||||
export function convertToBitArray(number) {
|
||||
let bitArray = []
|
||||
let binary = number.toString(2)
|
||||
for (let i = 0; i < binary.length; ++i) {
|
||||
if (binary[i] !== "0") {
|
||||
// last binary digit
|
||||
if (binary.slice(i).length === 1) {
|
||||
bitArray.push(1)
|
||||
} else {
|
||||
bitArray.push(parseInt(binary.slice(i), 2) - parseInt(binary.slice(i + 1), 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
return bitArray
|
||||
}
|
||||
|
||||
// takes an array of integers and adds them together
|
||||
export function convertFromBitArray(array) {
|
||||
let result = 0
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
result += array[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -34,4 +34,8 @@ export function validateEventID(val) {
|
||||
// validate script return code
|
||||
export function validateRetcode(val, done) {
|
||||
/^\d+$/.test(val) ? done(val) : done();
|
||||
}
|
||||
|
||||
export function validateTimePeriod(val) {
|
||||
return /^\d{1,3}(H|h|M|m|S|s|d|D)$/.test(val);
|
||||
}
|
||||
Reference in New Issue
Block a user