From de0753f0ce2ceb51e4be55b2177c76867a99f031 Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Fri, 13 Feb 2026 13:44:12 +0800 Subject: [PATCH] feat(usage): resolve credential names from auth files by auth_index --- src/components/usage/CredentialStatsCard.tsx | 61 +++++++++++++++++++- src/pages/UsagePage.module.scss | 13 +++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/src/components/usage/CredentialStatsCard.tsx b/src/components/usage/CredentialStatsCard.tsx index e15dd0f..d6940c5 100644 --- a/src/components/usage/CredentialStatsCard.tsx +++ b/src/components/usage/CredentialStatsCard.tsx @@ -1,7 +1,9 @@ -import { useMemo } from 'react'; +import { useMemo, useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Card } from '@/components/ui/Card'; import { computeKeyStats, formatCompactNumber } from '@/utils/usage'; +import { authFilesApi } from '@/services/api/authFiles'; +import type { AuthFileItem } from '@/types/authFile'; import type { UsagePayload } from './hooks/useUsageData'; import styles from '@/pages/UsagePage.module.scss'; @@ -12,14 +14,59 @@ export interface CredentialStatsCardProps { interface CredentialRow { key: string; + displayName: string; + type: string; success: number; failure: number; total: number; successRate: number; } +function normalizeAuthIndexValue(value: unknown): string | null { + if (typeof value === 'number' && Number.isFinite(value)) { + return value.toString(); + } + if (typeof value === 'string') { + const trimmed = value.trim(); + return trimmed || null; + } + return null; +} + +function buildAuthIndexMap(files: AuthFileItem[]): Map { + const map = new Map(); + files.forEach((file) => { + const rawAuthIndex = file['auth_index'] ?? file.authIndex; + const key = normalizeAuthIndexValue(rawAuthIndex); + if (key) { + map.set(key, { + name: file.name || key, + type: (file.type || file.provider || '').toString(), + }); + } + }); + return map; +} + export function CredentialStatsCard({ usage, loading }: CredentialStatsCardProps) { const { t } = useTranslation(); + const [authFiles, setAuthFiles] = useState([]); + + useEffect(() => { + let cancelled = false; + authFilesApi.list().then((res) => { + if (cancelled) return; + const files = Array.isArray(res) ? res : (res as { files?: AuthFileItem[] })?.files; + if (Array.isArray(files)) { + setAuthFiles(files); + } + }).catch(() => { + // silently ignore - credential names will just show raw auth_index + }); + return () => { cancelled = true; }; + }, []); + + const authIndexMap = useMemo(() => buildAuthIndexMap(authFiles), [authFiles]); const rows = useMemo((): CredentialRow[] => { if (!usage) return []; @@ -27,8 +74,11 @@ export function CredentialStatsCard({ usage, loading }: CredentialStatsCardProps return Object.entries(byAuthIndex) .map(([key, bucket]) => { const total = bucket.success + bucket.failure; + const mapped = authIndexMap.get(key); return { key, + displayName: mapped?.name || key, + type: mapped?.type || '', success: bucket.success, failure: bucket.failure, total, @@ -36,7 +86,7 @@ export function CredentialStatsCard({ usage, loading }: CredentialStatsCardProps }; }) .sort((a, b) => b.total - a.total); - }, [usage]); + }, [usage, authIndexMap]); return ( @@ -55,7 +105,12 @@ export function CredentialStatsCard({ usage, loading }: CredentialStatsCardProps {rows.map((row) => ( - {row.key} + + {row.displayName} + {row.type && ( + {row.type} + )} + {formatCompactNumber(row.total)} diff --git a/src/pages/UsagePage.module.scss b/src/pages/UsagePage.module.scss index 35254ad..f97f696 100644 --- a/src/pages/UsagePage.module.scss +++ b/src/pages/UsagePage.module.scss @@ -612,6 +612,19 @@ word-break: break-all; } +.credentialType { + display: inline-block; + margin-left: 6px; + padding: 1px 6px; + border-radius: $radius-full; + font-size: 10px; + font-weight: 600; + color: var(--text-secondary); + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + vertical-align: middle; +} + .requestCountCell { display: inline-flex; align-items: baseline;