Files
flvx/.entire/metadata/ses_3522df9f9ffed5y6qXQeuiZ8vT/summary.txt
T
2026-03-03 14:19:18 +08:00

316 lines
14 KiB
Plaintext

// List in frontend display isPort.)?
if (in_ip !== in.re.selected) {
tunnel ip !== node in_tunnel.port_range value
select(v4 or IPv6, and
.chainType = chainType === 1 ( then chain_node !== node to check if `in_ip` is still used:
"chain_node_id" for `chainTunnels.node_id = nodeId, && `chainType != "hop" fields
//
}
}
}
<Select
<div className="flex flex-col gap-4 mt-6">
<Select
isFilterModalOpen={false}
setFilterUserId, filterTunnelId(tunnelId)}
}}
<form.in_port}
:form.in_port !== null,
}}
} else {
setFilterTunnelId}
}
}}
// handleEntryNodeIDs
hop - entry port assignment
const { entryNodeID, entryPort, entry_ip, resolvedFor forwardIn_ip based on
forward data view:
In entity links the node,entry, remote_addr and`]
tdCompose groups
remote nodes by hostname.
`target_addr` is multiple ones, the `targetAddr`/ `name` || `strategy: data in the form, uses the latest text.
`line text: `grid gap-3 group` leading to border. from UI styles
`.sort order`actions`: data
? `no-frontend for/not directly modify forward model
so it doesn't have `inPort` in model.For forward creation.```
const forwardPort = entryNodeIDs with port = 0
are - but tunnels and tunnels have
after:. Based on.
to reach the entry from. specified port-based" entry_ip, `addr_text` field or tunnel IP-based tunnel values with data not used this rules
step pattern is`node-based:`peer` or a`IP_preference` + `ip_preference` header.
"IP_preference from}
`grep<include="*.tsx"" path="/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/mighty-nebula/vite-frontend/src/pages/forward.tsx" />
"peer_share" and "federation" share" APIs.
data models
/>
< Frontend> `in_ip` comes from the tunnel, and not the node model.In Go-gost.
. I will specifically look at `port` and `forward_port` data stored in `forward_port` table (` // When editing, forward, the UI keeps the current port value in the `inPort` state is checked for duplicates (`
forwards list ( addresses with multiple addresses.
</div
}
</div>
}
}
}
}
className="flex flex-col gap-4">
{/* form fields - in edit mode */}
</4-form.inPort in field and handleClick save
validation and numbers? `handleEdit` adds the `inPort` to the and `inPort` state.
// handleDragEnd ref={handleDragEnd} to scroll into view}
if (prev.forwardPorts.length === 0) {
// Create new forward
entry with not auto expanded
const inPort records = || const{index` === 0` ? record and `forward_ports` table
const inPort = records = or can be rendered when the to pickTunnelPort: the empty { inPort = null ?} => to persistent if port === 0 ( automatic assignment).
} else {
toast.error("请选择关联隧道")
}
const minPort =
const ports = oldPorts.map((p) => p)) // values from request
// value === 0 means "端口不能为空, else if (!port) {
const inPort = tunnelPorts.map((t) => {
const inIP = tunnel = in_ip
|| t.IP === the default) 'auto' (available, tunnel.ip_preference` || `:` if` in_ip` and `in_port` values ( listenAddr] which`tcp`/udp` addresses are the respectively
`forward` now supports select/un/selected tunnel. when not found ( a single ` address can be shown, and simplified overview.= `tunnel` but has `in_ip` display name="人口入口IP` and "端口转发" when tunnel is port-forward, the model oftrafficRatio` floats with `type` and `protocol` fields.
// `protocol`: 'tls' | 'wss' | 'mtls' | 'mtcp'
for `type` === 1: 繀 端口转发, 2: 隧道转发, and `type` determines the listeners on which forward ( protocol, and exit node ( configuration. Let `traffic_ratio` be on in/out, and the name` tunnelName, in the `UserTunnel` table
var chainType = chainType === "entry" ? # chainType === 2, value for represents entry node id for chain_type === "hop", fields indicate which hop number (1-based, `strategy` on the current value)
? newErrors.inPort = "端口必须在1-65535之间";
form.inPort = optional
required validation in if filled, values to 0 or null, then the port is is auto-assigned
} ValidateForm()
if (validateForm()) {
setErrors(newErrors);
}
}
};
const handleTunnelChange = (tunnelId: string) => {
setForm((prev) => ({ ...prev, tunnelId}));
: if (prev.inPort !== null && prev.inPort !== old, in port assignment will not assign the new port auto
});
}
);
} else {
inPort = null
: options.speedId = speedLimit }
onChange={(selectedKey) => {
setFilter((prev) => ({ ...prev, speedId, speedName }));
}
});
});
setBatchChangeTunnelModalOpen(false);
setBatchChangeTunnelModalOpen(false);
}
</div>
</div>
);
<input
inport}
*/
.tsx
<Input
description="指定入口端口,留空则从节点可用端口中自动分配"
placeholder="留空则自动分配可用端口"
type="number"
variant="bordered"
onChange={(e) => {
const value = e.target.value ? ""
: parseInt(value) || null)
setForm((prev) => ({ ...prev, inPort: null}));
</div>
</div>
<ModalContent>
<ModalHeader className="flex flex-col gap-1">
<h2 className="text-xl font-bold">
{isEdit ? "编辑转发" : "新增转发"}
</h2>
<p className="text-small text-default-500 mt-4">
{isEdit ? "修改现有转发配置的信息" : "创建新的转发配置"}
</p>
<ModalBody>
<div className="space-y-4 pb-4">
<Input
errorMessage={errors.name}
isInvalid={!!errors.name}
label="转发名称"
placeholder="请输入转发名称"
value={form.name}
variant="bordered"
onChange={(e) =>
setForm((prev) => ({ ...prev, name: e.target.value }))
}
/>
{/* Limit速规则选择 */}
<Select
description="限速规则"
placeholder="不限速"
selectedKeys={
selectedSpeedId !== null ? ?[selectedSpeedId.toString()] : []
}
variant="bordered"
onSelectionChange={(keys) => {
const selectedKey = Array.from(keys)[0] as string | undefined;
setForm((prev) => ({
...prev,
speedId: selectedKey ? Number(selectedKey) : null,
});
}}
/>
{/*隧道选择 */}
<Select
description={
isEdit
? "更改隧道将释放原端口并在新隧道分配端口"
: "更改隧道后重新分配端口并同步到节点"
}
}
errorMessage={errors.tunnelId}
isInvalid={!!errors.tunnelId}
label="选择隧道"
placeholder="请选择关联的隧道"
selectedKeys={
form.tunnelId ? [form.tunnelId.toString()] : []
}
variant="bordered"
onSelectionChange={(keys) => {
const selectedKey = Array.from(keys)[0] as string;
if (selectedKey) {
handleTunnelChange(selectedKey);
}
}
/>
{/*入口端口 */}
<Input
description="指定入口端口,留空则从节点可用端口中自动分配"
errorMessage={errors.inPort}
isInvalid={!!errors.inPort}
label="入口端口"
placeholder="留空则自动分配可用端口"
type="number"
value={form.inPort !== null ? form.inPort.toString() : ""}
variant="bordered"
onChange={(e) => {
const value = e.target.value;
setForm((prev) => ({ ...prev, inPort: value ? parseInt(value) : null }));
}
/>
</div>
</ModalBody>
</ModalContent>
</Modal>
</Modal>
);
};
// ===================== Summary of findings ===================
## Key File Paths and their purposes:
### Frontend: Forward Edit/create dialog component
- **Main file:** `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/mighty-nebula/vite-frontend/src/pages/forward.tsx`
- Displays the forwarding list and handles all create/edit/delete/forwards
- Has `inIp` and `inPort` fields
- Shows entry port and dialog/form with validation
- Uses React-hook-form with state management
- Calls `createForward`, `updateForward`, `deleteForward`, `pauseForwardService`, `resumeForwardService`, APIs
- Imports API functions from `@/api`
### Backend: Forwarding data model and API handlers
- **Main model file:** `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/mighty-nebula/go-backend/internal/store/model/model.go`
- GORM model definition for `Forward` struct does basic forwarding configuration
- Related models: `ForwardPort`, `Tunnel`, `UserForwardDetail` (view struct)
- **API handlers:** `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/mighty-nebula/go-backend/internal/http/handler/mutations.go`
- Create/update/delete operations
- Entry port handling via `inPort` field
- **Repository layer:** `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/mighty-nebula/go-backend/internal/store/repo/repository_mutations.go`
- CRUD operations for - `resolveForwardIngress` function in `/Users/sagit/.local/share/opencode/worktree/13650be91caa3a9e08a2e63127fef62757658384/mighty-nebula/go-backend/internal/store/repo/repository.go`
- Computes `inIp` and `inPort` from joins
### Current data model structure for forwards
From `go-backend/internal/store/model/model.go`:
```go
// Forward maps to the "forward" table.
type Forward struct {
ID int64 `gorm:"primaryKey;autoIncrement"`
UserID int64 `gorm:"column:user_id;not null"`
UserName string `gorm:"column:user_name;type:varchar(100);not null"`
Name string `gorm:"type:varchar(100);not null"`
TunnelID int64 `gorm:"column:tunnel_id;not null"`
RemoteAddr string `gorm:"column:remote_addr;type:text;not null"`
Strategy string `gorm:"type:varchar(100);not null;default:'fifo'"`
InFlow int64 `gorm:"not null;default:0"`
OutFlow int64 `gorm:"column:out_flow;not null;default:0"`
CreatedTime int64 `gorm:"column:created_time;not null"`
UpdatedTime int64 `gorm:"column:updated_time;not null"`
Status int `gorm:"not null"`
Inx int `gorm:"not null;default:0"`
SpeedID sql.NullInt64 `gorm:"column:speed_id"`
}
```
```go
// ForwardPort maps to the "forward_port" table - stores port assignments per entry node
type ForwardPort struct {
ID int64 `gorm:"primaryKey;autoIncrement"`
ForwardID int64 `gorm:"column:forward_id;not null"`
NodeID int64 `gorm:"column:node_id;not null"`
Port int `gorm:"not null"`
}
```
```go
// Tunnel maps to the "tunnel" table - has entry IP configuration
type Tunnel struct {
ID int64 `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"type:varchar(100);not null"`
TrafficRatio float64 `gorm:"column:traffic_ratio;not null;default:1.0"`
Type int `gorm:"not null"`
Protocol string `gorm:"type:varchar(10);not null;default:'tls'"`
Flow int64 `gorm:"not null"`
CreatedTime int64 `gorm:"column:created_time;not null"`
UpdatedTime int64 `gorm:"column:updated_time;not null"`
Status int `gorm:"not null"`
InIP sql.NullString `gorm:"column:in_ip;type:text"` // Optional entry IP hint
Inx int `gorm:"not null;default:0"`
IPPreference string `gorm:"column:ip_preference;type:varchar(10);not null;default:''"`
}
```
### How entry listener is currently configured:
1. **Port Storage**: `ForwardPort` table stores the mapping from `forward_id` -> `node_id` -> `port`
- Port is assigned by the handler or passed via `inPort` parameter ( or auto-picked)
- Entry nodes are determined by `tunnelEntryNodeIDs()` in handler
3. **Entry IP Resolution**: The `resolveForwardIngress()` function resolves `inIp` and `inPort` by:
- Joining `forward_port` with `node` table
- `inIp` comes from `tunnel.in_ip` if set (takes precedence over node.server_ip)
- For multi-entry-node forwards, comma-separated IPs and ports are returned
4. **Current IP-related fields**:
- `Forward` model: No `inIp` field (port is stored in `forward_port` table)
- `Tunnel` model: Has `InIP` field (nullable, - allows specifying of a preferred entry IP hint
- `UserForwardDetail` view struct ( joins tunnel table and includes `InIP` and `InPort`
5. **No explicit entry IP selection UI in current dialog** - The port field is a text input, and form validates range but but UI doesn't allow setting specific IP addresses