main
vue 145 lines 4.67 KB
Raw
1 <template>
2 <n-spin :show="loadingDetails" content-class="min-h-40">
3 <div v-if="mitigationDetails" class="flex flex-col gap-4 md:flex-row">
4 <div class="flex grow flex-col gap-3">
5 <code class="self-start">
6 {{ mitigationDetails.id ?? "" }}
7 </code>
8
9 <CardKV>
10 <template #key>name</template>
11 <template #value>
12 <span class="whitespace-pre-wrap">
13 {{ mitigationDetails.name ?? "" }}
14 </span>
15 </template>
16 </CardKV>
17 <CardKV v-if="mitigationDetails.description" class="[&_p]:text-white">
18 <template #key>description</template>
19 <template #value>
20 <Markdown :source="mitigationDetails.description" />
21 </template>
22 </CardKV>
23
24 <CardKV v-if="mitigationDetails.references?.length">
25 <template #key>references</template>
26 <template #value>
27 <References :references="mitigationDetails.references" />
28 </template>
29 </CardKV>
30 </div>
31 <div ref="sidebarRef" class="shrink-0 basis-1/3 md:max-w-70">
32 <div ref="sidebarCardRef" class="flex flex-col gap-2 will-change-transform">
33 <n-card content-class="bg-secondary flex flex-col gap-3 rounded-lg" size="small">
34 <div class="flex flex-col gap-0.5 text-sm">
35 <div class="text-secondary font-mono text-xs">external_id</div>
36 <div>{{ mitigationDetails.external_id }}</div>
37 </div>
38 <div class="flex flex-col gap-0.5 text-sm">
39 <div class="text-secondary font-mono text-xs">created_time</div>
40 <div>{{ formatDate(mitigationDetails.created_time, dFormats.datetime) }}</div>
41 </div>
42 <div class="flex flex-col gap-0.5 text-sm">
43 <div class="text-secondary font-mono text-xs">modified_time</div>
44 <div>{{ formatDate(mitigationDetails.modified_time, dFormats.datetime) }}</div>
45 </div>
46 <div class="flex flex-col gap-0.5 text-sm">
47 <div class="text-secondary font-mono text-xs">url</div>
48 <div>
49 <a :href="mitigationDetails.url" target="_blank" rel="nofollow noopener noreferrer">
50 {{ mitigationDetails.url }}
51 </a>
52 </div>
53 </div>
54 <div class="flex flex-col gap-0.5 text-sm">
55 <div class="text-secondary font-mono text-xs">source</div>
56 <div>{{ mitigationDetails.source }}</div>
57 </div>
58 <div class="flex flex-col gap-0.5 text-sm">
59 <div class="text-secondary font-mono text-xs">mitre_version</div>
60 <div>{{ mitigationDetails.mitre_version }}</div>
61 </div>
62 </n-card>
63
64 <div class="flex flex-wrap gap-1">
65 <Badge v-if="mitigationDetails.deprecated" color="primary" class="font-mono text-xs!">
66 <template #value>deprecated</template>
67 </Badge>
68 </div>
69 </div>
70 </div>
71 </div>
72 </n-spin>
73 </template>
74
75 <script setup lang="ts">
76 import type { MitreMitigationDetails } from "@/types/mitre.d"
77 import { useElementBounding, useRafFn } from "@vueuse/core"
78 import { useMotionProperties } from "@vueuse/motion"
79 import { NCard, NSpin, useMessage } from "naive-ui"
80 import { defineAsyncComponent, onBeforeMount, ref, watch } from "vue"
81 import Api from "@/api"
82 import Badge from "@/components/common/Badge.vue"
83 import CardKV from "@/components/common/cards/CardKV.vue"
84 import { useSettingsStore } from "@/stores/settings"
85 import { formatDate } from "@/utils/format"
86 import References from "../common/References.vue"
87
88 const { externalId, entity } = defineProps<{
89 externalId?: string
90 entity?: MitreMitigationDetails
91 }>()
92
93 const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
94
95 const dFormats = useSettingsStore().dateFormat
96 const message = useMessage()
97 const loadingDetails = ref(false)
98 const mitigationDetails = ref<MitreMitigationDetails | null>(null)
99
100 const sidebarRef = ref(null)
101 const sidebarCardRef = ref(null)
102 const { top: sidebarTop } = useElementBounding(sidebarRef)
103 const { transform: styleCardTransform } = useMotionProperties(sidebarCardRef)
104
105 const { resume } = useRafFn(
106 () => {
107 const targetY = sidebarTop.value <= 50 ? sidebarTop.value * -1 + 50 : 0
108 styleCardTransform.translateY = `${targetY}px`
109 },
110 { immediate: false }
111 )
112
113 watch(sidebarTop, () => {
114 resume()
115 })
116
117 function getDetails(id: string) {
118 loadingDetails.value = true
119
120 Api.wazuh.mitre
121 .getMitreMitigations({ id })
122 .then(res => {
123 if (res.data.success) {
124 mitigationDetails.value = res.data.results?.[0] || null
125 } else {
126 message.warning(res.data?.message || "An error occurred. Please try again later.")
127 }
128 })
129 .catch(err => {
130 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
131 })
132 .finally(() => {
133 loadingDetails.value = false
134 })
135 }
136
137 onBeforeMount(() => {
138 if (externalId) {
139 getDetails(externalId)
140 }
141 if (entity) {
142 mitigationDetails.value = entity
143 }
144 })
145 </script>