优化仪表盘自动刷新UI布局

- 调整Element Plus日期选择器宽度为400px,确保时间完整显示
- 重新设计自动刷新控制的样式和布局
- 统一控制栏所有元素的高度,保持视觉一致性
- 使用更精致的开关组件和优化的交互效果

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-30 15:36:52 +08:00
parent 1ca753c79a
commit 7116a6e043
29 changed files with 3869 additions and 2344 deletions

View File

@@ -1,56 +1,22 @@
<template>
<div>
<!-- 自动刷新控制栏 -->
<div class="mb-6 bg-white rounded-lg shadow-sm p-4">
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div class="flex items-center gap-2 sm:gap-4">
<h2 class="text-lg font-semibold text-gray-800">系统仪表盘</h2>
<div v-if="refreshCountdownDisplay" class="text-sm text-gray-500 whitespace-nowrap">
<i class="fas fa-clock"></i>
{{ refreshCountdownDisplay }}
</div>
</div>
<div class="flex items-center gap-2 sm:gap-4 w-full sm:w-auto">
<!-- 手动刷新按钮 -->
<button
@click="refreshAllData"
:disabled="isRefreshing"
class="px-4 py-2 text-sm bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
<i :class="['fas fa-sync-alt', { 'animate-spin': isRefreshing }]"></i>
{{ isRefreshing ? '刷新中...' : '刷新数据' }}
</button>
<!-- 自动刷新开关 -->
<div class="flex items-center gap-2">
<label class="flex items-center cursor-pointer">
<input
type="checkbox"
v-model="autoRefreshEnabled"
class="sr-only peer"
>
<div class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
<span class="ml-3 text-sm font-medium text-gray-700">
自动刷新
<span class="text-xs text-gray-500">(30)</span>
</span>
</label>
</div>
</div>
</div>
</div>
<!-- 主要统计 -->
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6 mb-8">
<div class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-semibold text-gray-600 mb-1">总API Keys</p>
<p class="text-3xl font-bold text-gray-900">{{ dashboardData.totalApiKeys }}</p>
<p class="text-xs text-gray-500 mt-1">活跃: {{ dashboardData.activeApiKeys || 0 }}</p>
<p class="text-sm font-semibold text-gray-600 mb-1">
总API Keys
</p>
<p class="text-3xl font-bold text-gray-900">
{{ dashboardData.totalApiKeys }}
</p>
<p class="text-xs text-gray-500 mt-1">
活跃: {{ dashboardData.activeApiKeys || 0 }}
</p>
</div>
<div class="stat-icon flex-shrink-0 bg-gradient-to-br from-blue-500 to-blue-600">
<i class="fas fa-key"></i>
<i class="fas fa-key" />
</div>
</div>
</div>
@@ -58,17 +24,24 @@
<div class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-semibold text-gray-600 mb-1">服务账户</p>
<p class="text-3xl font-bold text-gray-900">{{ dashboardData.totalAccounts }}</p>
<p class="text-sm font-semibold text-gray-600 mb-1">
服务账户
</p>
<p class="text-3xl font-bold text-gray-900">
{{ dashboardData.totalAccounts }}
</p>
<p class="text-xs text-gray-500 mt-1">
活跃: {{ dashboardData.activeAccounts || 0 }}
<span v-if="dashboardData.rateLimitedAccounts > 0" class="text-yellow-600">
<span
v-if="dashboardData.rateLimitedAccounts > 0"
class="text-yellow-600"
>
| 限流: {{ dashboardData.rateLimitedAccounts }}
</span>
</p>
</div>
<div class="stat-icon flex-shrink-0 bg-gradient-to-br from-green-500 to-green-600">
<i class="fas fa-user-circle"></i>
<i class="fas fa-user-circle" />
</div>
</div>
</div>
@@ -76,12 +49,18 @@
<div class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-semibold text-gray-600 mb-1">今日请求</p>
<p class="text-3xl font-bold text-gray-900">{{ dashboardData.todayRequests }}</p>
<p class="text-xs text-gray-500 mt-1">总请求: {{ formatNumber(dashboardData.totalRequests || 0) }}</p>
<p class="text-sm font-semibold text-gray-600 mb-1">
今日请求
</p>
<p class="text-3xl font-bold text-gray-900">
{{ dashboardData.todayRequests }}
</p>
<p class="text-xs text-gray-500 mt-1">
总请求: {{ formatNumber(dashboardData.totalRequests || 0) }}
</p>
</div>
<div class="stat-icon flex-shrink-0 bg-gradient-to-br from-purple-500 to-purple-600">
<i class="fas fa-chart-line"></i>
<i class="fas fa-chart-line" />
</div>
</div>
</div>
@@ -89,12 +68,18 @@
<div class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-semibold text-gray-600 mb-1">系统状态</p>
<p class="text-3xl font-bold text-green-600">{{ dashboardData.systemStatus }}</p>
<p class="text-xs text-gray-500 mt-1">运行时间: {{ formattedUptime }}</p>
<p class="text-sm font-semibold text-gray-600 mb-1">
系统状态
</p>
<p class="text-3xl font-bold text-green-600">
{{ dashboardData.systemStatus }}
</p>
<p class="text-xs text-gray-500 mt-1">
运行时间: {{ formattedUptime }}
</p>
</div>
<div class="stat-icon flex-shrink-0 bg-gradient-to-br from-yellow-500 to-orange-500">
<i class="fas fa-heartbeat"></i>
<i class="fas fa-heartbeat" />
</div>
</div>
</div>
@@ -105,22 +90,32 @@
<div class="stat-card">
<div class="flex items-center justify-between">
<div class="flex-1 mr-8">
<p class="text-sm font-semibold text-gray-600 mb-1">今日Token</p>
<p class="text-sm font-semibold text-gray-600 mb-1">
今日Token
</p>
<div class="flex items-baseline gap-2 mb-2 flex-wrap">
<p class="text-3xl font-bold text-blue-600">{{ formatNumber((dashboardData.todayInputTokens || 0) + (dashboardData.todayOutputTokens || 0) + (dashboardData.todayCacheCreateTokens || 0) + (dashboardData.todayCacheReadTokens || 0)) }}</p>
<p class="text-3xl font-bold text-blue-600">
{{ formatNumber((dashboardData.todayInputTokens || 0) + (dashboardData.todayOutputTokens || 0) + (dashboardData.todayCacheCreateTokens || 0) + (dashboardData.todayCacheReadTokens || 0)) }}
</p>
<span class="text-sm text-green-600 font-medium">/ {{ costsData.todayCosts.formatted.totalCost }}</span>
</div>
<div class="text-xs text-gray-500">
<div class="flex justify-between items-center flex-wrap gap-x-4">
<span>输入: <span class="font-medium">{{ formatNumber(dashboardData.todayInputTokens || 0) }}</span></span>
<span>输出: <span class="font-medium">{{ formatNumber(dashboardData.todayOutputTokens || 0) }}</span></span>
<span v-if="(dashboardData.todayCacheCreateTokens || 0) > 0" class="text-purple-600">缓存创建: <span class="font-medium">{{ formatNumber(dashboardData.todayCacheCreateTokens || 0) }}</span></span>
<span v-if="(dashboardData.todayCacheReadTokens || 0) > 0" class="text-purple-600">缓存读取: <span class="font-medium">{{ formatNumber(dashboardData.todayCacheReadTokens || 0) }}</span></span>
<span
v-if="(dashboardData.todayCacheCreateTokens || 0) > 0"
class="text-purple-600"
>缓存创建: <span class="font-medium">{{ formatNumber(dashboardData.todayCacheCreateTokens || 0) }}</span></span>
<span
v-if="(dashboardData.todayCacheReadTokens || 0) > 0"
class="text-purple-600"
>缓存读取: <span class="font-medium">{{ formatNumber(dashboardData.todayCacheReadTokens || 0) }}</span></span>
</div>
</div>
</div>
<div class="stat-icon flex-shrink-0 bg-gradient-to-br from-indigo-500 to-indigo-600">
<i class="fas fa-coins"></i>
<i class="fas fa-coins" />
</div>
</div>
</div>
@@ -128,22 +123,32 @@
<div class="stat-card">
<div class="flex items-center justify-between">
<div class="flex-1 mr-8">
<p class="text-sm font-semibold text-gray-600 mb-1">总Token消耗</p>
<p class="text-sm font-semibold text-gray-600 mb-1">
总Token消耗
</p>
<div class="flex items-baseline gap-2 mb-2 flex-wrap">
<p class="text-3xl font-bold text-emerald-600">{{ formatNumber((dashboardData.totalInputTokens || 0) + (dashboardData.totalOutputTokens || 0) + (dashboardData.totalCacheCreateTokens || 0) + (dashboardData.totalCacheReadTokens || 0)) }}</p>
<p class="text-3xl font-bold text-emerald-600">
{{ formatNumber((dashboardData.totalInputTokens || 0) + (dashboardData.totalOutputTokens || 0) + (dashboardData.totalCacheCreateTokens || 0) + (dashboardData.totalCacheReadTokens || 0)) }}
</p>
<span class="text-sm text-green-600 font-medium">/ {{ costsData.totalCosts.formatted.totalCost }}</span>
</div>
<div class="text-xs text-gray-500">
<div class="flex justify-between items-center flex-wrap gap-x-4">
<span>输入: <span class="font-medium">{{ formatNumber(dashboardData.totalInputTokens || 0) }}</span></span>
<span>输出: <span class="font-medium">{{ formatNumber(dashboardData.totalOutputTokens || 0) }}</span></span>
<span v-if="(dashboardData.totalCacheCreateTokens || 0) > 0" class="text-purple-600">缓存创建: <span class="font-medium">{{ formatNumber(dashboardData.totalCacheCreateTokens || 0) }}</span></span>
<span v-if="(dashboardData.totalCacheReadTokens || 0) > 0" class="text-purple-600">缓存读取: <span class="font-medium">{{ formatNumber(dashboardData.totalCacheReadTokens || 0) }}</span></span>
<span
v-if="(dashboardData.totalCacheCreateTokens || 0) > 0"
class="text-purple-600"
>缓存创建: <span class="font-medium">{{ formatNumber(dashboardData.totalCacheCreateTokens || 0) }}</span></span>
<span
v-if="(dashboardData.totalCacheReadTokens || 0) > 0"
class="text-purple-600"
>缓存读取: <span class="font-medium">{{ formatNumber(dashboardData.totalCacheReadTokens || 0) }}</span></span>
</div>
</div>
</div>
<div class="stat-icon flex-shrink-0 bg-gradient-to-br from-emerald-500 to-emerald-600">
<i class="fas fa-database"></i>
<i class="fas fa-database" />
</div>
</div>
</div>
@@ -155,16 +160,21 @@
实时RPM
<span class="text-xs text-gray-400">({{ dashboardData.metricsWindow }}分钟)</span>
</p>
<p class="text-3xl font-bold text-orange-600">{{ dashboardData.realtimeRPM || 0 }}</p>
<p class="text-3xl font-bold text-orange-600">
{{ dashboardData.realtimeRPM || 0 }}
</p>
<p class="text-xs text-gray-500 mt-1">
每分钟请求数
<span v-if="dashboardData.isHistoricalMetrics" class="text-yellow-600">
<i class="fas fa-exclamation-circle"></i> 历史数据
<span
v-if="dashboardData.isHistoricalMetrics"
class="text-yellow-600"
>
<i class="fas fa-exclamation-circle" /> 历史数据
</span>
</p>
</div>
<div class="stat-icon flex-shrink-0 bg-gradient-to-br from-orange-500 to-orange-600">
<i class="fas fa-tachometer-alt"></i>
<i class="fas fa-tachometer-alt" />
</div>
</div>
</div>
@@ -176,16 +186,21 @@
实时TPM
<span class="text-xs text-gray-400">({{ dashboardData.metricsWindow }}分钟)</span>
</p>
<p class="text-3xl font-bold text-rose-600">{{ formatNumber(dashboardData.realtimeTPM || 0) }}</p>
<p class="text-3xl font-bold text-rose-600">
{{ formatNumber(dashboardData.realtimeTPM || 0) }}
</p>
<p class="text-xs text-gray-500 mt-1">
每分钟Token数
<span v-if="dashboardData.isHistoricalMetrics" class="text-yellow-600">
<i class="fas fa-exclamation-circle"></i> 历史数据
<span
v-if="dashboardData.isHistoricalMetrics"
class="text-yellow-600"
>
<i class="fas fa-exclamation-circle" /> 历史数据
</span>
</p>
</div>
<div class="stat-icon flex-shrink-0 bg-gradient-to-br from-rose-500 to-rose-600">
<i class="fas fa-rocket"></i>
<i class="fas fa-rocket" />
</div>
</div>
</div>
@@ -193,21 +208,23 @@
<!-- 模型消费统计 -->
<div class="mb-8">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-gray-900">模型使用分布与Token使用趋势</h3>
<div class="flex gap-2 items-center">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6">
<h3 class="text-xl font-bold text-gray-900">
模型使用分布与Token使用趋势
</h3>
<div class="flex flex-wrap gap-2 items-center">
<!-- 快捷日期选择 -->
<div class="flex gap-1 bg-gray-100 rounded-lg p-1">
<button
v-for="option in dateFilter.presetOptions"
:key="option.value"
@click="setDateFilterPreset(option.value)"
:class="[
'px-3 py-1 rounded-md text-sm font-medium transition-colors',
dateFilter.preset === option.value && dateFilter.type === 'preset'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
]"
@click="setDateFilterPreset(option.value)"
>
{{ option.label }}
</button>
@@ -216,94 +233,163 @@
<!-- 粒度切换按钮 -->
<div class="flex gap-1 bg-gray-100 rounded-lg p-1">
<button
@click="setTrendGranularity('day')"
:class="[
'px-3 py-1 rounded-md text-sm font-medium transition-colors',
trendGranularity === 'day'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
]"
@click="setTrendGranularity('day')"
>
<i class="fas fa-calendar-day mr-1"></i>按天
<i class="fas fa-calendar-day mr-1" />按天
</button>
<button
@click="setTrendGranularity('hour')"
:class="[
'px-3 py-1 rounded-md text-sm font-medium transition-colors',
trendGranularity === 'hour'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
]"
@click="setTrendGranularity('hour')"
>
<i class="fas fa-clock mr-1"></i>按小时
<i class="fas fa-clock mr-1" />按小时
</button>
</div>
<!-- Element Plus 日期范围选择器 -->
<div class="flex items-center gap-2">
<el-date-picker
:default-time="defaultTime"
v-model="dateFilter.customRange"
:default-time="defaultTime"
type="datetimerange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
@change="onCustomDateRangeChange"
:disabled-date="disabledDate"
size="default"
style="width: 400px;"
class="custom-date-picker"
></el-date-picker>
<span v-if="trendGranularity === 'hour'" class="text-xs text-orange-600">
<i class="fas fa-info-circle"></i> 最多24小时
@change="onCustomDateRangeChange"
/>
<span
v-if="trendGranularity === 'hour'"
class="text-xs text-orange-600"
>
<i class="fas fa-info-circle" /> 最多24小时
</span>
</div>
<button
@click="refreshAllData()"
:disabled="isRefreshing"
class="btn btn-primary px-4 py-2 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
<i :class="['fas fa-sync-alt', { 'animate-spin': isRefreshing }]"></i>
{{ isRefreshing ? '刷新中' : '刷新' }}
</button>
<!-- 刷新控制 -->
<div class="flex items-center gap-2">
<!-- 自动刷新控制 -->
<div class="flex items-center bg-gray-100 rounded-lg px-3 py-1">
<label class="relative inline-flex items-center cursor-pointer">
<input
v-model="autoRefreshEnabled"
type="checkbox"
class="sr-only peer"
>
<!-- 更小的开关 -->
<div class="relative w-9 h-5 bg-gray-300 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300 rounded-full peer peer-checked:bg-blue-500 transition-all duration-200 after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:w-4 after:h-4 after:rounded-full after:shadow-sm after:transition-transform after:duration-200 peer-checked:after:translate-x-4" />
<span class="ml-2.5 text-sm font-medium text-gray-600 select-none flex items-center gap-1">
<i class="fas fa-redo-alt text-xs text-gray-500" />
<span>自动刷新</span>
<span
v-if="autoRefreshEnabled"
class="ml-1 text-xs text-blue-600 font-mono transition-opacity"
:class="refreshCountdown > 0 ? 'opacity-100' : 'opacity-0'"
>
{{ refreshCountdown }}s
</span>
</span>
</label>
</div>
<!-- 刷新按钮 -->
<button
:disabled="isRefreshing"
class="px-3 py-1 rounded-md text-sm font-medium transition-colors bg-white text-blue-600 shadow-sm hover:bg-gray-50 border border-gray-300 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
title="立即刷新数据"
@click="refreshAllData()"
>
<i :class="['fas fa-sync-alt text-xs', { 'animate-spin': isRefreshing }]" />
<span>{{ isRefreshing ? '刷新中' : '刷新' }}</span>
</button>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- 饼图 -->
<div class="card p-6">
<h4 class="text-lg font-semibold text-gray-800 mb-4">Token使用分布</h4>
<div class="relative" style="height: 300px;">
<canvas ref="modelUsageChart"></canvas>
<h4 class="text-lg font-semibold text-gray-800 mb-4">
Token使用分布
</h4>
<div
class="relative"
style="height: 300px;"
>
<canvas ref="modelUsageChart" />
</div>
</div>
<!-- 详细数据表格 -->
<div class="card p-6">
<h4 class="text-lg font-semibold text-gray-800 mb-4">详细统计数据</h4>
<div v-if="dashboardModelStats.length === 0" class="text-center py-8">
<p class="text-gray-500">暂无模型使用数据</p>
<h4 class="text-lg font-semibold text-gray-800 mb-4">
详细统计数据
</h4>
<div
v-if="dashboardModelStats.length === 0"
class="text-center py-8"
>
<p class="text-gray-500">
暂无模型使用数据
</p>
</div>
<div v-else class="overflow-auto max-h-[300px]">
<div
v-else
class="overflow-auto max-h-[300px]"
>
<table class="min-w-full">
<thead class="bg-gray-50 sticky top-0">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-700">模型</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">请求数</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">总Token</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">费用</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">占比</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-700">
模型
</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">
请求数
</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">
总Token
</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">
费用
</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">
占比
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr v-for="stat in dashboardModelStats" :key="stat.model" class="hover:bg-gray-50">
<td class="px-4 py-2 text-sm text-gray-900">{{ stat.model }}</td>
<td class="px-4 py-2 text-sm text-gray-600 text-right">{{ formatNumber(stat.requests) }}</td>
<td class="px-4 py-2 text-sm text-gray-600 text-right">{{ formatNumber(stat.allTokens) }}</td>
<td class="px-4 py-2 text-sm text-green-600 text-right font-medium">{{ stat.formatted ? stat.formatted.total : '$0.000000' }}</td>
<tr
v-for="stat in dashboardModelStats"
:key="stat.model"
class="hover:bg-gray-50"
>
<td class="px-4 py-2 text-sm text-gray-900">
{{ stat.model }}
</td>
<td class="px-4 py-2 text-sm text-gray-600 text-right">
{{ formatNumber(stat.requests) }}
</td>
<td class="px-4 py-2 text-sm text-gray-600 text-right">
{{ formatNumber(stat.allTokens) }}
</td>
<td class="px-4 py-2 text-sm text-green-600 text-right font-medium">
{{ stat.formatted ? stat.formatted.total : '$0.000000' }}
</td>
<td class="px-4 py-2 text-sm font-medium text-right">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{{ calculatePercentage(stat.allTokens, dashboardModelStats) }}%
@@ -321,7 +407,7 @@
<div class="mb-8">
<div class="card p-6">
<div style="height: 300px;">
<canvas ref="usageTrendChart"></canvas>
<canvas ref="usageTrendChart" />
</div>
</div>
</div>
@@ -330,30 +416,32 @@
<div class="mb-8">
<div class="card p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">API Keys 使用趋势</h3>
<h3 class="text-lg font-semibold text-gray-900">
API Keys 使用趋势
</h3>
<!-- 维度切换按钮 -->
<div class="flex gap-1 bg-gray-100 rounded-lg p-1">
<button
@click="apiKeysTrendMetric = 'requests'; updateApiKeysUsageTrendChart()"
:class="[
'px-3 py-1 rounded-md text-sm font-medium transition-colors',
apiKeysTrendMetric === 'requests'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
]"
@click="apiKeysTrendMetric = 'requests'; updateApiKeysUsageTrendChart()"
>
<i class="fas fa-exchange-alt mr-1"></i>请求次数
<i class="fas fa-exchange-alt mr-1" />请求次数
</button>
<button
@click="apiKeysTrendMetric = 'tokens'; updateApiKeysUsageTrendChart()"
:class="[
'px-3 py-1 rounded-md text-sm font-medium transition-colors',
apiKeysTrendMetric === 'tokens'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
]"
@click="apiKeysTrendMetric = 'tokens'; updateApiKeysUsageTrendChart()"
>
<i class="fas fa-coins mr-1"></i>Token 数量
<i class="fas fa-coins mr-1" />Token 数量
</button>
</div>
</div>
@@ -366,7 +454,7 @@
</span>
</div>
<div style="height: 350px;">
<canvas ref="apiKeysUsageTrendChart"></canvas>
<canvas ref="apiKeysUsageTrendChart" />
</div>
</div>
</div>
@@ -395,8 +483,6 @@ const {
const {
loadDashboardData,
loadUsageTrend,
loadModelStats,
loadApiKeysTrend,
setDateFilterPreset,
onCustomDateRangeChange,