main
vue 190 lines 5.19 KB
Raw
1 <template>
2 <div class="mb-6 flex justify-end">
3 <div>
4 <n-input-group>
5 <n-select
6 v-model:value="filterUnit"
7 :options="unitOptions"
8 placeholder="Time unit"
9 clearable
10 class="w-28!"
11 />
12 <n-input-number v-model:value="filterTime" :min="1" clearable placeholder="Time" class="w-32!" />
13 </n-input-group>
14 </div>
15 </div>
16 <n-spin :show="loading">
17 <div class="flex min-h-52 flex-col gap-6">
18 <n-collapse v-if="healthyList.length" :default-expanded-names="['healthy']">
19 <n-collapse-item name="healthy">
20 <template #header>
21 <div class="text-primary flex items-center gap-2">
22 <Icon :name="CheckIcon" :size="16" />
23 Healthy
24 <code>{{ healthyList.length }}</code>
25 </div>
26 </template>
27 <div class="flex flex-col gap-2">
28 <CustomerHealthcheckItem
29 v-for="item of healthyList"
30 :key="item.id"
31 :health-data="item"
32 :source
33 type="healthy"
34 embedded
35 class="item-appear item-appear-bottom item-appear-005"
36 />
37 </div>
38 </n-collapse-item>
39 </n-collapse>
40 <n-collapse v-if="unhealthyList.length" :default-expanded-names="['unhealthy']">
41 <n-collapse-item name="unhealthy">
42 <template #header>
43 <div class="text-warning flex items-center gap-2">
44 <Icon :name="AlertIcon" :size="16" />
45 Unhealthy
46 <code>{{ unhealthyList.length }}</code>
47 </div>
48 </template>
49 <div class="flex flex-col gap-2">
50 <CustomerHealthcheckItem
51 v-for="item of unhealthyList"
52 :key="item.id"
53 :health-data="item"
54 :source
55 type="unhealthy"
56 embedded
57 class="item-appear item-appear-bottom item-appear-005"
58 />
59 </div>
60 </n-collapse-item>
61 </n-collapse>
62 <n-empty v-if="!healthyList.length && !unhealthyList.length && !loading" class="h-48 justify-center" />
63 </div>
64 </n-spin>
65 </template>
66
67 <script setup lang="ts">
68 // TODO-FE: refactor
69 import type { CustomerAgentsHealthcheckQuery } from "@/api/endpoints/customers"
70 import type { CustomerAgentHealth, CustomerHealthcheckSource } from "@/types/customers.d"
71 import { watchDebounced } from "@vueuse/core"
72 import _get from "lodash/get"
73 import { NCollapse, NCollapseItem, NEmpty, NInputGroup, NInputNumber, NSelect, NSpin, useMessage } from "naive-ui"
74 import { computed, onBeforeMount, ref, watch } from "vue"
75 import Api from "@/api"
76 import Icon from "@/components/common/Icon.vue"
77 import CustomerHealthcheckItem from "./CustomerHealthcheckItem.vue"
78
79 const props = defineProps<{
80 source: CustomerHealthcheckSource
81 customerCode: string
82 filters: Partial<{ time: number; unit: "minutes" | "hours" | "days" }>
83 }>()
84
85 const emit = defineEmits<{
86 (e: "update:filters", value: Partial<{ time: number; unit: "minutes" | "hours" | "days" }>): void
87 }>()
88
89 const { source, customerCode } = props
90
91 const CheckIcon = "carbon:checkmark-outline"
92 const AlertIcon = "mdi:alert-outline"
93
94 const loading = ref(false)
95 const healthyList = ref<CustomerAgentHealth[]>([])
96 const unhealthyList = ref<CustomerAgentHealth[]>([])
97 const message = useMessage()
98
99 const unitOptions = [
100 { label: "Minutes", value: "minutes" },
101 { label: "Hours", value: "hours" },
102 { label: "Days", value: "days" }
103 ]
104
105 const filters = computed({
106 get: () => props.filters,
107 set: value => emit("update:filters", value)
108 })
109
110 const filterTime = computed({
111 get: () => props.filters.time,
112 set: value => emit("update:filters", { ...props.filters, time: value })
113 })
114
115 const filterUnit = computed({
116 get: () => props.filters.unit,
117 set: value => emit("update:filters", { ...props.filters, unit: value })
118 })
119
120 function getList() {
121 loading.value = true
122
123 const params = {
124 method: "" as "getCustomerAgentsHealthcheckWazuh" | "getCustomerAgentsHealthcheckVelociraptor",
125 healthy: "",
126 unhealthy: ""
127 }
128
129 switch (source) {
130 case "wazuh":
131 params.method = "getCustomerAgentsHealthcheckWazuh"
132 params.healthy = "healthy_wazuh_agents"
133 params.unhealthy = "unhealthy_wazuh_agents"
134 break
135 case "velociraptor":
136 params.method = "getCustomerAgentsHealthcheckVelociraptor"
137 params.healthy = "healthy_velociraptor_agents"
138 params.unhealthy = "unhealthy_velociraptor_agents"
139 break
140 }
141
142 if (!params.method) {
143 return
144 }
145
146 let query: CustomerAgentsHealthcheckQuery | undefined
147 if (filters.value.time && filters.value.unit) {
148 query = {}
149 query[filters.value.unit] = filters.value.time
150 }
151
152 Api.customers[params.method](customerCode, query)
153 .then(res => {
154 if (res.data.success) {
155 healthyList.value = _get(res, `data.${params.healthy}`, [])
156 unhealthyList.value = _get(res, `data.${params.unhealthy}`, [])
157 } else {
158 message.warning(res.data?.message || "An error occurred. Please try again later.")
159 }
160 })
161 .catch(err => {
162 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
163 })
164 .finally(() => {
165 loading.value = false
166 })
167 }
168
169 watchDebounced(
170 () => filters.value.time,
171 () => {
172 if ((filters.value.time && filters.value.unit) || (!filters.value.time && !filters.value.unit)) {
173 getList()
174 }
175 },
176 { debounce: 500 }
177 )
178 watch(
179 () => filters.value.unit,
180 () => {
181 if ((filters.value.time && filters.value.unit) || (!filters.value.time && !filters.value.unit)) {
182 getList()
183 }
184 }
185 )
186
187 onBeforeMount(() => {
188 getList()
189 })
190 </script>