Quota and better drive dashboard

This commit is contained in:
2025-07-27 18:08:39 +08:00
parent 4a0117906a
commit c875c82bdc
16 changed files with 758 additions and 46 deletions

View File

@@ -3,7 +3,16 @@
<div class="h-full flex justify-center items-center" v-if="!usage">
<n-spin />
</div>
<n-grid cols="1 s:2 m:3 l:4" responsive="screen" :x-gap="16" :y-gap="16" v-else>
<n-grid cols="1 s:2 l:4" responsive="screen" :x-gap="16" :y-gap="16" v-else>
<n-gi span="4">
<n-alert title="Billing Tips" size="small" type="info" closable>
<p>
The minimal billable unit is MiB, if your file is not enough 1 MiB it will be counted as
1 MiB.
</p>
<p>The <b>1 MiB = 1024 KiB = 1,048,576 B</b></p>
</n-alert>
</n-gi>
<n-gi>
<n-card class="h-stats">
<n-statistic label="All Uploads" tabular-nums>
@@ -25,23 +34,45 @@
</n-gi>
<n-gi>
<n-card class="h-stats">
<n-statistic label="Cost" tabular-nums>
<n-number-animation :from="0" :to="usage.total_cost" :precision="2" />
<template #suffix>NSD</template>
<n-statistic label="Quota" tabular-nums>
<n-number-animation :from="0" :to="usage.total_quota" />
<template #suffix>MiB</template>
</n-statistic>
</n-card>
</n-gi>
<n-gi>
<n-card class="h-stats">
<n-statistic label="Pools" tabular-nums>
<n-number-animation :from="0" :to="usage.pool_usages.length" />
</n-statistic>
<div class="flex gap-2 justify-between items-end">
<n-statistic label="Used Quota" tabular-nums>
<n-number-animation :from="0" :to="quotaUsagePercentage" :precision="2" />
<template #suffix>%</template>
</n-statistic>
<n-progress
type="circle"
:percentage="quotaUsagePercentage"
:show-indicator="false"
:stroke-width="16"
style="width: 40px"
/>
</div>
</n-card>
</n-gi>
<n-gi span="2">
<n-card class="ratio-video" title="Pool Usage">
<n-card class="aspect-video" title="Pool Usage">
<pie
:data="chartData"
:data="poolChartData"
:options="{
maintainAspectRatio: false,
responsive: true,
plugins: { legend: { position: isDesktop ? 'right' : 'bottom' } },
}"
/>
</n-card>
</n-gi>
<n-gi span="2">
<n-card class="aspect-video h-full" title="Verbose Quota">
<pie
:data="quotaChartData"
:options="{
maintainAspectRatio: false,
responsive: true,
@@ -55,7 +86,7 @@
</template>
<script setup lang="ts">
import { NSpin, NCard, NStatistic, NGrid, NGi, NNumberAnimation } from 'naive-ui'
import { NSpin, NCard, NStatistic, NGrid, NGi, NNumberAnimation, NAlert, NProgress } from 'naive-ui'
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement } from 'chart.js'
import { Pie } from 'vue-chartjs'
import { computed, onMounted, ref } from 'vue'
@@ -66,7 +97,7 @@ ChartJS.register(Title, Tooltip, Legend, ArcElement)
const breakpoints = useBreakpoints(breakpointsTailwind)
const isDesktop = breakpoints.greaterOrEqual('md')
const chartData = computed(() => ({
const poolChartData = computed(() => ({
labels: usage.value.pool_usages.map((pool: any) => pool.pool_name),
datasets: [
{
@@ -74,7 +105,7 @@ const chartData = computed(() => ({
backgroundColor: '#7D80BAFF',
data: usage.value.pool_usages.map((pool: any) => pool.usage_bytes),
},
]
],
}))
const usage = ref<any>()
@@ -91,6 +122,36 @@ async function fetchUsage() {
}
onMounted(() => fetchUsage())
const verboseQuota = ref<
{ based_quota: number; extra_quota: number; total_quota: number } | undefined
>()
async function fetchVerboseQuota() {
try {
const response = await fetch('/api/billing/quota')
if (!response.ok) {
throw new Error('Network response was not ok')
}
verboseQuota.value = await response.json()
} catch (error) {
console.error('Failed to fetch verbose data:', error)
}
}
onMounted(() => fetchVerboseQuota())
const quotaChartData = computed(() => ({
labels: ['Base Quota', 'Extra Quota'],
datasets: [
{
label: 'Verbose Quota',
backgroundColor: '#7D80BAFF',
data: [verboseQuota.value?.based_quota ?? 0, verboseQuota.value?.extra_quota ?? 0],
},
],
}))
const quotaUsagePercentage = computed(
() => (usage.value.used_quota / usage.value.total_quota) * 100,
)
function toGigabytes(bytes: number): number {
return bytes / (1024 * 1024 * 1024)
}