main
vue 113 lines 3.22 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="tacticDetails" class="text-default">
7 {{ tacticDetails.external_id }}
8 </span>
9 <n-skeleton v-else text :width="100" :height="18" />
10 </template>
11 <template #default>
12 <div v-if="tacticDetails">
13 {{ tacticDetails.name }}
14 </div>
15 <n-skeleton v-else text style="width: 60%" :height="20" />
16 </template>
17 <template #footer>
18 <p v-if="tacticDetails" class="cursor-text" @click.stop="() => {}">
19 <Markdown :source="tacticDetails.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="`Tactic • ${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 <TacticDetails :entity="tacticDetails" />
41 </div>
42 </n-tab-pane>
43 <n-tab-pane
44 name="Techniques"
45 :tab="`Techniques (${tacticDetails?.techniques?.length || 0})`"
46 display-directive="show:lazy"
47 >
48 <div class="px-6 pt-3 pb-6">
49 <TechniquesList v-if="tacticDetails" :list="tacticDetails.techniques" />
50 </div>
51 </n-tab-pane>
52 </n-tabs>
53 </n-modal>
54 </div>
55 </template>
56
57 <script setup lang="ts">
58 // TODO-FE: refactor
59 import type { MitreTacticDetails } from "@/types/mitre.d"
60 import { NModal, NSkeleton, NTabPane, NTabs, useMessage } from "naive-ui"
61 import { defineAsyncComponent, onBeforeMount, ref } from "vue"
62 import Api from "@/api"
63 import CardEntity from "@/components/common/cards/CardEntity.vue"
64 import TechniquesList from "../Technique/TechniquesList.vue"
65 import TacticDetails from "./TacticDetails.vue"
66
67 const { id, entity } = defineProps<{
68 id: string
69 entity?: MitreTacticDetails
70 }>()
71
72 const emit = defineEmits<{
73 (e: "loaded", value: MitreTacticDetails): void
74 }>()
75
76 const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
77
78 const showDetails = ref(false)
79 const message = useMessage()
80 const loadingDetails = ref(false)
81 const tacticDetails = ref<MitreTacticDetails | undefined>(undefined)
82
83 function getDetails(id: string) {
84 loadingDetails.value = true
85
86 Api.wazuh.mitre
87 .getMitreTactics({ id })
88 .then(res => {
89 if (res.data.success) {
90 if (res.data.results?.[0]) {
91 tacticDetails.value = res.data.results[0]
92 }
93 if (tacticDetails.value) emit("loaded", tacticDetails.value)
94 } else {
95 message.warning(res.data?.message || "An error occurred. Please try again later.")
96 }
97 })
98 .catch(err => {
99 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
100 })
101 .finally(() => {
102 loadingDetails.value = false
103 })
104 }
105
106 onBeforeMount(() => {
107 if (entity) {
108 tacticDetails.value = entity
109 } else if (id) {
110 getDetails(id)
111 }
112 })
113 </script>