refactor: 代理节点架构重构与功能增强

aether-proxy:
- 重构 main.rs,拆分为 app/state/hardware/net 模块
- setup.rs 拆分为 setup/tui.rs + setup/service.rs,支持 systemd 服务管理子命令
- 新增 delegate 端点,支持后端通过代理节点转发请求而非传统 CONNECT 代理
- 注册时上报硬件信息(CPU/内存/fd_limit)和估算最大并发数
- 心跳上报活跃连接数,支持远程下发 node_name 配置
- HTTP 转发时剥离 X-Forwarded-* 等敏感头部
- 切换到 rustls-tls,降低日志级别减少噪音

后端:
- 从 http_client.py 提取代理相关逻辑至 proxy_node/resolver.py
- 从 routes.py 提取业务逻辑至 proxy_node/service.py
- handler 支持 delegate 模式(通过代理节点 HTTP 端点转发而非 CONNECT 隧道)
- ProxyNode 模型新增 hardware_info 和 estimated_max_concurrency 字段

前端:
- 新增 HardwareTooltip 组件展示节点硬件信息
- 远程配置支持下发 node_name
This commit is contained in:
fawney19
2026-02-08 13:33:08 +08:00
parent 254d30d32d
commit 519ad67eb1
44 changed files with 3339 additions and 1709 deletions

View File

