main
vue 160 lines 5.16 KB
Raw
1 <template>
2 <n-spin :show="loadingDetails" content-class="min-h-40">
3 <div v-if="groupDetails" class="flex flex-col gap-4 md:flex-row">
4 <div class="flex grow flex-col gap-3">
5 <code class="self-start">
6 {{ groupDetails.id ?? "" }}
7 </code>
8
9 <CardKV>
10 <template #key>name</template>
11 <template #value>
12 <span class="whitespace-pre-wrap">
13 {{ groupDetails.name ?? "" }}
14 </span>
15 </template>
16 </CardKV>
17 <CardKV v-if="groupDetails.description" class="[&_p]:text-white">
18 <template #key>description</template>
19 <template #value>
20 <Markdown :source="groupDetails.description" />
21 </template>
22 </CardKV>
23
24 <CardKV v-if="groupDetails.references?.length">
25 <template #key>references</template>
26 <template #value>
27 <References :references="groupDetails.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>{{ groupDetails.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(groupDetails.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(groupDetails.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="groupDetails.url" target="_blank" rel="nofollow noopener noreferrer">
50 {{ groupDetails.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>{{ groupDetails.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>{{ groupDetails.mitre_version }}</div>
61 </div>
62 <div class="flex flex-col gap-0.5 text-sm">
63 <div class="text-secondary font-mono text-xs">country</div>
64 <div>{{ groupDetails.country || "—" }}</div>
65 </div>
66 <div class="flex flex-col gap-0.5 text-sm">
67 <div class="text-secondary font-mono text-xs">aliases</div>
68 <div class="mt-0.5 flex flex-wrap gap-1">
69 <template v-if="!groupDetails.aliases?.length"></template>
70 <template v-else>
71 <code v-for="item of groupDetails.aliases" :key="item" class="text-xs">
72 {{ item }}
73 </code>
74 </template>
75 </div>
76 </div>
77 </n-card>
78
79 <div class="flex flex-wrap gap-1">
80 <Badge v-if="groupDetails.deprecated" color="primary" class="font-mono text-xs!">
81 <template #value>deprecated</template>
82 </Badge>
83 </div>
84 </div>
85 </div>
86 </div>
87 </n-spin>
88 </template>
89
90 <script setup lang="ts">
91 import type { MitreGroupDetails } from "@/types/mitre.d"
92 import { useElementBounding, useRafFn } from "@vueuse/core"
93 import { useMotionProperties } from "@vueuse/motion"
94 import { NCard, NSpin, useMessage } from "naive-ui"
95 import { defineAsyncComponent, onBeforeMount, ref, watch } from "vue"
96 import Api from "@/api"
97 import Badge from "@/components/common/Badge.vue"
98 import CardKV from "@/components/common/cards/CardKV.vue"
99 import { useSettingsStore } from "@/stores/settings"
100 import { formatDate } from "@/utils/format"
101 import References from "../common/References.vue"
102
103 const { externalId, entity } = defineProps<{
104 externalId?: string
105 entity?: MitreGroupDetails
106 }>()
107
108 const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
109
110 const dFormats = useSettingsStore().dateFormat
111 const message = useMessage()
112 const loadingDetails = ref(false)
113 const groupDetails = ref<MitreGroupDetails | null>(null)
114
115 const sidebarRef = ref(null)
116 const sidebarCardRef = ref(null)
117 const { top: sidebarTop } = useElementBounding(sidebarRef)
118 const { transform: styleCardTransform } = useMotionProperties(sidebarCardRef)
119
120 const { resume } = useRafFn(
121 () => {
122 const targetY = sidebarTop.value <= 50 ? sidebarTop.value * -1 + 50 : 0
123 styleCardTransform.translateY = `${targetY}px`
124 },
125 { immediate: false }
126 )
127
128 watch(sidebarTop, () => {
129 resume()
130 })
131
132 function getDetails(id: string) {
133 loadingDetails.value = true
134
135 Api.wazuh.mitre
136 .getMitreGroups({ id })
137 .then(res => {
138 if (res.data.success) {
139 groupDetails.value = res.data.results?.[0] || null
140 } else {
141 message.warning(res.data?.message || "An error occurred. Please try again later.")
142 }
143 })
144 .catch(err => {
145 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
146 })
147 .finally(() => {
148 loadingDetails.value = false
149 })
150 }
151
152 onBeforeMount(() => {
153 if (externalId) {
154 getDetails(externalId)
155 }
156 if (entity) {
157 groupDetails.value = entity
158 }
159 })
160 </script>