main
vue 161 lines 4.71 KB
Raw
1 <template>
2 <n-spin :show="loading" class="flex grow flex-col" content-class="flex grow flex-col">
3 <n-tabs
4 v-if="alert"
5 v-model:value="activeTab"
6 type="line"
7 animated
8 :tabs-padding="24"
9 class="grow"
10 pane-wrapper-class="flex grow flex-col"
11 >
12 <n-tab-pane name="Overview" tab="Overview" display-directive="show:lazy" class="flex grow flex-col">
13 <div class="pt-1">
14 <AlertOverview :alert @updated="updateAlert($event)" @deleted="emit('deleted')" />
15 </div>
16 </n-tab-pane>
17 <n-tab-pane name="AiAnalyst" display-directive="show:lazy">
18 <template #tab>
19 <div class="flex items-center gap-1.5">
20 <Icon name="carbon:machine-learning-model" :size="14" class="text-primary" />
21 <span>AI Analyst</span>
22 <span v-if="hasAiReport" class="bg-primary inline-block h-2 w-2 animate-pulse rounded-full" />
23 </div>
24 </template>
25 <div class="p-6 pt-3">
26 <AlertAiAnalyst :alert-id="alert.id" />
27 </div>
28 </n-tab-pane>
29 <n-tab-pane name="Timeline" tab="Timeline" display-directive="show:lazy">
30 <div class="p-6 pt-3">
31 <AlertTimeline :alert />
32 </div>
33 </n-tab-pane>
34 <n-tab-pane name="Assets" tab="Assets" display-directive="show:lazy">
35 <div class="p-6 pt-3">
36 <AlertAssetsList :assets="alert.assets" />
37 </div>
38 </n-tab-pane>
39 <n-tab-pane name="Comments" tab="Comments" display-directive="show:lazy">
40 <div class="p-6 pt-3">
41 <AlertCommentsList
42 :comments="alert.comments"
43 :alert-id="alert.id"
44 @updated="updateComments($event)"
45 />
46 </div>
47 </n-tab-pane>
48 <n-tab-pane name="IoCs" tab="IoCs" display-directive="show:lazy">
49 <div class="p-6 pt-3">
50 <AlertIoCsList :iocs="alert.iocs" :alert-id="alert.id" @updated="updateIos($event)" />
51 </div>
52 </n-tab-pane>
53 <n-tab-pane name="TryMcp" display-directive="show:lazy">
54 <template #tab>
55 <div class="flex items-center gap-1.5">
56 <Icon name="carbon:bot" :size="14" class="text-primary" />
57 <span>Try MCP</span>
58 </div>
59 </template>
60 <div class="p-6 pt-3">
61 <AlertTryMcp :alert />
62 </div>
63 </n-tab-pane>
64 </n-tabs>
65 </n-spin>
66 </template>
67
68 <script setup lang="ts">
69 import type { Alert, AlertComment, AlertIOC } from "@/types/incidentManagement/alerts.d"
70 import _clone from "lodash/cloneDeep"
71 import { NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
72 import { defineAsyncComponent, onBeforeMount, ref, toRefs } from "vue"
73 import Api from "@/api"
74 import Icon from "@/components/common/Icon.vue"
75
76 const props = defineProps<{
77 alertData?: Alert
78 alertId?: number
79 }>()
80 const emit = defineEmits<{
81 (e: "deleted"): void
82 (e: "updated", value: Alert): void
83 }>()
84 const AlertTimeline = defineAsyncComponent(() => import("./AlertTimeline.vue"))
85 const AlertAssetsList = defineAsyncComponent(() => import("./AlertAssetsList.vue"))
86 const AlertCommentsList = defineAsyncComponent(() => import("./AlertCommentsList.vue"))
87 const AlertIoCsList = defineAsyncComponent(() => import("./AlertIoCsList.vue"))
88 const AlertOverview = defineAsyncComponent(() => import("./AlertOverview.vue"))
89 const AlertAiAnalyst = defineAsyncComponent(() => import("./AlertAiAnalyst.vue"))
90 const AlertTryMcp = defineAsyncComponent(() => import("./AlertTryMcp.vue"))
91
92 const { alertData, alertId } = toRefs(props)
93
94 const message = useMessage()
95 const loading = ref(false)
96 const alert = ref<Alert | null>(null)
97 const activeTab = ref("Overview")
98 const hasAiReport = ref(false)
99
100 function updateAlert(updatedAlert: Alert) {
101 alert.value = updatedAlert
102 emit("updated", updatedAlert)
103 }
104
105 function updateComments(comments: AlertComment[]) {
106 if (alert.value) {
107 alert.value.comments = comments
108 emit("updated", alert.value)
109 }
110 }
111 function updateIos(iocs: AlertIOC[]) {
112 if (alert.value) {
113 alert.value.iocs = iocs
114 emit("updated", alert.value)
115 }
116 }
117
118 function checkAiReport(alertId: number) {
119 Api.talon
120 .getJob(alertId)
121 .then(res => {
122 if (res.data.success && res.data.data?.reports?.length) {
123 hasAiReport.value = true
124 activeTab.value = "AiAnalyst"
125 }
126 })
127 .catch(() => {
128 // No report — stay on Overview
129 })
130 }
131
132 function getAlert(alertId: number) {
133 loading.value = true
134
135 Api.incidentManagement.alerts
136 .getAlert(alertId)
137 .then(res => {
138 if (res.data.success) {
139 alert.value = res.data?.alerts?.[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 loading.value = false
149 })
150 }
151
152 onBeforeMount(() => {
153 if (alertId.value) {
154 getAlert(alertId.value)
155 checkAiReport(alertId.value)
156 } else if (alertData.value) {
157 alert.value = _clone(alertData.value)
158 checkAiReport(alertData.value.id)
159 }
160 })
161 </script>