mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-18 23:25:10 +08:00
feat(proxy): add proxy IP management and account routing
Add admin CRUD and connectivity checks for SOCKS5/SOCKS5H proxy nodes. Allow accounts to bind to a proxy, route DeepSeek requests through the selected node, and expose proxy management in the admin UI.
This commit is contained in:
@@ -45,6 +45,7 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage,
|
||||
batchProgress,
|
||||
sessionCounts,
|
||||
deletingSessions,
|
||||
updatingProxy,
|
||||
addKey,
|
||||
deleteKey,
|
||||
addAccount,
|
||||
@@ -52,6 +53,7 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage,
|
||||
testAccount,
|
||||
testAllAccounts,
|
||||
deleteAllSessions,
|
||||
updateAccountProxy,
|
||||
} = useAccountActions({
|
||||
apiFetch,
|
||||
t,
|
||||
@@ -107,16 +109,19 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage,
|
||||
batchProgress={batchProgress}
|
||||
sessionCounts={sessionCounts}
|
||||
deletingSessions={deletingSessions}
|
||||
updatingProxy={updatingProxy}
|
||||
totalAccounts={totalAccounts}
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
totalPages={totalPages}
|
||||
resolveAccountIdentifier={resolveAccountIdentifier}
|
||||
proxies={config?.proxies || []}
|
||||
onTestAll={testAllAccounts}
|
||||
onShowAddAccount={() => setShowAddAccount(true)}
|
||||
onTestAccount={testAccount}
|
||||
onDeleteAccount={deleteAccount}
|
||||
onDeleteAllSessions={deleteAllSessions}
|
||||
onUpdateAccountProxy={updateAccountProxy}
|
||||
onPrevPage={() => fetchAccounts(page - 1)}
|
||||
onNextPage={() => fetchAccounts(page + 1)}
|
||||
onPageSizeChange={changePageSize}
|
||||
|
||||
@@ -11,16 +11,19 @@ export default function AccountsTable({
|
||||
batchProgress,
|
||||
sessionCounts,
|
||||
deletingSessions,
|
||||
updatingProxy,
|
||||
totalAccounts,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages,
|
||||
resolveAccountIdentifier,
|
||||
proxies,
|
||||
onTestAll,
|
||||
onShowAddAccount,
|
||||
onTestAccount,
|
||||
onDeleteAccount,
|
||||
onDeleteAllSessions,
|
||||
onUpdateAccountProxy,
|
||||
onPrevPage,
|
||||
onNextPage,
|
||||
onPageSizeChange,
|
||||
@@ -102,6 +105,7 @@ export default function AccountsTable({
|
||||
) : accounts.length > 0 ? (
|
||||
accounts.map((acc, i) => {
|
||||
const id = resolveAccountIdentifier(acc)
|
||||
const assignedProxy = proxies.find(proxy => proxy.id === acc.proxy_id)
|
||||
const runtimeUnknown = envBacked && !acc.test_status
|
||||
const isActive = acc.test_status === 'ok' || acc.has_token
|
||||
return (
|
||||
@@ -150,10 +154,28 @@ export default function AccountsTable({
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{acc.proxy_id && (
|
||||
<span className="font-mono bg-amber-500/10 text-amber-500 px-1.5 py-0.5 rounded text-[10px]">
|
||||
{t('accountManager.proxyBadge', { name: assignedProxy ? (assignedProxy.name || `${assignedProxy.host}:${assignedProxy.port}`) : acc.proxy_id })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 self-start lg:self-auto ml-5 lg:ml-0">
|
||||
<select
|
||||
value={acc.proxy_id || ''}
|
||||
onChange={e => onUpdateAccountProxy(id, e.target.value)}
|
||||
disabled={updatingProxy?.[id]}
|
||||
className="max-w-[180px] px-2.5 py-1.5 text-[10px] lg:text-xs bg-secondary border border-border rounded-md focus:outline-none focus:ring-1 focus:ring-ring disabled:opacity-50"
|
||||
>
|
||||
<option value="">{t('accountManager.proxyNone')}</option>
|
||||
{proxies.map(proxy => (
|
||||
<option key={proxy.id} value={proxy.id}>
|
||||
{proxy.name || `${proxy.host}:${proxy.port}`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={() => onTestAccount(id)}
|
||||
disabled={testing[id]}
|
||||
|
||||
@@ -12,6 +12,7 @@ export function useAccountActions({ apiFetch, t, onMessage, onRefresh, config, f
|
||||
const [batchProgress, setBatchProgress] = useState({ current: 0, total: 0, results: [] })
|
||||
const [sessionCounts, setSessionCounts] = useState({})
|
||||
const [deletingSessions, setDeletingSessions] = useState({})
|
||||
const [updatingProxy, setUpdatingProxy] = useState({})
|
||||
|
||||
const addKey = async () => {
|
||||
if (!newKey.trim()) return
|
||||
@@ -213,6 +214,34 @@ export function useAccountActions({ apiFetch, t, onMessage, onRefresh, config, f
|
||||
}
|
||||
}
|
||||
|
||||
const updateAccountProxy = async (identifier, proxyID) => {
|
||||
const accountID = String(identifier || '').trim()
|
||||
if (!accountID) {
|
||||
onMessage('error', t('accountManager.invalidIdentifier'))
|
||||
return
|
||||
}
|
||||
setUpdatingProxy(prev => ({ ...prev, [accountID]: true }))
|
||||
try {
|
||||
const res = await apiFetch(`/admin/accounts/${encodeURIComponent(accountID)}/proxy`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ proxy_id: proxyID || '' }),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (!res.ok) {
|
||||
onMessage('error', data.detail || t('messages.requestFailed'))
|
||||
return
|
||||
}
|
||||
onMessage('success', t('accountManager.proxyUpdateSuccess'))
|
||||
fetchAccounts()
|
||||
onRefresh()
|
||||
} catch (_err) {
|
||||
onMessage('error', t('messages.networkError'))
|
||||
} finally {
|
||||
setUpdatingProxy(prev => ({ ...prev, [accountID]: false }))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
showAddKey,
|
||||
setShowAddKey,
|
||||
@@ -230,6 +259,7 @@ export function useAccountActions({ apiFetch, t, onMessage, onRefresh, config, f
|
||||
batchProgress,
|
||||
sessionCounts,
|
||||
deletingSessions,
|
||||
updatingProxy,
|
||||
addKey,
|
||||
deleteKey,
|
||||
addAccount,
|
||||
@@ -237,5 +267,6 @@ export function useAccountActions({ apiFetch, t, onMessage, onRefresh, config, f
|
||||
testAccount,
|
||||
testAllAccounts,
|
||||
deleteAllSessions,
|
||||
updateAccountProxy,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user