feat: update agents.md and add a feat (#128)

* docs(agents): update knowledge base with encryption, API envelope, and build conventions

Add comprehensive documentation of project conventions including:
- Encryption patterns (AES with node secret PSK)
- API envelope structure (code, msg, data, ts)
- Build peculiarities (minify: false, rolldown-vite, UPX compression)
- Unique styles (flat monorepo, asymmetric Go layout, hybrid frontend mode)
- Module boundaries and anti-patterns
- Large file hotspots and code map references

Updated 7 AGENTS.md files across root and submodules.

* test(backend): add comprehensive dual-stack IP preference test suite

Added 43 tests covering:
- Core IP selection logic (selectTunnelDialHost)
- Node capability detection (nodeSupportsV4/V6)
- Address picker functions
- API contract tests for create/update/list
- Database compatibility (SQLite + PostgreSQL)

Fixed pre-existing broken test in federation_runtime_test.go
This commit is contained in:
sagit
2026-02-15 23:17:15 +08:00
committed by GitHub
parent 9a9e83dda0
commit 1b4500202a
7 changed files with 841 additions and 69 deletions
+52 -16
View File
@@ -67,6 +67,7 @@ interface Tunnel {
protocol?: string;
flow: number; // 1: 单向, 2: 双向
trafficRatio: number;
ipPreference?: string;
status: number;
createdTime: string;
}
@@ -87,6 +88,7 @@ interface TunnelForm {
flow: number;
trafficRatio: number;
inIp: string; // 入口IP
ipPreference: string;
status: number;
}
@@ -141,6 +143,7 @@ export default function TunnelPage() {
flow: 1,
trafficRatio: 1.0,
inIp: "",
ipPreference: "",
status: 1,
});
@@ -301,6 +304,7 @@ export default function TunnelPage() {
flow: 1,
trafficRatio: 1.0,
inIp: "",
ipPreference: "",
status: 1,
});
setErrors({});
@@ -313,21 +317,22 @@ export default function TunnelPage() {
// 直接使用列表数据,getAllTunnels 已经包含完整的节点信息
setForm({
id: tunnel.id,
name: tunnel.name,
type: tunnel.type,
inNodeId: tunnel.inNodeId || [],
outNodeId: tunnel.outNodeId || [],
chainNodes: tunnel.chainNodes || [],
flow: tunnel.flow,
trafficRatio: tunnel.trafficRatio,
inIp: tunnel.inIp
? tunnel.inIp
.split(",")
.map((ip) => ip.trim())
.join("\n")
: "",
status: tunnel.status,
id: tunnel.id,
name: tunnel.name,
type: tunnel.type,
inNodeId: tunnel.inNodeId || [],
outNodeId: tunnel.outNodeId || [],
chainNodes: tunnel.chainNodes || [],
flow: tunnel.flow,
trafficRatio: tunnel.trafficRatio,
inIp: tunnel.inIp
? tunnel.inIp
.split(",")
.map((ip: string) => ip.trim())
.join("\n")
: "",
ipPreference: tunnel.ipPreference || "",
status: tunnel.status,
});
setErrors({});
setModalOpen(true);
@@ -1045,7 +1050,7 @@ export default function TunnelPage() {
</div>
{/* 流量配置 */}
<div className="grid grid-cols-2 gap-2">
<div className={`grid gap-2 ${tunnel.type === 2 && tunnel.ipPreference ? "grid-cols-3" : "grid-cols-2"}`}>
<div className="text-center p-1.5 bg-default-50 dark:bg-default-100/30 rounded">
<div className="text-xs text-default-500">
流量计算
@@ -1062,6 +1067,16 @@ export default function TunnelPage() {
{tunnel.trafficRatio}x
</div>
</div>
{tunnel.type === 2 && tunnel.ipPreference && (
<div className="text-center p-1.5 bg-default-50 dark:bg-default-100/30 rounded">
<div className="text-xs text-default-500">
连接偏好
</div>
<div className="text-sm font-semibold text-foreground mt-0.5">
{tunnel.ipPreference === "v4" ? "IPv4" : "IPv6"}
</div>
</div>
)}
</div>
</div>
@@ -1293,6 +1308,27 @@ export default function TunnelPage() {
}
/>
{form.type === 2 && (
<Select
description="当节点同时拥有IPv4和IPv6地址时,选择隧道连接使用的地址类型"
label="隧道连接地址偏好"
selectedKeys={[form.ipPreference || ""]}
variant="bordered"
onSelectionChange={(keys) => {
const selectedKey = Array.from(keys)[0] as string;
setForm((prev) => ({
...prev,
ipPreference: selectedKey || "",
}));
}}
>
<SelectItem key="">自动选择</SelectItem>
<SelectItem key="v4">优先IPv4</SelectItem>
<SelectItem key="v6">优先IPv6</SelectItem>
</Select>
)}
<Divider />
<h3 className="text-lg font-semibold">入口配置</h3>