main
vue 255 lines 6.86 KB
Raw
1 <template>
2 <div :id="`alert-${alert?.alert_id}`">
3 <CardEntity
4 :loading
5 :loading-description="loadingDelete ? 'Deleting Soc Alert' : 'Loading Soc Alert'"
6 class="min-h-36"
7 clickable
8 hoverable
9 :status="isBookmark ? 'success' : undefined"
10 :embedded
11 :highlighted="!!highlight"
12 @click.stop="showDetails = true"
13 >
14 <template #headerMain>
15 <div class="flex items-center gap-2">
16 <div v-if="showCheckbox" class="check-box mr-2">
17 <n-checkbox v-model:checked="checked" size="large" />
18 </div>
19 <span v-if="alert">#{{ alert.alert_id }} - {{ alert.alert_uuid }}</span>
20 <SocAlertItemBookmarkToggler
21 v-if="!hideBookmarkAction && alert"
22 :alert
23 :is-bookmark
24 @bookmark="emit('bookmark', $event)"
25 />
26 </div>
27 </template>
28 <template v-if="alert" #headerExtra>
29 <SocAlertItemTime :alert />
30 </template>
31
32 <template v-if="alert" #default>
33 <div class="flex flex-col gap-1">
34 {{ alert.alert_title }}
35
36 <p v-if="alert.alert_description && alert.alert_title !== alert.alert_description" class=" ">
37 {{ alert.alert_description }}
38 </p>
39 </div>
40 </template>
41
42 <template #mainExtra>
43 <div class="flex justify-between gap-3">
44 <div>
45 <div
46 v-if="showBadgesToggle"
47 class="show-badges-toggle flex items-center gap-2"
48 @click.stop="showBadges = !showBadges"
49 >
50 {{ showBadges ? "Less info" : "More info" }}
51 <span class="flex items-center transition-transform" :class="{ 'rotate-90': showBadges }">
52 <Icon :name="ChevronIcon" :size="14" />
53 </span>
54 </div>
55 <n-collapse-transition :show="!showBadgesToggle || showBadges">
56 <SocAlertItemBadges v-if="alert" :alert :users @updated="updateAlert" />
57 </n-collapse-transition>
58 </div>
59
60 <SocAlertItemActions
61 v-if="!hideSocCaseAction && alert"
62 class="flex flex-row flex-wrap gap-2"
63 size="small"
64 :case-id
65 :alert-id="alert.alert_id"
66 @case-created="caseCreated($event)"
67 @deleted="deleted()"
68 @start-deleting="loadingDelete = true"
69 />
70 </div>
71 </template>
72 <template #footer>
73 <n-collapse>
74 <template #arrow>
75 <div class="mx-5 flex">
76 <Icon :name="ChevronIcon" />
77 </div>
78 </template>
79 <n-collapse-item>
80 <template #header>
81 <div class="-ml-2 py-3">Alert details</div>
82 </template>
83 <AlertItem :alert="alertObject" hide-actions class="-mt-4" />
84 </n-collapse-item>
85 </n-collapse>
86 </template>
87 </CardEntity>
88
89 <n-modal
90 v-model:show="showDetails"
91 preset="card"
92 content-class="p-0!"
93 :style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(550px, 90vh)', overflow: 'hidden' }"
94 :bordered="false"
95 display-directive="show"
96 segmented
97 >
98 <template #header>
99 <div class="whitespace-nowrap">SOC Alert: {{ alert?.alert_id }}</div>
100 </template>
101 <template #header-extra>
102 <ArtifactRecommendation v-if="alert" :context="alert.alert_context" />
103 </template>
104 <SocAlertItemDetails v-if="alert" :alert :users @updated="updateAlert" />
105 </n-modal>
106 </div>
107 </template>
108
109 <script setup lang="ts">
110 import type { Alert } from "@/types/alerts.d"
111 import type { SocAlert } from "@/types/soc/alert.d"
112 import type { SocUser } from "@/types/soc/user.d"
113 import { NCheckbox, NCollapse, NCollapseItem, NCollapseTransition, NModal, useMessage } from "naive-ui"
114 import { computed, defineAsyncComponent, onBeforeMount, ref, toRefs, watch } from "vue"
115 import Api from "@/api"
116 import CardEntity from "@/components/common/cards/CardEntity.vue"
117 import Icon from "@/components/common/Icon.vue"
118 import SocAlertItemActions from "./SocAlertItemActions.vue"
119 import SocAlertItemBookmarkToggler from "./SocAlertItemBookmarkToggler.vue"
120 import SocAlertItemTime from "./SocAlertItemTime.vue"
121
122 const props = defineProps<{
123 alertData?: SocAlert
124 alertId?: string | number
125 isBookmark?: boolean
126 highlight?: boolean | null | undefined
127 embedded?: boolean
128 users?: SocUser[]
129 hideSocCaseAction?: boolean
130 hideBookmarkAction?: boolean
131 showBadgesToggle?: boolean
132 showCheckbox?: boolean
133 }>()
134 const emit = defineEmits<{
135 (e: "bookmark", value: boolean): void
136 (e: "deleted"): void
137 (e: "checked"): void
138 (e: "unchecked"): void
139 (e: "check", value: boolean): void
140 }>()
141 const SocAlertItemDetails = defineAsyncComponent(() => import("./SocAlertItemDetails.vue"))
142 const SocAlertItemBadges = defineAsyncComponent(() => import("./SocAlertItemBadges.vue"))
143 const ArtifactRecommendation = defineAsyncComponent(() => import("@/components/artifacts/ArtifactRecommendation.vue"))
144 const AlertItem = defineAsyncComponent(() => import("@/components/alerts/Alert.vue"))
145
146 const checked = defineModel<boolean>("checked", { default: false })
147
148 const {
149 alertData,
150 alertId,
151 isBookmark,
152 highlight,
153 users,
154 embedded,
155 hideSocCaseAction,
156 hideBookmarkAction,
157 showBadgesToggle,
158 showCheckbox
159 } = toRefs(props)
160
161 const ChevronIcon = "carbon:chevron-right"
162
163 const showDetails = ref(false)
164 const showBadges = ref(false)
165 const loadingDelete = ref(false)
166 const loadingData = ref(false)
167 const loadingBookmark = ref(false)
168 const message = useMessage()
169
170 const alert = ref(alertData.value || null)
171 const alertObject = ref<Alert>({} as Alert)
172 const loading = computed(() => loadingBookmark.value || loadingData.value || loadingDelete.value)
173 const caseId = computed(() => (alert.value?.cases?.length ? alert.value?.cases[0] : null))
174
175 function updateAlert(alertUpdated: SocAlert) {
176 const ownerObject = alertUpdated.owner
177 const modificationHistory = alertUpdated.modification_history
178
179 if (alert.value) {
180 alert.value.owner = ownerObject
181 alert.value.modification_history = modificationHistory
182 }
183 }
184
185 function getAlert(id: string | number, cb?: () => void) {
186 loadingData.value = true
187
188 Api.soc
189 .getAlert(id.toString())
190 .then(res => {
191 if (res.data.success) {
192 alert.value = res.data?.alert || null
193 if (cb) cb()
194 } else {
195 message.warning(res.data?.message || "An error occurred. Please try again later.")
196 }
197 })
198 .catch(err => {
199 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
200 })
201 .finally(() => {
202 loadingData.value = false
203 })
204 }
205
206 function createAlertObject() {
207 alertObject.value = {
208 _index: "",
209 _id: alert.value?.alert_context.alert_id,
210 _source: alert.value?.alert_source_content
211 } as Alert
212 }
213
214 function caseCreated(caseId: string | number) {
215 if (alert.value) {
216 alert.value.cases = [caseId]
217 }
218 }
219
220 function deleted() {
221 loadingDelete.value = false
222 emit("deleted")
223 }
224
225 watch(checked, val => {
226 emit("check", val)
227 if (val) {
228 emit("checked")
229 } else {
230 emit("unchecked")
231 }
232 })
233
234 onBeforeMount(() => {
235 createAlertObject()
236
237 if (!alertData.value && alertId.value) {
238 getAlert(alertId.value, () => {
239 createAlertObject()
240 })
241 }
242 })
243 </script>
244
245 <style lang="scss" scoped>
246 .show-badges-toggle {
247 font-size: 14px;
248 cursor: pointer;
249 transition: color 0.2s var(--bezier-ease);
250
251 &:hover {
252 color: var(--primary-color);
253 }
254 }
255 </style>