| 1 | <template> |
| 2 | <n-card title="Storage per Customer" segmented content-class="pr-1!"> |
| 3 | <n-spin :show="loading"> |
| 4 | <div v-if="customerSizes && customerSizes.length > 0"> |
| 5 | <n-scrollbar style="max-height: 200px" trigger="none" content-class="flex flex-col gap-4 pr-5!"> |
| 6 | <div v-for="customer in customerSizes" :key="customer.customer" class="flex flex-col gap-1"> |
| 7 | <div class="flex items-center justify-between gap-2"> |
| 8 | <div class="flex items-center gap-2"> |
| 9 | <div class="font-mono font-bold">{{ customer.customer }}</div> |
| 10 | <n-popover trigger="click" placement="bottom" :width="300"> |
| 11 | <template #trigger> |
| 12 | <n-tag size="small" class="cursor-pointer!"> |
| 13 | {{ customer.index_count }} |
| 14 | {{ customer.index_count === 1 ? "index" : "indices" }} |
| 15 | </n-tag> |
| 16 | </template> |
| 17 | <div class="flex flex-col gap-2"> |
| 18 | <span class="text-sm font-semibold">Indices for {{ customer.customer }}</span> |
| 19 | <n-scrollbar style="max-height: 200px" trigger="none"> |
| 20 | <ul> |
| 21 | <li |
| 22 | v-for="index in customer.indices" |
| 23 | :key="index" |
| 24 | @click="selectIndex(index)" |
| 25 | > |
| 26 | <div |
| 27 | class="hover:text-primary flex cursor-pointer items-center gap-2 font-mono text-sm transition-colors" |
| 28 | > |
| 29 | {{ index }} |
| 30 | |
| 31 | <Icon name="carbon:launch" /> |
| 32 | </div> |
| 33 | </li> |
| 34 | </ul> |
| 35 | </n-scrollbar> |
| 36 | </div> |
| 37 | </n-popover> |
| 38 | </div> |
| 39 | <div class="text-secondary font-mono text-sm">{{ customer.total_size_human }}</div> |
| 40 | </div> |
| 41 | <n-progress |
| 42 | type="line" |
| 43 | :percentage="getPercentage(customer.total_size_bytes)" |
| 44 | :show-indicator="false" |
| 45 | :height="8" |
| 46 | :border-radius="4" |
| 47 | :color="getProgressColor(customer.total_size_bytes)" |
| 48 | /> |
| 49 | </div> |
| 50 | </n-scrollbar> |
| 51 | </div> |
| 52 | <n-empty v-else-if="!loading" description="No customer data available" /> |
| 53 | </n-spin> |
| 54 | </n-card> |
| 55 | </template> |
| 56 | |
| 57 | <script lang="ts" setup> |
| 58 | import { NCard, NEmpty, NPopover, NProgress, NScrollbar, NSpin, NTag, useMessage, useThemeVars } from "naive-ui" |
| 59 | import { computed, onBeforeMount, ref } from "vue" |
| 60 | import Api from "@/api" |
| 61 | import Icon from "@/components/common/Icon.vue" |
| 62 | |
| 63 | interface CustomerIndicesSize { |
| 64 | customer: string |
| 65 | total_size_bytes: number |
| 66 | total_size_human: string |
| 67 | index_count: number |
| 68 | indices: string[] |
| 69 | } |
| 70 | |
| 71 | const emit = defineEmits<{ |
| 72 | (e: "click", value: string): void |
| 73 | }>() |
| 74 | |
| 75 | const message = useMessage() |
| 76 | const themeVars = useThemeVars() |
| 77 | |
| 78 | const loading = ref(false) |
| 79 | const customerSizes = ref<CustomerIndicesSize[]>([]) |
| 80 | |
| 81 | const maxSize = computed(() => { |
| 82 | if (!customerSizes.value.length) return 0 |
| 83 | return Math.max(...customerSizes.value.map(c => c.total_size_bytes)) |
| 84 | }) |
| 85 | |
| 86 | function getPercentage(sizeBytes: number): number { |
| 87 | if (!maxSize.value) return 0 |
| 88 | return Math.round((sizeBytes / maxSize.value) * 100) |
| 89 | } |
| 90 | |
| 91 | function getProgressColor(sizeBytes: number): string { |
| 92 | const percentage = getPercentage(sizeBytes) |
| 93 | if (percentage >= 80) return themeVars.value.errorColor |
| 94 | if (percentage >= 60) return themeVars.value.warningColor |
| 95 | return themeVars.value.infoColor |
| 96 | } |
| 97 | |
| 98 | function selectIndex(indexName: string) { |
| 99 | emit("click", indexName) |
| 100 | } |
| 101 | |
| 102 | function getCustomerIndicesSize() { |
| 103 | loading.value = true |
| 104 | |
| 105 | Api.wazuh.indices |
| 106 | .getIndicesSizePerCustomer() |
| 107 | .then(res => { |
| 108 | if (res.data.success) { |
| 109 | customerSizes.value = res.data.customer_sizes || [] |
| 110 | } else { |
| 111 | message.error(res.data?.message || "An error occurred. Please try again later.") |
| 112 | } |
| 113 | }) |
| 114 | .catch(err => { |
| 115 | message.error(err.response?.data?.message || "Failed to retrieve customer indices size.") |
| 116 | }) |
| 117 | .finally(() => { |
| 118 | loading.value = false |
| 119 | }) |
| 120 | } |
| 121 | |
| 122 | onBeforeMount(() => { |
| 123 | getCustomerIndicesSize() |
| 124 | }) |
| 125 | </script> |