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

View File

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

View File

@@ -53,13 +53,26 @@ export interface GroupConfigOption {
default_value: number; 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; total_keys: number;
active_keys: number; active_keys: number;
requests_1h: number; invalid_keys: number;
requests_24h: number; }
requests_7d: number;
failure_rate_24h: 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 { export interface TaskInfo {