381 lines
11 KiB
Vue
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>
|