feat: 分组统计

This commit is contained in:
tbphp
2025-07-12 15:52:09 +08:00
parent 2796c05934
commit 22a67a136f
3 changed files with 109 additions and 45 deletions

View File

@@ -2,7 +2,7 @@ import type {
APIKey,
Group,
GroupConfigOption,
GroupStats,
GroupStatsResponse,
KeyStatus,
TaskInfo,
} from "@/types/models";
@@ -33,16 +33,9 @@ export const keysApi = {
},
// 获取分组统计信息
async getGroupStats(): Promise<GroupStats> {
await new Promise(resolve => setTimeout(resolve, 200));
return {
total_keys: 0,
active_keys: 0,
requests_1h: 0,
requests_24h: 0,
requests_7d: 0,
failure_rate_24h: 0,
} as GroupStats;
async getGroupStats(groupId: number): Promise<GroupStatsResponse> {
const res = await http.get(`/groups/${groupId}/stats`);
return res.data;
},
// 获取分组可配置参数

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { keysApi } from "@/api/keys";
import type { Group, GroupStats } from "@/types/models";
import type { Group, GroupStatsResponse } from "@/types/models";
import { getGroupDisplayName } from "@/utils/display";
import { Pencil, Trash } from "@vicons/ionicons5";
import {
@@ -32,7 +32,7 @@ const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const stats = ref<GroupStats | null>(null);
const stats = ref<GroupStatsResponse | null>(null);
const loading = ref(false);
const dialog = useDialog();
const showEditModal = ref(false);
@@ -60,7 +60,7 @@ async function loadStats() {
try {
loading.value = true;
if (props.group?.id) {
stats.value = await keysApi.getGroupStats();
stats.value = await keysApi.getGroupStats(props.group.id);
}
} finally {
loading.value = false;
@@ -118,7 +118,7 @@ function formatNumber(num: number): string {
}
function formatPercentage(num: number): string {
return `${num.toFixed(1)}%`;
return `${(num * 100).toFixed(1)}%`;
}
function copyUrl(url: string) {
@@ -177,38 +177,96 @@ function resetPage() {
<!-- 统计摘要区 -->
<div class="stats-summary">
<n-spin :show="loading" size="small">
<n-grid :cols="5" :x-gap="12" :y-gap="12" responsive="screen">
<n-grid :cols="4" :x-gap="12" :y-gap="12" responsive="screen">
<n-grid-item span="1">
<n-card
:title="`${stats?.active_keys || 0} / ${stats?.total_keys || 0}`"
size="large"
<n-statistic :label="`密钥数量:${stats?.key_stats?.total_keys ?? 0}`">
<n-tooltip trigger="hover">
<template #trigger>
<n-gradient-text type="success" size="20">
{{ stats?.key_stats?.active_keys ?? 0 }}
</n-gradient-text>
</template>
有效密钥数
</n-tooltip>
<n-divider vertical />
<n-tooltip trigger="hover">
<template #trigger>
<n-gradient-text type="error" size="20">
{{ stats?.key_stats?.invalid_keys ?? 0 }}
</n-gradient-text>
</template>
无效密钥数
</n-tooltip>
</n-statistic>
</n-grid-item>
<n-grid-item span="1">
<n-statistic
:label="`近1小时${formatNumber(stats?.hourly_stats?.total_requests ?? 0)}`"
>
<template #header-extra><span class="status-title">密钥数量</span></template>
</n-card>
<n-tooltip trigger="hover">
<template #trigger>
<n-gradient-text type="success" size="20">
{{ formatNumber(stats?.hourly_stats?.failed_requests ?? 0) }}
</n-gradient-text>
</template>
近1小时失败请求
</n-tooltip>
<n-divider vertical />
<n-tooltip trigger="hover">
<template #trigger>
<n-gradient-text type="error" size="20">
{{ formatPercentage(stats?.hourly_stats?.failure_rate ?? 0) }}
</n-gradient-text>
</template>
近1小时失败率
</n-tooltip>
</n-statistic>
</n-grid-item>
<n-grid-item span="1">
<n-card
class="status-card-failure"
:title="formatPercentage(stats?.failure_rate_24h || 0)"
size="large"
<n-statistic
:label="`近24小时${formatNumber(stats?.daily_stats?.total_requests ?? 0)}`"
>
<template #header-extra><span class="status-title">失败率</span></template>
</n-card>
<n-tooltip trigger="hover">
<template #trigger>
<n-gradient-text type="success" size="20">
{{ formatNumber(stats?.daily_stats?.failed_requests ?? 0) }}
</n-gradient-text>
</template>
近24小时失败请求
</n-tooltip>
<n-divider vertical />
<n-tooltip trigger="hover">
<template #trigger>
<n-gradient-text type="error" size="20">
{{ formatPercentage(stats?.daily_stats?.failure_rate ?? 0) }}
</n-gradient-text>
</template>
近24小时失败率
</n-tooltip>
</n-statistic>
</n-grid-item>
<n-grid-item span="1">
<n-card :title="formatNumber(stats?.requests_1h || 0)" size="large">
<template #header-extra><span class="status-title">近1小时</span></template>
</n-card>
</n-grid-item>
<n-grid-item span="1">
<n-card :title="formatNumber(stats?.requests_24h || 0)" size="large">
<template #header-extra><span class="status-title">近24小时</span></template>
</n-card>
</n-grid-item>
<n-grid-item span="1">
<n-card :title="formatNumber(stats?.requests_7d || 0)" size="large">
<template #header-extra><span class="status-title">近7天</span></template>
</n-card>
<n-statistic
:label="`近7天${formatNumber(stats?.weekly_stats?.total_requests ?? 0)}`"
>
<n-tooltip trigger="hover">
<template #trigger>
<n-gradient-text type="success" size="20">
{{ formatNumber(stats?.weekly_stats?.failed_requests ?? 0) }}
</n-gradient-text>
</template>
近7天失败请求
</n-tooltip>
<n-divider vertical />
<n-tooltip trigger="hover">
<template #trigger>
<n-gradient-text type="error" size="20">
{{ formatPercentage(stats?.weekly_stats?.failure_rate ?? 0) }}
</n-gradient-text>
</template>
近7天失败率
</n-tooltip>
</n-statistic>
</n-grid-item>
</n-grid>
</n-spin>

View File

@@ -53,13 +53,26 @@ export interface GroupConfigOption {
default_value: number;
}
export interface GroupStats {
// GroupStatsResponse defines the complete statistics for a group.
export interface GroupStatsResponse {
key_stats: KeyStats;
hourly_stats: RequestStats;
daily_stats: RequestStats;
weekly_stats: RequestStats;
}
// KeyStats defines the statistics for API keys in a group.
export interface KeyStats {
total_keys: number;
active_keys: number;
requests_1h: number;
requests_24h: number;
requests_7d: number;
failure_rate_24h: number;
invalid_keys: number;
}
// RequestStats defines the statistics for requests over a period.
export interface RequestStats {
total_requests: number;
failed_requests: number;
failure_rate: number;
}
export interface TaskInfo {