Files
tacticalrmm-web/src/components/scripts/ScriptFormModal.vue

275 lines
7.7 KiB
Vue

<template>
<q-dialog ref="dialogRef" @hide="onDialogHide" persistent @keydown.esc="onDialogHide" :maximized="maximized">
<q-card class="q-dialog-plugin" :style="maximized ? '' : 'width: 80vw; 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">
<div class="q-pt-sm q-px-sm row">
<q-input
filled
dense
class="col-2"
:readonly="readonly"
v-model="formScript.name"
label="Name"
:rules="[val => !!val || '*Required']"
/>
<q-select
class="q-pl-sm col-2"
:readonly="readonly"
options-dense
filled
dense
v-model="formScript.shell"
:options="shellOptions"
emit-value
map-options
label="Shell Type"
/>
<q-input
type="number"
class="q-pl-sm col-2"
filled
dense
:readonly="readonly"
v-model.number="formScript.default_timeout"
label="Timeout (seconds)"
:rules="[val => val >= 5 || 'Minimum is 5']"
/>
<tactical-dropdown
class="q-pl-sm col-3"
filled
v-model="formScript.category"
:options="categories"
use-input
clearable
new-value-mode="add-unique"
filterable
label="Category"
:readonly="readonly"
hide-bottom-space
/>
<q-input
class="q-pl-sm col-3"
filled
dense
:readonly="readonly"
v-model="formScript.description"
label="Description"
/>
<tactical-dropdown
v-model="formScript.args"
label="Script Arguments (press Enter after typing each argument)"
class="q-pb-sm col-12 row"
filled
use-input
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
:readonly="readonly"
/>
</div>
<v-ace-editor
v-model:value="code"
:lang="formScript.shell === 'cmd' ? 'batchfile' : formScript.shell"
:theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'"
:style="{ height: `${maximized ? '72vh' : '64vh'}` }"
wrap
:printMargin="false"
:options="{ fontSize: '14px' }"
/>
<q-card-actions>
<tactical-dropdown
style="width: 350px"
dense
filled
v-model="agent"
:options="agentOptions"
label="Agent to run test script on"
mapOptions
@filter="loadAgentOptions"
>
<template v-slot:after>
<q-btn
size="md"
color="primary"
dense
flat
label="Test Script"
:disable="!agent || !code || !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 } from "vue";
import { useQuasar, useDialogPluginComponent } from "quasar";
import { saveScript, editScript, downloadScript } from "@/api/scripts";
import { useAgentDropdown } from "@/composables/agents";
import { notifySuccess } from "@/utils/notify";
// ui imports
import TestScriptModal from "@/components/scripts/TestScriptModal";
import TacticalDropdown from "@/components/ui/TacticalDropdown";
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/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))
: ref({ shell: "powershell", default_timeout: 90, args: [] });
if (props.clone) script.value.name = `(Copy) ${script.value.name}`;
const code = ref("");
const maximized = ref(false);
const loading = ref(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";
}
});
// get code if editing or cloning script
if (props.script)
downloadScript(script.value.id, { with_snippets: props.readonly }).then(r => {
code.value = r.code;
});
async function submitForm() {
loading.value = true;
let result = "";
try {
// base64 encode the script text
script.value.code_base64 = btoa(code.value);
// 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) {}
loading.value = false;
}
function openTestScriptModal() {
$q.dialog({
component: TestScriptModal,
componentProps: {
script: { ...script.value, code: code.value },
agent: agent.value,
},
});
}
function loadAgentOptions(val, update, abort) {
if (agentOptions.value.length > 0) {
// already loaded
update();
return;
}
update(async () => {
await getAgentOptions();
});
}
return {
// reactive data
formScript: script.value,
code,
maximized,
loading,
agentOptions,
agent,
// non-reactive data
shellOptions,
//computed
getAgentOptions,
title,
//methods
loadAgentOptions,
submitForm,
openTestScriptModal,
// quasar dialog plugin
dialogRef,
onDialogHide,
};
},
};
</script>