main
vue 135 lines 3.32 KB
Raw
1 <template>
2 <div class="flex flex-col gap-4">
3 <div class="flex items-center justify-between gap-4">
4 <div class="text-secondary text-sm">
5 Recent notification dispatches for this customer (newest first, capped at 100).
6 </div>
7 <n-button size="small" :disabled="loading" @click="refreshList()">
8 <template #icon>
9 <Icon :name="RefreshIcon" :size="14" />
10 </template>
11 Refresh
12 </n-button>
13 </div>
14
15 <n-spin :show="loading">
16 <div class="min-h-52">
17 <n-empty
18 v-if="!loading && !entries.length"
19 description="No dispatches yet"
20 class="h-48 justify-center"
21 />
22
23 <n-data-table
24 v-else-if="entries.length"
25 :columns
26 :data="entries"
27 :scroll-x="1000"
28 size="small"
29 :row-key="(r: DispatchLogEntry) => r.id"
30 />
31 </div>
32 </n-spin>
33 </div>
34 </template>
35
36 <script setup lang="ts">
37 import type { DataTableColumns } from "naive-ui"
38 import type { NotificationDispatchLogEntry as DispatchLogEntry } from "@/types/notifications.d"
39 import { NButton, NDataTable, NEmpty, NSpin, useMessage } from "naive-ui"
40 import { computed, h, onBeforeMount, ref } from "vue"
41 import Api from "@/api"
42 import Badge from "@/components/common/Badge.vue"
43 import Icon from "@/components/common/Icon.vue"
44 import { useSettingsStore } from "@/stores/settings"
45 import { getApiErrorMessage } from "@/utils"
46 import { formatDate } from "@/utils/format"
47
48 const { customerCode } = defineProps<{
49 customerCode: string
50 }>()
51
52 const RefreshIcon = "carbon:renew"
53
54 const message = useMessage()
55 const loading = ref(false)
56 const entries = ref<DispatchLogEntry[]>([])
57
58 const dFormats = useSettingsStore().dateFormat
59
60 function statusColor(status: string): "success" | "warning" | "danger" | undefined {
61 if (status === "sent") return "success"
62 if (status === "skipped") return "warning"
63 if (status === "failed") return "danger"
64 return undefined
65 }
66
67 const columns = computed<DataTableColumns<DispatchLogEntry>>(() => [
68 {
69 title: "When",
70 key: "dispatched_at",
71 width: 160,
72 render: row => String(formatDate(row.dispatched_at, dFormats.datetime))
73 },
74 {
75 title: "Alert",
76 key: "alert_id",
77 width: 80,
78 render: row => `#${row.alert_id}`
79 },
80 {
81 title: "Trigger",
82 key: "trigger",
83 width: 220,
84 render: row => (row.trigger === "investigation_complete" ? "Every investigation" : "Critical / High only")
85 },
86 {
87 title: "Status",
88 key: "status",
89 minWidth: 130,
90 render: row =>
91 h(
92 Badge,
93 { type: "splitted", color: statusColor(row.status), class: "whitespace-nowrap" },
94 {
95 label: () => "Status",
96 value: () => row.status
97 }
98 )
99 },
100 {
101 title: "Latency",
102 key: "latency_ms",
103 width: 100,
104 render: row => (row.latency_ms == null ? "" : `${row.latency_ms} ms`)
105 },
106 {
107 title: "Error / Preview",
108 key: "detail",
109 maxWidth: 200,
110 ellipsis: { tooltip: { to: "body ", class: "max-w-[90vw] text-sm" } },
111 render: row => row.error_message || row.payload_preview || ""
112 }
113 ])
114
115 function refreshList() {
116 loading.value = true
117 Api.notifications
118 .listDispatchLog(customerCode)
119 .then(res => {
120 if (res.data.success) {
121 entries.value = res.data.entries
122 } else {
123 message.warning(res.data.message || "Failed to load dispatch log")
124 }
125 })
126 .catch(err => {
127 message.error(getApiErrorMessage(err) || "Failed to load dispatch log")
128 })
129 .finally(() => {
130 loading.value = false
131 })
132 }
133
134 onBeforeMount(refreshList)
135 </script>