Browse Source

feat(inbounds): align tunnel, tun, and hysteria UI with Xray docs

* tunnel: rename settings to Xray's current schema (address →
  rewriteAddress, port → rewritePort, network → allowedNetwork) in
  the model, form modal, info modal, and the bundled API inbound
  template; expose portMap so per-port forwarding can be configured
  from the panel.
* tun: add the full TUN protocol form and read-only info blocks
  (name, mtu, gateway, dns, userLevel, autoSystemRoutingTable,
  autoOutboundsInterface) — previously the protocol was selectable
  but the form rendered blank.
* hysteria: surface the stream-level version, obfs password, and
  udpIdleTimeout fields that the model already supported.

Refs https://xtls.github.io/config/inbounds/tunnel.html
Refs https://xtls.github.io/config/inbounds/tun.html
Refs https://xtls.github.io/config/transports/hysteria.html
MHSanaei 1 day ago
parent
commit
771bc7c8ef

+ 20 - 12
frontend/src/models/inbound.js

@@ -2967,37 +2967,45 @@ Inbound.HysteriaSettings.Hysteria = class extends Inbound.ClientBase {
 Inbound.TunnelSettings = class extends Inbound.Settings {
     constructor(
         protocol,
-        address,
-        port,
+        rewriteAddress,
+        rewritePort,
         portMap = [],
-        network = 'tcp,udp',
+        allowedNetwork = 'tcp,udp',
         followRedirect = false
     ) {
         super(protocol);
-        this.address = address;
-        this.port = port;
+        this.rewriteAddress = rewriteAddress;
+        this.rewritePort = rewritePort;
         this.portMap = portMap;
-        this.network = network;
+        this.allowedNetwork = allowedNetwork;
         this.followRedirect = followRedirect;
     }
 
+    addPortMap(port = '', target = '') {
+        this.portMap.push({ name: port, value: target });
+    }
+
+    removePortMap(index) {
+        this.portMap.splice(index, 1);
+    }
+
     static fromJson(json = {}) {
         return new Inbound.TunnelSettings(
             Protocols.TUNNEL,
-            json.address,
-            json.port,
+            json.rewriteAddress,
+            json.rewritePort,
             XrayCommonClass.toHeaders(json.portMap),
-            json.network,
+            json.allowedNetwork,
             json.followRedirect,
         );
     }
 
     toJson() {
         return {
-            address: this.address,
-            port: this.port,
+            rewriteAddress: this.rewriteAddress,
+            rewritePort: this.rewritePort,
             portMap: XrayCommonClass.toV2Headers(this.portMap, false),
-            network: this.network,
+            allowedNetwork: this.allowedNetwork,
             followRedirect: this.followRedirect,
         };
     }

+ 135 - 12
frontend/src/pages/inbounds/InboundFormModal.vue

@@ -679,10 +679,7 @@ watch(
       </a-tab-pane>
 
       <!-- ============================== PROTOCOL ============================== -->
-      <!-- TUN has no per-protocol form yet (interface/mtu/gateway live in
-           settings JSON), so the tab would render empty — hide it until
-           a TUN form is added. -->
-      <a-tab-pane v-if="protocol !== Protocols.TUN" key="protocol" :tab="t('pages.inbounds.protocol')">
+      <a-tab-pane key="protocol" :tab="t('pages.inbounds.protocol')">
         <!-- Multi-user inbounds: in add mode embed the first client form,
              in edit mode show a count summary. -->
         <template v-if="isMultiUser">
@@ -895,24 +892,126 @@ watch(
         <!-- Tunnel -->
         <a-form v-if="protocol === Protocols.TUNNEL" :colon="false" :label-col="{ sm: { span: 8 } }"
           :wrapper-col="{ sm: { span: 14 } }" class="mt-12">
-          <a-form-item label="Address">
-            <a-input v-model:value="inbound.settings.address" />
+          <a-form-item label="Rewrite address">
+            <a-input v-model:value="inbound.settings.rewriteAddress" />
           </a-form-item>
-          <a-form-item label="Destination port">
-            <a-input-number v-model:value="inbound.settings.port" :min="1" :max="65535" />
+          <a-form-item label="Rewrite port">
+            <a-input-number v-model:value="inbound.settings.rewritePort" :min="0" :max="65535" />
           </a-form-item>
-          <a-form-item label="Network">
-            <a-select v-model:value="inbound.settings.network">
+          <a-form-item label="Allowed network">
+            <a-select v-model:value="inbound.settings.allowedNetwork">
               <a-select-option value="tcp,udp">TCP, UDP</a-select-option>
               <a-select-option value="tcp">TCP</a-select-option>
               <a-select-option value="udp">UDP</a-select-option>
             </a-select>
           </a-form-item>
+          <a-form-item label="Port map">
+            <a-button size="small" @click="inbound.settings.addPortMap('', '')">
+              <template #icon>
+                <PlusOutlined />
+              </template>
+            </a-button>
+          </a-form-item>
+          <a-form-item v-if="inbound.settings.portMap.length > 0" :wrapper-col="{ span: 24 }">
+            <a-input-group v-for="(pm, idx) in inbound.settings.portMap" :key="`pm-${idx}`" compact class="mb-8">
+              <a-input :style="{ width: '30%' }" v-model:value="pm.name" placeholder="5555">
+                <template #addonBefore>{{ idx + 1 }}</template>
+              </a-input>
+              <a-input :style="{ width: '60%' }" v-model:value="pm.value" placeholder="1.1.1.1:7777" />
+              <a-button @click="inbound.settings.removePortMap(idx)">
+                <template #icon>
+                  <MinusOutlined />
+                </template>
+              </a-button>
+            </a-input-group>
+          </a-form-item>
           <a-form-item label="Follow redirect">
             <a-switch v-model:checked="inbound.settings.followRedirect" />
           </a-form-item>
         </a-form>
 
+        <!-- TUN -->
+        <a-form v-if="protocol === Protocols.TUN" :colon="false" :label-col="{ sm: { span: 8 } }"
+          :wrapper-col="{ sm: { span: 14 } }" class="mt-12">
+          <a-form-item label="Interface name">
+            <a-input v-model:value="inbound.settings.name" placeholder="xray0" />
+          </a-form-item>
+          <a-form-item label="MTU">
+            <a-input-number v-model:value="inbound.settings.mtu" :min="0" />
+          </a-form-item>
+          <a-form-item label="Gateway">
+            <a-button size="small" @click="inbound.settings.gateway.push('')">
+              <template #icon>
+                <PlusOutlined />
+              </template>
+            </a-button>
+            <a-input v-for="(_ip, j) in inbound.settings.gateway" :key="`tun-gw-${j}`"
+              v-model:value="inbound.settings.gateway[j]" class="mt-4"
+              :placeholder="j === 0 ? '10.0.0.1/16' : 'fc00::1/64'">
+              <template #addonAfter>
+                <a-button size="small" @click="inbound.settings.gateway.splice(j, 1)">
+                  <template #icon>
+                    <MinusOutlined />
+                  </template>
+                </a-button>
+              </template>
+            </a-input>
+          </a-form-item>
+          <a-form-item label="DNS">
+            <a-button size="small" @click="inbound.settings.dns.push('')">
+              <template #icon>
+                <PlusOutlined />
+              </template>
+            </a-button>
+            <a-input v-for="(_ip, j) in inbound.settings.dns" :key="`tun-dns-${j}`"
+              v-model:value="inbound.settings.dns[j]" class="mt-4" :placeholder="j === 0 ? '1.1.1.1' : '8.8.8.8'">
+              <template #addonAfter>
+                <a-button size="small" @click="inbound.settings.dns.splice(j, 1)">
+                  <template #icon>
+                    <MinusOutlined />
+                  </template>
+                </a-button>
+              </template>
+            </a-input>
+          </a-form-item>
+          <a-form-item label="User level">
+            <a-input-number v-model:value="inbound.settings.userLevel" :min="0" />
+          </a-form-item>
+          <a-form-item>
+            <template #label>
+              <a-tooltip
+                title="Windows-only. CIDRs added to the system routing table automatically so matching traffic goes through TUN.">
+                Auto system routes
+              </a-tooltip>
+            </template>
+            <a-button size="small" @click="inbound.settings.autoSystemRoutingTable.push('')">
+              <template #icon>
+                <PlusOutlined />
+              </template>
+            </a-button>
+            <a-input v-for="(_ip, j) in inbound.settings.autoSystemRoutingTable" :key="`tun-rt-${j}`"
+              v-model:value="inbound.settings.autoSystemRoutingTable[j]" class="mt-4"
+              :placeholder="j === 0 ? '0.0.0.0/0' : '::/0'">
+              <template #addonAfter>
+                <a-button size="small" @click="inbound.settings.autoSystemRoutingTable.splice(j, 1)">
+                  <template #icon>
+                    <MinusOutlined />
+                  </template>
+                </a-button>
+              </template>
+            </a-input>
+          </a-form-item>
+          <a-form-item>
+            <template #label>
+              <a-tooltip
+                title='Physical interface for outbound traffic. Use "auto" to detect; auto-enabled when Auto system routes is set.'>
+                Auto outbounds interface
+              </a-tooltip>
+            </template>
+            <a-input v-model:value="inbound.settings.autoOutboundsInterface" placeholder="auto" />
+          </a-form-item>
+        </a-form>
+
         <!-- WireGuard -->
         <a-form v-if="protocol === Protocols.WIREGUARD" :colon="false" :label-col="{ sm: { span: 8 } }"
           :wrapper-col="{ sm: { span: 14 } }" class="mt-12">
@@ -1723,9 +1822,33 @@ watch(
             </a-form-item>
           </template>
 
-          <!-- ====== Hysteria Masquerade ====== -->
-          <!-- Per https://xtls.github.io/config/transports/hysteria.html#masqobject -->
+          <!-- ====== Hysteria stream settings ====== -->
+          <!-- Per https://xtls.github.io/config/transports/hysteria.html -->
           <template v-if="protocol === Protocols.HYSTERIA">
+            <a-form-item>
+              <template #label>
+                <a-tooltip title="Hysteria protocol version. Currently must be 2.">
+                  Version
+                </a-tooltip>
+              </template>
+              <a-input-number v-model:value="inbound.stream.hysteria.version" :min="2" :max="2" />
+            </a-form-item>
+            <a-form-item>
+              <template #label>
+                <a-tooltip title="Obfuscation password. Must match between server and client.">
+                  Obfs password
+                </a-tooltip>
+              </template>
+              <a-input v-model:value="inbound.stream.hysteria.auth" />
+            </a-form-item>
+            <a-form-item>
+              <template #label>
+                <a-tooltip title="Idle timeout (seconds) for a single QUIC native UDP connection.">
+                  UDP idle timeout
+                </a-tooltip>
+              </template>
+              <a-input-number v-model:value="inbound.stream.hysteria.udpIdleTimeout" :min="0" />
+            </a-form-item>
             <a-form-item label="Masquerade">
               <a-switch v-model:checked="inbound.stream.hysteria.masqueradeSwitch" />
             </a-form-item>

+ 34 - 3
frontend/src/pages/inbounds/InboundInfoModal.vue

@@ -585,19 +585,50 @@ const showSubscriptionTab = computed(
             </tbody>
           </table>
 
+          <!-- TUN -->
+          <dl v-if="inbound.protocol === Protocols.TUN" class="info-list info-list-block">
+            <div class="info-row">
+              <dt>Interface name</dt>
+              <dd><a-tag color="green" class="value-tag">{{ inbound.settings.name }}</a-tag></dd>
+            </div>
+            <div class="info-row">
+              <dt>MTU</dt>
+              <dd><a-tag color="green">{{ inbound.settings.mtu }}</a-tag></dd>
+            </div>
+            <div v-if="inbound.settings.gateway?.length" class="info-row">
+              <dt>Gateway</dt>
+              <dd><a-tag v-for="(ip, j) in inbound.settings.gateway" :key="`tun-i-gw-${j}`" color="green"
+                  class="value-tag">{{ ip }}</a-tag></dd>
+            </div>
+            <div v-if="inbound.settings.dns?.length" class="info-row">
+              <dt>DNS</dt>
+              <dd><a-tag v-for="(ip, j) in inbound.settings.dns" :key="`tun-i-dns-${j}`" color="green">{{ ip }}</a-tag>
+              </dd>
+            </div>
+            <div class="info-row">
+              <dt>Outbounds interface</dt>
+              <dd><a-tag color="green">{{ inbound.settings.autoOutboundsInterface || 'auto' }}</a-tag></dd>
+            </div>
+            <div v-if="inbound.settings.autoSystemRoutingTable?.length" class="info-row">
+              <dt>Auto system routes</dt>
+              <dd><a-tag v-for="(cidr, j) in inbound.settings.autoSystemRoutingTable" :key="`tun-i-rt-${j}`"
+                  color="green">{{ cidr }}</a-tag></dd>
+            </div>
+          </dl>
+
           <!-- Tunnel -->
           <dl v-if="inbound.protocol === Protocols.TUNNEL" class="info-list info-list-block">
             <div class="info-row">
               <dt>{{ t('pages.inbounds.targetAddress') }}</dt>
-              <dd><a-tag color="green" class="value-tag">{{ inbound.settings.address }}</a-tag></dd>
+              <dd><a-tag color="green" class="value-tag">{{ inbound.settings.rewriteAddress }}</a-tag></dd>
             </div>
             <div class="info-row">
               <dt>{{ t('pages.inbounds.destinationPort') }}</dt>
-              <dd><a-tag color="green">{{ inbound.settings.port }}</a-tag></dd>
+              <dd><a-tag color="green">{{ inbound.settings.rewritePort }}</a-tag></dd>
             </div>
             <div class="info-row">
               <dt>{{ t('pages.inbounds.network') }}</dt>
-              <dd><a-tag color="green">{{ inbound.settings.network }}</a-tag></dd>
+              <dd><a-tag color="green">{{ inbound.settings.allowedNetwork }}</a-tag></dd>
             </div>
             <div class="info-row">
               <dt>FollowRedirect</dt>

+ 1 - 1
web/service/config.json

@@ -12,7 +12,7 @@
     "port": 62789,
     "protocol": "tunnel",
     "settings": {
-      "address": "127.0.0.1"
+      "rewriteAddress": "127.0.0.1"
     },
     "tag": "api"
   }],