Files
tacticalrmm-web/src/components/modals/agents/BulkAction.vue
2022-12-01 00:44:56 +00:00

438 lines
12 KiB
Vue

<template>
<q-dialog ref="dialogRef" @hide="onDialogHide">
<q-card class="q-dialog-plugin" style="min-width: 50vw">
<q-bar>
{{ modalTitle }}
<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.prevent="submit">
<q-card-section>
<p>Choose Target</p>
<q-option-group
v-model="state.target"
:options="targetOptions"
color="primary"
dense
inline
class="q-pl-sm"
/>
</q-card-section>
<q-card-section>
<tactical-dropdown
v-if="state.target === 'client'"
:rules="[(val) => !!val || '*Required']"
v-model="state.client"
:options="clientOptions"
label="Select Client"
outlined
mapOptions
filterable
/>
<tactical-dropdown
v-else-if="state.target === 'site'"
:rules="[(val) => !!val || '*Required']"
v-model="state.site"
:options="siteOptions"
label="Select Site"
outlined
mapOptions
filterable
/>
<tactical-dropdown
v-else-if="state.target === 'agents'"
:rules="[(val) => !!val || '*Required']"
v-model="state.agents"
:options="agentOptions"
label="Select Agents"
filled
multiple
mapOptions
filterable
/>
</q-card-section>
<q-card-section>
<p>Agent OS</p>
<q-option-group
v-model="state.osType"
:options="filteredOsTypeOptions"
color="primary"
dense
inline
class="q-pl-sm"
/>
</q-card-section>
<q-card-section v-show="state.target !== 'agents'">
<p>Agent Type</p>
<q-option-group
v-model="state.monType"
:options="monTypeOptions"
color="primary"
dense
inline
class="q-pl-sm"
/>
</q-card-section>
<q-card-section v-if="mode === 'script'" class="q-pt-none">
<tactical-dropdown
:rules="[(val) => !!val || '*Required']"
v-model="state.script"
:options="filteredScriptOptions"
label="Select Script"
outlined
mapOptions
filterable
/>
</q-card-section>
<q-card-section v-if="mode === 'script'" class="q-pt-none">
<tactical-dropdown
v-model="state.args"
label="Script Arguments (press Enter after typing each argument)"
filled
use-input
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
/>
</q-card-section>
<q-card-section v-if="mode === 'script'" class="q-pt-none">
<tactical-dropdown
v-model="state.env_vars"
:label="envVarsLabel"
filled
use-input
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
/>
</q-card-section>
<q-card-section v-if="mode === 'command'">
<p>Shell</p>
<q-option-group
v-model="state.shell"
:options="shellOptions"
color="primary"
dense
inline
class="q-pl-sm"
@update:model-value="state.custom_shell = null"
/>
</q-card-section>
<q-card-section v-if="state.shell === 'custom'">
<q-input
v-model="state.custom_shell"
outlined
label="Custom shell"
stack-label
placeholder="/usr/bin/python3"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<q-card-section v-if="mode === 'command'">
<q-input
v-model="state.cmd"
outlined
label="Command"
stack-label
:placeholder="cmdPlaceholder(state.shell)"
:rules="[(val) => !!val || '*Required']"
/>
</q-card-section>
<q-card-section v-if="supportsRunAsUser()" class="q-pt-none">
<q-checkbox v-model="state.run_as_user" label="Run As User">
<q-tooltip>{{ runAsUserToolTip }}</q-tooltip>
</q-checkbox>
</q-card-section>
<q-card-section v-if="mode === 'script' || mode === 'command'">
<q-input
v-model.number="state.timeout"
dense
outlined
type="number"
style="max-width: 150px"
label="Timeout (seconds)"
stack-label
:rules="[
(val) => !!val || '*Required',
(val) => val >= 5 || 'Minimum is 5 seconds',
]"
/>
</q-card-section>
<q-card-section v-if="mode === 'patch'">
<p>Action</p>
<q-option-group
v-model="state.patchMode"
:options="patchModeOptions"
color="primary"
dense
inline
class="q-pl-sm"
/>
</q-card-section>
<q-card-section v-show="false">
<q-checkbox
v-model="state.offlineAgents"
label="Offline Agents (Run on next checkin)"
>
<q-tooltip
>If the agent is offline, a pending action will be created to run
on agent checkin</q-tooltip
>
</q-checkbox>
</q-card-section>
<q-card-actions align="right">
<q-btn label="Cancel" v-close-popup />
<q-btn
label="Run"
color="primary"
type="submit"
:disable="loading"
:loading="loading"
/>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>
<script>
// composition imports
import { ref, computed, watch, onMounted } from "vue";
import { useStore } from "vuex";
import { useDialogPluginComponent } from "quasar";
import { useScriptDropdown } from "@/composables/scripts";
import { useAgentDropdown } from "@/composables/agents";
import { useClientDropdown, useSiteDropdown } from "@/composables/clients";
import { runBulkAction } from "@/api/agents";
import { notifySuccess } from "@/utils/notify";
import { cmdPlaceholder } from "@/composables/agents";
import { removeExtraOptionCategories } from "@/utils/format";
import { envVarsLabel, runAsUserToolTip } from "@/constants/constants";
// ui imports
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
// static data
const monTypeOptions = [
{ label: "All", value: "all" },
{ label: "Servers", value: "servers" },
{ label: "Workstations", value: "workstations" },
];
const osTypeOptions = [
{ label: "Windows", value: "windows" },
{ label: "Linux", value: "linux" },
{ label: "macOS", value: "darwin" },
{ label: "All", value: "all" },
];
const targetOptions = [
{ label: "Client", value: "client" },
{ label: "Site", value: "site" },
{ label: "Selected Agents", value: "agents" },
{ label: "All", value: "all" },
];
const patchModeOptions = [
{ label: "Scan", value: "scan" },
{ label: "Install", value: "install" },
];
export default {
name: "BulkAction",
components: { TacticalDropdown },
emits: [...useDialogPluginComponent.emits],
props: {
mode: !String,
},
setup(props) {
// setup vuex store
const store = useStore();
const showCommunityScripts = computed(
() => store.state.showCommunityScripts
);
const shellOptions = computed(() => {
if (state.value.osType === "windows") {
return [
{ label: "CMD", value: "cmd" },
{ label: "Powershell", value: "powershell" },
];
} else {
return [
{ label: "Bash", value: "/bin/bash" },
{ label: "Custom", value: "custom" },
];
}
});
const filteredOsTypeOptions = computed(() => {
if (props.mode === "command")
return osTypeOptions.filter((i) => i.value !== "all");
else if (props.mode === "patch")
return osTypeOptions.filter((i) => i.value === "windows");
return osTypeOptions;
});
// quasar dialog setup
const { dialogRef, onDialogHide } = useDialogPluginComponent();
// dropdown setup
const {
script,
scriptOptions,
defaultTimeout,
defaultArgs,
defaultEnvVars,
getScriptOptions,
} = useScriptDropdown();
const { agents, agentOptions, getAgentOptions } = useAgentDropdown();
const { site, siteOptions, getSiteOptions } = useSiteDropdown();
const { client, clientOptions, getClientOptions } = useClientDropdown();
// bulk action logic
const state = ref({
mode: props.mode,
target: "client",
monType: "all",
osType: "windows",
cmd: "",
shell: "cmd",
custom_shell: null,
patchMode: "scan",
offlineAgents: false,
client,
site,
agents,
script,
timeout: defaultTimeout,
args: defaultArgs,
env_vars: defaultEnvVars,
run_as_user: false,
});
const loading = ref(false);
watch(
() => state.value.target,
() => {
client.value = null;
site.value = null;
agents.value = [];
}
);
watch(
() => state.value.osType,
(newValue) => {
state.value.custom_shell = null;
state.value.run_as_user = false;
if (newValue === "windows") {
state.value.shell = "cmd";
} else {
state.value.shell = "/bin/bash";
}
}
);
async function submit() {
loading.value = true;
try {
const data = await runBulkAction(state.value);
notifySuccess(data);
onDialogHide();
} catch (e) {}
loading.value = false;
}
const supportsRunAsUser = () => {
const modes = ["script", "command"];
return (
state.value.osType === "windows" && modes.includes(state.value.mode)
);
};
// set modal title and caption
const modalTitle = computed(() => {
return props.mode === "command"
? "Run Bulk Command"
: props.mode === "script"
? "Run Bulk Script"
: props.mode === "patch"
? "Bulk Patch Management"
: "";
});
const filteredScriptOptions = computed(() => {
if (props.mode !== "script") return [];
if (state.value.osType === "all") return scriptOptions.value;
return removeExtraOptionCategories(
scriptOptions.value.filter(
(script) =>
script.category ||
!script.supported_platforms ||
script.supported_platforms.length === 0 ||
script.supported_platforms.includes(state.value.osType)
)
);
});
// component lifecycle hooks
onMounted(() => {
getAgentOptions();
getSiteOptions();
getClientOptions();
if (props.mode === "script") getScriptOptions(showCommunityScripts.value);
});
return {
// reactive data
state,
agentOptions,
clientOptions,
siteOptions,
filteredScriptOptions,
loading,
shellOptions,
filteredOsTypeOptions,
// non-reactive data
monTypeOptions,
osTypeOptions,
targetOptions,
patchModeOptions,
runAsUserToolTip,
envVarsLabel,
//computed
modalTitle,
//methods
submit,
cmdPlaceholder,
supportsRunAsUser,
// quasar dialog plugin
dialogRef,
onDialogHide,
};
},
};
</script>