main
vue 292 lines 7.7 KB
Raw
1 <template>
2 <div class="artifacts-command">
3 <div class="header flex items-start gap-2">
4 <div class="flex w-full flex-col gap-2">
5 <div class="flex grow flex-wrap items-center gap-2">
6 <div v-if="!hideHostnameField" class="grow basis-56">
7 <n-select
8 v-model:value="filters.hostname"
9 :options="agentHostnameOptions"
10 placeholder="Agent hostname"
11 clearable
12 filterable
13 size="small"
14 :disabled="loading"
15 :loading="loadingAgents"
16 />
17 </div>
18 <div class="grow basis-56">
19 <n-select
20 v-model:value="filters.artifact_name"
21 :options="artifactsOptions"
22 placeholder="Artifact name"
23 clearable
24 filterable
25 size="small"
26 :disabled="loading"
27 :loading="loadingArtifacts"
28 />
29 </div>
30 <div v-if="!hideVelociraptorIdField" class="grow basis-56">
31 <n-input
32 v-model:value="filters.velociraptor_id"
33 placeholder="Velociraptor id"
34 :readonly="loading"
35 clearable
36 size="small"
37 />
38 </div>
39 </div>
40 <div class="flex grow flex-wrap items-center gap-2">
41 <n-input
42 v-model:value="filters.command"
43 placeholder="Command"
44 clearable
45 :readonly="loading"
46 type="textarea"
47 :autosize="{
48 minRows: 3,
49 maxRows: 10
50 }"
51 />
52 </div>
53 <div class="flex grow flex-wrap-reverse items-center justify-end gap-2">
54 <div class="flex grow flex-wrap gap-2">
55 <n-tooltip v-if="commandTime" trigger="hover">
56 <template #trigger>
57 <Badge type="splitted" color="primary" hint-cursor>
58 <template #iconLeft>
59 <Icon :name="TimeIcon" />
60 </template>
61 <template #value>
62 <span class="flex">
63 {{ formatDate(commandTime, dFormats.timesec) }}
64
65 <n-spin v-if="loading" :size="12" class="ml-2" />
66
67 {{ responseTime ? ` / ${formatDate(responseTime, dFormats.timesec)}` : "" }}
68 </span>
69 </template>
70 </Badge>
71 </template>
72 Last request time / last response time
73 </n-tooltip>
74
75 <Badge v-if="diffTime" type="splitted" color="primary">
76 <template #iconLeft>
77 <Icon :name="StopWatchIcon" :size="15" />
78 </template>
79 <template #value>
80 {{ diffTime }}
81 </template>
82 </Badge>
83 </div>
84 <n-button
85 size="small"
86 type="primary"
87 secondary
88 :loading
89 :disabled="!areFiltersValid"
90 @click="getData()"
91 >
92 Submit
93 </n-button>
94 </div>
95 </div>
96 </div>
97 <n-spin :show="loading">
98 <div class="my-7 flex min-h-52 flex-col gap-3">
99 <template v-if="commandList.length">
100 <CommandItem
101 v-for="command of commandList"
102 :key="command.Stdout"
103 :command
104 class="item-appear item-appear-bottom item-appear-005"
105 />
106 </template>
107 <template v-else>
108 <n-empty v-if="!loading" description="No items found" class="h-48 justify-center" />
109 </template>
110 </div>
111 </n-spin>
112 </div>
113 </template>
114
115 <script setup lang="ts">
116 import type { CommandRequest } from "@/api/endpoints/artifacts"
117 import type { Agent } from "@/types/agents.d"
118 import type { Artifact, CommandResult } from "@/types/artifacts.d"
119 import { NButton, NEmpty, NInput, NSelect, NSpin, NTooltip, useMessage } from "naive-ui"
120 import { computed, nextTick, onBeforeMount, ref, toRefs } from "vue"
121 import Api from "@/api"
122 import Badge from "@/components/common/Badge.vue"
123 import Icon from "@/components/common/Icon.vue"
124 import { useSettingsStore } from "@/stores/settings"
125 import dayjs from "@/utils/dayjs"
126 import { formatDate } from "@/utils/format"
127 import CommandItem from "./CommandItem.vue"
128
129 const props = defineProps<{
130 hostname?: string
131 agents?: Agent[]
132 artifacts?: Artifact[]
133 hideHostnameField?: boolean
134 hideVelociraptorIdField?: boolean
135 }>()
136
137 const emit = defineEmits<{
138 (e: "loaded-agents", value: Agent[]): void
139 (e: "loaded-artifacts", value: Artifact[]): void
140 }>()
141
142 const { hostname, agents, artifacts, hideHostnameField, hideVelociraptorIdField } = toRefs(props)
143
144 const TimeIcon = "carbon:time"
145 const StopWatchIcon = "quill:stopwatch"
146
147 const message = useMessage()
148 const loadingAgents = ref(false)
149 const loadingArtifacts = ref(false)
150 const loading = ref(false)
151 const agentsList = ref<Agent[]>([])
152 const artifactsList = ref<Artifact[]>([])
153 const commandList = ref<CommandResult[]>([])
154 const commandTime = ref<Date | null>(null)
155 const responseTime = ref<Date | null>(null)
156 const dFormats = useSettingsStore().dateFormat
157
158 const diffTime = computed(() => {
159 if (commandTime.value && responseTime.value) {
160 return `${dayjs.duration(dayjs(responseTime.value).diff(commandTime.value, "ms", true)).asSeconds()}s`
161 } else {
162 return null
163 }
164 })
165
166 const filters = ref<Partial<CommandRequest>>({})
167
168 const areFiltersValid = computed(() => {
169 return !!filters.value.artifact_name && !!filters.value.hostname && !!filters.value.command
170 })
171
172 const agentHostnameOptions = computed(() => {
173 if (hostname?.value) {
174 return [{ value: hostname.value, label: hostname.value }]
175 }
176 return (agentsList.value || []).map(o => ({ value: o.hostname, label: o.hostname }))
177 })
178
179 const artifactsOptions = computed(() => {
180 return (artifactsList.value || []).map(o => ({ value: o.name, label: o.name }))
181 })
182
183 function getData() {
184 if (areFiltersValid.value) {
185 loading.value = true
186 commandList.value = []
187 commandTime.value = new Date()
188 responseTime.value = null
189
190 Api.artifacts
191 .command(filters.value as CommandRequest)
192 .then(res => {
193 if (res.data.success) {
194 commandList.value = res.data?.results || []
195 } else {
196 message.warning(res.data?.message || "An error occurred. Please try again later.")
197 }
198 })
199 .catch(err => {
200 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
201 })
202 .finally(() => {
203 responseTime.value = new Date()
204 loading.value = false
205 })
206 }
207 }
208
209 function getAgents(cb?: (agents: Agent[]) => void) {
210 loadingAgents.value = true
211
212 Api.agents
213 .getAgents()
214 .then(res => {
215 if (res.data.success) {
216 agentsList.value = res.data.agents || []
217
218 if (cb && typeof cb === "function") {
219 cb(agentsList.value)
220 }
221 } else {
222 message.error(res.data?.message || "An error occurred. Please try again later.")
223 }
224 })
225 .catch(err => {
226 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
227 })
228 .finally(() => {
229 loadingAgents.value = false
230 })
231 }
232
233 function getArtifacts(cb?: (artifacts: Artifact[]) => void) {
234 loadingArtifacts.value = true
235
236 Api.artifacts
237 .getAll()
238 .then(res => {
239 if (res.data.success) {
240 artifactsList.value = res.data.artifacts || []
241
242 if (cb && typeof cb === "function") {
243 cb(artifactsList.value)
244 }
245 } else {
246 message.error(res.data?.message || "An error occurred. Please try again later.")
247 }
248 })
249 .catch(err => {
250 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
251 })
252 .finally(() => {
253 loadingArtifacts.value = false
254 })
255 }
256
257 onBeforeMount(() => {
258 artifactsList.value = ["Windows.System.PowerShell", "Windows.System.CmdShell", "Linux.Sys.BashShell"].map(
259 o => ({ name: o }) as Artifact
260 )
261
262 if (hostname?.value) {
263 filters.value.hostname = hostname.value
264 }
265
266 if (agents?.value?.length && !agentsList.value.length) {
267 agentsList.value = agents.value
268 }
269
270 if (artifacts?.value?.length && !artifactsList.value.length) {
271 artifactsList.value = artifacts.value
272 }
273
274 nextTick(() => {
275 if (!agentsList.value.length && !hostname?.value) {
276 getAgents((agents: Agent[]) => {
277 emit("loaded-agents", agents)
278 })
279 }
280 if (!artifactsList.value.length) {
281 getArtifacts((artifacts: Artifact[]) => {
282 emit("loaded-artifacts", artifacts)
283 })
284 }
285 })
286
287 // MOCK
288 /*
289 commandList.value = commandResult
290 */
291 })
292 </script>