From cceefd3ce33c10a7d30d002f7907383facee9e54 Mon Sep 17 00:00:00 2001 From: zyycn Date: Sat, 27 Jun 2026 03:59:12 +0800 Subject: [PATCH] feat: add account connection test panel Co-authored-by: Codex --- web/src/views/accounts/index.vue | 230 ++++++++++++++++++++++++++++++- 1 file changed, 224 insertions(+), 6 deletions(-) diff --git a/web/src/views/accounts/index.vue b/web/src/views/accounts/index.vue index f167237e2..bae315872 100644 --- a/web/src/views/accounts/index.vue +++ b/web/src/views/accounts/index.vue @@ -3,6 +3,8 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { AlertTriangle, ChevronDown, + CheckCircle2, + Clock3, Gauge, MoreHorizontal, Pencil, @@ -14,6 +16,7 @@ import { Trash2, Users, Wifi, + XCircle, } from '@lucide/vue' import BaseButton from '@/components/base/BaseButton.vue' @@ -23,6 +26,7 @@ import BaseConfirmModal from '@/components/base/BaseConfirmModal.vue' import BaseInput from '@/components/base/BaseInput.vue' import BaseModal from '@/components/base/BaseModal.vue' import BasePopover from '@/components/base/BasePopover.vue' +import BaseScrollbar from '@/components/base/BaseScrollbar.vue' import BaseSelect from '@/components/base/BaseSelect.vue' import BaseTable from '@/components/base/BaseTable.vue' import { toast } from '@/components/base/BaseToast' @@ -55,8 +59,15 @@ const expandedAccountIds = ref>(new Set()) const showCreateModal = ref(false) const showDeleteModal = ref(false) const showSingleDeleteModal = ref(false) +const showConnectionTestModal = ref(false) const editingAccount = ref(null) const pendingDeleteAccount = ref(null) +const testingAccount = ref(null) +const connectionTestStatus = ref('idle') +const connectionTestResult = ref(null) +const connectionTestError = ref('') +const connectionTestStartedAt = ref('') +const connectionTestFinishedAt = ref('') const refreshingAccountIds = ref>(new Set()) const refreshingQuotaAccountIds = ref>(new Set()) const updatingStatusAccountIds = ref>(new Set()) @@ -183,6 +194,42 @@ const accountPagination = computed(() => ({ total: totalAccounts.value, pageSizes: [10, 20, 50, 100], })) +const connectionTestStatusView = computed(() => { + if (connectionTestStatus.value === 'running') { + return { + label: '正在测试', + description: '正在验证账号到 Codex 模型端点的连接。', + icon: Clock3, + badge: 'bg-(--cp-info-bg) text-(--cp-info-text)', + iconClass: 'text-(--cp-info)', + } + } + if (connectionTestStatus.value === 'success') { + return { + label: '连接正常', + description: '账号令牌可用,网关可以访问 Codex 模型端点。', + icon: CheckCircle2, + badge: 'bg-(--cp-success-bg) text-(--cp-success-text)', + iconClass: 'text-(--cp-success)', + } + } + if (connectionTestStatus.value === 'error') { + return { + label: '测试失败', + description: '连接未通过,优先检查令牌状态、账号权限或上游网络。', + icon: XCircle, + badge: 'bg-(--cp-danger-bg) text-(--cp-danger-text)', + iconClass: 'text-(--cp-danger)', + } + } + return { + label: '准备测试', + description: '点击开始后验证账号连接状态。', + icon: Wifi, + badge: 'bg-(--cp-bg-subtle) text-(--cp-text-secondary)', + iconClass: 'text-(--cp-text-muted)', + } +}) const showEditModal = computed({ get: () => editingAccount.value !== null, set: (value: boolean) => { @@ -360,25 +407,55 @@ async function handleRefresh(accountId: string) { } } -async function handleTestConnection(account: any) { +function openConnectionTest(account: any) { + testingAccount.value = account + showConnectionTestModal.value = true + resetConnectionTest() + void handleTestConnection(account) +} + +function resetConnectionTest() { + connectionTestStatus.value = 'idle' + connectionTestResult.value = null + connectionTestError.value = '' + connectionTestStartedAt.value = '' + connectionTestFinishedAt.value = '' +} + +function formattedDateTime(value: Date) { + const pad = (item: number) => String(item).padStart(2, '0') + return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())} ${pad(value.getHours())}:${pad(value.getMinutes())}:${pad(value.getSeconds())}` +} + +async function handleTestConnection(account = testingAccount.value) { + if (!account?.id) return if (testingConnectionIds.value.has(account.id)) return + connectionTestStatus.value = 'running' + connectionTestResult.value = null + connectionTestError.value = '' + connectionTestStartedAt.value = formattedDateTime(new Date()) + connectionTestFinishedAt.value = '' testingConnectionIds.value = new Set(testingConnectionIds.value).add(account.id) try { const result = await withMinimumDuration(async () => testAccountConnection({ ids: [account.id] }), ) const check = result.results.find((item: any) => item.id === account.id) || result.results[0] + connectionTestResult.value = check || null if (check?.result === 'alive') { - toast.success(`连接正常${check.durationMs !== undefined ? ` · ${check.durationMs}ms` : ''}`) + connectionTestStatus.value = 'success' } else { - toast.error(check?.error || '测试连接失败') + connectionTestStatus.value = 'error' + connectionTestError.value = check?.error || '测试连接失败' } } catch (error: any) { - toast.error(error.message || '测试连接失败') + connectionTestStatus.value = 'error' + connectionTestError.value = error.message || '测试连接失败' } finally { const next = new Set(testingConnectionIds.value) next.delete(account.id) testingConnectionIds.value = next + connectionTestFinishedAt.value = formattedDateTime(new Date()) } } @@ -866,7 +943,7 @@ onBeforeUnmount(() => { type="button" class="flex h-8.5 w-full items-center gap-2 rounded-(--cp-input-radius-small) border-0 bg-transparent px-3 text-left text-[13px] leading-none font-[650] text-(--cp-text-primary) transition-colors hover:bg-(--cp-default-bg-hover) disabled:cursor-not-allowed disabled:text-(--cp-disabled-text)" :disabled="testingConnectionIds.has(row.id)" - @click.stop="(close(), handleTestConnection(row))" + @click.stop="(close(), openConnectionTest(row))" > 测试连接 @@ -1104,7 +1181,148 @@ onBeforeUnmount(() => { - + +
+
+
+ + {{ accountInitial(testingAccount) }} + +
+

+ {{ accountDisplayTitle(testingAccount) }} +

+

+ {{ accountSecondaryText(testingAccount) }} +

+
+
+ + {{ statusLabel(testingAccount.status) || testingAccount.status }} + +
+ +
+
+
+ + + +
+

+ {{ connectionTestStatusView.label }} +

+

+ {{ connectionTestStatusView.description }} +

+
+
+ + {{ connectionTestStatus === 'running' ? '检测中' : connectionTestStatusView.label }} + +
+ +
+
+

开始时间

+

+ {{ connectionTestStartedAt || '-' }} +

+
+
+

完成时间

+

+ {{ connectionTestFinishedAt || '-' }} +

+
+
+

响应耗时

+

+ {{ + connectionTestResult?.durationMs !== undefined + ? `${connectionTestResult.durationMs}ms` + : '-' + }} +

+
+
+ +
+
+

HTTP 状态

+

+ {{ connectionTestResult?.status ?? '-' }} +

+
+
+

探测端点

+ + + {{ connectionTestResult?.endpoint || '-' }} + + +
+
+ +
+

错误信息

+ +

+ {{ connectionTestError }} +

+
+
+
+
+ + +
+