Files
tacticalrmm-web/src/components/agents/AutomatedTasksTab.vue
sadnub 0fbd3a59bd init
2024-04-16 22:03:54 -04:00

552 lines
15 KiB
Vue

<template>
<div v-if="!selectedAgent" class="q-pa-sm">No agent selected</div>
<div v-else-if="agentPlatform.toLowerCase() !== 'windows'" class="q-pa-sm">
Only supported for Windows agents at this time
</div>
<div v-else>
<q-table
dense
:table-class="{
'table-bgcolor': !$q.dark.isActive,
'table-bgcolor-dark': $q.dark.isActive,
}"
class="tabs-tbl-sticky"
:style="{ 'max-height': tabHeight }"
:rows="tasks"
:columns="columns"
row-key="id"
binary-state-sort
virtual-scroll
v-model:pagination="pagination"
:loading="loading"
:rows-per-page-options="[0]"
no-data-label="No tasks"
>
<template v-slot:top>
<q-btn
class="q-mr-sm"
dense
flat
push
@click="getTasks"
icon="refresh"
/>
<q-btn
icon="add"
label="Add Task"
no-caps
dense
flat
push
@click="showAddTask"
/>
</template>
<template v-slot:loading>
<q-inner-loading showing color="primary" />
</template>
<!-- header slots -->
<template v-slot:header-cell-enabled="props">
<q-th auto-width :props="props">
<q-icon name="power_settings_new" size="1.5em">
<q-tooltip>Enabled</q-tooltip>
</q-icon>
</q-th>
</template>
<template v-slot:header-cell-smsalert="props">
<q-th auto-width :props="props">
<q-icon name="phone_android" size="1.5em">
<q-tooltip>SMS Alert</q-tooltip>
</q-icon>
</q-th>
</template>
<template v-slot:header-cell-emailalert="props">
<q-th auto-width :props="props">
<q-icon name="email" size="1.5em">
<q-tooltip>Email Alert</q-tooltip>
</q-icon>
</q-th>
</template>
<template v-slot:header-cell-dashboardalert="props">
<q-th auto-width :props="props">
<q-icon name="notifications" size="1.5em">
<q-tooltip>Dashboard Alert</q-tooltip>
</q-icon>
</q-th>
</template>
<template v-slot:header-cell-policystatus="props">
<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>
<!-- body slots -->
<template v-slot:body="props">
<q-tr
:props="props"
class="cursor-pointer"
@dblclick="showEditTask(props.row)"
>
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
<q-item clickable v-close-popup @click="runWinTask(props.row)">
<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="showEditTask(props.row)"
v-if="!props.row.policy"
>
<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)"
v-if="!props.row.policy"
>
<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
@update:model-value="
editTask(props.row, { enabled: !props.row.enabled })
"
v-model="props.row.enabled"
:disable="!!props.row.policy"
/>
</q-td>
<!-- text alert -->
<q-td>
<q-checkbox
v-if="
props.row.alert_template &&
props.row.alert_template.always_text !== null
"
v-model="props.row.alert_template.always_text"
disable
dense
>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="
editTask(props.row, { text_alert: !props.row.text_alert })
"
v-model="props.row.text_alert"
:disable="!!props.row.policy"
/>
</q-td>
<!-- email alert -->
<q-td>
<q-checkbox
v-if="
props.row.alert_template &&
props.row.alert_template.always_email !== null
"
v-model="props.row.alert_template.always_email"
disable
dense
>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="
editTask(props.row, { email_alert: !props.row.email_alert })
"
v-model="props.row.email_alert"
:disable="!!props.row.policy"
/>
</q-td>
<!-- dashboard alert -->
<q-td>
<q-checkbox
v-if="
props.row.alert_template &&
props.row.alert_template.always_alert !== null
"
v-model="props.row.alert_template.always_alert"
disable
dense
>
<q-tooltip>
Setting is overridden by alert template:
{{ props.row.alert_template.name }}
</q-tooltip>
</q-checkbox>
<q-checkbox
v-else
dense
@update:model-value="
editTask(props.row, {
dashboard_alert: !props.row.dashboard_alert,
})
"
v-model="props.row.dashboard_alert"
:disable="!!props.row.policy"
/>
</q-td>
<!-- policy check icon -->
<q-td>
<q-icon
v-if="props.row.policy"
style="font-size: 1.3rem"
name="policy"
>
<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="Object.keys(props.row.task_result).length === 0"></q-td>
<q-td v-else-if="props.row.task_result.status === 'passing'">
<q-icon
style="font-size: 1.3rem"
:color="dashPositiveColor"
name="check_circle"
>
<q-tooltip>Passing</q-tooltip>
</q-icon>
</q-td>
<q-td v-else-if="props.row.task_result.status === 'failing'">
<q-icon
v-if="props.row.alert_severity === 'info'"
style="font-size: 1.3rem"
:color="dashInfoColor"
name="info"
>
<q-tooltip>Informational</q-tooltip>
</q-icon>
<q-icon
v-else-if="props.row.alert_severity === 'warning'"
style="font-size: 1.3rem"
:color="dashWarningColor"
name="warning"
>
<q-tooltip>Warning</q-tooltip>
</q-icon>
<q-icon
v-else
style="font-size: 1.3rem"
:color="dashNegativeColor"
name="error"
>
<q-tooltip>Error</q-tooltip>
</q-icon>
</q-td>
<q-td v-else></q-td>
<!-- name -->
<q-td>{{ props.row.name }}</q-td>
<!-- sync status -->
<q-td v-if="props.row.task_result.sync_status === 'notsynced'"
>Will sync on next agent checkin</q-td
>
<q-td v-else-if="props.row.task_result.sync_status === 'synced'"
>Synced with agent</q-td
>
<q-td
v-else-if="props.row.task_result.sync_status === 'pendingdeletion'"
>Pending deletion on agent</q-td
>
<q-td v-else>Waiting for task creation on agent</q-td>
<q-td
v-if="
props.row.task_result.retcode !== null ||
props.row.task_result.stdout ||
props.row.task_result.stderr
"
>
<span
style="cursor: pointer; text-decoration: underline"
class="text-primary"
@click="showScriptOutput(props.row)"
>output</span
>
</q-td>
<q-td v-else>Awaiting output</q-td>
<q-td v-if="props.row.task_result.last_run">{{
formatDate(props.row.task_result.last_run)
}}</q-td>
<q-td v-else>Has not run yet</q-td>
<q-td>{{ props.row.schedule }}</q-td>
<q-td>
<span v-if="props.row.check_name">
{{ truncateText(props.row.check_name, 40) }}
<q-tooltip v-if="props.row.check_name.length > 40">{{
props.row.check_name
}}</q-tooltip>
</span>
</q-td>
</q-tr>
</template>
</q-table>
</div>
</template>
<script setup>
// composition imports
import { ref, computed, watch, onMounted } from "vue";
import { useStore } from "vuex";
import { useQuasar } from "quasar";
import { updateTask, removeTask, runTask } from "@/api/tasks";
import { fetchAgentTasks } from "@/api/agents";
import { notifySuccess, notifyError } from "@/utils/notify";
import { truncateText } from "@/utils/format";
// ui imports
import AutomatedTaskForm from "@/components/tasks/AutomatedTaskForm.vue";
import ScriptOutput from "@/components/checks/ScriptOutput.vue";
// static data
const columns = [
{ name: "enabled", align: "left", field: "enabled" },
{ name: "smsalert", field: "text_alert", align: "left" },
{ 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", 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,
},
];
// setup vuex
const store = useStore();
const selectedAgent = computed(() => store.state.selectedRow);
const tabHeight = computed(() => store.state.tabHeight);
const agentPlatform = computed(() => store.state.agentPlatform);
const dashWarningColor = computed(() => store.state.dash_warning_color);
const dashNegativeColor = computed(() => store.state.dash_negative_color);
const dashPositiveColor = computed(() => store.state.dash_positive_color);
const dashInfoColor = computed(() => store.state.dash_info_color);
const formatDate = computed(() => store.getters.formatDate);
// setup quasar
const $q = useQuasar();
// automated tasks logic
const tasks = ref([]);
const loading = ref(false);
const pagination = ref({
rowsPerPage: 0,
sortBy: "name",
descending: false,
});
async function getTasks() {
loading.value = true;
try {
const result = await fetchAgentTasks(selectedAgent.value);
tasks.value = result.filter(
(task) => task.sync_status !== "pendingdeletion",
);
} catch (e) {
console.error(e);
}
loading.value = false;
}
async function editTask(task, data) {
if (task.policy) return;
loading.value = true;
try {
const result = await updateTask(task.id, data);
notifySuccess(result);
getTasks();
} catch (e) {
console.error(e);
}
loading.value = false;
}
function deleteTask(task) {
if (task.policy) return;
$q.dialog({
title: "Are you sure?",
message: `Delete ${task.name} task`,
cancel: true,
persistent: true,
}).onOk(async () => {
loading.value = true;
try {
const result = await removeTask(task.id);
notifySuccess(result);
getTasks();
} catch (e) {
console.error(e);
}
loading.value = false;
});
}
async function runWinTask(task) {
if (!task.enabled) {
notifyError("Task cannot be run when it's disabled. Enable it first.");
return;
}
loading.value = true;
try {
const result = await runTask(
task.id,
task.policy ? { agent_id: selectedAgent.value } : {},
);
notifySuccess(result);
} catch (e) {
console.error(e);
}
loading.value = false;
}
function showAddTask() {
$q.dialog({
component: AutomatedTaskForm,
componentProps: {
type: "agent",
parent: selectedAgent.value,
plat: agentPlatform.value,
},
}).onOk(() => {
getTasks();
});
}
function showEditTask(task) {
if (task.policy) return;
$q.dialog({
component: AutomatedTaskForm,
componentProps: {
task: task,
type: "agent",
parent: selectedAgent.value,
plat: agentPlatform.value,
},
}).onOk(() => {
getTasks();
});
}
function showScriptOutput(script) {
$q.dialog({
component: ScriptOutput,
componentProps: {
scriptInfo: script.task_result,
},
});
}
watch(selectedAgent, (newValue) => {
if (newValue) {
getTasks();
}
});
onMounted(() => {
if (selectedAgent.value) getTasks();
});
</script>