feat: add max_claude_code_version setting and disable auto-upgrade env var
Add maximum Claude Code version limit to complement the existing minimum version check. Refactor the version cache from single-value to unified bounds struct (min+max) with a single atomic.Value and singleflight group. - Backend: new constant, struct field, cache refactor, validation (semver format + cross-validation max >= min), gateway enforcement, audit diff - Frontend: settings UI input, TypeScript types, zh/en i18n - Add CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 to all Claude Code tutorials on /keys page (unix/cmd/powershell/vscode settings.json)
This commit is contained in:
@@ -125,6 +125,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
||||
OpsQueryModeDefault: settings.OpsQueryModeDefault,
|
||||
OpsMetricsIntervalSeconds: settings.OpsMetricsIntervalSeconds,
|
||||
MinClaudeCodeVersion: settings.MinClaudeCodeVersion,
|
||||
MaxClaudeCodeVersion: settings.MaxClaudeCodeVersion,
|
||||
AllowUngroupedKeyScheduling: settings.AllowUngroupedKeyScheduling,
|
||||
BackendModeEnabled: settings.BackendModeEnabled,
|
||||
})
|
||||
@@ -199,6 +200,7 @@ type UpdateSettingsRequest struct {
|
||||
OpsMetricsIntervalSeconds *int `json:"ops_metrics_interval_seconds"`
|
||||
|
||||
MinClaudeCodeVersion string `json:"min_claude_code_version"`
|
||||
MaxClaudeCodeVersion string `json:"max_claude_code_version"`
|
||||
|
||||
// 分组隔离
|
||||
AllowUngroupedKeyScheduling bool `json:"allow_ungrouped_key_scheduling"`
|
||||
@@ -442,6 +444,22 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 验证最高版本号格式(空字符串=禁用,或合法 semver)
|
||||
if req.MaxClaudeCodeVersion != "" {
|
||||
if !semverPattern.MatchString(req.MaxClaudeCodeVersion) {
|
||||
response.Error(c, http.StatusBadRequest, "max_claude_code_version must be empty or a valid semver (e.g. 3.0.0)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 交叉验证:如果同时设置了最低和最高版本号,最高版本号必须 >= 最低版本号
|
||||
if req.MinClaudeCodeVersion != "" && req.MaxClaudeCodeVersion != "" {
|
||||
if service.CompareVersions(req.MaxClaudeCodeVersion, req.MinClaudeCodeVersion) < 0 {
|
||||
response.Error(c, http.StatusBadRequest, "max_claude_code_version must be greater than or equal to min_claude_code_version")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
settings := &service.SystemSettings{
|
||||
RegistrationEnabled: req.RegistrationEnabled,
|
||||
EmailVerifyEnabled: req.EmailVerifyEnabled,
|
||||
@@ -488,6 +506,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
EnableIdentityPatch: req.EnableIdentityPatch,
|
||||
IdentityPatchPrompt: req.IdentityPatchPrompt,
|
||||
MinClaudeCodeVersion: req.MinClaudeCodeVersion,
|
||||
MaxClaudeCodeVersion: req.MaxClaudeCodeVersion,
|
||||
AllowUngroupedKeyScheduling: req.AllowUngroupedKeyScheduling,
|
||||
BackendModeEnabled: req.BackendModeEnabled,
|
||||
OpsMonitoringEnabled: func() bool {
|
||||
@@ -588,6 +607,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
OpsQueryModeDefault: updatedSettings.OpsQueryModeDefault,
|
||||
OpsMetricsIntervalSeconds: updatedSettings.OpsMetricsIntervalSeconds,
|
||||
MinClaudeCodeVersion: updatedSettings.MinClaudeCodeVersion,
|
||||
MaxClaudeCodeVersion: updatedSettings.MaxClaudeCodeVersion,
|
||||
AllowUngroupedKeyScheduling: updatedSettings.AllowUngroupedKeyScheduling,
|
||||
BackendModeEnabled: updatedSettings.BackendModeEnabled,
|
||||
})
|
||||
@@ -744,6 +764,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
||||
if before.MinClaudeCodeVersion != after.MinClaudeCodeVersion {
|
||||
changed = append(changed, "min_claude_code_version")
|
||||
}
|
||||
if before.MaxClaudeCodeVersion != after.MaxClaudeCodeVersion {
|
||||
changed = append(changed, "max_claude_code_version")
|
||||
}
|
||||
if before.AllowUngroupedKeyScheduling != after.AllowUngroupedKeyScheduling {
|
||||
changed = append(changed, "allow_ungrouped_key_scheduling")
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ type SystemSettings struct {
|
||||
OpsMetricsIntervalSeconds int `json:"ops_metrics_interval_seconds"`
|
||||
|
||||
MinClaudeCodeVersion string `json:"min_claude_code_version"`
|
||||
MaxClaudeCodeVersion string `json:"max_claude_code_version"`
|
||||
|
||||
// 分组隔离
|
||||
AllowUngroupedKeyScheduling bool `json:"allow_ungrouped_key_scheduling"`
|
||||
|
||||
@@ -1281,7 +1281,7 @@ func (h *GatewayHandler) ensureForwardErrorResponse(c *gin.Context, streamStarte
|
||||
return true
|
||||
}
|
||||
|
||||
// checkClaudeCodeVersion 检查 Claude Code 客户端版本是否满足最低要求
|
||||
// checkClaudeCodeVersion 检查 Claude Code 客户端版本是否满足版本要求
|
||||
// 仅对已识别的 Claude Code 客户端执行,count_tokens 路径除外
|
||||
func (h *GatewayHandler) checkClaudeCodeVersion(c *gin.Context) bool {
|
||||
ctx := c.Request.Context()
|
||||
@@ -1294,8 +1294,8 @@ func (h *GatewayHandler) checkClaudeCodeVersion(c *gin.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
minVersion := h.settingService.GetMinClaudeCodeVersion(ctx)
|
||||
if minVersion == "" {
|
||||
minVersion, maxVersion := h.settingService.GetClaudeCodeVersionBounds(ctx)
|
||||
if minVersion == "" && maxVersion == "" {
|
||||
return true // 未设置,不检查
|
||||
}
|
||||
|
||||
@@ -1306,13 +1306,22 @@ func (h *GatewayHandler) checkClaudeCodeVersion(c *gin.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if service.CompareVersions(clientVersion, minVersion) < 0 {
|
||||
if minVersion != "" && service.CompareVersions(clientVersion, minVersion) < 0 {
|
||||
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error",
|
||||
fmt.Sprintf("Your Claude Code version (%s) is below the minimum required version (%s). Please update: npm update -g @anthropic-ai/claude-code",
|
||||
clientVersion, minVersion))
|
||||
return false
|
||||
}
|
||||
|
||||
if maxVersion != "" && service.CompareVersions(clientVersion, maxVersion) > 0 {
|
||||
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error",
|
||||
fmt.Sprintf("Your Claude Code version (%s) exceeds the maximum allowed version (%s). "+
|
||||
"Please downgrade: npm install -g @anthropic-ai/claude-code@%s && "+
|
||||
"set CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 to prevent auto-upgrade",
|
||||
clientVersion, maxVersion, maxVersion))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -226,6 +226,9 @@ const (
|
||||
// SettingKeyMinClaudeCodeVersion 最低 Claude Code 版本号要求 (semver, 如 "2.1.0",空值=不检查)
|
||||
SettingKeyMinClaudeCodeVersion = "min_claude_code_version"
|
||||
|
||||
// SettingKeyMaxClaudeCodeVersion 最高 Claude Code 版本号限制 (semver, 如 "3.0.0",空值=不检查)
|
||||
SettingKeyMaxClaudeCodeVersion = "max_claude_code_version"
|
||||
|
||||
// SettingKeyAllowUngroupedKeyScheduling 允许未分组 API Key 调度(默认 false:未分组 Key 返回 403)
|
||||
SettingKeyAllowUngroupedKeyScheduling = "allow_ungrouped_key_scheduling"
|
||||
|
||||
|
||||
@@ -44,26 +44,27 @@ type SettingRepository interface {
|
||||
Delete(ctx context.Context, key string) error
|
||||
}
|
||||
|
||||
// cachedMinVersion 缓存最低 Claude Code 版本号(进程内缓存,60s TTL)
|
||||
type cachedMinVersion struct {
|
||||
value string // 空字符串 = 不检查
|
||||
// cachedVersionBounds 缓存 Claude Code 版本号上下限(进程内缓存,60s TTL)
|
||||
type cachedVersionBounds struct {
|
||||
min string // 空字符串 = 不检查
|
||||
max string // 空字符串 = 不检查
|
||||
expiresAt int64 // unix nano
|
||||
}
|
||||
|
||||
// minVersionCache 最低版本号进程内缓存
|
||||
var minVersionCache atomic.Value // *cachedMinVersion
|
||||
// versionBoundsCache 版本号上下限进程内缓存
|
||||
var versionBoundsCache atomic.Value // *cachedVersionBounds
|
||||
|
||||
// minVersionSF 防止缓存过期时 thundering herd
|
||||
var minVersionSF singleflight.Group
|
||||
// versionBoundsSF 防止缓存过期时 thundering herd
|
||||
var versionBoundsSF singleflight.Group
|
||||
|
||||
// minVersionCacheTTL 缓存有效期
|
||||
const minVersionCacheTTL = 60 * time.Second
|
||||
// versionBoundsCacheTTL 缓存有效期
|
||||
const versionBoundsCacheTTL = 60 * time.Second
|
||||
|
||||
// minVersionErrorTTL DB 错误时的短缓存,快速重试
|
||||
const minVersionErrorTTL = 5 * time.Second
|
||||
// versionBoundsErrorTTL DB 错误时的短缓存,快速重试
|
||||
const versionBoundsErrorTTL = 5 * time.Second
|
||||
|
||||
// minVersionDBTimeout singleflight 内 DB 查询超时,独立于请求 context
|
||||
const minVersionDBTimeout = 5 * time.Second
|
||||
// versionBoundsDBTimeout singleflight 内 DB 查询超时,独立于请求 context
|
||||
const versionBoundsDBTimeout = 5 * time.Second
|
||||
|
||||
// cachedBackendMode Backend Mode cache (in-process, 60s TTL)
|
||||
type cachedBackendMode struct {
|
||||
@@ -484,6 +485,7 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
|
||||
|
||||
// Claude Code version check
|
||||
updates[SettingKeyMinClaudeCodeVersion] = settings.MinClaudeCodeVersion
|
||||
updates[SettingKeyMaxClaudeCodeVersion] = settings.MaxClaudeCodeVersion
|
||||
|
||||
// 分组隔离
|
||||
updates[SettingKeyAllowUngroupedKeyScheduling] = strconv.FormatBool(settings.AllowUngroupedKeyScheduling)
|
||||
@@ -494,10 +496,11 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
|
||||
err = s.settingRepo.SetMultiple(ctx, updates)
|
||||
if err == nil {
|
||||
// 先使 inflight singleflight 失效,再刷新缓存,缩小旧值覆盖新值的竞态窗口
|
||||
minVersionSF.Forget("min_version")
|
||||
minVersionCache.Store(&cachedMinVersion{
|
||||
value: settings.MinClaudeCodeVersion,
|
||||
expiresAt: time.Now().Add(minVersionCacheTTL).UnixNano(),
|
||||
versionBoundsSF.Forget("version_bounds")
|
||||
versionBoundsCache.Store(&cachedVersionBounds{
|
||||
min: settings.MinClaudeCodeVersion,
|
||||
max: settings.MaxClaudeCodeVersion,
|
||||
expiresAt: time.Now().Add(versionBoundsCacheTTL).UnixNano(),
|
||||
})
|
||||
backendModeSF.Forget("backend_mode")
|
||||
backendModeCache.Store(&cachedBackendMode{
|
||||
@@ -760,6 +763,7 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
|
||||
|
||||
// Claude Code version check (default: empty = disabled)
|
||||
SettingKeyMinClaudeCodeVersion: "",
|
||||
SettingKeyMaxClaudeCodeVersion: "",
|
||||
|
||||
// 分组隔离(默认不允许未分组 Key 调度)
|
||||
SettingKeyAllowUngroupedKeyScheduling: "false",
|
||||
@@ -895,6 +899,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
|
||||
|
||||
// Claude Code version check
|
||||
result.MinClaudeCodeVersion = settings[SettingKeyMinClaudeCodeVersion]
|
||||
result.MaxClaudeCodeVersion = settings[SettingKeyMaxClaudeCodeVersion]
|
||||
|
||||
// 分组隔离
|
||||
result.AllowUngroupedKeyScheduling = settings[SettingKeyAllowUngroupedKeyScheduling] == "true"
|
||||
@@ -1281,51 +1286,61 @@ func (s *SettingService) IsUngroupedKeySchedulingAllowed(ctx context.Context) bo
|
||||
return value == "true"
|
||||
}
|
||||
|
||||
// GetMinClaudeCodeVersion 获取最低 Claude Code 版本号要求
|
||||
// GetClaudeCodeVersionBounds 获取 Claude Code 版本号上下限要求
|
||||
// 使用进程内 atomic.Value 缓存,60 秒 TTL,热路径零锁开销
|
||||
// singleflight 防止缓存过期时 thundering herd
|
||||
// 返回空字符串表示不做版本检查
|
||||
func (s *SettingService) GetMinClaudeCodeVersion(ctx context.Context) string {
|
||||
if cached, ok := minVersionCache.Load().(*cachedMinVersion); ok {
|
||||
// 返回空字符串表示不做对应方向的版本检查
|
||||
func (s *SettingService) GetClaudeCodeVersionBounds(ctx context.Context) (min, max string) {
|
||||
if cached, ok := versionBoundsCache.Load().(*cachedVersionBounds); ok {
|
||||
if time.Now().UnixNano() < cached.expiresAt {
|
||||
return cached.value
|
||||
return cached.min, cached.max
|
||||
}
|
||||
}
|
||||
// singleflight: 同一时刻只有一个 goroutine 查询 DB,其余复用结果
|
||||
result, err, _ := minVersionSF.Do("min_version", func() (any, error) {
|
||||
type bounds struct{ min, max string }
|
||||
result, err, _ := versionBoundsSF.Do("version_bounds", func() (any, error) {
|
||||
// 二次检查,避免排队的 goroutine 重复查询
|
||||
if cached, ok := minVersionCache.Load().(*cachedMinVersion); ok {
|
||||
if cached, ok := versionBoundsCache.Load().(*cachedVersionBounds); ok {
|
||||
if time.Now().UnixNano() < cached.expiresAt {
|
||||
return cached.value, nil
|
||||
return bounds{cached.min, cached.max}, nil
|
||||
}
|
||||
}
|
||||
// 使用独立 context:断开请求取消链,避免客户端断连导致空值被长期缓存
|
||||
dbCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), minVersionDBTimeout)
|
||||
dbCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), versionBoundsDBTimeout)
|
||||
defer cancel()
|
||||
value, err := s.settingRepo.GetValue(dbCtx, SettingKeyMinClaudeCodeVersion)
|
||||
values, err := s.settingRepo.GetMultiple(dbCtx, []string{
|
||||
SettingKeyMinClaudeCodeVersion,
|
||||
SettingKeyMaxClaudeCodeVersion,
|
||||
})
|
||||
if err != nil {
|
||||
// fail-open: DB 错误时不阻塞请求,但记录日志并使用短 TTL 快速重试
|
||||
slog.Warn("failed to get min claude code version setting, skipping version check", "error", err)
|
||||
minVersionCache.Store(&cachedMinVersion{
|
||||
value: "",
|
||||
expiresAt: time.Now().Add(minVersionErrorTTL).UnixNano(),
|
||||
slog.Warn("failed to get claude code version bounds setting, skipping version check", "error", err)
|
||||
versionBoundsCache.Store(&cachedVersionBounds{
|
||||
min: "",
|
||||
max: "",
|
||||
expiresAt: time.Now().Add(versionBoundsErrorTTL).UnixNano(),
|
||||
})
|
||||
return "", nil
|
||||
return bounds{"", ""}, nil
|
||||
}
|
||||
minVersionCache.Store(&cachedMinVersion{
|
||||
value: value,
|
||||
expiresAt: time.Now().Add(minVersionCacheTTL).UnixNano(),
|
||||
b := bounds{
|
||||
min: values[SettingKeyMinClaudeCodeVersion],
|
||||
max: values[SettingKeyMaxClaudeCodeVersion],
|
||||
}
|
||||
versionBoundsCache.Store(&cachedVersionBounds{
|
||||
min: b.min,
|
||||
max: b.max,
|
||||
expiresAt: time.Now().Add(versionBoundsCacheTTL).UnixNano(),
|
||||
})
|
||||
return value, nil
|
||||
return b, nil
|
||||
})
|
||||
if err != nil {
|
||||
return ""
|
||||
return "", ""
|
||||
}
|
||||
ver, ok := result.(string)
|
||||
b, ok := result.(bounds)
|
||||
if !ok {
|
||||
return ""
|
||||
return "", ""
|
||||
}
|
||||
return ver
|
||||
return b.min, b.max
|
||||
}
|
||||
|
||||
// GetRectifierSettings 获取请求整流器配置
|
||||
|
||||
@@ -67,6 +67,7 @@ type SystemSettings struct {
|
||||
|
||||
// Claude Code version check
|
||||
MinClaudeCodeVersion string
|
||||
MaxClaudeCodeVersion string
|
||||
|
||||
// 分组隔离:允许未分组 Key 调度(默认 false → 403)
|
||||
AllowUngroupedKeyScheduling bool
|
||||
|
||||
@@ -81,6 +81,7 @@ export interface SystemSettings {
|
||||
|
||||
// Claude Code version check
|
||||
min_claude_code_version: string
|
||||
max_claude_code_version: string
|
||||
|
||||
// 分组隔离
|
||||
allow_ungrouped_key_scheduling: boolean
|
||||
@@ -137,6 +138,7 @@ export interface UpdateSettingsRequest {
|
||||
ops_query_mode_default?: 'auto' | 'raw' | 'preagg' | string
|
||||
ops_metrics_interval_seconds?: number
|
||||
min_claude_code_version?: string
|
||||
max_claude_code_version?: string
|
||||
allow_ungrouped_key_scheduling?: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -441,17 +441,20 @@ function generateAnthropicFiles(baseUrl: string, apiKey: string): FileConfig[] {
|
||||
case 'unix':
|
||||
path = 'Terminal'
|
||||
content = `export ANTHROPIC_BASE_URL="${baseUrl}"
|
||||
export ANTHROPIC_AUTH_TOKEN="${apiKey}"`
|
||||
export ANTHROPIC_AUTH_TOKEN="${apiKey}"
|
||||
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`
|
||||
break
|
||||
case 'cmd':
|
||||
path = 'Command Prompt'
|
||||
content = `set ANTHROPIC_BASE_URL=${baseUrl}
|
||||
set ANTHROPIC_AUTH_TOKEN=${apiKey}`
|
||||
set ANTHROPIC_AUTH_TOKEN=${apiKey}
|
||||
set CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`
|
||||
break
|
||||
case 'powershell':
|
||||
path = 'PowerShell'
|
||||
content = `$env:ANTHROPIC_BASE_URL="${baseUrl}"
|
||||
$env:ANTHROPIC_AUTH_TOKEN="${apiKey}"`
|
||||
$env:ANTHROPIC_AUTH_TOKEN="${apiKey}"
|
||||
$env:CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`
|
||||
break
|
||||
default:
|
||||
path = 'Terminal'
|
||||
@@ -466,6 +469,7 @@ $env:ANTHROPIC_AUTH_TOKEN="${apiKey}"`
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "${baseUrl}",
|
||||
"ANTHROPIC_AUTH_TOKEN": "${apiKey}",
|
||||
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
|
||||
"CLAUDE_CODE_ATTRIBUTION_HEADER": "0"
|
||||
}
|
||||
}`
|
||||
|
||||
@@ -4119,7 +4119,11 @@ export default {
|
||||
minVersion: 'Minimum Version',
|
||||
minVersionPlaceholder: 'e.g. 2.1.63',
|
||||
minVersionHint:
|
||||
'Reject Claude Code clients below this version (semver format). Leave empty to disable version check.'
|
||||
'Reject Claude Code clients below this version (semver format). Leave empty to disable version check.',
|
||||
maxVersion: 'Maximum Version',
|
||||
maxVersionPlaceholder: 'e.g. 2.5.0',
|
||||
maxVersionHint:
|
||||
'Reject Claude Code clients above this version (semver format). Leave empty to allow any version.'
|
||||
},
|
||||
scheduling: {
|
||||
title: 'Gateway Scheduling Settings',
|
||||
|
||||
@@ -4283,7 +4283,10 @@ export default {
|
||||
description: '控制 Claude Code 客户端访问要求',
|
||||
minVersion: '最低版本号',
|
||||
minVersionPlaceholder: '例如 2.1.63',
|
||||
minVersionHint: '拒绝低于此版本的 Claude Code 客户端请求(semver 格式)。留空则不检查版本。'
|
||||
minVersionHint: '拒绝低于此版本的 Claude Code 客户端请求(semver 格式)。留空则不检查版本。',
|
||||
maxVersion: '最高版本号',
|
||||
maxVersionPlaceholder: '例如 2.5.0',
|
||||
maxVersionHint: '拒绝高于此版本的 Claude Code 客户端请求(semver 格式)。留空则不限制最高版本。'
|
||||
},
|
||||
scheduling: {
|
||||
title: '网关调度设置',
|
||||
|
||||
@@ -1127,6 +1127,20 @@
|
||||
{{ t('admin.settings.claudeCode.minVersionHint') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.settings.claudeCode.maxVersion') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.max_claude_code_version"
|
||||
type="text"
|
||||
class="input max-w-xs font-mono text-sm"
|
||||
:placeholder="t('admin.settings.claudeCode.maxVersionPlaceholder')"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.claudeCode.maxVersionHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1967,6 +1981,7 @@ const form = reactive<SettingsForm>({
|
||||
ops_metrics_interval_seconds: 60,
|
||||
// Claude Code version check
|
||||
min_claude_code_version: '',
|
||||
max_claude_code_version: '',
|
||||
// 分组隔离
|
||||
allow_ungrouped_key_scheduling: false
|
||||
})
|
||||
@@ -2232,6 +2247,7 @@ async function saveSettings() {
|
||||
enable_identity_patch: form.enable_identity_patch,
|
||||
identity_patch_prompt: form.identity_patch_prompt,
|
||||
min_claude_code_version: form.min_claude_code_version,
|
||||
max_claude_code_version: form.max_claude_code_version,
|
||||
allow_ungrouped_key_scheduling: form.allow_ungrouped_key_scheduling
|
||||
}
|
||||
const updated = await adminAPI.settings.updateSettings(payload)
|
||||
|
||||
Reference in New Issue
Block a user