main
vue 166 lines 4.37 KB
Raw
1 <template>
2 <div class="flex flex-col gap-4">
3 <Filters @update="filters = $event" />
4
5 <div class="flex flex-col">
6 <div ref="header" class="header flex items-center justify-end gap-2">
7 <div class="info flex grow gap-5">
8 <n-popover overlap placement="bottom-start">
9 <template #trigger>
10 <div class="bg-default rounded-lg">
11 <n-button size="small" class="cursor-help!">
12 <template #icon>
13 <Icon :name="InfoIcon" />
14 </template>
15 </n-button>
16 </div>
17 </template>
18 <div class="flex flex-col gap-2">
19 <div class="box">
20 Total:
21 <code>{{ total }}</code>
22 </div>
23 </div>
24 </n-popover>
25 </div>
26 <n-pagination
27 v-model:page="currentPage"
28 v-model:page-size="pageSize"
29 :item-count="total"
30 :page-slot
31 :show-size-picker
32 :page-sizes
33 :simple="simpleMode"
34 />
35 </div>
36
37 <n-spin :show="loading">
38 <div class="my-3 flex min-h-28 flex-col gap-2">
39 <template v-if="alertsList.length">
40 <TechniqueEventCard
41 v-for="alert of alertsList"
42 :key="alert.id"
43 :alert
44 embedded
45 use-details-tab
46 />
47 </template>
48 <template v-else>
49 <n-empty v-if="!loading" description="No items found" class="h-48 justify-center" />
50 </template>
51 </div>
52 </n-spin>
53 <div class="flex justify-end">
54 <n-pagination
55 v-if="alertsList.length > 3"
56 v-model:page="currentPage"
57 :page-size
58 :item-count="total"
59 :page-slot="6"
60 />
61 </div>
62 </div>
63 </div>
64 </template>
65
66 <script setup lang="ts">
67 // TODO-FE: refactor
68 import type { MitreEventsQuery, MitreTechniquesAlertsQueryTimeRange } from "@/api/endpoints/wazuh/mitre"
69 import type { MitreEventDetails } from "@/types/mitre.d"
70 import { useResizeObserver, watchDebounced } from "@vueuse/core"
71 import axios from "axios"
72 import { NButton, NEmpty, NPagination, NPopover, NSpin, useMessage } from "naive-ui"
73 import { computed, ref } from "vue"
74 import Api from "@/api"
75 import Icon from "@/components/common/Icon.vue"
76 import Filters from "./Filters.vue"
77 import TechniqueEventCard from "./TechniqueEventCard.vue"
78
79 const { externalId } = defineProps<{
80 externalId: string
81 }>()
82
83 const filters = ref<{ type: string; value: string }[]>([])
84 const loading = ref(false)
85 const message = useMessage()
86 const alertsList = ref<MitreEventDetails[]>([])
87 const header = ref()
88 const currentPage = ref(1)
89 const total = ref(0)
90 const compactMode = ref(false)
91 const simpleMode = ref(false)
92 const showSizePicker = computed(() => !compactMode.value)
93 const pageSizes = [25, 50, 100, 150, 200]
94 const pageSize = ref(pageSizes[0])
95 const pageSlot = ref(8)
96 const InfoIcon = "carbon:information"
97
98 let abortController: AbortController | null = null
99
100 function getList() {
101 abortController?.abort()
102 abortController = new AbortController()
103
104 loading.value = true
105
106 const query: MitreEventsQuery = {
107 technique_id: externalId,
108 time_range: filters.value?.find(o => o.type === "time_range")?.value as
109 | MitreTechniquesAlertsQueryTimeRange
110 | undefined,
111 size: pageSize.value,
112 page: currentPage.value,
113 rule_level: filters.value?.find(o => o.type === "rule_level")?.value,
114 rule_group: filters.value?.find(o => o.type === "rule_group")?.value,
115 mitre_field: filters.value?.find(o => o.type === "mitre_field")?.value,
116 index_pattern: filters.value?.find(o => o.type === "index_pattern")?.value
117 }
118
119 Api.wazuh.mitre
120 .getMitreEvents(query, abortController.signal)
121 .then(res => {
122 loading.value = false
123
124 if (res.data.success) {
125 alertsList.value = res.data?.alerts || []
126 total.value = res.data?.total_alerts || 0
127 } else {
128 message.warning(res.data?.message || "An error occurred. Please try again later.")
129 }
130 })
131 .catch(err => {
132 if (!axios.isCancel(err)) {
133 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
134 loading.value = false
135 }
136 })
137 }
138
139 useResizeObserver(header, entries => {
140 const entry = entries[0]
141 if (!entry) return
142
143 const { width } = entry.contentRect
144
145 if (width < 650) {
146 compactMode.value = true
147 pageSlot.value = 5
148 } else {
149 compactMode.value = false
150 pageSlot.value = 8
151 }
152
153 simpleMode.value = width < 450
154 })
155
156 watchDebounced([filters, currentPage, pageSize], getList, {
157 deep: true,
158 debounce: 300,
159 immediate: true
160 })
161 // MOCK
162 /*
163 alertsList.value = techniqueAlertsResponse.alerts
164 total.value = techniqueAlertsResponse.total_alerts
165 */
166 </script>