mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-13 20:46:38 +08:00
316 lines
14 KiB
Plaintext
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 |