main
vue 135 lines 3.38 KB
Raw
1 <template>
2 <n-card class="cluster-health" segmented>
3 <template #header>
4 <div class="align-center flex justify-between">
5 <span>Overall Health</span>
6 <IndexIcon v-if="cluster" :health="cluster.status" color />
7 </div>
8 </template>
9 <n-spin :show="loading">
10 <div v-if="cluster" class="min-h-14">
11 <n-scrollbar style="max-height: 500px" trigger="none">
12 <div class="card-wrap">
13 <div v-for="prop of propsOrder" :key="prop" class="box">
14 <template v-if="prop === 'status'">
15 <div class="value align-center flex gap-2 uppercase">
16 <IndexIcon :health="cluster.status" color />
17 {{ cluster.status }}
18 </div>
19 </template>
20 <template v-else>
21 <div class="value">
22 {{ cluster[prop] }}
23 </div>
24 </template>
25 <div class="label">
26 {{ sanitizeLabel(prop) }}
27 </div>
28 </div>
29 </div>
30 </n-scrollbar>
31 </div>
32 <template v-else>
33 <n-empty v-if="!loading" description="No cluster found" class="h-48 justify-center" />
34 </template>
35 </n-spin>
36 </n-card>
37 </template>
38
39 <script setup lang="ts">
40 import type { ClusterHealth } from "@/types/indices.d"
41 import { NCard, NEmpty, NScrollbar, NSpin, useMessage } from "naive-ui"
42 import { onBeforeMount, ref } from "vue"
43 import Api from "@/api"
44 import IndexIcon from "@/components/indices/IndexIcon.vue"
45
46 const message = useMessage()
47 const cluster = ref<ClusterHealth | null>(null)
48 const loading = ref(true)
49
50 const propsOrder = ref<Array<keyof ClusterHealth>>([
51 "cluster_name",
52 "status",
53 "active_primary_shards",
54 "active_shards",
55 "active_shards_percent_as_number",
56 "delayed_unassigned_shards",
57 "discovered_cluster_manager",
58 "discovered_master",
59 "initializing_shards",
60 "number_of_data_nodes",
61 "number_of_in_flight_fetch",
62 "number_of_nodes",
63 "number_of_pending_tasks",
64 "relocating_shards",
65 "task_max_waiting_in_queue_millis",
66 "timed_out",
67 "unassigned_shards"
68 ])
69
70 const UNDERSCORE_REGEX = /_/g
71
72 function sanitizeLabel(label: string) {
73 return label.replace(UNDERSCORE_REGEX, " ")
74 }
75
76 function getClusterHealth() {
77 loading.value = true
78 Api.wazuh.indices
79 .getClusterHealth()
80 .then(res => {
81 if (res.data.success) {
82 cluster.value = res.data.cluster_health
83 } else {
84 message.error(res.data?.message || "An error occurred. Please try again later.")
85 }
86 })
87 .catch(err => {
88 if (err.response?.status === 401) {
89 message.error(
90 err.response?.data?.message ||
91 "Wazuh-Indexer returned Unauthorized. Please check your connector credentials."
92 )
93 } else if (err.response?.status === 404) {
94 message.error(err.response?.data?.message || "No alerts were found.")
95 } else {
96 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
97 }
98 })
99 .finally(() => {
100 loading.value = false
101 })
102 }
103
104 onBeforeMount(() => {
105 getClusterHealth()
106 })
107 </script>
108
109 <style lang="scss" scoped>
110 .cluster-health {
111 .card-wrap {
112 gap: calc(var(--spacing) * 6);
113 column-gap: calc(var(--spacing) * 6);
114 padding-inline: calc(var(--spacing) * 4);
115 padding-block: calc(var(--spacing) * 3);
116 column-width: 12rem;
117 column-count: auto;
118
119 .box {
120 overflow: hidden;
121 margin-bottom: calc(var(--spacing) * 6);
122 .value {
123 font-weight: bold;
124 margin-bottom: 2px;
125 white-space: nowrap;
126 }
127 .label {
128 font-size: var(--text-xs);
129 font-family: var(--font-family-mono);
130 opacity: 0.8;
131 }
132 }
133 }
134 }
135 </style>