diff --git a/frontend/src/views/user/KeysView.vue b/frontend/src/views/user/KeysView.vue
index 023757650a..e915e8148f 100644
--- a/frontend/src/views/user/KeysView.vue
+++ b/frontend/src/views/user/KeysView.vue
@@ -1823,14 +1823,18 @@ const setExpirationDays = (days: number) => {
// Reset quota used for an API key
const resetQuotaUsed = async () => {
- if (!selectedKey.value) return
+ const key = selectedKey.value
+ if (!key) return
showResetQuotaDialog.value = false
try {
- await keysAPI.update(selectedKey.value.id, { reset_quota: true })
+ const updatedKey = await keysAPI.update(key.id, { reset_quota: true })
appStore.showSuccess(t('keys.quotaResetSuccess'))
- // Update local state
- if (selectedKey.value) {
- selectedKey.value.quota_used = 0
+ key.quota_used = updatedKey.quota_used
+ if (key.status !== updatedKey.status) {
+ key.status = updatedKey.status
+ if (selectedKey.value?.id === key.id) {
+ formData.value.status = updatedKey.status === 'active' ? 'active' : 'inactive'
+ }
}
} catch (error: any) {
const errorMsg = error.response?.data?.detail || t('keys.failedToResetQuota')
diff --git a/frontend/src/views/user/__tests__/KeysView.spec.ts b/frontend/src/views/user/__tests__/KeysView.spec.ts
index b66072e94f..4ec5b4c53b 100644
--- a/frontend/src/views/user/__tests__/KeysView.spec.ts
+++ b/frontend/src/views/user/__tests__/KeysView.spec.ts
@@ -7,6 +7,7 @@ import KeysView from '../KeysView.vue'
const {
listKeys,
+ updateKey,
getPublicSettings,
getDashboardApiKeysUsage,
getAvailableGroups,
@@ -18,6 +19,7 @@ const {
nextStep,
} = vi.hoisted(() => ({
listKeys: vi.fn(),
+ updateKey: vi.fn(),
getPublicSettings: vi.fn(),
getDashboardApiKeysUsage: vi.fn(),
getAvailableGroups: vi.fn(),
@@ -59,7 +61,7 @@ vi.mock('@/api', () => ({
keysAPI: {
list: listKeys,
create: vi.fn(),
- update: vi.fn(),
+ update: updateKey,
delete: vi.fn(),
toggleStatus: vi.fn(),
},
@@ -170,6 +172,7 @@ const DataTableStub = {
+
@@ -223,7 +226,10 @@ const mountView = async () => {
TablePageLayout: TablePageLayoutStub,
DataTable: DataTableStub,
Pagination: PaginationStub,
- BaseDialog: true,
+ BaseDialog: {
+ props: ['show'],
+ template: '
',
+ },
ConfirmDialog: true,
EmptyState: true,
Select: SelectStub,
@@ -261,6 +267,7 @@ describe('user KeysView column settings', () => {
localStorage.clear()
listKeys.mockReset()
+ updateKey.mockReset()
getPublicSettings.mockReset()
getDashboardApiKeysUsage.mockReset()
getAvailableGroups.mockReset()
@@ -285,6 +292,43 @@ describe('user KeysView column settings', () => {
isCurrentStep.mockReturnValue(false)
})
+ it.each([
+ { initialStatus: 'quota_exhausted', status: 'active', formStatus: 'active' },
+ { initialStatus: 'inactive', status: 'inactive', formStatus: 'inactive' },
+ { initialStatus: 'active', status: 'active', formStatus: 'inactive' },
+ ] as const)('syncs quota reset from $initialStatus to $status with form status $formStatus', async ({ initialStatus, status, formStatus }) => {
+ const key: ApiKey = {
+ ...createApiKey(), group_id: 1, quota: 10, quota_used: 10,
+ status: initialStatus,
+ }
+ listKeys.mockResolvedValueOnce({ items: [key], total: 1, page: 1, page_size: 20, pages: 1 })
+ updateKey.mockResolvedValue({ ...key, status, quota_used: 0 })
+ const wrapper = await mountView()
+ await getButtonByText(wrapper, 'common.edit').trigger('click')
+ await wrapper.get('[data-tour="key-form-name"]').setValue('Unsaved name')
+ const statusSelect = wrapper.findAllComponents({ name: 'Select' })
+ .find((select) => select.props('options').length === 2 &&
+ select.props('options')[0].value === 'active')!
+ statusSelect.vm.$emit('update:modelValue', 'inactive')
+ await wrapper.get('button[title="keys.resetQuotaUsed"]').trigger('click')
+ const confirmation = wrapper.findAllComponents({ name: 'ConfirmDialog' })
+ .find((dialog) => dialog.props('title') === 'keys.resetQuotaTitle')!
+ confirmation.vm.$emit('confirm')
+ await flushPromises()
+
+ expect(updateKey).toHaveBeenNthCalledWith(1, key.id, { reset_quota: true })
+ expect(wrapper.findComponent({ name: 'DataTable' }).props('data')[0])
+ .toMatchObject({ status, quota_used: 0 })
+ expect(statusSelect.props('modelValue')).toBe(formStatus)
+ expect((wrapper.get('[data-tour="key-form-name"]').element as HTMLInputElement).value)
+ .toBe('Unsaved name')
+
+ await wrapper.get('#key-form').trigger('submit')
+ await flushPromises()
+ expect(updateKey).toHaveBeenNthCalledWith(2, key.id, expect.objectContaining({ name: 'Unsaved name', status: formStatus }))
+ wrapper.unmount()
+ })
+
it('uses the default API key columns with low-frequency columns hidden', async () => {
const wrapper = await mountView()