| 1 | <template> |
| 2 | <slot :open-evaluation /> |
| 3 | |
| 4 | <n-modal |
| 5 | v-model:show="showDetails" |
| 6 | preset="card" |
| 7 | content-class="p-0!" |
| 8 | :style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(550px, 90vh)' }" |
| 9 | :title="`Process Analysis: ${processName}`" |
| 10 | :bordered="false" |
| 11 | segmented |
| 12 | > |
| 13 | <n-spin :show="loading" class="min-h-48"> |
| 14 | <n-tabs v-if="mcpResponse" type="line" animated :tabs-padding="24"> |
| 15 | <n-tab-pane |
| 16 | v-if="mcpResponse.structured_result?.response" |
| 17 | name="Analysis" |
| 18 | tab="Analysis" |
| 19 | display-directive="show" |
| 20 | > |
| 21 | <div class="p-6 pt-3"> |
| 22 | <Markdown :source="mcpResponse.structured_result.response" /> |
| 23 | </div> |
| 24 | </n-tab-pane> |
| 25 | <n-tab-pane |
| 26 | v-if="mcpResponse.structured_result?.thinking_process" |
| 27 | name="ThinkingProcess" |
| 28 | tab="Thinking Process" |
| 29 | display-directive="show:lazy" |
| 30 | > |
| 31 | <div class="p-6 pt-3"> |
| 32 | <Markdown :source="mcpResponse.structured_result.thinking_process" /> |
| 33 | </div> |
| 34 | </n-tab-pane> |
| 35 | <!-- Legacy tabs for backward compatibility --> |
| 36 | <n-tab-pane |
| 37 | v-if="evaluation" |
| 38 | name="Overview" |
| 39 | tab="Overview" |
| 40 | display-directive="show:lazy" |
| 41 | class="flex flex-col gap-4 py-8!" |
| 42 | > |
| 43 | <div class="px-7"> |
| 44 | <n-card embedded class="overflow-hidden"> |
| 45 | <div class="flex flex-wrap justify-between gap-8"> |
| 46 | <n-statistic label="Rank" :value="evaluation.rank" tabular-nums /> |
| 47 | <n-statistic label="EPS" :value="eps" tabular-nums /> |
| 48 | <n-statistic label="Host Prevalence" :value="`${evaluation.host_prev}%`" tabular-nums /> |
| 49 | </div> |
| 50 | </n-card> |
| 51 | </div> |
| 52 | |
| 53 | <div class="px-7"> |
| 54 | {{ evaluation.description }} |
| 55 | </div> |
| 56 | </n-tab-pane> |
| 57 | <n-tab-pane v-if="evaluation" name="Intel" tab="Intel" display-directive="show:lazy"> |
| 58 | <div class="p-6 pt-3"> |
| 59 | <n-input |
| 60 | :value="evaluation.intel" |
| 61 | type="textarea" |
| 62 | readonly |
| 63 | placeholder="Empty" |
| 64 | size="large" |
| 65 | :autosize="{ |
| 66 | minRows: 3, |
| 67 | maxRows: 18 |
| 68 | }" |
| 69 | /> |
| 70 | </div> |
| 71 | </n-tab-pane> |
| 72 | <n-tab-pane v-if="evaluation?.hashes?.length" name="Hashes" tab="Hashes" display-directive="show:lazy"> |
| 73 | <ListPercentage |
| 74 | class="p-6 pt-3" |
| 75 | :list="evaluation.hashes" |
| 76 | label-key="hash" |
| 77 | percentage-key="percentage" |
| 78 | /> |
| 79 | </n-tab-pane> |
| 80 | <n-tab-pane |
| 81 | v-if="evaluation?.network?.length" |
| 82 | name="Network" |
| 83 | tab="Network" |
| 84 | display-directive="show:lazy" |
| 85 | > |
| 86 | <ListPercentage |
| 87 | class="p-6 pt-3" |
| 88 | :list="evaluation.network" |
| 89 | label-key="port" |
| 90 | percentage-key="usage" |
| 91 | /> |
| 92 | </n-tab-pane> |
| 93 | <n-tab-pane |
| 94 | v-if="evaluation?.parents?.length" |
| 95 | name="Parents" |
| 96 | tab="Parents" |
| 97 | display-directive="show:lazy" |
| 98 | > |
| 99 | <ListPercentage |
| 100 | class="p-6 pt-3" |
| 101 | :list="evaluation.parents" |
| 102 | label-key="name" |
| 103 | percentage-key="percentage" |
| 104 | /> |
| 105 | </n-tab-pane> |
| 106 | <n-tab-pane v-if="evaluation?.paths?.length" name="Paths" tab="Paths" display-directive="show:lazy"> |
| 107 | <ListPercentage |
| 108 | class="p-6 pt-3" |
| 109 | :list="evaluation.paths" |
| 110 | label-key="directory" |
| 111 | percentage-key="percentage" |
| 112 | /> |
| 113 | </n-tab-pane> |
| 114 | </n-tabs> |
| 115 | <n-empty |
| 116 | v-if="!loading && !mcpResponse && !evaluation" |
| 117 | description="Process analysis not found" |
| 118 | class="h-48 justify-center" |
| 119 | /> |
| 120 | </n-spin> |
| 121 | </n-modal> |
| 122 | </template> |
| 123 | |
| 124 | <script setup lang="ts"> |
| 125 | import type { EvaluationData, MCPQueryResponse } from "@/types/threatIntel.d" |
| 126 | import _toSafeInteger from "lodash/toSafeInteger" |
| 127 | import { NCard, NEmpty, NInput, NModal, NSpin, NStatistic, NTabPane, NTabs, useMessage } from "naive-ui" |
| 128 | import { computed, defineAsyncComponent, ref } from "vue" |
| 129 | import Api from "@/api" |
| 130 | |
| 131 | const { processName } = defineProps<{ |
| 132 | processName: string |
| 133 | }>() |
| 134 | |
| 135 | const ListPercentage = defineAsyncComponent(() => import("@/components/common/ListPercentage.vue")) |
| 136 | const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue")) |
| 137 | |
| 138 | const evaluation = ref<EvaluationData | null>(null) |
| 139 | const mcpResponse = ref<MCPQueryResponse | null>(null) |
| 140 | const showDetails = ref<boolean>(false) |
| 141 | const loading = ref<boolean>(false) |
| 142 | const message = useMessage() |
| 143 | |
| 144 | const eps = computed(() => _toSafeInteger(evaluation.value?.eps || 0)) |
| 145 | |
| 146 | function openEvaluation() { |
| 147 | if (!mcpResponse.value && !evaluation.value) { |
| 148 | getEvaluation() |
| 149 | } |
| 150 | showDetails.value = true |
| 151 | } |
| 152 | |
| 153 | function getEvaluation() { |
| 154 | loading.value = true |
| 155 | |
| 156 | Api.threatIntel |
| 157 | .processNameEvaluation(processName) |
| 158 | .then(res => { |
| 159 | if (res.data.success) { |
| 160 | // Check if response is in new MCP format by checking for structured_result property |
| 161 | if ("structured_result" in res.data && res.data.structured_result) { |
| 162 | mcpResponse.value = res.data as MCPQueryResponse |
| 163 | } else { |
| 164 | // Fallback to legacy format |
| 165 | const legacyResponse = res.data as { data?: EvaluationData } |
| 166 | evaluation.value = legacyResponse?.data || null |
| 167 | } |
| 168 | } else { |
| 169 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 170 | } |
| 171 | }) |
| 172 | .catch(err => { |
| 173 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 174 | }) |
| 175 | .finally(() => { |
| 176 | loading.value = false |
| 177 | }) |
| 178 | } |
| 179 | </script> |