main
vue 189 lines 4.54 KB
Raw
1 <template>
2 <n-spin :show="loading">
3 <n-card class="index-details-box" segmented>
4 <template #header>
5 <div class="box-header">
6 <div class="title">
7 <span v-if="currentIndex">Below the details for index</span>
8 <span v-else>Select an index to see the details</span>
9 </div>
10 <div v-if="indices && indices.length" class="select-box">
11 <n-select
12 v-model:value="selectValue"
13 placeholder="Indices list"
14 clearable
15 filterable
16 :options="selectOptions"
17 ></n-select>
18 </div>
19 </div>
20 </template>
21
22 <div v-if="currentIndex" class="details-box">
23 <div class="info">
24 <IndexCard :index="currentIndex" show-actions @delete="clearCurrentIndex()" />
25 </div>
26 <n-card class="shards overflow-hidden" content-style="padding:0">
27 <n-scrollbar x-scrollable style="width: 100%">
28 <n-table :bordered="false" class="min-w-max">
29 <thead>
30 <tr>
31 <th>Node</th>
32 <th>Shard</th>
33 <th>Size</th>
34 <th>State</th>
35 </tr>
36 </thead>
37 <tbody>
38 <tr v-for="shard of filteredShards" :key="shard.id">
39 <td>{{ shard.node || "-" }}</td>
40 <td>{{ shard.shard || "-" }}</td>
41 <td>{{ shard.size || "-" }}</td>
42 <td>
43 <span class="shard-state" :class="shard.state">
44 {{ shard.state || "-" }}
45 </span>
46 </td>
47 </tr>
48 </tbody>
49 </n-table>
50 </n-scrollbar>
51 </n-card>
52 </div>
53 </n-card>
54 </n-spin>
55 </template>
56
57 <script setup lang="ts">
58 import type { IndexShard, IndexStats } from "@/types/indices.d"
59 import { NCard, NScrollbar, NSelect, NSpin, NTable, useMessage } from "naive-ui"
60 import { nanoid } from "nanoid"
61 import { computed, onBeforeMount, ref, toRefs, watch } from "vue"
62 import Api from "@/api"
63 import IndexCard from "@/components/indices/IndexCard.vue"
64
65 type IndexModel = IndexStats | null | ""
66
67 const props = defineProps<{
68 indices: IndexStats[] | null
69 modelValue: IndexModel
70 }>()
71
72 const emit = defineEmits<{
73 (e: "update:modelValue", value: IndexModel): void
74 }>()
75
76 const { indices, modelValue } = toRefs(props)
77
78 const message = useMessage()
79 const shards = ref<IndexShard[]>([])
80 const loadingShards = ref(false)
81 const loading = computed(() => !indices?.value || indices.value === null || loadingShards.value)
82
83 const currentIndex = computed<IndexModel>({
84 get() {
85 return modelValue.value
86 },
87 set(value) {
88 emit("update:modelValue", value)
89 }
90 })
91
92 const filteredShards = computed(() =>
93 shards.value.filter((shard: IndexShard) => {
94 if (!currentIndex.value || typeof currentIndex.value === "string") return false
95 return shard.index === currentIndex.value?.index
96 })
97 )
98
99 const selectValue = ref<string | undefined>(undefined)
100 const selectOptions = computed(() => {
101 return (indices.value || []).map(o => ({ value: o.index, label: o.index }))
102 })
103
104 watch(modelValue, val => {
105 selectValue.value = typeof val !== "string" ? val?.index : undefined
106 })
107
108 watch(selectValue, val => {
109 currentIndex.value = (indices.value || []).find(o => o.index === val) || null
110 })
111
112 function clearCurrentIndex() {
113 currentIndex.value = null
114 }
115
116 function getShards() {
117 loadingShards.value = true
118 Api.wazuh.indices
119 .getShards()
120 .then(res => {
121 if (res.data.success) {
122 shards.value = (res.data?.shards || []).map(obj => {
123 obj.id = nanoid()
124 return obj
125 })
126 } else {
127 message.error(res.data?.message || "An error occurred. Please try again later.")
128 }
129 })
130 .catch(err => {
131 if (err.response?.status === 401) {
132 message.error(
133 err.response?.data?.message ||
134 "Wazuh-Indexer returned Unauthorized. Please check your connector credentials."
135 )
136 } else if (err.response?.status === 404) {
137 message.error(err.response?.data?.message || "No alerts were found.")
138 } else {
139 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
140 }
141 })
142 .finally(() => {
143 loadingShards.value = false
144 })
145 }
146
147 onBeforeMount(() => {
148 getShards()
149 })
150 </script>
151
152 <style lang="scss" scoped>
153 .index-details-box {
154 .box-header {
155 display: flex;
156 align-items: center;
157 justify-content: space-between;
158 gap: calc(var(--spacing) * 4);
159 }
160
161 .details-box {
162 .shards {
163 margin-top: calc(var(--spacing) * 4);
164
165 .shard-state {
166 font-weight: bold;
167 &.STARTED {
168 color: var(--success-color);
169 }
170 &.UNASSIGNED {
171 color: var(--warning-color);
172 }
173 }
174 }
175 }
176
177 @media (max-width: 700px) {
178 .box-header {
179 flex-direction: column;
180 align-items: flex-start;
181 gap: calc(var(--spacing) * 2);
182
183 .select-box {
184 width: 100%;
185 }
186 }
187 }
188 }
189 </style>