117 lines
3.0 KiB
Vue
117 lines
3.0 KiB
Vue
<script setup>
|
|
import { computed, onMounted, ref } from "vue";
|
|
import { useI18n } from "vue-i18n";
|
|
import { listModuleUsageSummary } from "@/services/moduleUsage.js";
|
|
|
|
const props = defineProps({
|
|
moduleKey: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
metricKey: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
});
|
|
|
|
const { t, locale } = useI18n();
|
|
const loading = ref(false);
|
|
const metric = ref(null);
|
|
|
|
const usagePercent = computed(() => Number(metric.value?.usage_percent ?? 0));
|
|
const hasMetric = computed(() => metric.value && (metric.value.used !== null || metric.value.limit !== null));
|
|
|
|
const load = async () => {
|
|
loading.value = true;
|
|
try {
|
|
const response = await listModuleUsageSummary({ module: props.moduleKey });
|
|
const metrics = Array.isArray(response?.data?.data?.metrics) ? response.data.data.metrics : [];
|
|
metric.value = metrics.find((row) => row.metric_key === props.metricKey) || null;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
function formatNumber(value) {
|
|
if (value === null || value === undefined || Number.isNaN(Number(value))) {
|
|
return "--";
|
|
}
|
|
return new Intl.NumberFormat(locale.value || "da").format(Number(value));
|
|
}
|
|
|
|
function formatPercent(value) {
|
|
if (value === null || value === undefined || Number.isNaN(Number(value))) {
|
|
return "--";
|
|
}
|
|
return `${Number(value).toFixed(1)}%`;
|
|
}
|
|
|
|
function progressClass(value) {
|
|
const percent = Number(value);
|
|
if (!Number.isFinite(percent)) {
|
|
return "";
|
|
}
|
|
if (percent >= 100) {
|
|
return "is-danger";
|
|
}
|
|
if (percent >= 90) {
|
|
return "is-warning";
|
|
}
|
|
return "is-success";
|
|
}
|
|
|
|
onMounted(load);
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="loading || hasMetric" class="module-usage-meter" :data-testid="`module-usage-meter-${moduleKey}-${metricKey}`">
|
|
<div class="module-usage-meter__top">
|
|
<span>{{ metric?.metric_label || $t("system_status.labels.quota_usage") }}</span>
|
|
<strong>{{ loading ? "--" : formatPercent(metric?.usage_percent) }}</strong>
|
|
</div>
|
|
<progress
|
|
class="progress module-usage-meter__progress"
|
|
:class="progressClass(usagePercent)"
|
|
:value="Math.max(0, Math.min(100, usagePercent))"
|
|
max="100"
|
|
/>
|
|
<div class="module-usage-meter__stats">
|
|
<span>{{ t("system_status.labels.used") }}: {{ formatNumber(metric?.used) }}</span>
|
|
<span>{{ t("system_status.labels.limit") }}: {{ formatNumber(metric?.limit) }}</span>
|
|
<span>{{ t("system_status.labels.remaining") }}: {{ formatNumber(metric?.remaining) }}</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.module-usage-meter {
|
|
display: grid;
|
|
gap: 0.4rem;
|
|
margin-top: 0.75rem;
|
|
padding: 0.75rem;
|
|
border: 1px solid #d7dde7;
|
|
border-radius: 8px;
|
|
background: #f8fafc;
|
|
}
|
|
|
|
.module-usage-meter__top {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
color: #334155;
|
|
}
|
|
|
|
.module-usage-meter__progress {
|
|
height: 0.55rem;
|
|
margin: 0;
|
|
}
|
|
|
|
.module-usage-meter__stats {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.75rem;
|
|
color: #475569;
|
|
font-size: 0.85rem;
|
|
}
|
|
</style>
|