fix(web): handle unlimited API key quota validation (#4881)

This commit is contained in:
yyhhyyyyyy 2026-05-16 14:54:35 +08:00 committed by GitHub
parent 554defe4f4
commit 8a10dedb7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 59 additions and 18 deletions

View File

@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact support@quantumnous.com
*/ */
import { useEffect, useState, type ReactNode } from 'react' import { useEffect, useState, type ReactNode } from 'react'
import { useForm } from 'react-hook-form' import { useForm, type SubmitErrorHandler } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod' import { zodResolver } from '@hookform/resolvers/zod'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { import {
@ -65,7 +65,7 @@ import { MultiSelect } from '@/components/multi-select'
import { createApiKey, updateApiKey, getApiKey } from '../api' import { createApiKey, updateApiKey, getApiKey } from '../api'
import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../constants' import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../constants'
import { import {
apiKeyFormSchema, getApiKeyFormSchema,
type ApiKeyFormValues, type ApiKeyFormValues,
getApiKeyFormDefaultValues, getApiKeyFormDefaultValues,
transformFormDataToPayload, transformFormDataToPayload,
@ -152,9 +152,10 @@ export function ApiKeysMutateDrawer({
}) })
) )
const backendHasAuto = groups.some((g) => g.value === 'auto') const backendHasAuto = groups.some((g) => g.value === 'auto')
const schema = getApiKeyFormSchema(t)
const form = useForm<ApiKeyFormValues>({ const form = useForm<ApiKeyFormValues>({
resolver: zodResolver(apiKeyFormSchema), resolver: zodResolver(schema),
defaultValues: getApiKeyFormDefaultValues(defaultUseAutoGroup), defaultValues: getApiKeyFormDefaultValues(defaultUseAutoGroup),
}) })
@ -239,6 +240,10 @@ export function ApiKeysMutateDrawer({
} }
} }
const onInvalid: SubmitErrorHandler<ApiKeyFormValues> = () => {
toast.error(t('Please fix the highlighted fields before saving'))
}
const handleSetExpiry = (months: number, days: number, hours: number) => { const handleSetExpiry = (months: number, days: number, hours: number) => {
if (months === 0 && days === 0 && hours === 0) { if (months === 0 && days === 0 && hours === 0) {
form.setValue('expired_time', undefined) form.setValue('expired_time', undefined)
@ -291,7 +296,7 @@ export function ApiKeysMutateDrawer({
<Form {...form}> <Form {...form}>
<form <form
id='api-key-form' id='api-key-form'
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit, onInvalid)}
className='min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain px-3 py-3 sm:space-y-4 sm:px-4 sm:py-4' className='min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain px-3 py-3 sm:space-y-4 sm:px-4 sm:py-4'
> >
<ApiKeyFormSection <ApiKeyFormSection

View File

@ -17,6 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact support@quantumnous.com
*/ */
import { z } from 'zod' import { z } from 'zod'
import type { TFunction } from 'i18next'
import { parseQuotaFromDollars, quotaUnitsToDollars } from '@/lib/format' import { parseQuotaFromDollars, quotaUnitsToDollars } from '@/lib/format'
import { DEFAULT_GROUP } from '../constants' import { DEFAULT_GROUP } from '../constants'
import { type ApiKeyFormData, type ApiKey } from '../types' import { type ApiKeyFormData, type ApiKey } from '../types'
@ -25,19 +26,40 @@ import { type ApiKeyFormData, type ApiKey } from '../types'
// Form Schema // Form Schema
// ============================================================================ // ============================================================================
export const apiKeyFormSchema = z.object({ export function getApiKeyFormSchema(t: TFunction) {
name: z.string().min(1, 'Name is required'), return z
remain_quota_dollars: z.number().min(0).optional(), .object({
expired_time: z.date().optional(), name: z.string().min(1, t('Please enter a name')),
unlimited_quota: z.boolean(), remain_quota_dollars: z.number().optional(),
model_limits: z.array(z.string()), expired_time: z.date().optional(),
allow_ips: z.string().optional(), unlimited_quota: z.boolean(),
group: z.string().optional(), model_limits: z.array(z.string()),
cross_group_retry: z.boolean().optional(), allow_ips: z.string().optional(),
tokenCount: z.number().min(1).optional(), group: z.string().optional(),
}) cross_group_retry: z.boolean().optional(),
tokenCount: z.number().min(1).optional(),
})
.superRefine((data, ctx) => {
if (data.unlimited_quota) {
return
}
export type ApiKeyFormValues = z.infer<typeof apiKeyFormSchema> if (
data.remain_quota_dollars === undefined ||
data.remain_quota_dollars < 0
) {
ctx.addIssue({
code: 'custom',
path: ['remain_quota_dollars'],
message: t('Quota must be zero or greater'),
})
}
})
}
export type ApiKeyFormValues = z.infer<
ReturnType<typeof getApiKeyFormSchema>
>
// ============================================================================ // ============================================================================
// Form Defaults // Form Defaults
@ -100,7 +122,9 @@ export function transformApiKeyToFormDefaults(
): ApiKeyFormValues { ): ApiKeyFormValues {
return { return {
name: apiKey.name, name: apiKey.name,
remain_quota_dollars: quotaUnitsToDollars(apiKey.remain_quota), remain_quota_dollars: apiKey.unlimited_quota
? 0
: quotaUnitsToDollars(apiKey.remain_quota),
expired_time: expired_time:
apiKey.expired_time > 0 apiKey.expired_time > 0
? new Date(apiKey.expired_time * 1000) ? new Date(apiKey.expired_time * 1000)

View File

@ -20,7 +20,7 @@ For commercial licensing, please contact support@quantumnous.com
// Form Utilities // Form Utilities
// ============================================================================ // ============================================================================
export { export {
apiKeyFormSchema, getApiKeyFormSchema,
type ApiKeyFormValues, type ApiKeyFormValues,
API_KEY_FORM_DEFAULT_VALUES, API_KEY_FORM_DEFAULT_VALUES,
getApiKeyFormDefaultValues, getApiKeyFormDefaultValues,

View File

@ -2921,6 +2921,7 @@
"Please enter your verification code": "Please enter your verification code", "Please enter your verification code": "Please enter your verification code",
"Please enter your verification code or backup code": "Please enter your verification code or backup code", "Please enter your verification code or backup code": "Please enter your verification code or backup code",
"Please fix JSON errors before saving": "Please fix JSON errors before saving", "Please fix JSON errors before saving": "Please fix JSON errors before saving",
"Please fix the highlighted fields before saving": "Please fix the highlighted fields before saving",
"Please log in with the appropriate credentials": "Please log in with the appropriate credentials", "Please log in with the appropriate credentials": "Please log in with the appropriate credentials",
"Please manually copy and open the authorization link": "Please manually copy and open the authorization link", "Please manually copy and open the authorization link": "Please manually copy and open the authorization link",
"Please select a container": "Please select a container", "Please select a container": "Please select a container",
@ -3097,6 +3098,7 @@
"Quota given to invited users": "Quota given to invited users", "Quota given to invited users": "Quota given to invited users",
"Quota given to users who invite others": "Quota given to users who invite others", "Quota given to users who invite others": "Quota given to users who invite others",
"Quota must be a positive number": "Quota must be a positive number", "Quota must be a positive number": "Quota must be a positive number",
"Quota must be zero or greater": "Quota must be zero or greater",
"Quota Per Unit": "Quota Per Unit", "Quota Per Unit": "Quota Per Unit",
"Quota reminder (tokens)": "Quota reminder (tokens)", "Quota reminder (tokens)": "Quota reminder (tokens)",
"Quota Reset": "Quota Reset", "Quota Reset": "Quota Reset",

View File

@ -2921,6 +2921,7 @@
"Please enter your verification code": "Veuillez saisir votre code de vérification", "Please enter your verification code": "Veuillez saisir votre code de vérification",
"Please enter your verification code or backup code": "Veuillez saisir votre code de vérification ou votre code de secours", "Please enter your verification code or backup code": "Veuillez saisir votre code de vérification ou votre code de secours",
"Please fix JSON errors before saving": "Veuillez corriger les erreurs JSON avant denregistrer", "Please fix JSON errors before saving": "Veuillez corriger les erreurs JSON avant denregistrer",
"Please fix the highlighted fields before saving": "Veuillez corriger les champs en surbrillance avant denregistrer",
"Please log in with the appropriate credentials": "Veuillez vous connecter avec les identifiants appropriés", "Please log in with the appropriate credentials": "Veuillez vous connecter avec les identifiants appropriés",
"Please manually copy and open the authorization link": "Veuillez copier et ouvrir manuellement le lien d'autorisation", "Please manually copy and open the authorization link": "Veuillez copier et ouvrir manuellement le lien d'autorisation",
"Please select a container": "Veuillez sélectionner un conteneur", "Please select a container": "Veuillez sélectionner un conteneur",
@ -3097,6 +3098,7 @@
"Quota given to invited users": "Quota attribué aux utilisateurs invités", "Quota given to invited users": "Quota attribué aux utilisateurs invités",
"Quota given to users who invite others": "Quota attribué aux utilisateurs qui invitent d'autres personnes", "Quota given to users who invite others": "Quota attribué aux utilisateurs qui invitent d'autres personnes",
"Quota must be a positive number": "Le quota doit être un nombre positif", "Quota must be a positive number": "Le quota doit être un nombre positif",
"Quota must be zero or greater": "Le quota ne peut pas être négatif",
"Quota Per Unit": "Quota par unité", "Quota Per Unit": "Quota par unité",
"Quota reminder (tokens)": "Rappel de quota (jetons)", "Quota reminder (tokens)": "Rappel de quota (jetons)",
"Quota Reset": "Réinitialisation du quota", "Quota Reset": "Réinitialisation du quota",

View File

@ -2921,6 +2921,7 @@
"Please enter your verification code": "認証コードを入力してください", "Please enter your verification code": "認証コードを入力してください",
"Please enter your verification code or backup code": "認証コードまたはバックアップコードを入力してください", "Please enter your verification code or backup code": "認証コードまたはバックアップコードを入力してください",
"Please fix JSON errors before saving": "保存する前に JSON エラーを直してください", "Please fix JSON errors before saving": "保存する前に JSON エラーを直してください",
"Please fix the highlighted fields before saving": "保存する前に強調表示された項目を修正してください",
"Please log in with the appropriate credentials": "適切な認証情報でログインしてください", "Please log in with the appropriate credentials": "適切な認証情報でログインしてください",
"Please manually copy and open the authorization link": "認証リンクを手動でコピーして開いてください", "Please manually copy and open the authorization link": "認証リンクを手動でコピーして開いてください",
"Please select a container": "コンテナを選択してください", "Please select a container": "コンテナを選択してください",
@ -3097,6 +3098,7 @@
"Quota given to invited users": "招待されたユーザーに付与されるクォータ", "Quota given to invited users": "招待されたユーザーに付与されるクォータ",
"Quota given to users who invite others": "他のユーザーを招待したユーザーに付与されるクォータ", "Quota given to users who invite others": "他のユーザーを招待したユーザーに付与されるクォータ",
"Quota must be a positive number": "クォータは正の数である必要があります", "Quota must be a positive number": "クォータは正の数である必要があります",
"Quota must be zero or greater": "クォータは負の値にできません",
"Quota Per Unit": "ユニットあたりのクォータ", "Quota Per Unit": "ユニットあたりのクォータ",
"Quota reminder (tokens)": "クォータリマインダー (トークン)", "Quota reminder (tokens)": "クォータリマインダー (トークン)",
"Quota Reset": "クォータリセット", "Quota Reset": "クォータリセット",

View File

@ -2921,6 +2921,7 @@
"Please enter your verification code": "Пожалуйста, введите код подтверждения", "Please enter your verification code": "Пожалуйста, введите код подтверждения",
"Please enter your verification code or backup code": "Пожалуйста, введите код подтверждения или резервный код", "Please enter your verification code or backup code": "Пожалуйста, введите код подтверждения или резервный код",
"Please fix JSON errors before saving": "Исправьте ошибки JSON перед сохранением", "Please fix JSON errors before saving": "Исправьте ошибки JSON перед сохранением",
"Please fix the highlighted fields before saving": "Исправьте выделенные поля перед сохранением",
"Please log in with the appropriate credentials": "Пожалуйста, войдите с соответствующими учетными данными", "Please log in with the appropriate credentials": "Пожалуйста, войдите с соответствующими учетными данными",
"Please manually copy and open the authorization link": "Скопируйте и откройте ссылку авторизации вручную", "Please manually copy and open the authorization link": "Скопируйте и откройте ссылку авторизации вручную",
"Please select a container": "Пожалуйста, выберите контейнер", "Please select a container": "Пожалуйста, выберите контейнер",
@ -3097,6 +3098,7 @@
"Quota given to invited users": "Квота, предоставляемая приглашенным пользователям", "Quota given to invited users": "Квота, предоставляемая приглашенным пользователям",
"Quota given to users who invite others": "Квота, предоставляемая пользователям, которые приглашают других", "Quota given to users who invite others": "Квота, предоставляемая пользователям, которые приглашают других",
"Quota must be a positive number": "Квота должна быть положительным числом", "Quota must be a positive number": "Квота должна быть положительным числом",
"Quota must be zero or greater": "Квота не может быть отрицательной",
"Quota Per Unit": "Квота на единицу", "Quota Per Unit": "Квота на единицу",
"Quota reminder (tokens)": "Напоминание о квоте (токены)", "Quota reminder (tokens)": "Напоминание о квоте (токены)",
"Quota Reset": "Сброс квоты", "Quota Reset": "Сброс квоты",

View File

@ -2921,6 +2921,7 @@
"Please enter your verification code": "Vui lòng nhập mã xác thực của bạn", "Please enter your verification code": "Vui lòng nhập mã xác thực của bạn",
"Please enter your verification code or backup code": "Vui lòng nhập mã xác thực hoặc mã dự phòng của bạn", "Please enter your verification code or backup code": "Vui lòng nhập mã xác thực hoặc mã dự phòng của bạn",
"Please fix JSON errors before saving": "Vui lòng sửa lỗi JSON trước khi lưu", "Please fix JSON errors before saving": "Vui lòng sửa lỗi JSON trước khi lưu",
"Please fix the highlighted fields before saving": "Vui lòng sửa các trường được đánh dấu trước khi lưu",
"Please log in with the appropriate credentials": "Vui lòng đăng nhập bằng thông tin xác thực phù hợp", "Please log in with the appropriate credentials": "Vui lòng đăng nhập bằng thông tin xác thực phù hợp",
"Please manually copy and open the authorization link": "Vui lòng tự sao chép và mở liên kết ủy quyền", "Please manually copy and open the authorization link": "Vui lòng tự sao chép và mở liên kết ủy quyền",
"Please select a container": "Vui lòng chọn một container", "Please select a container": "Vui lòng chọn một container",
@ -3097,6 +3098,7 @@
"Quota given to invited users": "Hạn mức được cấp cho người dùng được mời", "Quota given to invited users": "Hạn mức được cấp cho người dùng được mời",
"Quota given to users who invite others": "Limit for users inviting others", "Quota given to users who invite others": "Limit for users inviting others",
"Quota must be a positive number": "Hạn mức phải là một số dương", "Quota must be a positive number": "Hạn mức phải là một số dương",
"Quota must be zero or greater": "Hạn mức không được âm",
"Quota Per Unit": "Định mức mỗi đơn vị", "Quota Per Unit": "Định mức mỗi đơn vị",
"Quota reminder (tokens)": "Nhắc nhở hạn mức (token)", "Quota reminder (tokens)": "Nhắc nhở hạn mức (token)",
"Quota Reset": "Đặt lại hạn mức", "Quota Reset": "Đặt lại hạn mức",

View File

@ -2921,6 +2921,7 @@
"Please enter your verification code": "请输入您的验证码", "Please enter your verification code": "请输入您的验证码",
"Please enter your verification code or backup code": "请输入您的验证码或备用码", "Please enter your verification code or backup code": "请输入您的验证码或备用码",
"Please fix JSON errors before saving": "请先修复 JSON 错误再保存", "Please fix JSON errors before saving": "请先修复 JSON 错误再保存",
"Please fix the highlighted fields before saving": "请先修复高亮字段后再保存",
"Please log in with the appropriate credentials": "请使用适当的凭据登录", "Please log in with the appropriate credentials": "请使用适当的凭据登录",
"Please manually copy and open the authorization link": "请手动复制并打开授权链接", "Please manually copy and open the authorization link": "请手动复制并打开授权链接",
"Please select a container": "请选择一个容器", "Please select a container": "请选择一个容器",
@ -3097,6 +3098,7 @@
"Quota given to invited users": "授予被邀请用户的配额", "Quota given to invited users": "授予被邀请用户的配额",
"Quota given to users who invite others": "授予邀请其他用户的配额", "Quota given to users who invite others": "授予邀请其他用户的配额",
"Quota must be a positive number": "配额必须是正数", "Quota must be a positive number": "配额必须是正数",
"Quota must be zero or greater": "额度不能为负数",
"Quota Per Unit": "每单位配额", "Quota Per Unit": "每单位配额",
"Quota reminder (tokens)": "配额提醒token", "Quota reminder (tokens)": "配额提醒token",
"Quota Reset": "额度重置", "Quota Reset": "额度重置",