allowed dismissing persistent modals on Esc press. allow filtering on certain scripts and agent dropdowns. moved other dropdowns to tactical dropdown. Fixes with bulk actions

This commit is contained in:
sadnub
2021-07-31 11:56:47 -04:00
parent 1de1fe69ef
commit 7147877769
10 changed files with 173 additions and 259 deletions

View File

@@ -407,7 +407,7 @@
</q-dialog>
</div>
<!-- send command modal -->
<q-dialog v-model="showSendCommand" persistent>
<q-dialog v-model="showSendCommand" persistent @keydown.esc="showSendCommand = false">
<SendCommand @close="showSendCommand = false" :pk="selectedAgentPk" />
</q-dialog>
<!-- agent recovery modal -->

View File

@@ -10,46 +10,34 @@
</q-bar>
<q-form @submit="submit">
<q-card-section v-if="options.length > 0">
<q-select
<tactical-dropdown
v-if="type === 'client' || type === 'site'"
class="q-mb-md"
v-model="selectedServerPolicy"
:options="options"
outlined
dense
options-dense
clearable
map-options
emit-value
label="Server Policy"
>
</q-select>
<q-select
outlined
clearable
mapOptions
/>
<tactical-dropdown
v-if="type === 'client' || type === 'site'"
v-model="selectedWorkstationPolicy"
:options="options"
outlined
options-dense
dense
clearable
map-options
emit-value
label="Workstation Policy"
>
</q-select>
<q-select
outlined
clearable
mapOptions
/>
<tactical-dropdown
v-if="type === 'agent'"
v-model="selectedAgentPolicy"
:options="options"
outlined
options-dense
dense
clearable
map-options
emit-value
label="Policy"
>
</q-select>
outlined
clearable
mapOptions
/>
<q-checkbox label="Block policy inheritance" v-model="blockInheritance">
<q-tooltip>This {{ type }} will not inherit from higher policies</q-tooltip>
@@ -69,9 +57,11 @@
<script>
import mixins from "@/mixins/mixins";
import TacticalDropdown from "@/components/ui/TacticalDropdown";
export default {
name: "PolicyAdd",
components: { TacticalDropdown },
emits: ["hide", "ok", "cancel"],
props: {
object: !Object,

View File

@@ -10,68 +10,34 @@
</q-bar>
<q-form ref="form" @submit.prevent="onSubmit">
<q-card-section>
<q-select
label="Excluded Clients"
dense
options-dense
outlined
multiple
<tactical-dropdown
v-model="localPolicy.excluded_clients"
:options="clientOptions"
use-chips
map-options
emit-value
label="Excluded Clients"
outlined
multiple
mapOptions
/>
</q-card-section>
<q-card-section>
<q-select
label="Excluded Sites"
dense
options-dense
outlined
multiple
<tactical-dropdown
v-model="localPolicy.excluded_sites"
:options="siteOptions"
use-chips
map-options
emit-value
>
<template v-slot:option="scope">
<q-item v-if="!scope.opt.category" v-bind="scope.itemProps" class="q-pl-lg">
<q-item-section>
<q-item-label v-html="scope.opt.label"></q-item-label>
</q-item-section>
</q-item>
<q-item-label v-if="scope.opt.category" v-bind="scope.itemProps" header class="q-pa-sm">{{
scope.opt.category
}}</q-item-label>
</template>
</q-select>
</q-card-section>
<q-card-section>
<q-select
label="Excluded Agents"
dense
options-dense
label="Excluded Sites"
outlined
multiple
mapOptions
/>
</q-card-section>
<q-card-section>
<tactical-dropdown
v-model="localPolicy.excluded_agents"
:options="agentOptions"
use-chips
map-options
emit-value
>
<template v-slot:option="scope">
<q-item v-if="!scope.opt.category" v-bind="scope.itemProps" class="q-pl-lg">
<q-item-section>
<q-item-label v-html="scope.opt.label"></q-item-label>
</q-item-section>
</q-item>
<q-item-label v-if="scope.opt.category" v-bind="scope.itemProps" header class="q-pa-sm">{{
scope.opt.category
}}</q-item-label>
</template>
</q-select>
label="Excluded Agents"
outlined
multiple
mapOptions
/>
</q-card-section>
<q-card-actions align="right">
@@ -84,9 +50,11 @@
</template>
<script>
import TacticalDropdown from "@/components/ui/TacticalDropdown";
import mixins from "@/mixins/mixins";
export default {
name: "PolicyExclusions",
components: { TacticalDropdown },
emits: ["hide", "ok", "cancel"],
props: { policy: !Object },
mixins: [mixins],

View File

@@ -9,20 +9,23 @@
</q-btn>
</q-bar>
<q-form @submit.prevent="submit">
<q-card-section>
<p>Agent Type</p>
<q-option-group v-model="monType" :options="monTypeOptions" color="primary" dense inline class="q-pl-sm" />
</q-card-section>
<q-card-section>
<p>Choose Target</p>
<q-option-group v-model="target" :options="targetOptions" color="primary" dense inline class="q-pl-sm" />
<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="target === 'client'"
v-if="state.target === 'client'"
:rules="[val => !!val || '*Required']"
v-model="client"
v-model="state.client"
:options="clientOptions"
label="Select Client"
outlined
@@ -30,9 +33,9 @@
filterable
/>
<tactical-dropdown
v-else-if="target === 'site'"
v-else-if="state.target === 'site'"
:rules="[val => !!val || '*Required']"
v-model="site"
v-model="state.site"
:options="siteOptions"
label="Select Site"
outlined
@@ -40,8 +43,9 @@
filterable
/>
<tactical-dropdown
v-else-if="target === 'agents'"
v-model="agents"
v-else-if="state.target === 'agents'"
:rules="[val => !!val || '*Required']"
v-model="state.agents"
:options="agentOptions"
label="Select Agents"
filled
@@ -51,10 +55,22 @@
/>
</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="script"
v-model="state.script"
:options="scriptOptions"
label="Select Script"
outlined
@@ -63,13 +79,11 @@
/>
</q-card-section>
<q-card-section v-if="mode === 'script'" class="q-pt-none">
<q-select
<tactical-dropdown
v-model="state.args"
label="Script Arguments (press Enter after typing each argument)"
filled
dense
v-model="args"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
@@ -79,11 +93,11 @@
<q-card-section v-if="mode === 'command'">
<p>Shell</p>
<q-option-group v-model="shell" :options="shellOptions" color="primary" dense inline class="q-pl-sm" />
<q-option-group v-model="state.shell" :options="shellOptions" color="primary" dense inline class="q-pl-sm" />
</q-card-section>
<q-card-section v-if="mode === 'command'">
<q-input
v-model="cmd"
v-model="state.cmd"
outlined
label="Command"
stack-label
@@ -98,7 +112,7 @@
<q-card-section v-if="mode === 'script' || mode === 'command'">
<q-input
v-model.number="timeout"
v-model.number="state.timeout"
dense
outlined
type="number"
@@ -112,7 +126,7 @@
<q-card-section v-if="mode === 'patch'">
<p>Action</p>
<q-option-group
v-model="patchMode"
v-model="state.patchMode"
:options="patchModeOptions"
color="primary"
dense
@@ -122,7 +136,7 @@
</q-card-section>
<q-card-section v-show="false">
<q-checkbox v-model="offlineAgents" label="Offline Agents (Run on next checkin)">
<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>
@@ -196,37 +210,37 @@ export default {
const { client, clientOptions, getClientOptions } = useClientDropdown();
// bulk action logic
const target = ref("client");
const monType = ref("all");
const cmd = ref("");
const shell = ref("cmd");
const patchMode = ref("scan");
const offlineAgents = ref(false);
const state = ref({
mode: props.mode,
target: "client",
monType: "all",
cmd: "",
shell: "cmd",
patchMode: "scan",
offlineAgents: false,
client,
site,
agents,
script,
timeout: defaultTimeout,
args: defaultArgs,
});
const loading = ref(false);
watch(target, () => (agents.value = []));
watch(
() => state.value.target,
(newValue, oldValue) => {
client.value = null;
site.value = null;
agents.value = [];
}
);
async function submit() {
loading.value = true;
const payload = {
mode: props.mode,
monType: monType.value,
target: target.value,
site: site.value,
client: client.value,
agents: agents.value,
script: script.value,
timeout: defaultTimeout.value,
args: defaultArgs.value,
shell: shell.value,
cmd: cmd.value,
patchMode: patchMode.value,
offlineAgents: offlineAgents.value,
};
try {
const data = await runBulkAction(payload);
const data = await runBulkAction(state.value);
notifySuccess(data);
} catch (e) {}
@@ -249,27 +263,16 @@ export default {
getAgentOptions();
getSiteOptions();
getClientOptions();
if (props.mode === "script") getScriptOptions(showCommunityScripts);
if (props.mode === "script") getScriptOptions(showCommunityScripts.value);
});
return {
// reactive data
target,
monType,
client,
site,
agents,
cmd,
shell,
patchMode,
script,
scriptOptions,
timeout: defaultTimeout.value,
args: defaultArgs.value,
state,
agentOptions,
clientOptions,
siteOptions,
offlineAgents,
scriptOptions,
loading,
// non-reactive data

View File

@@ -21,27 +21,7 @@
<q-card-section class="row">
<div class="col-2">Site:</div>
<div class="col-2"></div>
<q-select
dense
options-dense
outlined
v-model="agent.site"
:options="siteOptions"
map-options
emit-value
class="col-8"
>
<template v-slot:option="scope">
<q-item v-if="!scope.opt.category" v-bind="scope.itemProps" class="q-pl-lg">
<q-item-section>
<q-item-label v-html="scope.opt.label"></q-item-label>
</q-item-section>
</q-item>
<q-item-label v-if="scope.opt.category" v-bind="scope.itemProps" header class="q-pa-sm">{{
scope.opt.category
}}</q-item-label>
</template>
</q-select>
<tactical-dropdown class="col-8" v-model="agent.site" :options="siteOptions" outlined mapOptions />
</q-card-section>
<q-card-section class="row">
<div class="col-2">Type:</div>
@@ -149,11 +129,12 @@ import { mapGetters } from "vuex";
import mixins from "@/mixins/mixins";
import PatchPolicyForm from "@/components/modals/agents/PatchPolicyForm";
import CustomField from "@/components/ui/CustomField";
import TacticalDropdown from "@/components/ui/TacticalDropdown";
export default {
name: "EditAgent",
emits: ["edit", "close"],
components: { PatchPolicyForm, CustomField },
components: { PatchPolicyForm, CustomField, TacticalDropdown },
mixins: [mixins],
data() {
return {

View File

@@ -1,5 +1,5 @@
<template>
<q-dialog ref="dialogRef" @hide="onDialogHide" :maximized="maximized">
<q-dialog ref="dialogRef" @hide="onDialogHide" persistent @keydown.esc="onDialogHide" :maximized="maximized">
<q-card class="dialog-plugin" style="min-width: 50vw">
<q-bar>
Run a script on {{ agent.hostname }}
@@ -18,20 +18,20 @@
<q-card-section>
<tactical-dropdown
:rules="[val => !!val || '*Required']"
outlined
v-model="scriptPK"
v-model="state.script"
:options="scriptOptions"
label="Select script"
outlined
mapOptions
filterable
/>
</q-card-section>
<q-card-section>
<q-select
<tactical-dropdown
v-model="state.args"
label="Script Arguments (press Enter after typing each argument)"
filled
v-model="args"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
@@ -39,55 +39,40 @@
/>
</q-card-section>
<q-card-section>
<q-option-group v-model="state.output" :options="outputOptions" color="primary" inline dense />
</q-card-section>
<q-card-section v-if="state.output === 'email'">
<div class="q-gutter-sm">
<q-radio dense v-model="output" val="wait" label="Wait for Output" />
<q-radio dense v-model="output" val="forget" label="Fire and Forget" />
<q-radio dense v-model="output" val="email" label="Email results" />
<q-radio dense v-model="output" val="collector" label="Save results to Custom Field" />
<q-radio dense v-model="output" val="note" label="Save results to Agent Notes" />
<q-radio dense v-model="state.emailMode" val="default" label="Use email addresses from global settings" />
<q-radio dense v-model="state.emailMode" val="custom" label="Custom emails" />
</div>
</q-card-section>
<q-card-section v-if="output === 'email'">
<div class="q-gutter-sm">
<q-radio
dense
v-model="emailmode"
val="default"
label="Use email addresses from global settings"
@update:model-value="emails = []"
/>
<q-radio dense v-model="emailmode" val="custom" label="Custom emails" />
</div>
</q-card-section>
<q-card-section v-if="emailmode === 'custom' && output === 'email'">
<q-select
<q-card-section v-if="state.emailMode === 'custom' && state.output === 'email'">
<tactical-dropdown
v-model="state.emails"
label="Email recipients (press Enter after typing each email)"
filled
v-model="emails"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
/>
</q-card-section>
<q-card-section v-if="output === 'collector'">
<q-card-section v-if="state.output === 'collector'">
<tactical-dropdown
:rules="[val => !!val || '*Required']"
dense
outlined
v-model="custom_field"
v-model="state.custom_field"
:options="customFieldOptions"
label="Select custom field"
mapOptions
options-dense
/>
<q-checkbox v-model="save_all_output" label="Save all output" />
<q-checkbox v-model="state.save_all_output" label="Save all output" />
</q-card-section>
<q-card-section>
<q-input
v-model.number="timeout"
v-model.number="state.timeout"
dense
outlined
type="number"
@@ -97,8 +82,9 @@
:rules="[val => !!val || '*Required', val => val >= 5 || 'Minimum is 5 seconds']"
/>
</q-card-section>
<q-card-actions align="center">
<q-btn :loading="loading" label="Run" color="primary" class="full-width" type="submit" />
<q-card-actions align="right">
<q-btn label="Cancel" v-close-popup />
<q-btn :loading="loading" :disabled="loading" label="Run" color="primary" type="submit" />
</q-card-actions>
<q-card-section v-if="ret !== null" class="q-pl-md q-pr-md q-pt-none q-ma-none scroll" style="max-height: 50vh">
<pre>{{ ret }}</pre>
@@ -112,14 +98,24 @@
// composition imports
import { ref, watch, computed, onMounted } from "vue";
import { useStore } from "vuex";
import { useDialogPluginComponent, useQuasar } from "quasar";
import { useDialogPluginComponent } from "quasar";
import { useScriptDropdown } from "@/composables/scripts";
import { useCustomFieldDropdown } from "@/composables/core";
import { runScript } from "@/api/agents";
import { notifySuccess } from "@/utils/notify";
//ui imports
import TacticalDropdown from "@/components/ui/TacticalDropdown";
// static data
const outputOptions = [
{ label: "Wait for Output", value: "wait" },
{ label: "Fire and Forget", value: "forget" },
{ label: "Email results", value: "email" },
{ label: "Save results to Custom Field", value: "collector" },
{ label: "Save results to Agent Notes", value: "note" },
];
export default {
name: "RunScript",
emits: [...useDialogPluginComponent.emits],
@@ -128,57 +124,48 @@ export default {
agent: !Object,
},
setup(props) {
const $q = useQuasar();
// setup vuex store
const { state } = useStore();
const showCommunityScripts = computed(() => state.showCommunityScripts);
const store = useStore();
const showCommunityScripts = computed(() => store.state.showCommunityScripts);
// setup quasar dialog plugin
const { dialogRef, onDialogHide } = useDialogPluginComponent();
// setup dropdowns
const { scriptPK, scriptOptions, defaultTimeout, defaultArgs, getScriptOptions } = useScriptDropdown();
const { script, scriptOptions, defaultTimeout, defaultArgs, getScriptOptions } = useScriptDropdown();
const { customFieldOptions, getCustomFieldOptions } = useCustomFieldDropdown();
// main run script functionaity
const loading = ref(false);
const output = ref("wait");
const state = ref({
pk: props.agent.id,
output: "wait",
email: [],
emailMode: "default",
custom_field: null,
save_all_output: false,
script,
args: defaultArgs,
timeout: defaultTimeout,
});
const ret = ref(null);
const emails = ref([]);
const emailmode = ref("default");
const custom_field = ref(null);
const save_all_output = ref(false);
const loading = ref(false);
const maximized = ref(false);
async function sendScript() {
ret.value = null;
loading.value = true;
const data = {
pk: props.agent.id,
timeout: defaultTimeout.value,
scriptPK: scriptPK.value,
output: output.value,
args: defaultArgs.value,
emails: emails.value,
emailmode: emailmode.value,
custom_field: custom_field.value,
save_all_output: save_all_output.value,
};
ret.value = await runScript(data);
ret.value = await runScript(state.value);
loading.value = false;
if (output.value === "forget") {
if (state.value.output === "forget") {
onDialogHide();
$q.notify({
message: ret.value,
color: "positive",
});
notifySuccess(ret.value);
}
}
// watchers
watch(output, () => (emails.value = []));
watch([() => state.value.output, () => state.value.emailMode], () => (state.value.emails = []));
// vue component hooks
onMounted(() => {
@@ -188,19 +175,15 @@ export default {
return {
// reactive data
state,
loading,
scriptPK,
scriptOptions,
timeout: defaultTimeout,
output,
ret,
args: defaultArgs,
emails,
emailmode,
maximized,
customFieldOptions,
custom_field,
save_all_output,
// non-reactive data
outputOptions,
//methods
sendScript,

View File

@@ -1,5 +1,5 @@
<template>
<q-dialog ref="dialogRef" @hide="onDialogHide" persistent :maximized="maximized">
<q-dialog ref="dialogRef" @hide="onDialogHide" persistent @keydown.esc="onDialogHide" :maximized="maximized">
<q-card class="q-dialog-plugin" :style="maximized ? '' : 'width: 70vw; max-width: 90vw'">
<q-bar>
{{ title }}
@@ -79,15 +79,16 @@
</q-card-section>
<div class="q-px-sm q-pt-none q-pb-sm q-mt-none row">
<tactical-dropdown
label="Script Arguments (press Enter after typing each argument)"
filled
class="col-12"
v-model="formScript.args"
label="Script Arguments (press Enter after typing each argument)"
class="col-12"
filled
use-input
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
:readonly="readonly"
/>
</div>

View File

@@ -19,11 +19,11 @@
<q-card-section>
<tactical-dropdown
v-model="script.category"
:options="categories"
label="Category"
hint="Press Enter or Tab when adding a new value"
outlined
v-model="script.category"
:options="categories"
filterable
clearable
new-value-mode="add-unique"
@@ -47,28 +47,17 @@
</q-card-section>
<q-card-section>
<q-select
label="Type"
dense
options-dense
outlined
v-model="script.shell"
:options="shellOptions"
emit-value
map-options
/>
<tactical-dropdown v-model="script.shell" :options="shellOptions" label="Type" outlined mapOptions />
</q-card-section>
<q-card-section>
<q-select
<tactical-dropdown
v-model="script.args"
label="Script Arguments"
placeholder="(press Enter after typing each argument)"
filled
v-model="script.args"
use-input
use-chips
multiple
dense
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"

View File

@@ -21,17 +21,15 @@
/>
</q-card-section>
<q-card-section>
<q-select
<tactical-dropdown
v-model="args"
label="Script Arguments (press Enter after typing each argument)"
filled
v-model="args"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add"
dense
/>
</q-card-section>
<q-card-section>
@@ -47,6 +45,7 @@
/>
</q-card-section>
<q-card-actions align="right">
<q-btn label="Cancel" v-close-popup />
<q-btn :loading="loading" label="Run" color="primary" type="submit" />
</q-card-actions>
<q-card-section v-if="ret" class="q-pl-md q-pr-md q-pt-none q-ma-none scroll" style="max-height: 50vh">

View File

@@ -17,7 +17,7 @@
v-if="!scope.opt.category"
v-bind="scope.itemProps"
class="q-pl-lg"
:key="mapOptions ? scope.opt.value : null"
:key="mapOptions ? scope.opt.value : scope.opt"
>
<q-item-section>
<q-item-label v-html="mapOptions ? scope.opt.label : scope.opt"></q-item-label>
@@ -61,8 +61,8 @@ export default {
});
return {
filterFn,
filteredOptions,
filterFn,
filterEvent,
};
},