diff --git a/admin-ui/src/api/credentials.ts b/admin-ui/src/api/credentials.ts index 9ba8d9c7..d2152dc3 100644 --- a/admin-ui/src/api/credentials.ts +++ b/admin-ui/src/api/credentials.ts @@ -224,6 +224,8 @@ export interface BatchImportCredentialsRequest { credentials: AddCredentialRequest[] /** 顶层统一代理覆盖;缺省时尊重每条凭据字段 */ proxyUrl?: string + proxyId?: number + browserEgressIp?: string /** 顶层统一 RPM 覆盖;缺省时尊重每条凭据字段 */ rpmLimit?: number /** 并发度,缺省 8,服务端 clamp 到 [1, 16] */ @@ -362,6 +364,17 @@ export async function updateRefreshToken( return data } +export async function promoteCredential(id: number): Promise<{ + id: number + model: string + modelCount: number + unary200: boolean + sseMessageStop: boolean +}> { + const { data } = await api.post(`/credentials/${id}/promote`) + return data +} + // 更新凭据可编辑字段 export async function updateCredential( id: number, @@ -695,10 +708,13 @@ export async function startSocialRelogin( // 轮询 Social 重新登录状态 export async function pollSocialRelogin( credentialId: number, - sessionId: string + sessionId: string, + sessionNonce?: string, ): Promise { const { data } = await api.post( - `/credentials/${credentialId}/relogin/social/poll/${sessionId}` + `/credentials/${credentialId}/relogin/social/poll/${sessionId}`, + undefined, + { headers: sessionNonce ? { 'x-kiro-egress-nonce': sessionNonce } : undefined }, ) return data } @@ -707,11 +723,13 @@ export async function pollSocialRelogin( export async function completeSocialRelogin( credentialId: number, sessionId: string, - req: CompleteSocialLoginRequest + req: CompleteSocialLoginRequest, + sessionNonce?: string, ): Promise { const { data } = await api.post( `/credentials/${credentialId}/relogin/social/complete/${sessionId}`, - req + req, + { headers: sessionNonce ? { 'x-kiro-egress-nonce': sessionNonce } : undefined }, ) return data } @@ -731,10 +749,13 @@ export async function startIdcRelogin( // 轮询 IdC 重新登录状态 export async function pollIdcRelogin( credentialId: number, - sessionId: string + sessionId: string, + sessionNonce?: string, ): Promise { const { data } = await api.post( - `/credentials/${credentialId}/relogin/idc/poll/${sessionId}` + `/credentials/${credentialId}/relogin/idc/poll/${sessionId}`, + undefined, + { headers: sessionNonce ? { 'x-kiro-egress-nonce': sessionNonce } : undefined }, ) return data } diff --git a/admin-ui/src/components/add-credential-dialog.tsx b/admin-ui/src/components/add-credential-dialog.tsx index 8285e7f0..b254fdfe 100644 --- a/admin-ui/src/components/add-credential-dialog.tsx +++ b/admin-ui/src/components/add-credential-dialog.tsx @@ -1,4 +1,5 @@ import { useState } from 'react' +import { useQuery } from '@tanstack/react-query' import { toast } from 'sonner' import { Dialog, @@ -21,6 +22,7 @@ import { useAddCredential } from '@/hooks/use-credentials' import { useGroupOptions } from '@/hooks/use-groups' import { extractErrorMessage } from '@/lib/utils' import { GroupMultiSelect } from '@/components/group-select' +import { getEgressLoginCapability, getProxyPool } from '@/api/credentials' interface AddCredentialDialogProps { open: boolean @@ -44,6 +46,8 @@ export function AddCredentialDialog({ open, onOpenChange }: AddCredentialDialogP const [proxyUrl, setProxyUrl] = useState('') const [proxyUsername, setProxyUsername] = useState('') const [proxyPassword, setProxyPassword] = useState('') + const [proxyId, setProxyId] = useState('') + const [browserEgressIp, setBrowserEgressIp] = useState('') const [endpoint, setEndpoint] = useState('') const [groups, setGroups] = useState([]) const [sourceChannel, setSourceChannel] = useState('') @@ -52,6 +56,18 @@ export function AddCredentialDialog({ open, onOpenChange }: AddCredentialDialogP const groupOptions = useGroupOptions() const { mutate, isPending } = useAddCredential() + const { data: egressCapability } = useQuery({ + queryKey: ['egress-login-v2'], + queryFn: getEgressLoginCapability, + enabled: open, + }) + const { data: proxyPool } = useQuery({ + queryKey: ['proxy-pool'], + queryFn: getProxyPool, + enabled: open, + }) + const egressEnabled = egressCapability?.enabled === true + const enabledProxies = proxyPool?.proxies.filter((proxy) => proxy.enabled) ?? [] const resetForm = () => { setRefreshToken('') @@ -68,6 +84,8 @@ export function AddCredentialDialog({ open, onOpenChange }: AddCredentialDialogP setProxyUrl('') setProxyUsername('') setProxyPassword('') + setProxyId('') + setBrowserEgressIp('') setEndpoint('') setGroups([]) setSourceChannel('') @@ -80,6 +98,11 @@ export function AddCredentialDialog({ open, onOpenChange }: AddCredentialDialogP const handleSubmit = (e: React.FormEvent) => { e.preventDefault() + if (egressEnabled && !proxyId) { + toast.error('请先选择托管出口') + return + } + // 验证必填字段 if (isApiKey) { if (!kiroApiKey.trim()) { @@ -117,9 +140,11 @@ export function AddCredentialDialog({ open, onOpenChange }: AddCredentialDialogP issuerUrl: isExternalIdp ? issuerUrl.trim() || undefined : undefined, scopes: isExternalIdp ? scopes.trim() || undefined : undefined, machineId: machineId.trim() || undefined, - proxyUrl: proxyUrl.trim() || undefined, - proxyUsername: proxyUsername.trim() || undefined, - proxyPassword: proxyPassword.trim() || undefined, + proxyUrl: egressEnabled ? undefined : proxyUrl.trim() || undefined, + proxyUsername: egressEnabled ? undefined : proxyUsername.trim() || undefined, + proxyPassword: egressEnabled ? undefined : proxyPassword.trim() || undefined, + proxyId: egressEnabled ? Number(proxyId) : undefined, + browserEgressIp: egressEnabled ? browserEgressIp.trim() || undefined : undefined, endpoint: endpoint.trim() || undefined, groups: groups, sourceChannel: sourceChannel.trim() || undefined, @@ -406,15 +431,34 @@ export function AddCredentialDialog({ open, onOpenChange }: AddCredentialDialogP {/* 代理配置 */}
-