main
vue 111 lines 3.66 KB
Raw
1 <template>
2 <n-spin :show="loading" class="min-h-40">
3 <div ref="jobsListRef" class="flex flex-col gap-2">
4 <template v-if="jobs.length">
5 <CardEntity v-for="job of jobs" :key="job.id" size="small" embedded>
6 <template #default>
7 <div class="flex flex-wrap items-center gap-3">
8 <Badge type="splitted" bright :color="statusColor(job.status)">
9 <template #label>Status</template>
10 <template #value>{{ job.status }}</template>
11 </Badge>
12 <Badge type="splitted">
13 <template #label>Triggered By</template>
14 <template #value>{{ job.triggered_by }}</template>
15 </Badge>
16 <Badge v-if="job.alert_type" type="splitted">
17 <template #label>Alert Type</template>
18 <template #value>{{ job.alert_type }}</template>
19 </Badge>
20 <Badge v-if="job.template_used" type="splitted">
21 <template #label>Template</template>
22 <template #value>{{ job.template_used }}</template>
23 </Badge>
24 </div>
25 </template>
26 <template v-if="job.error_message" #mainExtra>
27 <n-alert type="error" :bordered="false" size="small">
28 {{ job.error_message }}
29 </n-alert>
30 </template>
31 <template #footer>
32 <n-timeline :horizontal="horizontalMode">
33 <n-timeline-item title="Created">
34 <div class="text-secondary font-mono text-sm">
35 {{ formatDate(job.created_at, dFormats.datetime).toString() }}
36 </div>
37 </n-timeline-item>
38 <n-timeline-item v-if="job.started_at" title="Started" type="warning">
39 <div class="text-secondary font-mono text-sm">
40 {{ formatDate(job.started_at, dFormats.datetime).toString() }}
41 </div>
42 </n-timeline-item>
43 <n-timeline-item v-if="job.completed_at" title="Completed" type="success">
44 <div class="text-secondary font-mono text-sm">
45 {{ formatDate(job.completed_at, dFormats.datetime).toString() }}
46 </div>
47 </n-timeline-item>
48 </n-timeline>
49 </template>
50 </CardEntity>
51 </template>
52 <n-empty v-else-if="!loading" description="No jobs found for this alert" />
53 </div>
54 </n-spin>
55 </template>
56
57 <script setup lang="ts">
58 import type { AiAnalystJob } from "@/types/aiAnalyst.d"
59 import { useElementSize } from "@vueuse/core"
60 import { NAlert, NEmpty, NSpin, NTimeline, NTimelineItem, useMessage } from "naive-ui"
61 import { computed, onBeforeMount, ref, toRefs, useTemplateRef } from "vue"
62 import Api from "@/api"
63 import Badge from "@/components/common/Badge.vue"
64 import CardEntity from "@/components/common/cards/CardEntity.vue"
65 import { useSettingsStore } from "@/stores/settings"
66 import { formatDate } from "@/utils/format"
67
68 const props = defineProps<{
69 alertId: number
70 }>()
71
72 const { alertId } = toRefs(props)
73
74 const { width: jobsListWidthRef } = useElementSize(useTemplateRef("jobsListRef"))
75 const horizontalMode = computed(() => jobsListWidthRef.value > 550)
76 const message = useMessage()
77 const dFormats = useSettingsStore().dateFormat
78 const loading = ref(false)
79 const jobs = ref<AiAnalystJob[]>([])
80
81 function statusColor(status: string) {
82 if (status === "completed") return "success"
83 if (status === "running") return "warning"
84 if (status === "failed") return "danger"
85 return undefined
86 }
87
88 function getData() {
89 loading.value = true
90
91 Api.aiAnalyst
92 .getJobsByAlert(alertId.value)
93 .then(res => {
94 if (res.data.success) {
95 jobs.value = res.data?.jobs || []
96 } else {
97 message.warning(res.data?.message || "An error occurred. Please try again later.")
98 }
99 })
100 .catch(err => {
101 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
102 })
103 .finally(() => {
104 loading.value = false
105 })
106 }
107
108 onBeforeMount(() => {
109 getData()
110 })
111 </script>