main
vue 140 lines 3.5 KB
Raw
1 <template>
2 <n-spin :show="loadingDetails">
3 <div class="@container min-h-50">
4 <n-alert v-if="detailsError" title="Error" type="error" :description="detailsError" />
5
6 <n-tabs v-else-if="alert" type="line" animated>
7 <n-tab-pane name="overview" tab="Overview">
8 <AlertOverview :alert @status-updated="handleStatusUpdated" />
9 </n-tab-pane>
10
11 <n-tab-pane name="assets" tab="Assets">
12 <AlertAssets :alert />
13 </n-tab-pane>
14
15 <n-tab-pane
16 name="linked-cases"
17 :tab="`Linked Cases (${alert?.linked_cases?.length || alert?.case_ids?.length || 0})`"
18 >
19 <AlertCases
20 :alert
21 @created="handleCaseCreated"
22 @updated="handleCaseCreated"
23 @unlinked="handleCaseUnlinked"
24 @linked="handleCaseLinked"
25 />
26 </n-tab-pane>
27
28 <n-tab-pane name="iocs" tab="Indicators of Compromise (IoCs)">
29 <AlertIocs :alert />
30 </n-tab-pane>
31
32 <n-tab-pane name="comments" :tab="`Comments (${alert.comments?.length || 0})`">
33 <AlertComments
34 :alert
35 @added="handleCommentAdded"
36 @updated="handleCommentUpdated"
37 @deleted="handleCommentDeleted"
38 />
39 </n-tab-pane>
40 </n-tabs>
41 </div>
42 </n-spin>
43 </template>
44
45 <script setup lang="ts">
46 import type { AlertStatusUpdateSuccessPayload } from "../AlertStatusSelect.vue"
47 import type { Alert } from "@/types/alerts"
48 import type { CommentItem } from "@/types/comments"
49 import type { ApiError } from "@/types/common"
50 import { NAlert, NSpin, NTabPane, NTabs } from "naive-ui"
51 import { ref, watch } from "vue"
52 import Api from "@/api"
53 import { getApiErrorMessage } from "@/utils"
54 import AlertAssets from "./AlertAssets.vue"
55 import AlertCases from "./AlertCases.vue"
56 import AlertComments from "./AlertComments.vue"
57 import AlertIocs from "./AlertIocs.vue"
58 import AlertOverview from "./AlertOverview.vue"
59
60 const props = defineProps<{
61 alertId: number | null
62 }>()
63
64 const emit = defineEmits<{
65 (e: "statusUpdated", value: AlertStatusUpdateSuccessPayload): void
66 }>()
67
68 const alert = ref<Alert | null>(null)
69 const detailsError = ref<string | null>(null)
70 const loadingDetails = ref(false)
71
72 async function loadAlertDetails() {
73 if (props.alertId === null) return
74
75 loadingDetails.value = true
76 detailsError.value = null
77
78 try {
79 const response = await Api.alerts.getAlert(props.alertId)
80 alert.value = response.data.alerts[0] || null
81 if (!alert.value) {
82 detailsError.value = "Alert not found."
83 }
84 } catch (err) {
85 detailsError.value = getApiErrorMessage(err as ApiError)
86 } finally {
87 loadingDetails.value = false
88 }
89 }
90
91 function handleCommentAdded(comment: CommentItem) {
92 if (!alert.value) return
93
94 if (!alert.value.comments) {
95 alert.value.comments = []
96 }
97 alert.value.comments.push(comment)
98 }
99
100 function handleCommentUpdated(comment: CommentItem) {
101 if (!alert.value) return
102 alert.value.comments = alert.value.comments.map(c => (c.id === comment.id ? comment : c))
103 }
104
105 function handleCommentDeleted(commentId: number) {
106 if (!alert.value) return
107 alert.value.comments = alert.value.comments.filter(c => c.id !== commentId)
108 }
109
110 function handleCaseCreated() {
111 loadAlertDetails()
112 }
113
114 function handleCaseUnlinked() {
115 loadAlertDetails()
116 }
117
118 function handleCaseLinked() {
119 loadAlertDetails()
120 }
121
122 function handleStatusUpdated(payload: AlertStatusUpdateSuccessPayload) {
123 if (!alert.value) return
124 alert.value.status = payload.status
125 emit("statusUpdated", payload)
126 }
127
128 watch(
129 () => props.alertId,
130 async newAlertId => {
131 alert.value = null
132 detailsError.value = null
133
134 if (newAlertId !== null) {
135 await loadAlertDetails()
136 }
137 },
138 { immediate: true }
139 )
140 </script>