main
vue 154 lines 4.68 KB
Raw
1 <template>
2 <div>
3 <CardEntity embedded clickable hoverable size="small" :loading="loadingDetails" @click="showDetails = true">
4 <template #headerMain>{{ id }}</template>
5 <template #headerExtra>
6 <span v-if="techniqueDetails" class="text-default">
7 {{ techniqueDetails.external_id }}
8 </span>
9 <n-skeleton v-else text :width="100" :height="18" />
10 </template>
11 <template #default>
12 <div v-if="techniqueDetails">
13 {{ techniqueDetails.name }}
14 </div>
15 <n-skeleton v-else text style="width: 60%" :height="20" />
16 </template>
17 <template #footer>
18 <p v-if="techniqueDetails" class="cursor-text" @click.stop="() => {}">
19 <Markdown :source="techniqueDetails.description" />
20 </p>
21 <div v-else>
22 <n-skeleton text :repeat="2" :height="16" />
23 <n-skeleton text style="width: 40%" :height="16" />
24 </div>
25 </template>
26 </CardEntity>
27 <n-modal
28 v-model:show="showDetails"
29 display-directive="show"
30 preset="card"
31 content-class="p-0!"
32 :style="{ maxWidth: 'min(900px, 90vw)', minHeight: 'min(600px, 90vh)', overflow: 'hidden' }"
33 :title="`Technique • ${id}`"
34 :bordered="false"
35 segmented
36 >
37 <n-tabs type="line" animated :tabs-padding="24">
38 <n-tab-pane name="Overview" tab="Overview" display-directive="show:lazy">
39 <div class="px-6 pt-3 pb-6">
40 <TechniqueAlertDetails :entity="techniqueDetails" />
41 </div>
42 </n-tab-pane>
43 <n-tab-pane
44 name="Tactics"
45 :tab="`Tactics (${techniqueDetails?.tactics?.length || 0})`"
46 display-directive="show:lazy"
47 >
48 <div class="px-6 pt-3 pb-6">
49 <TacticsList v-if="techniqueDetails" :list="techniqueDetails.tactics" />
50 </div>
51 </n-tab-pane>
52 <n-tab-pane
53 name="Mitigations"
54 :tab="`Mitigations (${techniqueDetails?.mitigations?.length || 0})`"
55 display-directive="show:lazy"
56 >
57 <div class="px-6 pt-3 pb-6">
58 <MitigationsList v-if="techniqueDetails" :list="techniqueDetails.mitigations" />
59 </div>
60 </n-tab-pane>
61 <n-tab-pane
62 v-if="techniqueDetails?.techniques?.length"
63 name="Techniques"
64 :tab="`Techniques (${techniqueDetails?.techniques?.length || 0})`"
65 display-directive="show:lazy"
66 >
67 <div class="px-6 pt-3 pb-6">
68 <TechniquesList :list="techniqueDetails.techniques" />
69 </div>
70 </n-tab-pane>
71 <n-tab-pane
72 name="Groups"
73 :tab="`Groups (${techniqueDetails?.groups?.length || 0})`"
74 display-directive="show:lazy"
75 >
76 <div class="px-6 pt-3 pb-6">
77 <GroupsList v-if="techniqueDetails" :list="techniqueDetails.groups" />
78 </div>
79 </n-tab-pane>
80 <n-tab-pane
81 name="Software"
82 :tab="`Software (${techniqueDetails?.software?.length || 0})`"
83 display-directive="show:lazy"
84 >
85 <div class="px-6 pt-3 pb-6">
86 <SoftwareList v-if="techniqueDetails" :list="techniqueDetails.software" />
87 </div>
88 </n-tab-pane>
89 </n-tabs>
90 </n-modal>
91 </div>
92 </template>
93
94 <script setup lang="ts">
95 // TODO-FE: refactor
96 import type { MitreTechniqueDetails } from "@/types/mitre.d"
97 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
98 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
99 import Api from "@/api"
100 import CardEntity from "@/components/common/cards/CardEntity.vue"
101 import GroupsList from "../Group/GroupsList.vue"
102 import MitigationsList from "../Mitigation/MitigationsList.vue"
103 import SoftwareList from "../Software/SoftwareList.vue"
104 import TacticsList from "../Tactic/TacticsList.vue"
105 import TechniqueAlertDetails from "../TechniqueAlert/TechniqueAlertDetails.vue"
106 import TechniquesList from "./TechniquesList.vue"
107
108 const { id, entity } = defineProps<{
109 id: string
110 entity?: MitreTechniqueDetails
111 }>()
112
113 const emit = defineEmits<{
114 (e: "loaded", value: MitreTechniqueDetails): void
115 }>()
116
117 const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
118
119 const showDetails = ref(false)
120 const message = useMessage()
121 const loadingDetails = ref(false)
122 const techniqueDetails = ref<MitreTechniqueDetails | undefined>(undefined)
123
124 function getDetails(id: string) {
125 loadingDetails.value = true
126
127 Api.wazuh.mitre
128 .getMitreTechniques({ id })
129 .then(res => {
130 if (res.data.success) {
131 if (res.data.results?.[0]) {
132 techniqueDetails.value = res.data.results[0]
133 }
134 if (techniqueDetails.value) emit("loaded", techniqueDetails.value)
135 } else {
136 message.warning(res.data?.message || "An error occurred. Please try again later.")
137 }
138 })
139 .catch(err => {
140 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
141 })
142 .finally(() => {
143 loadingDetails.value = false
144 })
145 }
146
147 onBeforeMount(() => {
148 if (entity) {
149 techniqueDetails.value = entity
150 } else if (id) {
151 getDetails(id)
152 }
153 })
154 </script>