Files
tacticalrmm-web/src/components/scripts/ScriptFormModal.vue
2022-07-31 22:02:50 +00:00

381 lines
11 KiB
Vue

<template>
<q-dialog
ref="dialogRef"
@hide="onDialogHide"
persistent
@keydown.esc="onDialogHide"
:maximized="maximized"
>
<q-card
class="q-dialog-plugin"
:style="maximized ? '' : 'width: 90vw; max-width: 90vw'"
>
<q-bar>
{{ title }}
<q-space />
<q-btn
dense
flat
icon="minimize"
@click="maximized = false"
:disable="!maximized"
>
<q-tooltip v-if="maximized" class="bg-white text-primary"
>Minimize</q-tooltip
>
</q-btn>
<q-btn
dense
flat
icon="crop_square"
@click="maximized = true"
:disable="maximized"
>
<q-tooltip v-if="!maximized" class="bg-white text-primary"
>Maximize</q-tooltip
>
</q-btn>
<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="submitForm">
<q-banner
v-if="missingShebang"
dense
inline-actions
class="text-black bg-warning"
>
<template v-slot:avatar>
<q-icon
class="text-center"
name="warning"
color="black"
/> </template
>Shell/Python scripts on Linux/Mac need a shebang at the top of the
script e.g. <code>#!/bin/bash</code> or <code>#!/usr/bin/python3</code
><br />Add one to get rid of this warning. Ignore if windows.
</q-banner>
<div class="row q-pa-sm">
<div class="col-4 q-gutter-sm q-pr-sm">
<q-input
filled
dense
:readonly="readonly"
v-model="formScript.name"
label="Name"
:rules="[(val) => !!val || '*Required']"
hide-bottom-space
/>
<q-input
filled
dense
:readonly="readonly"
v-model="formScript.description"
label="Description"
/>
<q-select
:readonly="readonly"
options-dense
filled
dense
v-model="formScript.shell"
:options="shellOptions"
emit-value
map-options
label="Shell Type"
/>
<tactical-dropdown
v-model="formScript.supported_platforms"
:options="agentPlatformOptions"
label="Supported Platforms (All supported if blank)"
clearable
mapOptions
filled
multiple
:readonly="readonly"
/>
<tactical-dropdown
filled
v-model="formScript.category"
:options="categories"
use-input
clearable
new-value-mode="add-unique"
filterable
label="Category"
:readonly="readonly"
hide-bottom-space
/>
<tactical-dropdown
v-model="formScript.args"
label="Script Arguments (press Enter after typing each argument)"
filled
use-input
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
:readonly="readonly"
/>
<q-input
type="number"
filled
dense
:readonly="readonly"
v-model.number="formScript.default_timeout"
label="Timeout (seconds)"
:rules="[(val) => val >= 5 || 'Minimum is 5']"
hide-bottom-space
/>
<q-checkbox
v-model="formScript.run_as_user"
label="Run As User (Windows only)"
>
<q-tooltip
>Setting this value on the script model will always override any
'Run As User' checkboxes in the UI and force this script to
always be run in the context of the logged in user. If no user
is logged in, the script will not run and an error will be
returned. Not supported on Windows Server.
</q-tooltip>
</q-checkbox>
<q-input
label="Syntax"
type="textarea"
style="height: 150px; overflow-y: auto; resize: none"
v-model="formScript.syntax"
dense
filled
:readonly="readonly"
/>
</div>
<v-ace-editor
v-model:value="formScript.script_body"
class="col-8"
:lang="lang"
:theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'"
:style="{ height: `${maximized ? '87vh' : '64vh'}` }"
wrap
:printMargin="false"
:options="{ fontSize: '14px' }"
/>
</div>
<q-card-actions>
<tactical-dropdown
style="width: 350px"
dense
:loading="agentLoading"
filled
v-model="agent"
:options="agentOptions"
label="Agent to run test script on"
mapOptions
filterable
>
<template v-slot:after>
<q-btn
size="md"
color="primary"
dense
flat
label="Test Script"
:disable="
!agent ||
!formScript.script_body ||
!formScript.default_timeout
"
@click="openTestScriptModal"
/>
</template>
</tactical-dropdown>
<q-space />
<q-btn dense flat label="Cancel" v-close-popup />
<q-btn
v-if="!readonly"
:loading="loading"
dense
flat
label="Save"
color="primary"
type="submit"
/>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>
<script>
// composable imports
import { ref, computed, onMounted } from "vue";
import { useQuasar, useDialogPluginComponent } from "quasar";
import { saveScript, editScript, downloadScript } from "@/api/scripts";
import { useAgentDropdown, agentPlatformOptions } from "@/composables/agents";
import { notifySuccess } from "@/utils/notify";
// ui imports
import TestScriptModal from "@/components/scripts/TestScriptModal.vue";
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
import { VAceEditor } from "vue3-ace-editor";
// imports for ace editor
import "ace-builds/src-noconflict/mode-powershell";
import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/mode-batchfile";
import "ace-builds/src-noconflict/mode-sh";
import "ace-builds/src-noconflict/theme-tomorrow_night_eighties";
import "ace-builds/src-noconflict/theme-tomorrow";
// static data
import { shellOptions } from "@/composables/scripts";
export default {
name: "ScriptFormModal",
emits: [...useDialogPluginComponent.emits],
components: {
TacticalDropdown,
VAceEditor,
},
props: {
script: Object,
categories: !Array,
readonly: {
type: Boolean,
default: false,
},
clone: {
type: Boolean,
default: false,
},
},
setup(props) {
// setup quasar plugins
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
const $q = useQuasar();
// setup agent dropdown
const { agent, agentOptions, getAgentOptions } = useAgentDropdown();
// script form logic
const script = props.script
? ref(Object.assign({}, { ...props.script, script_body: "" }))
: ref({
shell: "powershell",
default_timeout: 90,
args: [],
script_body: "",
run_as_user: false,
});
if (props.clone) script.value.name = `(Copy) ${script.value.name}`;
const maximized = ref(false);
const loading = ref(false);
const agentLoading = ref(false);
const missingShebang = computed(() => {
if (script.value.shell === "shell" || script.value.shell === "python") {
return !script.value.script_body.includes("#!");
} else {
return false;
}
});
const title = computed(() => {
if (props.script) {
return props.readonly
? `Viewing ${script.value.name}`
: props.clone
? `Copying ${script.value.name}`
: `Editing ${script.value.name}`;
} else {
return "Adding new script";
}
});
// convert highlighter language to match what ace expects
const lang = computed(() => {
if (script.value.shell === "cmd") return "batchfile";
else if (script.value.shell === "powershell") return "powershell";
else if (script.value.shell === "python") return "python";
else if (script.value.shell === "shell") return "sh";
else return "";
});
// get code if editing or cloning script
if (props.script)
downloadScript(script.value.id, { with_snippets: props.readonly }).then(
(r) => {
script.value.script_body = r.code;
}
);
async function submitForm() {
loading.value = true;
let result = "";
try {
// edit existing script
if (props.script && !props.clone) {
result = await editScript(script.value);
// add or save cloned script
} else {
result = await saveScript(script.value);
}
onDialogOK();
notifySuccess(result);
} catch (e) {
console.error(e);
}
loading.value = false;
}
function openTestScriptModal() {
$q.dialog({
component: TestScriptModal,
componentProps: {
script: { ...script.value },
agent: agent.value,
},
});
}
// component life cycle hooks
onMounted(async () => {
agentLoading.value = true;
await getAgentOptions();
agentLoading.value = false;
});
return {
// reactive data
formScript: script.value,
maximized,
loading,
agentOptions,
agent,
agentLoading,
lang,
missingShebang,
// non-reactive data
shellOptions,
agentPlatformOptions,
//computed
title,
//methods
submitForm,
openTestScriptModal,
// quasar dialog plugin
dialogRef,
onDialogHide,
};
},
};
</script>