mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat(admin): 完善代理节点与 OAuth 授权管理
This commit is contained in:
@@ -370,7 +370,7 @@ export interface CreateStandaloneApiKeyRequest {
|
||||
allowed_api_formats?: string[] | null
|
||||
allowed_models?: string[] | null
|
||||
rate_limit?: number | null // null = 跟随系统默认,0 = 不限制
|
||||
expires_at?: string | null // ISO 日期字符串,如 "2025-12-31",null = 永不过期
|
||||
expires_at?: string | null // RFC3339 时间,null = 永不过期
|
||||
initial_balance_usd: number | null // 初始余额,null = 无限制
|
||||
unlimited_balance?: boolean | null // 编辑时仅切换额度模式,不调整余额数值
|
||||
auto_delete_on_expiry?: boolean // 过期后是否自动删除
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
<span
|
||||
v-else
|
||||
class="flex h-10 w-full items-center rounded-lg border bg-background px-3 text-sm text-muted-foreground opacity-60"
|
||||
>{{ form.unlimited_balance ? '无限制' : '按钱包余额限制' }}</span>
|
||||
>{{ balanceDisplayText }}</span>
|
||||
</div>
|
||||
<Switch
|
||||
:model-value="form.unlimited_balance ?? false"
|
||||
@@ -230,6 +230,12 @@
|
||||
@update:model-value="(v) => form.unlimited_balance = v"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
v-if="isEditMode"
|
||||
class="text-xs text-muted-foreground"
|
||||
>
|
||||
{{ form.unlimited_balance ? '该 Key 当前使用独立无限额度。' : '该 Key 当前按独立钱包余额限制;增减金额请在列表页使用“资金”操作。' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -278,6 +284,7 @@ export interface StandaloneKeyFormData {
|
||||
id?: string
|
||||
name: string
|
||||
initial_balance_usd?: number
|
||||
current_balance_usd?: number | null
|
||||
unlimited_balance?: boolean
|
||||
expires_at?: string // ISO 日期字符串,如 "2025-12-31",undefined = 永不过期
|
||||
rate_limit?: number | null
|
||||
@@ -291,6 +298,7 @@ interface StandaloneKeyFormState {
|
||||
id?: string
|
||||
name: string
|
||||
initial_balance_usd?: number
|
||||
current_balance_usd?: number | null
|
||||
unlimited_balance?: boolean
|
||||
expires_at?: string
|
||||
rate_limit_inherited: boolean
|
||||
@@ -345,6 +353,7 @@ const modelOptions = computed(() =>
|
||||
const form = ref<StandaloneKeyFormState>({
|
||||
name: '',
|
||||
initial_balance_usd: 10,
|
||||
current_balance_usd: undefined,
|
||||
unlimited_balance: false,
|
||||
expires_at: undefined,
|
||||
rate_limit_inherited: true,
|
||||
@@ -358,17 +367,37 @@ const form = ref<StandaloneKeyFormState>({
|
||||
allowed_models: [],
|
||||
})
|
||||
|
||||
function formatDateInputValue(date: Date): string {
|
||||
const year = date.getFullYear()
|
||||
const month = `${date.getMonth() + 1}`.padStart(2, '0')
|
||||
const day = `${date.getDate()}`.padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
// 计算最小可选日期(明天)
|
||||
const minExpiryDate = computed(() => {
|
||||
const tomorrow = new Date()
|
||||
tomorrow.setHours(0, 0, 0, 0)
|
||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||
return tomorrow.toISOString().split('T')[0]
|
||||
return formatDateInputValue(tomorrow)
|
||||
})
|
||||
|
||||
const balanceDisplayText = computed(() => {
|
||||
if (form.value.unlimited_balance) {
|
||||
return '独立无限额度'
|
||||
}
|
||||
if (isEditMode.value) {
|
||||
const currentBalance = form.value.current_balance_usd ?? form.value.initial_balance_usd ?? 0
|
||||
return `当前独立钱包余额 $${currentBalance.toFixed(2)}`
|
||||
}
|
||||
return '按独立钱包余额限制'
|
||||
})
|
||||
|
||||
function resetForm() {
|
||||
form.value = {
|
||||
name: '',
|
||||
initial_balance_usd: 10,
|
||||
current_balance_usd: undefined,
|
||||
unlimited_balance: false,
|
||||
expires_at: undefined,
|
||||
rate_limit_inherited: true,
|
||||
@@ -389,6 +418,7 @@ function loadKeyData() {
|
||||
id: props.apiKey.id,
|
||||
name: props.apiKey.name || '',
|
||||
initial_balance_usd: props.apiKey.initial_balance_usd,
|
||||
current_balance_usd: props.apiKey.current_balance_usd ?? props.apiKey.initial_balance_usd ?? null,
|
||||
unlimited_balance: props.apiKey.initial_balance_usd == null,
|
||||
expires_at: props.apiKey.expires_at,
|
||||
rate_limit_inherited: props.apiKey.rate_limit == null,
|
||||
|
||||
@@ -2213,7 +2213,7 @@ registerDynamicRoute('GET', '/api/admin/api-keys/:keyId', async (_config, params
|
||||
})
|
||||
|
||||
// API Key 更新
|
||||
registerDynamicRoute('PATCH', '/api/admin/api-keys/:keyId', async (config, params) => {
|
||||
registerDynamicRoute('PUT', '/api/admin/api-keys/:keyId', async (config, params) => {
|
||||
await delay()
|
||||
requireAdmin()
|
||||
const key = MOCK_ADMIN_API_KEYS.api_keys.find(k => k.id === params.keyId)
|
||||
|
||||
@@ -814,9 +814,16 @@ async function refreshApiKeys() {
|
||||
skip: skip.value,
|
||||
limit: limit.value
|
||||
})
|
||||
apiKeys.value = response.api_keys
|
||||
const standaloneKeys = response.api_keys.filter((key) => key.is_standalone === true)
|
||||
if (standaloneKeys.length !== response.api_keys.length) {
|
||||
log.warn('独立 Key 页面收到了非 standalone 记录,已在前端过滤', {
|
||||
received: response.api_keys.length,
|
||||
kept: standaloneKeys.length
|
||||
})
|
||||
}
|
||||
apiKeys.value = standaloneKeys
|
||||
total.value = response.total
|
||||
apiKeyWalletMap.value = buildApiKeyWalletMap(response.api_keys)
|
||||
apiKeyWalletMap.value = buildApiKeyWalletMap(standaloneKeys)
|
||||
} catch (err: unknown) {
|
||||
log.error('加载独立Keys失败:', err)
|
||||
error(parseApiError(err, '加载独立 Keys 失败'))
|
||||
@@ -864,20 +871,52 @@ async function deleteApiKey(apiKey: AdminApiKey) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatDateForInput(dateString: string): string | undefined {
|
||||
const date = new Date(dateString)
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return undefined
|
||||
}
|
||||
const year = date.getFullYear()
|
||||
const month = `${date.getMonth() + 1}`.padStart(2, '0')
|
||||
const day = `${date.getDate()}`.padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
function parseDateInput(dateString: string): Date | null {
|
||||
const [year, month, day] = dateString.split('-').map(part => Number.parseInt(part, 10))
|
||||
if (!year || !month || !day) {
|
||||
return null
|
||||
}
|
||||
const date = new Date(year, month - 1, day)
|
||||
return Number.isNaN(date.getTime()) ? null : date
|
||||
}
|
||||
|
||||
function serializeExpiryDate(dateString?: string): string | null {
|
||||
if (!dateString) {
|
||||
return null
|
||||
}
|
||||
const date = parseDateInput(dateString)
|
||||
if (!date) {
|
||||
return null
|
||||
}
|
||||
date.setHours(23, 59, 59, 999)
|
||||
return date.toISOString()
|
||||
}
|
||||
|
||||
function editApiKey(apiKey: AdminApiKey) {
|
||||
// 解析过期日期为 YYYY-MM-DD 格式
|
||||
// 保留原始日期,不做时间过滤(避免编辑当天过期的 Key 时意外清空)
|
||||
let expiresAt: string | undefined = undefined
|
||||
|
||||
if (apiKey.expires_at) {
|
||||
const expiresDate = new Date(apiKey.expires_at)
|
||||
expiresAt = expiresDate.toISOString().split('T')[0]
|
||||
expiresAt = formatDateForInput(apiKey.expires_at)
|
||||
}
|
||||
|
||||
editingKeyData.value = {
|
||||
id: apiKey.id,
|
||||
name: apiKey.name || '',
|
||||
initial_balance_usd: isApiKeyUnlimited(apiKey) ? undefined : (getApiKeyWalletTotalBalance(apiKey) ?? undefined),
|
||||
current_balance_usd: isApiKeyUnlimited(apiKey) ? null : getApiKeyWalletTotalBalance(apiKey),
|
||||
unlimited_balance: isApiKeyUnlimited(apiKey),
|
||||
expires_at: expiresAt,
|
||||
rate_limit: apiKey.rate_limit ?? undefined,
|
||||
@@ -1049,7 +1088,12 @@ function closeKeyFormDialog() {
|
||||
async function handleKeyFormSubmit(data: StandaloneKeyFormData) {
|
||||
// 验证过期日期(如果设置了,必须晚于今天)
|
||||
if (data.expires_at) {
|
||||
const selectedDate = new Date(data.expires_at)
|
||||
const selectedDate = parseDateInput(data.expires_at)
|
||||
if (!selectedDate) {
|
||||
error('过期日期格式无效')
|
||||
return
|
||||
}
|
||||
selectedDate.setHours(0, 0, 0, 0)
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
if (selectedDate <= today) {
|
||||
@@ -1066,7 +1110,7 @@ async function handleKeyFormSubmit(data: StandaloneKeyFormData) {
|
||||
name: data.name || undefined,
|
||||
unlimited_balance: Boolean(data.unlimited_balance),
|
||||
rate_limit: data.rate_limit ?? null, // undefined = 跟随系统默认,显式传 null
|
||||
expires_at: data.expires_at || null, // undefined/空 = 永不过期
|
||||
expires_at: serializeExpiryDate(data.expires_at),
|
||||
auto_delete_on_expiry: data.auto_delete_on_expiry,
|
||||
// 空数组表示清除限制(允许全部),后端会将空数组存为 NULL
|
||||
allowed_providers: data.allowed_providers,
|
||||
@@ -1095,7 +1139,7 @@ async function handleKeyFormSubmit(data: StandaloneKeyFormData) {
|
||||
name: data.name || undefined,
|
||||
initial_balance_usd: isUnlimited ? null : (data.initial_balance_usd as number),
|
||||
rate_limit: data.rate_limit ?? null, // undefined = 跟随系统默认,显式传 null
|
||||
expires_at: data.expires_at || null, // undefined/空 = 永不过期
|
||||
expires_at: serializeExpiryDate(data.expires_at),
|
||||
auto_delete_on_expiry: data.auto_delete_on_expiry,
|
||||
// 空数组表示不设置限制(允许全部),后端会将空数组存为 NULL
|
||||
allowed_providers: data.allowed_providers,
|
||||
|
||||
Reference in New Issue
Block a user