feat: 分组统计
This commit is contained in:
@@ -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;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取分组可配置参数
|
// 获取分组可配置参数
|
||||||
|
@@ -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>
|
||||||
|
@@ -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 {
|
||||||
|
Reference in New Issue
Block a user