212 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			212 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <q-dialog
 | |
|     ref="dialogRef"
 | |
|     @hide="onDialogHide"
 | |
|     persistent
 | |
|     @keydown.esc="onDialogHide"
 | |
|   >
 | |
|     <q-card
 | |
|       class="q-dialog-plugin"
 | |
|       :style="{ 'min-width': !ret ? '40vw' : '70vw' }"
 | |
|     >
 | |
|       <q-bar>
 | |
|         Send command on {{ agent.hostname }}
 | |
|         <q-space />
 | |
|         <q-chip v-if="!wsConnected" color="red" text-color="white" icon="error"
 | |
|           >Websocket diconnected!</q-chip
 | |
|         >
 | |
|         <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="submit">
 | |
|         <q-card-section>
 | |
|           <p>Shell</p>
 | |
|           <div class="q-gutter-sm">
 | |
|             <q-radio
 | |
|               v-if="agent.plat !== 'windows'"
 | |
|               dense
 | |
|               v-model="state.shell"
 | |
|               val="/bin/bash"
 | |
|               label="Bash"
 | |
|               @update:model-value="state.custom_shell = null"
 | |
|             />
 | |
|             <q-radio
 | |
|               v-if="agent.plat !== 'windows'"
 | |
|               dense
 | |
|               v-model="state.shell"
 | |
|               val="custom"
 | |
|               label="Custom"
 | |
|             />
 | |
|             <q-radio
 | |
|               v-if="agent.plat === 'windows'"
 | |
|               dense
 | |
|               v-model="state.shell"
 | |
|               val="cmd"
 | |
|               label="CMD"
 | |
|             />
 | |
|             <q-radio
 | |
|               v-if="agent.plat === 'windows'"
 | |
|               dense
 | |
|               v-model="state.shell"
 | |
|               val="powershell"
 | |
|               label="Powershell"
 | |
|             />
 | |
|           </div>
 | |
|         </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>
 | |
|           <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 >= 10 || 'Minimum is 10 seconds',
 | |
|               (val) => val <= 3600 || 'Maximum is 3600 seconds',
 | |
|             ]"
 | |
|           />
 | |
|         </q-card-section>
 | |
|         <q-card-section>
 | |
|           <q-input
 | |
|             v-model="state.cmd"
 | |
|             outlined
 | |
|             label="Command"
 | |
|             stack-label
 | |
|             :placeholder="cmdPlaceholder(state.shell)"
 | |
|             :rules="[(val) => !!val || '*Required']"
 | |
|           />
 | |
|         </q-card-section>
 | |
|         <q-card-actions align="right">
 | |
|           <q-btn flat dense push label="Cancel" v-close-popup />
 | |
|           <q-btn
 | |
|             :loading="loading"
 | |
|             :disable="!wsConnected"
 | |
|             flat
 | |
|             dense
 | |
|             push
 | |
|             label="Send"
 | |
|             color="primary"
 | |
|             type="submit"
 | |
|           >
 | |
|           </q-btn>
 | |
|         </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>
 | |
|         </q-card-section>
 | |
|       </q-form>
 | |
|     </q-card>
 | |
|   </q-dialog>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| // composition imports
 | |
| import { ref, computed, onMounted, onBeforeUnmount } from "vue";
 | |
| import { useStore } from "vuex";
 | |
| import { useDialogPluginComponent } from "quasar";
 | |
| import { cmdPlaceholder } from "@/composables/agents";
 | |
| import { getWSUrl } from "@/websocket/channels";
 | |
| 
 | |
| export default {
 | |
|   name: "SendCommand",
 | |
|   emits: [...useDialogPluginComponent.emits],
 | |
|   props: {
 | |
|     agent: !Object,
 | |
|   },
 | |
|   setup(props) {
 | |
|     const store = useStore();
 | |
|     // setup quasar dialog plugin
 | |
|     const { dialogRef, onDialogHide } = useDialogPluginComponent();
 | |
| 
 | |
|     // run command logic
 | |
|     const state = ref({
 | |
|       shell: props.agent.plat === "windows" ? "cmd" : "/bin/bash",
 | |
|       cmd: null,
 | |
|       timeout: 30,
 | |
|       custom_shell: null,
 | |
|       agent_id: props.agent.agent_id,
 | |
|     });
 | |
| 
 | |
|     const loading = ref(false);
 | |
|     const ret = ref(null);
 | |
| 
 | |
|     // websocket
 | |
|     const ws = ref(null);
 | |
|     const wsConnected = ref(false);
 | |
| 
 | |
|     function setupWS() {
 | |
|       const token = computed(() => store.state.token);
 | |
|       console.log("Starting send command websocket");
 | |
|       let url = getWSUrl("sendcmd", token.value);
 | |
|       ws.value = new WebSocket(url);
 | |
|       ws.value.onopen = () => {
 | |
|         wsConnected.value = true;
 | |
|         console.log("Send command websocket connected");
 | |
|       };
 | |
|       ws.value.onmessage = (e) => {
 | |
|         const data = JSON.parse(e.data);
 | |
|         ret.value = data.ret;
 | |
|         loading.value = false;
 | |
|       };
 | |
|       ws.value.onclose = () => {
 | |
|         console.log("Send command websocket disconnected");
 | |
|         wsConnected.value = false;
 | |
|       };
 | |
|       ws.value.onerror = () => {
 | |
|         wsConnected.value = false;
 | |
|         console.log("Send command websocket error");
 | |
|         ws.value.onclose();
 | |
|       };
 | |
|     }
 | |
| 
 | |
|     function submit() {
 | |
|       ret.value = null;
 | |
|       loading.value = true;
 | |
|       ret.value = ws.value.send(JSON.stringify(state.value));
 | |
|     }
 | |
| 
 | |
|     onMounted(() => {
 | |
|       setupWS();
 | |
|     });
 | |
| 
 | |
|     onBeforeUnmount(() => {
 | |
|       ws.value.close();
 | |
|     });
 | |
| 
 | |
|     return {
 | |
|       // reactive data
 | |
|       state,
 | |
|       loading,
 | |
|       ret,
 | |
|       wsConnected,
 | |
| 
 | |
|       // methods
 | |
|       submit,
 | |
|       cmdPlaceholder,
 | |
| 
 | |
|       // quasar dialog
 | |
|       dialogRef,
 | |
|       onDialogHide,
 | |
|     };
 | |
|   },
 | |
| };
 | |
| </script>
 |