From c6727ed4c96bb8981041c54ce3a5cd7843363ef8 Mon Sep 17 00:00:00 2001 From: Shen-An <2021902023@chd.edu.cn> Date: Sat, 12 Sep 2026 15:19:19 +0800 Subject: [PATCH] fix(admin): surface partial Antigravity refresh warnings Return the updated account alongside partial refresh warnings so the admin UI can keep its state current and tell operators when project ID retrieval must retry. Co-Authored-By: Claude Sonnet 5 --- .../internal/handler/admin/account_handler.go | 1 + frontend/src/api/admin/accounts.ts | 10 ++++-- frontend/src/views/admin/AccountsView.vue | 5 +-- .../admin/__tests__/AccountsView.lite.spec.ts | 35 ++++++++++++++++--- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index 754105c46b..f97dc4886a 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -1545,6 +1545,7 @@ func (h *AccountHandler) Refresh(c *gin.Context) { if warning == "missing_project_id_temporary" { response.Success(c, gin.H{ + "account": h.buildAccountResponseWithRuntime(c.Request.Context(), updatedAccount), "message": "Token refreshed successfully, but project_id could not be retrieved (will retry automatically)", "warning": "missing_project_id_temporary", }) diff --git a/frontend/src/api/admin/accounts.ts b/frontend/src/api/admin/accounts.ts index 9f4506182c..6bd533214e 100644 --- a/frontend/src/api/admin/accounts.ts +++ b/frontend/src/api/admin/accounts.ts @@ -311,9 +311,13 @@ export async function testAccount(id: number): Promise<{ * @param id - Account ID * @returns Updated account */ -export async function refreshCredentials(id: number): Promise { - const { data } = await apiClient.post(`/admin/accounts/${id}/refresh`) - return data +export type RefreshCredentialsResult = + | { account: Account; message: string; warning: 'missing_project_id_temporary' } + | { account: Account; message?: never; warning?: never } + +export async function refreshCredentials(id: number): Promise { + const { data } = await apiClient.post(`/admin/accounts/${id}/refresh`) + return 'account' in data ? data : { account: data } } /** diff --git a/frontend/src/views/admin/AccountsView.vue b/frontend/src/views/admin/AccountsView.vue index 233fef9791..5df2c8afaa 100644 --- a/frontend/src/views/admin/AccountsView.vue +++ b/frontend/src/views/admin/AccountsView.vue @@ -2352,9 +2352,10 @@ const handleDuplicateAccount = async (a: Account) => { } const handleRefresh = async (a: Account) => { try { - const updated = await adminAPI.accounts.refreshCredentials(a.id) - patchAccountInList(updated) + const result = await adminAPI.accounts.refreshCredentials(a.id) + patchAccountInList(result.account) enterAutoRefreshSilentWindow() + if (result.warning) appStore.showWarning(result.message) } catch (error) { console.error('Failed to refresh credentials:', error) } diff --git a/frontend/src/views/admin/__tests__/AccountsView.lite.spec.ts b/frontend/src/views/admin/__tests__/AccountsView.lite.spec.ts index 86d29caad1..828f20d6e8 100644 --- a/frontend/src/views/admin/__tests__/AccountsView.lite.spec.ts +++ b/frontend/src/views/admin/__tests__/AccountsView.lite.spec.ts @@ -13,7 +13,9 @@ const { getUpstreamBillingProbeSettings, getAllProxies, getAllGroups, - showError + refreshCredentials, + showError, + showWarning } = vi.hoisted(() => ({ listAccounts: vi.fn(), listWithEtag: vi.fn(), @@ -22,7 +24,9 @@ const { getUpstreamBillingProbeSettings: vi.fn(), getAllProxies: vi.fn(), getAllGroups: vi.fn(), - showError: vi.fn() + refreshCredentials: vi.fn(), + showError: vi.fn(), + showWarning: vi.fn() })) vi.mock('@/api/admin', () => ({ @@ -36,7 +40,8 @@ vi.mock('@/api/admin', () => ({ delete: vi.fn(), batchClearError: vi.fn(), batchRefresh: vi.fn(), - toggleSchedulable: vi.fn() + toggleSchedulable: vi.fn(), + refreshCredentials }, proxies: { getAll: getAllProxies }, groups: { getAll: getAllGroups } @@ -44,7 +49,7 @@ vi.mock('@/api/admin', () => ({ })) vi.mock('@/stores/app', () => ({ - useAppStore: () => ({ showError, showSuccess: vi.fn(), showInfo: vi.fn() }) + useAppStore: () => ({ showError, showWarning, showSuccess: vi.fn(), showInfo: vi.fn() }) })) vi.mock('@/stores/auth', () => ({ @@ -60,7 +65,7 @@ const DataTableStub = defineComponent({ props: { data: { type: Array, default: () => [] } }, template: `
-
+
@@ -161,7 +166,9 @@ describe('admin AccountsView lite account list', () => { getUpstreamBillingProbeSettings.mockReset().mockResolvedValue({ enabled: true }) getAllProxies.mockReset().mockResolvedValue([]) getAllGroups.mockReset().mockResolvedValue([{ id: 7, name: 'codex', platform: 'openai' }]) + refreshCredentials.mockReset() showError.mockReset() + showWarning.mockReset() }) afterEach(() => { @@ -254,6 +261,24 @@ describe('admin AccountsView lite account list', () => { wrapper.unmount() }) + it('shows the warning and patches the account after a partial Antigravity refresh', async () => { + refreshCredentials.mockResolvedValue({ + account: { ...fullAccount, name: 'refreshed account' }, + message: 'Token refreshed, but project_id is temporarily unavailable', + warning: 'missing_project_id_temporary' + }) + const wrapper = mountView(false) + await flushPromises() + + wrapper.findComponent(AccountActionMenu).vm.$emit('refresh-token', listRow) + await flushPromises() + + expect(refreshCredentials).toHaveBeenCalledWith(42) + expect(wrapper.get('[data-account-name]').attributes('data-account-name')).toBe('refreshed account') + expect(showWarning).toHaveBeenCalledWith('Token refreshed, but project_id is temporarily unavailable') + wrapper.unmount() + }) + it('shows an error and keeps the modal closed when detail loading fails', async () => { const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) getById.mockRejectedValueOnce(new Error('detail failed'))