Commit 8f0ea7a0 authored by InCerry's avatar InCerry
Browse files

Merge branch 'main' into fix/enc_coot

parents e4a4dfd0 a1dc0089
......@@ -8,12 +8,24 @@ const props = defineProps<{
totalLimit: number | null
dailyLimit: number | null
weeklyLimit: number | null
dailyResetMode: 'rolling' | 'fixed' | null
dailyResetHour: number | null
weeklyResetMode: 'rolling' | 'fixed' | null
weeklyResetDay: number | null
weeklyResetHour: number | null
resetTimezone: string | null
}>()
const emit = defineEmits<{
'update:totalLimit': [value: number | null]
'update:dailyLimit': [value: number | null]
'update:weeklyLimit': [value: number | null]
'update:dailyResetMode': [value: 'rolling' | 'fixed' | null]
'update:dailyResetHour': [value: number | null]
'update:weeklyResetMode': [value: 'rolling' | 'fixed' | null]
'update:weeklyResetDay': [value: number | null]
'update:weeklyResetHour': [value: number | null]
'update:resetTimezone': [value: string | null]
}>()
const enabled = computed(() =>
......@@ -35,9 +47,56 @@ watch(localEnabled, (val) => {
emit('update:totalLimit', null)
emit('update:dailyLimit', null)
emit('update:weeklyLimit', null)
emit('update:dailyResetMode', null)
emit('update:dailyResetHour', null)
emit('update:weeklyResetMode', null)
emit('update:weeklyResetDay', null)
emit('update:weeklyResetHour', null)
emit('update:resetTimezone', null)
}
})
// Whether any fixed mode is active (to show timezone selector)
const hasFixedMode = computed(() =>
props.dailyResetMode === 'fixed' || props.weeklyResetMode === 'fixed'
)
// Common timezone options
const timezoneOptions = [
'UTC',
'Asia/Shanghai',
'Asia/Tokyo',
'Asia/Seoul',
'Asia/Singapore',
'Asia/Kolkata',
'Asia/Dubai',
'Europe/London',
'Europe/Paris',
'Europe/Berlin',
'Europe/Moscow',
'America/New_York',
'America/Chicago',
'America/Denver',
'America/Los_Angeles',
'America/Sao_Paulo',
'Australia/Sydney',
'Pacific/Auckland',
]
// Hours for dropdown (0-23)
const hourOptions = Array.from({ length: 24 }, (_, i) => i)
// Day of week options
const dayOptions = [
{ value: 1, key: 'monday' },
{ value: 2, key: 'tuesday' },
{ value: 3, key: 'wednesday' },
{ value: 4, key: 'thursday' },
{ value: 5, key: 'friday' },
{ value: 6, key: 'saturday' },
{ value: 0, key: 'sunday' },
]
const onTotalInput = (e: Event) => {
const raw = (e.target as HTMLInputElement).valueAsNumber
emit('update:totalLimit', Number.isNaN(raw) ? null : raw)
......@@ -50,6 +109,25 @@ const onWeeklyInput = (e: Event) => {
const raw = (e.target as HTMLInputElement).valueAsNumber
emit('update:weeklyLimit', Number.isNaN(raw) ? null : raw)
}
const onDailyModeChange = (e: Event) => {
const val = (e.target as HTMLSelectElement).value as 'rolling' | 'fixed'
emit('update:dailyResetMode', val)
if (val === 'fixed') {
if (props.dailyResetHour == null) emit('update:dailyResetHour', 0)
if (!props.resetTimezone) emit('update:resetTimezone', 'UTC')
}
}
const onWeeklyModeChange = (e: Event) => {
const val = (e.target as HTMLSelectElement).value as 'rolling' | 'fixed'
emit('update:weeklyResetMode', val)
if (val === 'fixed') {
if (props.weeklyResetDay == null) emit('update:weeklyResetDay', 1)
if (props.weeklyResetHour == null) emit('update:weeklyResetHour', 0)
if (!props.resetTimezone) emit('update:resetTimezone', 'UTC')
}
}
</script>
<template>
......@@ -94,7 +172,37 @@ const onWeeklyInput = (e: Event) => {
:placeholder="t('admin.accounts.quotaLimitPlaceholder')"
/>
</div>
<p class="input-hint">{{ t('admin.accounts.quotaDailyLimitHint') }}</p>
<!-- 日配额重置模式 -->
<div class="mt-2 flex items-center gap-2">
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaResetMode') }}</label>
<select
:value="dailyResetMode || 'rolling'"
@change="onDailyModeChange"
class="input py-1 text-xs"
>
<option value="rolling">{{ t('admin.accounts.quotaResetModeRolling') }}</option>
<option value="fixed">{{ t('admin.accounts.quotaResetModeFixed') }}</option>
</select>
</div>
<!-- 固定模式:小时选择 -->
<div v-if="dailyResetMode === 'fixed'" class="mt-2 flex items-center gap-2">
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaResetHour') }}</label>
<select
:value="dailyResetHour ?? 0"
@change="emit('update:dailyResetHour', Number(($event.target as HTMLSelectElement).value))"
class="input py-1 text-xs w-24"
>
<option v-for="h in hourOptions" :key="h" :value="h">{{ String(h).padStart(2, '0') }}:00</option>
</select>
</div>
<p class="input-hint">
<template v-if="dailyResetMode === 'fixed'">
{{ t('admin.accounts.quotaDailyLimitHintFixed', { hour: String(dailyResetHour ?? 0).padStart(2, '0'), timezone: resetTimezone || 'UTC' }) }}
</template>
<template v-else>
{{ t('admin.accounts.quotaDailyLimitHint') }}
</template>
</p>
</div>
<!-- 周配额 -->
......@@ -112,7 +220,57 @@ const onWeeklyInput = (e: Event) => {
:placeholder="t('admin.accounts.quotaLimitPlaceholder')"
/>
</div>
<p class="input-hint">{{ t('admin.accounts.quotaWeeklyLimitHint') }}</p>
<!-- 周配额重置模式 -->
<div class="mt-2 flex items-center gap-2">
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaResetMode') }}</label>
<select
:value="weeklyResetMode || 'rolling'"
@change="onWeeklyModeChange"
class="input py-1 text-xs"
>
<option value="rolling">{{ t('admin.accounts.quotaResetModeRolling') }}</option>
<option value="fixed">{{ t('admin.accounts.quotaResetModeFixed') }}</option>
</select>
</div>
<!-- 固定模式星期几 + 小时 -->
<div v-if="weeklyResetMode === 'fixed'" class="mt-2 flex items-center gap-2 flex-wrap">
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaWeeklyResetDay') }}</label>
<select
:value="weeklyResetDay ?? 1"
@change="emit('update:weeklyResetDay', Number(($event.target as HTMLSelectElement).value))"
class="input py-1 text-xs w-28"
>
<option v-for="d in dayOptions" :key="d.value" :value="d.value">{{ t('admin.accounts.dayOfWeek.' + d.key) }}</option>
</select>
<label class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ t('admin.accounts.quotaResetHour') }}</label>
<select
:value="weeklyResetHour ?? 0"
@change="emit('update:weeklyResetHour', Number(($event.target as HTMLSelectElement).value))"
class="input py-1 text-xs w-24"
>
<option v-for="h in hourOptions" :key="h" :value="h">{{ String(h).padStart(2, '0') }}:00</option>
</select>
</div>
<p class="input-hint">
<template v-if="weeklyResetMode === 'fixed'">
{{ t('admin.accounts.quotaWeeklyLimitHintFixed', { day: t('admin.accounts.dayOfWeek.' + (dayOptions.find(d => d.value === (weeklyResetDay ?? 1))?.key || 'monday')), hour: String(weeklyResetHour ?? 0).padStart(2, '0'), timezone: resetTimezone || 'UTC' }) }}
</template>
<template v-else>
{{ t('admin.accounts.quotaWeeklyLimitHint') }}
</template>
</p>
</div>
<!-- 时区选择当任一维度使用固定模式时显示 -->
<div v-if="hasFixedMode">
<label class="input-label">{{ t('admin.accounts.quotaResetTimezone') }}</label>
<select
:value="resetTimezone || 'UTC'"
@change="emit('update:resetTimezone', ($event.target as HTMLSelectElement).value)"
class="input text-sm"
>
<option v-for="tz in timezoneOptions" :key="tz" :value="tz">{{ tz }}</option>
</select>
</div>
<!-- 总配额 -->
......
......@@ -76,7 +76,7 @@ const hasRecoverableState = computed(() => {
return props.account?.status === 'error' || Boolean(isRateLimited.value) || Boolean(isOverloaded.value) || Boolean(isTempUnschedulable.value)
})
const hasQuotaLimit = computed(() => {
return props.account?.type === 'apikey' && (
return (props.account?.type === 'apikey' || props.account?.type === 'bedrock') && (
(props.account?.quota_limit ?? 0) > 0 ||
(props.account?.quota_daily_limit ?? 0) > 0 ||
(props.account?.quota_weekly_limit ?? 0) > 0
......
......@@ -83,7 +83,7 @@ const typeLabel = computed(() => {
case 'apikey':
return 'Key'
case 'bedrock':
return 'Bedrock'
return 'AWS'
default:
return props.type
}
......
......@@ -82,7 +82,7 @@
</template>
<!-- Regular User View -->
<template v-else>
<template v-else-if="!appStore.backendModeEnabled">
<div class="sidebar-section">
<router-link
v-for="item in userNavItems"
......
......@@ -84,9 +84,7 @@ onUnmounted(() => {
}
.table-scroll-container :deep(th) {
/* 表头高度和文字加粗优化 */
@apply px-5 py-4 text-left text-sm font-bold text-gray-900 dark:text-white border-b border-gray-200 dark:border-dark-700;
@apply uppercase tracking-wider; /* 让表头更有设计感 */
@apply px-5 py-4 text-left text-sm font-medium text-gray-600 dark:text-dark-300 border-b border-gray-200 dark:border-dark-700;
}
.table-scroll-container :deep(td) {
......
......@@ -412,7 +412,7 @@ export function getPresetMappingsByPlatform(platform: string) {
if (platform === 'gemini') return geminiPresetMappings
if (platform === 'sora') return soraPresetMappings
if (platform === 'antigravity') return antigravityPresetMappings
if (platform === 'bedrock' || platform === 'bedrock-apikey') return bedrockPresetMappings
if (platform === 'bedrock') return bedrockPresetMappings
return anthropicPresetMappings
}
......
......@@ -1866,6 +1866,23 @@ export default {
quotaWeeklyLimitHint: 'Automatically resets every 7 days from first usage.',
quotaTotalLimit: 'Total Limit',
quotaTotalLimitHint: 'Cumulative spending limit. Does not auto-reset — use "Reset Quota" to clear.',
quotaResetMode: 'Reset Mode',
quotaResetModeRolling: 'Rolling Window',
quotaResetModeFixed: 'Fixed Time',
quotaResetHour: 'Reset Hour',
quotaWeeklyResetDay: 'Reset Day',
quotaResetTimezone: 'Reset Timezone',
quotaDailyLimitHintFixed: 'Resets daily at {hour}:00 ({timezone}).',
quotaWeeklyLimitHintFixed: 'Resets every {day} at {hour}:00 ({timezone}).',
dayOfWeek: {
monday: 'Monday',
tuesday: 'Tuesday',
wednesday: 'Wednesday',
thursday: 'Thursday',
friday: 'Friday',
saturday: 'Saturday',
sunday: 'Sunday',
},
quotaLimitAmount: 'Total Limit',
quotaLimitAmountHint: 'Cumulative spending limit. Does not auto-reset.',
testConnection: 'Test Connection',
......@@ -1934,7 +1951,7 @@ export default {
claudeCode: 'Claude Code',
claudeConsole: 'Claude Console',
bedrockLabel: 'AWS Bedrock',
bedrockDesc: 'SigV4 Signing',
bedrockDesc: 'SigV4 / API Key',
oauthSetupToken: 'OAuth / Setup Token',
addMethod: 'Add Method',
setupTokenLongLived: 'Setup Token (Long-lived)',
......@@ -2136,6 +2153,9 @@ export default {
bedrockRegionRequired: 'Please select AWS Region',
bedrockSessionTokenHint: 'Optional, for temporary credentials',
bedrockSecretKeyLeaveEmpty: 'Leave empty to keep current key',
bedrockAuthMode: 'Authentication Mode',
bedrockAuthModeSigv4: 'SigV4 Signing',
bedrockAuthModeApikey: 'Bedrock API Key',
bedrockApiKeyLabel: 'Bedrock API Key',
bedrockApiKeyDesc: 'Bearer Token',
bedrockApiKeyInput: 'API Key',
......@@ -2555,7 +2575,16 @@ export default {
unlimited: 'Unlimited'
},
ineligibleWarning:
'This account is not eligible for Antigravity, but API forwarding still works. Use at your own risk.'
'This account is not eligible for Antigravity, but API forwarding still works. Use at your own risk.',
forbidden: 'Forbidden',
forbiddenValidation: 'Verification Required',
forbiddenViolation: 'Violation Ban',
openVerification: 'Open Verification Link',
copyLink: 'Copy Link',
linkCopied: 'Link Copied',
needsReauth: 'Re-auth Required',
rateLimited: 'Rate Limited',
usageError: 'Fetch Error'
},
// Scheduled Tests
......@@ -3709,6 +3738,11 @@ export default {
refreshInterval15s: '15 seconds',
refreshInterval30s: '30 seconds',
refreshInterval60s: '60 seconds',
dashboardCards: 'Dashboard Cards',
displayAlertEvents: 'Display alert events',
displayAlertEventsHint: 'Show or hide the recent alert events card on the ops dashboard. Enabled by default.',
displayOpenAITokenStats: 'Display OpenAI token request stats',
displayOpenAITokenStatsHint: 'Show or hide the OpenAI token request stats card on the ops dashboard. Hidden by default.',
autoRefreshCountdown: 'Auto refresh: {seconds}s',
validation: {
title: 'Please fix the following issues',
......@@ -3888,6 +3922,9 @@ export default {
site: {
title: 'Site Settings',
description: 'Customize site branding',
backendMode: 'Backend Mode',
backendModeDescription:
'Disables user registration, public site, and self-service features. Only admin can log in and manage the platform.',
siteName: 'Site Name',
siteNamePlaceholder: 'Sub2API',
siteNameHint: 'Displayed in emails and page titles',
......@@ -4127,6 +4164,7 @@ export default {
scopeAll: 'All accounts',
scopeOAuth: 'OAuth only',
scopeAPIKey: 'API Key only',
scopeBedrock: 'Bedrock only',
errorMessage: 'Error message',
errorMessagePlaceholder: 'Custom error message when blocked',
errorMessageHint: 'Leave empty for default message',
......
......@@ -1872,6 +1872,23 @@ export default {
quotaWeeklyLimitHint: '从首次使用起每 7 天自动重置。',
quotaTotalLimit: '总限额',
quotaTotalLimitHint: '累计消费上限,不会自动重置 — 使用「重置配额」手动清零。',
quotaResetMode: '重置方式',
quotaResetModeRolling: '滚动窗口',
quotaResetModeFixed: '固定时间',
quotaResetHour: '重置时间',
quotaWeeklyResetDay: '重置日',
quotaResetTimezone: '重置时区',
quotaDailyLimitHintFixed: '每天 {hour}:00({timezone})重置。',
quotaWeeklyLimitHintFixed: '每{day} {hour}:00({timezone})重置。',
dayOfWeek: {
monday: '周一',
tuesday: '周二',
wednesday: '周三',
thursday: '周四',
friday: '周五',
saturday: '周六',
sunday: '周日',
},
quotaLimitAmount: '总限额',
quotaLimitAmountHint: '累计消费上限,不会自动重置。',
testConnection: '测试连接',
......@@ -1992,6 +2009,15 @@ export default {
},
ineligibleWarning:
'该账号无 Antigravity 使用权限,但仍能进行 API 转发。继续使用请自行承担风险。',
forbidden: '已封禁',
forbiddenValidation: '需要验证',
forbiddenViolation: '违规封禁',
openVerification: '打开验证链接',
copyLink: '复制链接',
linkCopied: '链接已复制',
needsReauth: '需要重新授权',
rateLimited: '限流中',
usageError: '获取失败',
form: {
nameLabel: '账号名称',
namePlaceholder: '请输入账号名称',
......@@ -2082,7 +2108,7 @@ export default {
claudeCode: 'Claude Code',
claudeConsole: 'Claude Console',
bedrockLabel: 'AWS Bedrock',
bedrockDesc: 'SigV4 签名',
bedrockDesc: 'SigV4 / API Key',
oauthSetupToken: 'OAuth / Setup Token',
addMethod: '添加方式',
setupTokenLongLived: 'Setup Token(长期有效)',
......@@ -2277,6 +2303,9 @@ export default {
bedrockRegionRequired: '请选择 AWS Region',
bedrockSessionTokenHint: '可选,用于临时凭证',
bedrockSecretKeyLeaveEmpty: '留空以保持当前密钥',
bedrockAuthMode: '认证方式',
bedrockAuthModeSigv4: 'SigV4 签名',
bedrockAuthModeApikey: 'Bedrock API Key',
bedrockApiKeyLabel: 'Bedrock API Key',
bedrockApiKeyDesc: 'Bearer Token 认证',
bedrockApiKeyInput: 'API Key',
......@@ -3883,6 +3912,11 @@ export default {
refreshInterval15s: '15 秒',
refreshInterval30s: '30 秒',
refreshInterval60s: '60 秒',
dashboardCards: '仪表盘卡片',
displayAlertEvents: '展示告警事件',
displayAlertEventsHint: '控制运维监控仪表盘中告警事件卡片是否显示,默认开启。',
displayOpenAITokenStats: '展示 OpenAI Token 请求统计',
displayOpenAITokenStatsHint: '控制运维监控仪表盘中 OpenAI Token 请求统计卡片是否显示,默认关闭。',
autoRefreshCountdown: '自动刷新:{seconds}s',
validation: {
title: '请先修正以下问题',
......@@ -4060,6 +4094,9 @@ export default {
site: {
title: '站点设置',
description: '自定义站点品牌',
backendMode: 'Backend 模式',
backendModeDescription:
'禁用用户注册、公开页面和自助服务功能。仅管理员可以登录和管理平台。',
siteName: '站点名称',
siteNameHint: '显示在邮件和页面标题中',
siteNamePlaceholder: 'Sub2API',
......@@ -4300,6 +4337,7 @@ export default {
scopeAll: '全部账号',
scopeOAuth: '仅 OAuth 账号',
scopeAPIKey: '仅 API Key 账号',
scopeBedrock: '仅 Bedrock 账号',
errorMessage: '错误消息',
errorMessagePlaceholder: '拦截时返回的自定义错误消息',
errorMessageHint: '留空则使用默认错误消息',
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment