main
vue 204 lines 4.97 KB
Raw
1 <template>
2 <div class="flex flex-col gap-2">
3 <div ref="headerRef" class="flex items-center justify-between">
4 <Chip size="small" :value="loading ? 'Loading...' : paginatedTotal" label="items" />
5
6 <div class="flex items-center gap-2 whitespace-nowrap">
7 <n-pagination
8 v-model:page="pagination.page"
9 v-model:page-size="pagination.pageSize"
10 :page-slot
11 :show-size-picker
12 :page-sizes
13 :item-count="paginatedTotal"
14 :simple="simpleMode"
15 size="small"
16 />
17 </div>
18 </div>
19
20 <div class="grow overflow-hidden">
21 <n-data-table
22 bordered
23 :loading
24 size="small"
25 :data="dataPaginated"
26 :columns
27 :scroll-x="1150"
28 class="[&_.n-data-table-th\_\_title]:whitespace-nowrap"
29 >
30 <template #empty>
31 <n-empty description="No dashboards enabled">
32 <template #extra>Contact your administrator to enable dashboards for your account</template>
33 </n-empty>
34 </template>
35 </n-data-table>
36 </div>
37
38 <div class="flex justify-end">
39 <n-pagination
40 v-if="paginatedTotal > pagination.pageSize"
41 v-model:page="pagination.page"
42 :page-size="pagination.pageSize"
43 :item-count="paginatedTotal"
44 :page-slot="6"
45 size="small"
46 :simple="simpleMode"
47 />
48 </div>
49 </div>
50 </template>
51
52 <script setup lang="tsx">
53 import type { DataTableColumns } from "naive-ui"
54 import type { ApiError } from "@/types/common"
55 import type { EnabledDashboard } from "@/types/siem"
56 import { useDebounceFn, useElementSize } from "@vueuse/core"
57 import axios from "axios"
58 import { NButton, NDataTable, NEmpty, NPagination, useMessage } from "naive-ui"
59 import { computed, onBeforeMount, ref, useTemplateRef, watch } from "vue"
60 import Api from "@/api"
61 import Chip from "@/components/common/Chip.vue"
62 import Icon from "@/components/common/Icon.vue"
63 import { useNavigation } from "@/composables/common/useNavigation"
64 import { useCustomerFilterStore } from "@/stores/customerFilter"
65 import { useSettingsStore } from "@/stores/settings"
66 import { getApiErrorMessage } from "@/utils"
67 import { formatDate } from "@/utils/format"
68
69 const emit = defineEmits<{
70 (e: "loaded", value: EnabledDashboard[]): void
71 (e: "loading", value: boolean): void
72 }>()
73
74 const customerFilterStore = useCustomerFilterStore()
75 const { routeDashboardViewer } = useNavigation()
76 const message = useMessage()
77 const loading = ref(false)
78 const dFormats = useSettingsStore().dateFormat
79
80 const { width: headerWidthRef } = useElementSize(useTemplateRef("headerRef"))
81 const pageSizes = [10, 25, 50, 100]
82 const pageSlot = computed(() => (headerWidthRef.value < 800 ? 5 : 8))
83 const simpleMode = computed(() => headerWidthRef.value < 600)
84 const showSizePicker = ref(true)
85
86 const pagination = ref({
87 page: 1,
88 pageSize: pageSizes[1]
89 })
90
91 const data = ref<EnabledDashboard[]>([])
92
93 const dataPaginated = computed(() => {
94 const from = (pagination.value.page - 1) * pagination.value.pageSize
95 const to = pagination.value.page * pagination.value.pageSize
96
97 return data.value.slice(from, to)
98 })
99
100 const paginatedTotal = computed(() => data.value.length)
101
102 const columns = computed<DataTableColumns<EnabledDashboard>>(() => [
103 {
104 title: "Dashboard",
105 key: "display_name",
106 fixed: simpleMode.value ? undefined : "left",
107 width: 280,
108 render: row => <div>{row.display_name}</div>
109 },
110 {
111 title: "Customer",
112 key: "customer_code",
113 width: 150,
114 render: row => <div>{row.customer_code}</div>
115 },
116 {
117 title: "Category",
118 key: "library_card",
119 width: 230,
120 render: row => <div>{row.library_card}</div>
121 },
122 {
123 title: "Template",
124 key: "template_id",
125 render: row => <div>{row.template_id}</div>
126 },
127 {
128 title: "Created",
129 key: "created_at",
130 width: 180,
131 render: row => <div>{formatDate(row.created_at, dFormats.datetime)}</div>
132 },
133 {
134 title: "Actions",
135 key: "actions",
136 width: 180,
137 render: row => {
138 return (
139 <NButton
140 onClick={() => routeDashboardViewer(row.id).navigate()}
141 v-slots={{
142 icon: () => <Icon name="carbon:launch" />
143 }}
144 >
145 View Dashboard
146 </NButton>
147 )
148 }
149 }
150 ])
151
152 let abortController = new AbortController()
153
154 const loadDashboards = useDebounceFn(async () => {
155 loading.value = true
156
157 abortController?.abort()
158 abortController = new AbortController()
159
160 try {
161 const response = await Api.siem.getEnabledDashboardsForCustomers(customerFilterStore.queryCustomerCodes)
162
163 data.value = response.data?.enabled_dashboards || []
164 emit("loaded", data.value)
165 loading.value = false
166 } catch (err) {
167 if (!axios.isCancel(err)) {
168 message.error(getApiErrorMessage(err as ApiError))
169 loading.value = false
170 }
171 }
172 }, 400)
173
174 function resetPage() {
175 pagination.value.page = 1
176 }
177
178 watch(
179 loading,
180 value => {
181 emit("loading", value)
182 },
183 { immediate: true }
184 )
185
186 watch([() => pagination.value.pageSize], resetPage, {
187 deep: true,
188 immediate: true
189 })
190
191 // Re-fetch when the global customer filter changes (the list is scoped server-side).
192 watch(
193 () => customerFilterStore.selectedCustomerCodes,
194 () => {
195 pagination.value.page = 1
196 loadDashboards()
197 },
198 { deep: true }
199 )
200
201 onBeforeMount(() => {
202 loadDashboards()
203 })
204 </script>