feat(proxy): 重构 Proxy 节点管理与隧道系统

- 重构 proxy_nodes 管理端,支持节点注册、心跳、隧道生命周期管理
- 增强 tunnel 嵌入式 hub 和隧道协议
- 重构 aether-proxy 配置、隧道客户端、心跳和调度机制
- 调整 admin OAuth/配额/导入等处理器的参数传递
- 扩展数据迁移模块
- 补充 proxy nodes、OAuth、配额、系统导入等测试
- 更新前端 proxy nodes 视图和 API
This commit is contained in:
fawney19
2026-04-14 22:51:02 +08:00
parent fb31928e44
commit a4e7ac1df6
55 changed files with 3477 additions and 892 deletions

View File

@@ -22,10 +22,10 @@ export interface ProxyNode {
tunnel_mode: boolean
tunnel_connected: boolean
tunnel_connected_at: string | null
// 手动节点专用字段
// 手动节点专用字段。列表接口返回脱敏密码,详情接口返回明文密码。
proxy_url?: string
proxy_username?: string
proxy_password?: string // 脱敏后的密码
proxy_password?: string
// 硬件信息aether-proxy 节点)
hardware_info: Record<string, unknown> | null
estimated_max_concurrency: number | null
@@ -191,6 +191,11 @@ export const proxyNodesApi = {
return response.data
},
async getNode(nodeId: string): Promise<{ node: ProxyNode }> {
const response = await apiClient.get<{ node: ProxyNode }>(`/api/admin/proxy-nodes/${nodeId}`)
return response.data
},
async createManualNode(data: ManualProxyNodeCreateRequest): Promise<{ node_id: string; node: ProxyNode }> {
const response = await apiClient.post<{ node_id: string; node: ProxyNode }>('/api/admin/proxy-nodes/manual', data)
return response.data

View File

@@ -1208,6 +1208,7 @@ import {
type ProxyNodeEvent,
type ProxyNodeRemoteConfig,
type ProxyNodeSchedulingState,
type ProxyNodeTestResult,
type ProxyNodeUpgradeRolloutTrackedNode,
} from '@/api/proxy-nodes'
@@ -1428,6 +1429,12 @@ async function refresh() {
await store.fetchNodes()
}
function formatConnectivityTestParts(result: ProxyNodeTestResult): string[] {
const parts = [`延迟: ${result.latency_ms != null ? `${result.latency_ms}ms` : '暂无样本'}`]
if (result.exit_ip) parts.push(`出口IP: ${result.exit_ip}`)
return parts
}
async function handleTestUrl() {
if (!addForm.value.proxy_url || testingUrl.value) return
testingUrl.value = true
@@ -1438,9 +1445,7 @@ async function handleTestUrl() {
password: addForm.value.password || undefined,
})
if (result.success) {
const parts = [`延迟: ${result.latency_ms}ms`]
if (result.exit_ip) parts.push(`出口IP: ${result.exit_ip}`)
success(`连通性测试通过,${parts.join('')}`)
success(`连通性测试通过,${formatConnectivityTestParts(result).join('')}`)
} else {
toastError(`连通性测试失败: ${result.error || '未知错误'}`)
}
@@ -1451,16 +1456,21 @@ async function handleTestUrl() {
}
}
function handleEdit(node: ProxyNode) {
editingNode.value = node
addForm.value = {
name: node.name,
proxy_url: node.proxy_url || '',
username: node.proxy_username || '',
password: '', // 不回填密码(已脱敏)
region: node.region || '',
async function handleEdit(node: ProxyNode) {
try {
const { node: detail } = await proxyNodesApi.getNode(node.id)
editingNode.value = detail
addForm.value = {
name: detail.name,
proxy_url: detail.proxy_url || '',
username: detail.proxy_username || '',
password: detail.proxy_password || '',
region: detail.region || '',
}
showAddDialog.value = true
} catch (err: unknown) {
toastError(parseApiError(err, '读取代理节点详情失败'))
}
showAddDialog.value = true
}
function handleDialogClose(open: boolean) {
@@ -1757,9 +1767,7 @@ async function handleTest(node: ProxyNode) {
try {
const result = await proxyNodesApi.testNode(node.id)
if (result.success) {
const parts = [`延迟: ${result.latency_ms}ms`]
if (result.exit_ip) parts.push(`出口IP: ${result.exit_ip}`)
success(`连通性测试通过,${parts.join('')}`)
success(`连通性测试通过,${formatConnectivityTestParts(result).join('')}`)
} else {
toastError(`连通性测试失败: ${result.error || '未知错误'}`)
}