mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-21 23:24:46 +08:00
feat(ops): add ignore insufficient balance errors toggle and extract error constants
- Add 5th error filter switch IgnoreInsufficientBalanceErrors to suppress
upstream insufficient balance / insufficient_quota errors from ops log
- Extract hardcoded error strings into package-level constants for
shouldSkipOpsErrorLog, normalizeOpsErrorType, classifyOpsPhase, and
classifyOpsIsBusinessLimited
- Define ErrNoAvailableAccounts sentinel error and replace all
errors.New("no available accounts") call sites
- Update tests to use require.ErrorIs with the sentinel error
This commit is contained in:
@@ -26,6 +26,21 @@ const (
|
|||||||
opsStreamKey = "ops_stream"
|
opsStreamKey = "ops_stream"
|
||||||
opsRequestBodyKey = "ops_request_body"
|
opsRequestBodyKey = "ops_request_body"
|
||||||
opsAccountIDKey = "ops_account_id"
|
opsAccountIDKey = "ops_account_id"
|
||||||
|
|
||||||
|
// 错误过滤匹配常量 — shouldSkipOpsErrorLog 和错误分类共用
|
||||||
|
opsErrContextCanceled = "context canceled"
|
||||||
|
opsErrNoAvailableAccounts = "no available accounts"
|
||||||
|
opsErrInvalidAPIKey = "invalid_api_key"
|
||||||
|
opsErrAPIKeyRequired = "api_key_required"
|
||||||
|
opsErrInsufficientBalance = "insufficient balance"
|
||||||
|
opsErrInsufficientQuota = "insufficient_quota"
|
||||||
|
|
||||||
|
// 上游错误码常量 — 错误分类 (normalizeOpsErrorType / classifyOpsPhase / classifyOpsIsBusinessLimited)
|
||||||
|
opsCodeInsufficientBalance = "INSUFFICIENT_BALANCE"
|
||||||
|
opsCodeUsageLimitExceeded = "USAGE_LIMIT_EXCEEDED"
|
||||||
|
opsCodeSubscriptionNotFound = "SUBSCRIPTION_NOT_FOUND"
|
||||||
|
opsCodeSubscriptionInvalid = "SUBSCRIPTION_INVALID"
|
||||||
|
opsCodeUserInactive = "USER_INACTIVE"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -1024,9 +1039,9 @@ func normalizeOpsErrorType(errType string, code string) string {
|
|||||||
return errType
|
return errType
|
||||||
}
|
}
|
||||||
switch strings.TrimSpace(code) {
|
switch strings.TrimSpace(code) {
|
||||||
case "INSUFFICIENT_BALANCE":
|
case opsCodeInsufficientBalance:
|
||||||
return "billing_error"
|
return "billing_error"
|
||||||
case "USAGE_LIMIT_EXCEEDED", "SUBSCRIPTION_NOT_FOUND", "SUBSCRIPTION_INVALID":
|
case opsCodeUsageLimitExceeded, opsCodeSubscriptionNotFound, opsCodeSubscriptionInvalid:
|
||||||
return "subscription_error"
|
return "subscription_error"
|
||||||
default:
|
default:
|
||||||
return "api_error"
|
return "api_error"
|
||||||
@@ -1038,7 +1053,7 @@ func classifyOpsPhase(errType, message, code string) string {
|
|||||||
// Standardized phases: request|auth|routing|upstream|network|internal
|
// Standardized phases: request|auth|routing|upstream|network|internal
|
||||||
// Map billing/concurrency/response => request; scheduling => routing.
|
// Map billing/concurrency/response => request; scheduling => routing.
|
||||||
switch strings.TrimSpace(code) {
|
switch strings.TrimSpace(code) {
|
||||||
case "INSUFFICIENT_BALANCE", "USAGE_LIMIT_EXCEEDED", "SUBSCRIPTION_NOT_FOUND", "SUBSCRIPTION_INVALID":
|
case opsCodeInsufficientBalance, opsCodeUsageLimitExceeded, opsCodeSubscriptionNotFound, opsCodeSubscriptionInvalid:
|
||||||
return "request"
|
return "request"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1057,7 +1072,7 @@ func classifyOpsPhase(errType, message, code string) string {
|
|||||||
case "upstream_error", "overloaded_error":
|
case "upstream_error", "overloaded_error":
|
||||||
return "upstream"
|
return "upstream"
|
||||||
case "api_error":
|
case "api_error":
|
||||||
if strings.Contains(msg, "no available accounts") {
|
if strings.Contains(msg, opsErrNoAvailableAccounts) {
|
||||||
return "routing"
|
return "routing"
|
||||||
}
|
}
|
||||||
return "internal"
|
return "internal"
|
||||||
@@ -1103,7 +1118,7 @@ func classifyOpsIsRetryable(errType string, statusCode int) bool {
|
|||||||
|
|
||||||
func classifyOpsIsBusinessLimited(errType, phase, code string, status int, message string) bool {
|
func classifyOpsIsBusinessLimited(errType, phase, code string, status int, message string) bool {
|
||||||
switch strings.TrimSpace(code) {
|
switch strings.TrimSpace(code) {
|
||||||
case "INSUFFICIENT_BALANCE", "USAGE_LIMIT_EXCEEDED", "SUBSCRIPTION_NOT_FOUND", "SUBSCRIPTION_INVALID", "USER_INACTIVE":
|
case opsCodeInsufficientBalance, opsCodeUsageLimitExceeded, opsCodeSubscriptionNotFound, opsCodeSubscriptionInvalid, opsCodeUserInactive:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if phase == "billing" || phase == "concurrency" {
|
if phase == "billing" || phase == "concurrency" {
|
||||||
@@ -1197,21 +1212,29 @@ func shouldSkipOpsErrorLog(ctx context.Context, ops *service.OpsService, message
|
|||||||
|
|
||||||
// Check if context canceled errors should be ignored (client disconnects)
|
// Check if context canceled errors should be ignored (client disconnects)
|
||||||
if settings.IgnoreContextCanceled {
|
if settings.IgnoreContextCanceled {
|
||||||
if strings.Contains(msgLower, "context canceled") || strings.Contains(bodyLower, "context canceled") {
|
if strings.Contains(msgLower, opsErrContextCanceled) || strings.Contains(bodyLower, opsErrContextCanceled) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if "no available accounts" errors should be ignored
|
// Check if "no available accounts" errors should be ignored
|
||||||
if settings.IgnoreNoAvailableAccounts {
|
if settings.IgnoreNoAvailableAccounts {
|
||||||
if strings.Contains(msgLower, "no available accounts") || strings.Contains(bodyLower, "no available accounts") {
|
if strings.Contains(msgLower, opsErrNoAvailableAccounts) || strings.Contains(bodyLower, opsErrNoAvailableAccounts) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if invalid/missing API key errors should be ignored (user misconfiguration)
|
// Check if invalid/missing API key errors should be ignored (user misconfiguration)
|
||||||
if settings.IgnoreInvalidApiKeyErrors {
|
if settings.IgnoreInvalidApiKeyErrors {
|
||||||
if strings.Contains(bodyLower, "invalid_api_key") || strings.Contains(bodyLower, "api_key_required") {
|
if strings.Contains(bodyLower, opsErrInvalidAPIKey) || strings.Contains(bodyLower, opsErrAPIKeyRequired) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if insufficient balance errors should be ignored
|
||||||
|
if settings.IgnoreInsufficientBalanceErrors {
|
||||||
|
if strings.Contains(bodyLower, opsErrInsufficientBalance) || strings.Contains(bodyLower, opsErrInsufficientQuota) ||
|
||||||
|
strings.Contains(msgLower, opsErrInsufficientBalance) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ func TestGatewayService_SelectAccountForModelWithPlatform_NoAvailableAccounts(t
|
|||||||
acc, err := svc.selectAccountForModelWithPlatform(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, PlatformAnthropic)
|
acc, err := svc.selectAccountForModelWithPlatform(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, PlatformAnthropic)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Nil(t, acc)
|
require.Nil(t, acc)
|
||||||
require.Contains(t, err.Error(), "no available accounts")
|
require.ErrorIs(t, err, ErrNoAvailableAccounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestGatewayService_SelectAccountForModelWithPlatform_AllExcluded 测试所有账户被排除
|
// TestGatewayService_SelectAccountForModelWithPlatform_AllExcluded 测试所有账户被排除
|
||||||
@@ -1073,7 +1073,7 @@ func TestGatewayService_SelectAccountForModelWithPlatform_NoAccounts(t *testing.
|
|||||||
acc, err := svc.selectAccountForModelWithPlatform(ctx, nil, "", "", nil, PlatformAnthropic)
|
acc, err := svc.selectAccountForModelWithPlatform(ctx, nil, "", "", nil, PlatformAnthropic)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Nil(t, acc)
|
require.Nil(t, acc)
|
||||||
require.Contains(t, err.Error(), "no available accounts")
|
require.ErrorIs(t, err, ErrNoAvailableAccounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGatewayService_isModelSupportedByAccount(t *testing.T) {
|
func TestGatewayService_isModelSupportedByAccount(t *testing.T) {
|
||||||
@@ -1734,7 +1734,7 @@ func TestGatewayService_selectAccountWithMixedScheduling(t *testing.T) {
|
|||||||
acc, err := svc.selectAccountWithMixedScheduling(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, PlatformAnthropic)
|
acc, err := svc.selectAccountWithMixedScheduling(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, PlatformAnthropic)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Nil(t, acc)
|
require.Nil(t, acc)
|
||||||
require.Contains(t, err.Error(), "no available accounts")
|
require.ErrorIs(t, err, ErrNoAvailableAccounts)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("混合调度-不支持模型返回错误", func(t *testing.T) {
|
t.Run("混合调度-不支持模型返回错误", func(t *testing.T) {
|
||||||
@@ -2290,7 +2290,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
|
|||||||
result, err := svc.SelectAccountWithLoadAwareness(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, "")
|
result, err := svc.SelectAccountWithLoadAwareness(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, "")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Nil(t, result)
|
require.Nil(t, result)
|
||||||
require.Contains(t, err.Error(), "no available accounts")
|
require.ErrorIs(t, err, ErrNoAvailableAccounts)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("过滤不可调度账号-限流账号被跳过", func(t *testing.T) {
|
t.Run("过滤不可调度账号-限流账号被跳过", func(t *testing.T) {
|
||||||
|
|||||||
@@ -346,6 +346,9 @@ var systemBlockFilterPrefixes = []string{
|
|||||||
"x-anthropic-billing-header",
|
"x-anthropic-billing-header",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrNoAvailableAccounts 表示没有可用的账号
|
||||||
|
var ErrNoAvailableAccounts = errors.New("no available accounts")
|
||||||
|
|
||||||
// ErrClaudeCodeOnly 表示分组仅允许 Claude Code 客户端访问
|
// ErrClaudeCodeOnly 表示分组仅允许 Claude Code 客户端访问
|
||||||
var ErrClaudeCodeOnly = errors.New("this group only allows Claude Code clients")
|
var ErrClaudeCodeOnly = errors.New("this group only allows Claude Code clients")
|
||||||
|
|
||||||
@@ -1205,7 +1208,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(accounts) == 0 {
|
if len(accounts) == 0 {
|
||||||
return nil, errors.New("no available accounts")
|
return nil, ErrNoAvailableAccounts
|
||||||
}
|
}
|
||||||
ctx = s.withWindowCostPrefetch(ctx, accounts)
|
ctx = s.withWindowCostPrefetch(ctx, accounts)
|
||||||
ctx = s.withRPMPrefetch(ctx, accounts)
|
ctx = s.withRPMPrefetch(ctx, accounts)
|
||||||
@@ -1553,7 +1556,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(candidates) == 0 {
|
if len(candidates) == 0 {
|
||||||
return nil, errors.New("no available accounts")
|
return nil, ErrNoAvailableAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
accountLoads := make([]AccountWithConcurrency, 0, len(candidates))
|
accountLoads := make([]AccountWithConcurrency, 0, len(candidates))
|
||||||
@@ -1642,7 +1645,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
|
|||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("no available accounts")
|
return nil, ErrNoAvailableAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GatewayService) tryAcquireByLegacyOrder(ctx context.Context, candidates []*Account, groupID *int64, sessionHash string, preferOAuth bool) (*AccountSelectionResult, bool) {
|
func (s *GatewayService) tryAcquireByLegacyOrder(ctx context.Context, candidates []*Account, groupID *int64, sessionHash string, preferOAuth bool) (*AccountSelectionResult, bool) {
|
||||||
@@ -2852,9 +2855,9 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
|
|||||||
if selected == nil {
|
if selected == nil {
|
||||||
stats := s.logDetailedSelectionFailure(ctx, groupID, sessionHash, requestedModel, platform, accounts, excludedIDs, false)
|
stats := s.logDetailedSelectionFailure(ctx, groupID, sessionHash, requestedModel, platform, accounts, excludedIDs, false)
|
||||||
if requestedModel != "" {
|
if requestedModel != "" {
|
||||||
return nil, fmt.Errorf("no available accounts supporting model: %s (%s)", requestedModel, summarizeSelectionFailureStats(stats))
|
return nil, fmt.Errorf("%w supporting model: %s (%s)", ErrNoAvailableAccounts, requestedModel, summarizeSelectionFailureStats(stats))
|
||||||
}
|
}
|
||||||
return nil, errors.New("no available accounts")
|
return nil, ErrNoAvailableAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 建立粘性绑定
|
// 4. 建立粘性绑定
|
||||||
@@ -3090,9 +3093,9 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
|
|||||||
if selected == nil {
|
if selected == nil {
|
||||||
stats := s.logDetailedSelectionFailure(ctx, groupID, sessionHash, requestedModel, nativePlatform, accounts, excludedIDs, true)
|
stats := s.logDetailedSelectionFailure(ctx, groupID, sessionHash, requestedModel, nativePlatform, accounts, excludedIDs, true)
|
||||||
if requestedModel != "" {
|
if requestedModel != "" {
|
||||||
return nil, fmt.Errorf("no available accounts supporting model: %s (%s)", requestedModel, summarizeSelectionFailureStats(stats))
|
return nil, fmt.Errorf("%w supporting model: %s (%s)", ErrNoAvailableAccounts, requestedModel, summarizeSelectionFailureStats(stats))
|
||||||
}
|
}
|
||||||
return nil, errors.New("no available accounts")
|
return nil, ErrNoAvailableAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 建立粘性绑定
|
// 4. 建立粘性绑定
|
||||||
|
|||||||
@@ -725,7 +725,7 @@ func (s *defaultOpenAIAccountScheduler) selectByLoadBalance(
|
|||||||
}, len(candidates), topK, loadSkew, nil
|
}, len(candidates), topK, loadSkew, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, len(candidates), topK, loadSkew, errors.New("no available accounts")
|
return nil, len(candidates), topK, loadSkew, ErrNoAvailableAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *defaultOpenAIAccountScheduler) isAccountTransportCompatible(account *Account, requiredTransport OpenAIUpstreamTransport) bool {
|
func (s *defaultOpenAIAccountScheduler) isAccountTransportCompatible(account *Account, requiredTransport OpenAIUpstreamTransport) bool {
|
||||||
|
|||||||
@@ -1312,7 +1312,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(accounts) == 0 {
|
if len(accounts) == 0 {
|
||||||
return nil, errors.New("no available accounts")
|
return nil, ErrNoAvailableAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
isExcluded := func(accountID int64) bool {
|
isExcluded := func(accountID int64) bool {
|
||||||
@@ -1382,7 +1382,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(candidates) == 0 {
|
if len(candidates) == 0 {
|
||||||
return nil, errors.New("no available accounts")
|
return nil, ErrNoAvailableAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
accountLoads := make([]AccountWithConcurrency, 0, len(candidates))
|
accountLoads := make([]AccountWithConcurrency, 0, len(candidates))
|
||||||
@@ -1489,7 +1489,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("no available accounts")
|
return nil, ErrNoAvailableAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OpenAIGatewayService) listSchedulableAccounts(ctx context.Context, groupID *int64) ([]Account, error) {
|
func (s *OpenAIGatewayService) listSchedulableAccounts(ctx context.Context, groupID *int64) ([]Account, error) {
|
||||||
|
|||||||
@@ -467,7 +467,7 @@ func (s *OpsService) executeClientRetry(ctx context.Context, reqType opsRetryReq
|
|||||||
return &opsRetryExecution{status: opsRetryStatusFailed, errorMessage: selErr.Error()}
|
return &opsRetryExecution{status: opsRetryStatusFailed, errorMessage: selErr.Error()}
|
||||||
}
|
}
|
||||||
if selection == nil || selection.Account == nil {
|
if selection == nil || selection.Account == nil {
|
||||||
return &opsRetryExecution{status: opsRetryStatusFailed, errorMessage: "no available accounts"}
|
return &opsRetryExecution{status: opsRetryStatusFailed, errorMessage: ErrNoAvailableAccounts.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
account := selection.Account
|
account := selection.Account
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ func defaultOpsAdvancedSettings() *OpsAdvancedSettings {
|
|||||||
IgnoreCountTokensErrors: true, // count_tokens 404 是预期行为,默认忽略
|
IgnoreCountTokensErrors: true, // count_tokens 404 是预期行为,默认忽略
|
||||||
IgnoreContextCanceled: true, // Default to true - client disconnects are not errors
|
IgnoreContextCanceled: true, // Default to true - client disconnects are not errors
|
||||||
IgnoreNoAvailableAccounts: false, // Default to false - this is a real routing issue
|
IgnoreNoAvailableAccounts: false, // Default to false - this is a real routing issue
|
||||||
|
IgnoreInsufficientBalanceErrors: false, // 默认不忽略,余额不足可能需要关注
|
||||||
DisplayOpenAITokenStats: false,
|
DisplayOpenAITokenStats: false,
|
||||||
DisplayAlertEvents: true,
|
DisplayAlertEvents: true,
|
||||||
AutoRefreshEnabled: false,
|
AutoRefreshEnabled: false,
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ type OpsAdvancedSettings struct {
|
|||||||
IgnoreContextCanceled bool `json:"ignore_context_canceled"`
|
IgnoreContextCanceled bool `json:"ignore_context_canceled"`
|
||||||
IgnoreNoAvailableAccounts bool `json:"ignore_no_available_accounts"`
|
IgnoreNoAvailableAccounts bool `json:"ignore_no_available_accounts"`
|
||||||
IgnoreInvalidApiKeyErrors bool `json:"ignore_invalid_api_key_errors"`
|
IgnoreInvalidApiKeyErrors bool `json:"ignore_invalid_api_key_errors"`
|
||||||
|
IgnoreInsufficientBalanceErrors bool `json:"ignore_insufficient_balance_errors"`
|
||||||
DisplayOpenAITokenStats bool `json:"display_openai_token_stats"`
|
DisplayOpenAITokenStats bool `json:"display_openai_token_stats"`
|
||||||
DisplayAlertEvents bool `json:"display_alert_events"`
|
DisplayAlertEvents bool `json:"display_alert_events"`
|
||||||
AutoRefreshEnabled bool `json:"auto_refresh_enabled"`
|
AutoRefreshEnabled bool `json:"auto_refresh_enabled"`
|
||||||
|
|||||||
@@ -841,6 +841,7 @@ export interface OpsAdvancedSettings {
|
|||||||
ignore_context_canceled: boolean
|
ignore_context_canceled: boolean
|
||||||
ignore_no_available_accounts: boolean
|
ignore_no_available_accounts: boolean
|
||||||
ignore_invalid_api_key_errors: boolean
|
ignore_invalid_api_key_errors: boolean
|
||||||
|
ignore_insufficient_balance_errors: boolean
|
||||||
display_openai_token_stats: boolean
|
display_openai_token_stats: boolean
|
||||||
display_alert_events: boolean
|
display_alert_events: boolean
|
||||||
auto_refresh_enabled: boolean
|
auto_refresh_enabled: boolean
|
||||||
|
|||||||
@@ -3842,6 +3842,8 @@ export default {
|
|||||||
ignoreNoAvailableAccountsHint: 'When enabled, "No available accounts" errors will not be written to the error log (not recommended; usually a config issue).',
|
ignoreNoAvailableAccountsHint: 'When enabled, "No available accounts" errors will not be written to the error log (not recommended; usually a config issue).',
|
||||||
ignoreInvalidApiKeyErrors: 'Ignore invalid API key errors',
|
ignoreInvalidApiKeyErrors: 'Ignore invalid API key errors',
|
||||||
ignoreInvalidApiKeyErrorsHint: 'When enabled, invalid or missing API key errors (INVALID_API_KEY, API_KEY_REQUIRED) will not be written to the error log.',
|
ignoreInvalidApiKeyErrorsHint: 'When enabled, invalid or missing API key errors (INVALID_API_KEY, API_KEY_REQUIRED) will not be written to the error log.',
|
||||||
|
ignoreInsufficientBalanceErrors: 'Ignore Insufficient Balance Errors',
|
||||||
|
ignoreInsufficientBalanceErrorsHint: 'When enabled, upstream insufficient account balance errors will not be written to the error log.',
|
||||||
autoRefresh: 'Auto Refresh',
|
autoRefresh: 'Auto Refresh',
|
||||||
enableAutoRefresh: 'Enable auto refresh',
|
enableAutoRefresh: 'Enable auto refresh',
|
||||||
enableAutoRefreshHint: 'Automatically refresh dashboard data at a fixed interval.',
|
enableAutoRefreshHint: 'Automatically refresh dashboard data at a fixed interval.',
|
||||||
|
|||||||
@@ -4016,6 +4016,8 @@ export default {
|
|||||||
ignoreNoAvailableAccountsHint: '启用后,"No available accounts" 错误将不会写入错误日志(不推荐,这通常是配置问题)。',
|
ignoreNoAvailableAccountsHint: '启用后,"No available accounts" 错误将不会写入错误日志(不推荐,这通常是配置问题)。',
|
||||||
ignoreInvalidApiKeyErrors: '忽略无效 API Key 错误',
|
ignoreInvalidApiKeyErrors: '忽略无效 API Key 错误',
|
||||||
ignoreInvalidApiKeyErrorsHint: '启用后,无效或缺失 API Key 的错误(INVALID_API_KEY、API_KEY_REQUIRED)将不会写入错误日志。',
|
ignoreInvalidApiKeyErrorsHint: '启用后,无效或缺失 API Key 的错误(INVALID_API_KEY、API_KEY_REQUIRED)将不会写入错误日志。',
|
||||||
|
ignoreInsufficientBalanceErrors: '忽略余额不足错误',
|
||||||
|
ignoreInsufficientBalanceErrorsHint: '启用后,上游账号余额不足(Insufficient balance)的错误将不会写入错误日志。',
|
||||||
autoRefresh: '自动刷新',
|
autoRefresh: '自动刷新',
|
||||||
enableAutoRefresh: '启用自动刷新',
|
enableAutoRefresh: '启用自动刷新',
|
||||||
enableAutoRefreshHint: '自动刷新仪表板数据,启用后会定期拉取最新数据。',
|
enableAutoRefreshHint: '自动刷新仪表板数据,启用后会定期拉取最新数据。',
|
||||||
|
|||||||
@@ -516,6 +516,16 @@ async function saveAllSettings() {
|
|||||||
</div>
|
</div>
|
||||||
<Toggle v-model="advancedSettings.ignore_invalid_api_key_errors" />
|
<Toggle v-model="advancedSettings.ignore_invalid_api_key_errors" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.ops.settings.ignoreInsufficientBalanceErrors') }}</label>
|
||||||
|
<p class="mt-1 text-xs text-gray-500">
|
||||||
|
{{ t('admin.ops.settings.ignoreInsufficientBalanceErrorsHint') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Toggle v-model="advancedSettings.ignore_insufficient_balance_errors" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Auto Refresh -->
|
<!-- Auto Refresh -->
|
||||||
|
|||||||
Reference in New Issue
Block a user