main
vue 185 lines 4.88 KB
Raw
1 <template>
2 <div class="alerts-list">
3 <div class="header flex items-center justify-end gap-2">
4 <div class="info flex grow gap-5">
5 <n-popover overlap placement="bottom-start">
6 <template #trigger>
7 <div class="bg-default rounded-lg">
8 <n-button size="small" class="cursor-help!">
9 <template #icon>
10 <Icon :name="InfoIcon" />
11 </template>
12 </n-button>
13 </div>
14 </template>
15 <div class="flex flex-col gap-2">
16 <div class="box">
17 Total Summaries:
18 <code>{{ totalAlertsSummary }}</code>
19 </div>
20 <div class="box">
21 Total Alerts:
22 <code>{{ totalAlerts }}</code>
23 </div>
24 </div>
25 </n-popover>
26 </div>
27 <div class="actions flex items-center gap-2">
28 <n-button size="small" @click="showFiltersDrawer = true">
29 <template #icon>
30 <Icon :name="FilterIcon" :size="15" />
31 </template>
32 Filters
33 </n-button>
34 <ThreatIntelButton size="small" type="primary" />
35 </div>
36 </div>
37 <n-spin :show="loading">
38 <template #description>Alerts are being fetched, this may take up to 1 minute.</template>
39
40 <div class="my-3 flex min-h-52 flex-col gap-2">
41 <template v-if="alertsSummaryList.length">
42 <AlertsSummaryItem
43 v-for="alertsSummary of alertsSummaryList"
44 :key="alertsSummary.index_name"
45 :alerts-summary
46 class="item-appear item-appear-bottom item-appear-005"
47 />
48 </template>
49 <template v-else>
50 <n-empty v-if="!loading" description="No items found" class="h-48 justify-center" />
51 </template>
52 </div>
53 </n-spin>
54
55 <n-drawer
56 v-model:show="showFiltersDrawer"
57 display-directive="show"
58 :trap-focus="false"
59 style="max-width: 90vw; width: 500px"
60 :show-mask="loadingFilters ? 'transparent' : undefined"
61 :class="{ 'opacity-0': loadingFilters }"
62 >
63 <n-drawer-content title="Alerts filters" closable :native-scrollbar="false">
64 <AlertsGraylogFilters :filters @search="startSearch(true)" />
65 </n-drawer-content>
66 </n-drawer>
67 </div>
68 </template>
69
70 <script setup lang="ts">
71 import type { AlertsSummaryExt } from "./AlertsSummary.vue"
72 import type { SocAlertField } from "./type.d"
73 import type { GraylogAlertsQuery } from "@/api/endpoints/alerts"
74 import type { IndexStats } from "@/types/indices.d"
75 import axios from "axios"
76 import { NButton, NDrawer, NDrawerContent, NEmpty, NPopover, NSpin, useMessage } from "naive-ui"
77 import { computed, defineAsyncComponent, nextTick, onBeforeMount, onBeforeUnmount, onMounted, provide, ref } from "vue"
78 import Api from "@/api"
79 import Icon from "@/components/common/Icon.vue"
80 import AlertsGraylogFilters from "./AlertsGraylogFilters.vue"
81 import AlertsSummaryItem from "./AlertsSummary.vue"
82
83 const ThreatIntelButton = defineAsyncComponent(() => import("@/components/threatIntel/ThreatIntelButton.vue"))
84
85 const message = useMessage()
86 const loading = ref(false)
87 const indices = ref<IndexStats[]>([])
88 const alertsSummaryList = ref<AlertsSummaryExt[]>([])
89 const loadingFilters = ref(true)
90 const showFiltersDrawer = ref(true)
91 let abortController: AbortController | null = null
92
93 const InfoIcon = "carbon:information"
94 const FilterIcon = "carbon:filter-edit"
95
96 const totalAlertsSummary = computed<number>(() => {
97 return alertsSummaryList.value.length || 0
98 })
99 const totalAlerts = computed<number>(() => {
100 return alertsSummaryList.value.reduce((acc: number, val: AlertsSummaryExt) => {
101 return acc + val.alerts.length
102 }, 0)
103 })
104
105 const filters = ref<Partial<GraylogAlertsQuery>>({})
106
107 function addIndexInfo() {
108 if (indices.value?.length && alertsSummaryList.value.length) {
109 for (const alert of alertsSummaryList.value) {
110 const index = indices.value.find(o => o.index === alert.index_name)
111 alert.indexStats = index
112 }
113 }
114 }
115
116 function getData() {
117 loading.value = true
118
119 abortController = new AbortController()
120
121 Api.alerts
122 .getGraylogAlertsList(filters.value, abortController.signal)
123 .then(res => {
124 alertsSummaryList.value = res.data?.alerts_summary || []
125
126 if (res.data.success) {
127 nextTick(() => {
128 addIndexInfo()
129 })
130 } else {
131 message.warning(res.data?.message || "An error occurred. Please try again later.")
132 }
133 })
134 .catch(err => {
135 if (!axios.isCancel(err)) {
136 alertsSummaryList.value = []
137
138 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
139 }
140 })
141 .finally(() => {
142 loading.value = false
143 })
144 }
145
146 function startSearch(closeDrawer?: boolean) {
147 cancelSearch()
148
149 setTimeout(() => {
150 getData()
151 }, 200)
152
153 if (closeDrawer) {
154 showFiltersDrawer.value = false
155 }
156 }
157
158 function cancelSearch() {
159 abortController?.abort()
160 }
161
162 provide<SocAlertField>("soc-alert-creation-field", "alert_id")
163
164 onBeforeMount(() => {
165 nextTick(() => {
166 // MOCK
167 /*
168 alertsSummaryList.value = alerts_summary
169 */
170 startSearch()
171 })
172 })
173
174 onMounted(() => {
175 showFiltersDrawer.value = false
176
177 setTimeout(() => {
178 loadingFilters.value = false
179 }, 1000)
180 })
181
182 onBeforeUnmount(() => {
183 cancelSearch()
184 })
185 </script>