mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
Add user API key IP whitelist UI
This commit is contained in:
@@ -169,6 +169,7 @@ export interface ApiKey {
|
|||||||
total_cost_usd?: number
|
total_cost_usd?: number
|
||||||
rate_limit?: number | null
|
rate_limit?: number | null
|
||||||
concurrent_limit?: number | null
|
concurrent_limit?: number | null
|
||||||
|
allowed_ips?: string[] | null
|
||||||
allowed_providers?: ProviderConfig[]
|
allowed_providers?: ProviderConfig[]
|
||||||
force_capabilities?: Record<string, boolean> | null // 强制能力配置
|
force_capabilities?: Record<string, boolean> | null // 强制能力配置
|
||||||
feature_settings?: FeatureSettingsMap | null
|
feature_settings?: FeatureSettingsMap | null
|
||||||
@@ -248,7 +249,7 @@ export const meApi = {
|
|||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
|
|
||||||
async createApiKey(data: { name: string; rate_limit?: number | null; concurrent_limit?: number | null; feature_settings?: FeatureSettingsMap | null }): Promise<ApiKey> {
|
async createApiKey(data: { name: string; rate_limit?: number | null; concurrent_limit?: number | null; allowed_ips?: string[] | null; feature_settings?: FeatureSettingsMap | null }): Promise<ApiKey> {
|
||||||
const response = await apiClient.post<ApiKey>('/api/users/me/api-keys', data)
|
const response = await apiClient.post<ApiKey>('/api/users/me/api-keys', data)
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
@@ -281,7 +282,7 @@ export const meApi = {
|
|||||||
|
|
||||||
async updateApiKey(
|
async updateApiKey(
|
||||||
keyId: string,
|
keyId: string,
|
||||||
data: { name?: string; rate_limit?: number | null; concurrent_limit?: number | null; feature_settings?: FeatureSettingsMap | null | undefined }
|
data: { name?: string; rate_limit?: number | null; concurrent_limit?: number | null; allowed_ips?: string[] | null; feature_settings?: FeatureSettingsMap | null | undefined }
|
||||||
): Promise<ApiKey & { message: string }> {
|
): Promise<ApiKey & { message: string }> {
|
||||||
const response = await apiClient.put<ApiKey & { message: string }>(
|
const response = await apiClient.put<ApiKey & { message: string }>(
|
||||||
`/api/users/me/api-keys/${keyId}`,
|
`/api/users/me/api-keys/${keyId}`,
|
||||||
|
|||||||
@@ -111,8 +111,11 @@
|
|||||||
>
|
>
|
||||||
{{ apiKey.name }}
|
{{ apiKey.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-muted-foreground mt-0.5">
|
<div class="text-xs text-muted-foreground mt-0.5">
|
||||||
创建于 {{ formatDate(apiKey.created_at) }}
|
创建于 {{ formatDate(apiKey.created_at) }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-muted-foreground mt-0.5 truncate">
|
||||||
|
IP 白名单:{{ formatAllowedIps(apiKey.allowed_ips) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -355,6 +358,9 @@
|
|||||||
{{ formatRateLimitSimple(apiKey.rate_limit) }}
|
{{ formatRateLimitSimple(apiKey.rate_limit) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-xs text-muted-foreground truncate">
|
||||||
|
IP 白名单:{{ formatAllowedIps(apiKey.allowed_ips) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -451,6 +457,23 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label
|
||||||
|
for="key-allowed-ips"
|
||||||
|
class="text-sm font-semibold"
|
||||||
|
>IP 白名单</Label>
|
||||||
|
<Input
|
||||||
|
id="key-allowed-ips"
|
||||||
|
v-model="newKeyAllowedIpsText"
|
||||||
|
placeholder="例如:203.0.113.10, 10.0.0.0/24"
|
||||||
|
class="h-11 border-border/60"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-muted-foreground">
|
||||||
|
留空表示不限制来源 IP;支持单个 IP 或 CIDR,多个值用英文逗号分隔
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="rounded-lg border border-border/60 bg-muted/30 p-4">
|
<div class="rounded-lg border border-border/60 bg-muted/30 p-4">
|
||||||
<div class="flex items-center justify-between gap-4">
|
<div class="flex items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
@@ -779,6 +802,7 @@ const showInstallDialog = ref(false)
|
|||||||
const newKeyName = ref('')
|
const newKeyName = ref('')
|
||||||
const newKeyRateLimit = ref<number | undefined>(undefined)
|
const newKeyRateLimit = ref<number | undefined>(undefined)
|
||||||
const newKeyConcurrentLimit = ref<number | undefined>(undefined)
|
const newKeyConcurrentLimit = ref<number | undefined>(undefined)
|
||||||
|
const newKeyAllowedIpsText = ref('')
|
||||||
const keyRedactionMode = ref<'inherit' | 'custom'>('inherit')
|
const keyRedactionMode = ref<'inherit' | 'custom'>('inherit')
|
||||||
const newKeyRedactionEnabled = ref(false)
|
const newKeyRedactionEnabled = ref(false)
|
||||||
const newKeyRedactionInjectNotice = ref(true)
|
const newKeyRedactionInjectNotice = ref(true)
|
||||||
@@ -867,6 +891,7 @@ function openEditApiKeyDialog(apiKey: ApiKey) {
|
|||||||
newKeyName.value = apiKey.name || ''
|
newKeyName.value = apiKey.name || ''
|
||||||
newKeyRateLimit.value = apiKey.rate_limit ?? undefined
|
newKeyRateLimit.value = apiKey.rate_limit ?? undefined
|
||||||
newKeyConcurrentLimit.value = apiKey.concurrent_limit ?? undefined
|
newKeyConcurrentLimit.value = apiKey.concurrent_limit ?? undefined
|
||||||
|
newKeyAllowedIpsText.value = apiKey.allowed_ips?.join(', ') ?? ''
|
||||||
keyRedactionMode.value = hasRedactionFeature ? 'custom' : 'inherit'
|
keyRedactionMode.value = hasRedactionFeature ? 'custom' : 'inherit'
|
||||||
newKeyRedactionEnabled.value = redactionFeature.enabled
|
newKeyRedactionEnabled.value = redactionFeature.enabled
|
||||||
newKeyRedactionInjectNotice.value = redactionFeature.inject_model_instruction
|
newKeyRedactionInjectNotice.value = redactionFeature.inject_model_instruction
|
||||||
@@ -878,6 +903,7 @@ function openCreateApiKeyDialog() {
|
|||||||
newKeyName.value = ''
|
newKeyName.value = ''
|
||||||
newKeyRateLimit.value = undefined
|
newKeyRateLimit.value = undefined
|
||||||
newKeyConcurrentLimit.value = undefined
|
newKeyConcurrentLimit.value = undefined
|
||||||
|
newKeyAllowedIpsText.value = ''
|
||||||
keyRedactionMode.value = 'inherit'
|
keyRedactionMode.value = 'inherit'
|
||||||
newKeyRedactionEnabled.value = false
|
newKeyRedactionEnabled.value = false
|
||||||
newKeyRedactionInjectNotice.value = true
|
newKeyRedactionInjectNotice.value = true
|
||||||
@@ -957,6 +983,7 @@ function closeApiKeyDialog() {
|
|||||||
newKeyName.value = ''
|
newKeyName.value = ''
|
||||||
newKeyRateLimit.value = undefined
|
newKeyRateLimit.value = undefined
|
||||||
newKeyConcurrentLimit.value = undefined
|
newKeyConcurrentLimit.value = undefined
|
||||||
|
newKeyAllowedIpsText.value = ''
|
||||||
keyRedactionMode.value = 'inherit'
|
keyRedactionMode.value = 'inherit'
|
||||||
newKeyRedactionEnabled.value = false
|
newKeyRedactionEnabled.value = false
|
||||||
newKeyRedactionInjectNotice.value = true
|
newKeyRedactionInjectNotice.value = true
|
||||||
@@ -970,12 +997,14 @@ async function saveApiKey() {
|
|||||||
|
|
||||||
creating.value = true
|
creating.value = true
|
||||||
try {
|
try {
|
||||||
|
const allowedIps = parseAllowedIpsInput(newKeyAllowedIpsText.value)
|
||||||
const isCreatingFirstApiKey = !editingApiKey.value && apiKeys.value.length === 0
|
const isCreatingFirstApiKey = !editingApiKey.value && apiKeys.value.length === 0
|
||||||
if (editingApiKey.value) {
|
if (editingApiKey.value) {
|
||||||
await meApi.updateApiKey(editingApiKey.value.id, {
|
await meApi.updateApiKey(editingApiKey.value.id, {
|
||||||
name: newKeyName.value,
|
name: newKeyName.value,
|
||||||
rate_limit: newKeyRateLimit.value ?? 0,
|
rate_limit: newKeyRateLimit.value ?? 0,
|
||||||
concurrent_limit: newKeyConcurrentLimit.value,
|
concurrent_limit: newKeyConcurrentLimit.value,
|
||||||
|
allowed_ips: allowedIps,
|
||||||
feature_settings: keyRedactionMode.value === 'custom'
|
feature_settings: keyRedactionMode.value === 'custom'
|
||||||
? mergeChatPiiRedactionFeatureSettings(editingApiKey.value.feature_settings, {
|
? mergeChatPiiRedactionFeatureSettings(editingApiKey.value.feature_settings, {
|
||||||
enabled: newKeyRedactionEnabled.value,
|
enabled: newKeyRedactionEnabled.value,
|
||||||
@@ -989,6 +1018,7 @@ async function saveApiKey() {
|
|||||||
name: newKeyName.value,
|
name: newKeyName.value,
|
||||||
rate_limit: newKeyRateLimit.value ?? 0,
|
rate_limit: newKeyRateLimit.value ?? 0,
|
||||||
concurrent_limit: newKeyConcurrentLimit.value,
|
concurrent_limit: newKeyConcurrentLimit.value,
|
||||||
|
allowed_ips: allowedIps,
|
||||||
...(keyRedactionMode.value === 'custom'
|
...(keyRedactionMode.value === 'custom'
|
||||||
? {
|
? {
|
||||||
feature_settings: mergeChatPiiRedactionFeatureSettings(null, {
|
feature_settings: mergeChatPiiRedactionFeatureSettings(null, {
|
||||||
@@ -1118,6 +1148,18 @@ function formatConcurrentLimitSimple(concurrentLimit?: number | null): string {
|
|||||||
return `${concurrentLimit} 并发`
|
return `${concurrentLimit} 并发`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatAllowedIps(allowedIps?: string[] | null): string {
|
||||||
|
return allowedIps && allowedIps.length > 0 ? allowedIps.join(', ') : '不限制'
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAllowedIpsInput(value: string): string[] | null {
|
||||||
|
const items = value
|
||||||
|
.split(',')
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
return items.length > 0 ? items : null
|
||||||
|
}
|
||||||
|
|
||||||
function formatDate(dateString?: string | null): string {
|
function formatDate(dateString?: string | null): string {
|
||||||
if (!dateString) return '未知'
|
if (!dateString) return '未知'
|
||||||
const date = new Date(dateString)
|
const date = new Date(dateString)
|
||||||
|
|||||||
Reference in New Issue
Block a user