@@ -1,6 +1,7 @@
import apiClient from './client'
export interface ProxyNodeRemoteConfig {
node_name?: string
allowed_ports?: number[]
log_level?: string
heartbeat_interval?: number
@@ -19,6 +20,9 @@ export interface ProxyNode {
proxy_url?: string
proxy_username?: string
proxy_password?: string // 脱敏后的密码
// 硬件信息aether-proxy 节点)
hardware_info: Record<string, any> | null
estimated_max_concurrency: number | null
// 远程配置aether-proxy 节点)
remote_config: ProxyNodeRemoteConfig | null
config_version: number

View File

@@ -7,7 +7,10 @@
:z-index="70"
@update:model-value="$emit('update:open', $event)"
>
<template v-if="providerId && items.length > 0" #header-actions>
<template
v-if="providerId && items.length > 0"
#header-actions
>
<DropdownMenu :modal="false">
<DropdownMenuTrigger as-child>
<Button

View File

@@ -107,7 +107,10 @@
>
<!-- 主区域拖拽 粘贴输入框同一位置切换 -->
<div v-if="!importText" class="mt-3">
<div
v-if="!importText"
class="mt-3"
>
<!-- 拖拽模式 -->
<div
v-if="!showManualInput"
@@ -168,7 +171,10 @@
</div>
<!-- 已有内容文件导入后显示文本框 -->
<div v-if="importText" class="space-y-2">
<div
v-if="importText"
class="space-y-2"
>
<div class="flex items-center justify-between">
<span class="text-xs text-muted-foreground">{{ importFileName || '已粘贴内容' }}</span>
<button

View File

@@ -1,6 +1,9 @@
<template>
<div class="space-y-6 pb-8">
<Card variant="default" class="overflow-hidden">
<Card
variant="default"
class="overflow-hidden"
>
<!-- 标题和筛选器 -->
<div class="px-4 sm:px-6 py-3.5 border-b border-border/60">
<!-- 移动端 -->
@@ -39,10 +42,18 @@
<SelectValue placeholder="状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">全部</SelectItem>
<SelectItem value="online">在线</SelectItem>
<SelectItem value="unhealthy">异常</SelectItem>
<SelectItem value="offline">离线</SelectItem>
<SelectItem value="all">
全部
</SelectItem>
<SelectItem value="online">
在线
</SelectItem>
<SelectItem value="unhealthy">
异常
</SelectItem>
<SelectItem value="offline">
离线
</SelectItem>
</SelectContent>
</Select>
</div>
@@ -69,10 +80,18 @@
<SelectValue placeholder="全部状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">全部状态</SelectItem>
<SelectItem value="online">在线</SelectItem>
<SelectItem value="unhealthy">异常</SelectItem>
<SelectItem value="offline">离线</SelectItem>
<SelectItem value="all">
全部状态
</SelectItem>
<SelectItem value="online">
在线
</SelectItem>
<SelectItem value="unhealthy">
异常
</SelectItem>
<SelectItem value="offline">
离线
</SelectItem>
</SelectContent>
</Select>
<div class="h-4 w-px bg-border" />
@@ -98,15 +117,33 @@
<Table>
<TableHeader>
<TableRow class="border-b border-border/60 hover:bg-transparent">
<TableHead class="w-[160px] h-12 font-semibold">名称</TableHead>
<TableHead class="w-[180px] h-12 font-semibold">地址</TableHead>
<TableHead class="w-[100px] h-12 font-semibold">区域</TableHead>
<TableHead class="w-[90px] h-12 font-semibold text-center">状态</TableHead>
<TableHead class="w-[100px] h-12 font-semibold text-center">连接数</TableHead>
<TableHead class="w-[100px] h-12 font-semibold text-center">总请求</TableHead>
<TableHead class="w-[100px] h-12 font-semibold text-center">延迟</TableHead>
<TableHead class="w-[160px] h-12 font-semibold">最后心跳</TableHead>
<TableHead class="w-[80px] h-12 font-semibold text-center">操作</TableHead>
<TableHead class="w-[160px] h-12 font-semibold">
名称
</TableHead>
<TableHead class="w-[180px] h-12 font-semibold">
地址
</TableHead>
<TableHead class="w-[100px] h-12 font-semibold">
区域
</TableHead>
<TableHead class="w-[90px] h-12 font-semibold text-center">
状态
</TableHead>
<TableHead class="w-[100px] h-12 font-semibold text-center">
连接数
</TableHead>
<TableHead class="w-[100px] h-12 font-semibold text-center">
总请求
</TableHead>
<TableHead class="w-[100px] h-12 font-semibold text-center">
延迟
</TableHead>
<TableHead class="w-[160px] h-12 font-semibold">
最后心跳
</TableHead>
<TableHead class="w-[80px] h-12 font-semibold text-center">
操作
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -125,6 +162,7 @@
>
手动
</Badge>
<HardwareTooltip :node="node" />
</div>
</TableCell>
<TableCell class="py-4">
@@ -134,7 +172,10 @@
<span class="text-sm text-muted-foreground">{{ node.region || '-' }}</span>
</TableCell>
<TableCell class="py-4 text-center">
<Badge :variant="statusVariant(node.status)" class="font-medium px-2.5 py-0.5 text-xs">
<Badge
:variant="statusVariant(node.status)"
class="font-medium px-2.5 py-0.5 text-xs"
>
{{ statusLabel(node.status) }}
</Badge>
</TableCell>
@@ -160,8 +201,14 @@
:disabled="testingNodes.has(node.id)"
@click="handleTest(node)"
>
<Loader2 v-if="testingNodes.has(node.id)" class="h-4 w-4 animate-spin" />
<Activity v-else class="h-4 w-4" />
<Loader2
v-if="testingNodes.has(node.id)"
class="h-4 w-4 animate-spin"
/>
<Activity
v-else
class="h-4 w-4"
/>
</Button>
<Button
v-if="node.is_manual"
@@ -196,7 +243,10 @@
</TableCell>
</TableRow>
<TableRow v-if="paginatedNodes.length === 0">
<TableCell colspan="9" class="py-12 text-center text-muted-foreground text-sm">
<TableCell
colspan="9"
class="py-12 text-center text-muted-foreground text-sm"
>
{{ store.loading ? '加载中...' : '暂无代理节点' }}
</TableCell>
</TableRow>
@@ -222,10 +272,14 @@
>
手动
</Badge>
<HardwareTooltip :node="node" />
</div>
<code class="text-xs text-muted-foreground">{{ node.is_manual ? (node.proxy_url || `${node.ip}:${node.port}`) : `${node.ip}:${node.port}` }}</code>
</div>
<Badge :variant="statusVariant(node.status)" class="text-xs">
<Badge
:variant="statusVariant(node.status)"
class="text-xs"
>
{{ statusLabel(node.status) }}
</Badge>
</div>
@@ -253,8 +307,14 @@
:disabled="testingNodes.has(node.id)"
@click="handleTest(node)"
>
<Loader2 v-if="testingNodes.has(node.id)" class="h-3 w-3 mr-1 animate-spin" />
<Activity v-else class="h-3 w-3 mr-1" />
<Loader2
v-if="testingNodes.has(node.id)"
class="h-3 w-3 mr-1 animate-spin"
/>
<Activity
v-else
class="h-3 w-3 mr-1"
/>
{{ testingNodes.has(node.id) ? '测试中' : '测试' }}
</Button>
<Button
@@ -289,7 +349,10 @@
</div>
</div>
</div>
<div v-if="paginatedNodes.length === 0" class="p-8 text-center text-muted-foreground text-sm">
<div
v-if="paginatedNodes.length === 0"
class="p-8 text-center text-muted-foreground text-sm"
>
{{ store.loading ? '加载中...' : '暂无代理节点' }}
</div>
</div>
@@ -390,25 +453,40 @@
size="md"
@update:model-value="handleConfigDialogClose"
>
<form class="space-y-4" @submit.prevent>
<form
class="space-y-4"
@submit.prevent
>
<div class="space-y-1.5">
<Label>允许的端口</Label>
<Input
v-model="configForm.allowed_ports"
placeholder="80, 443, 8080, 8443"
/>
<p class="text-xs text-muted-foreground">逗号分隔的目标端口白名单</p>
<p class="text-xs text-muted-foreground">
逗号分隔的目标端口白名单
</p>
</div>
<div class="space-y-1.5">
<Label>日志级别</Label>
<Select v-model="configForm.log_level">
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="trace">trace</SelectItem>
<SelectItem value="debug">debug</SelectItem>
<SelectItem value="info">info</SelectItem>
<SelectItem value="warn">warn</SelectItem>
<SelectItem value="error">error</SelectItem>
<SelectItem value="trace">
trace
</SelectItem>
<SelectItem value="debug">
debug
</SelectItem>
<SelectItem value="info">
info
</SelectItem>
<SelectItem value="warn">
warn
</SelectItem>
<SelectItem value="error">
error
</SelectItem>
</SelectContent>
</Select>
</div>
@@ -432,7 +510,10 @@
/>
</div>
</div>
<div v-if="configNode" class="text-xs text-muted-foreground">
<div
v-if="configNode"
class="text-xs text-muted-foreground"
>
配置版本: v{{ configNode.config_version }}
</div>
</form>
@@ -484,6 +565,7 @@ import {
} from '@/components/ui'
import { Search, Trash2, Plus, SquarePen, Activity, Loader2, Settings } from 'lucide-vue-next'
import HardwareTooltip from './components/HardwareTooltip.vue'
const { success, error: toastError } = useToast()
const { confirmDanger } = useConfirm()

View File

@@ -0,0 +1,51 @@
<script setup lang="ts">
import type { ProxyNode } from '@/api/proxy-nodes'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui'
import { Cpu } from 'lucide-vue-next'
defineProps<{ node: ProxyNode }>()
function formatMemory(mb: number | null) {
if (mb == null) return '-'
if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`
return `${mb} MB`
}
function formatNumber(n: number) {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`
return String(n)
}
</script>
<template>
<TooltipProvider v-if="!node.is_manual && node.hardware_info">
<Tooltip>
<TooltipTrigger as-child>
<Cpu class="h-3.5 w-3.5 text-muted-foreground cursor-default" />
</TooltipTrigger>
<TooltipContent
side="right"
class="text-xs space-y-0.5"
>
<div v-if="node.hardware_info.cpu_cores">
CPU: {{ node.hardware_info.cpu_cores }} cores
</div>
<div v-if="node.hardware_info.total_memory_mb">
RAM: {{ formatMemory(node.hardware_info.total_memory_mb) }}
</div>
<div v-if="node.hardware_info.os_info">
OS: {{ node.hardware_info.os_info }}
</div>
<div v-if="node.estimated_max_concurrency">
Max Concurrency: ~{{ formatNumber(node.estimated_max_concurrency) }}
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</template>