소스 검색

fix(fallbacks): allow free-form dest entries for external servers (#4748)

Since v3.1.0 every fallback row had to reference a panel inbound via childId, so rows with only a free-form dest (e.g. 8080 or 127.0.0.1:8080 to an external Nginx) were silently dropped at three layers: the frontend save filter, the backend SetByMaster guard, and BuildFallbacksJSON. A row is now valid when it has a child OR an explicit dest; self-references normalize to childId 0, and BuildFallbacksJSON prefers an explicit dest (also fixing rows whose child was deleted). UI gains allowClear on the child picker; help text updated across all locales. Verified end-to-end in Docker: a free-form dest fallback now persists and is injected into the live xray config. Refs #4554, #4639.
MHSanaei 1 일 전
부모
커밋
49bec1db0f

+ 2 - 1
frontend/src/pages/inbounds/form/FallbacksCard.tsx

@@ -44,12 +44,13 @@ export default function FallbacksCard({
               value={record.childId}
               options={fallbackChildOptions}
               placeholder={t('pages.inbounds.fallbacks.pickInbound') || 'Pick an inbound'}
+              allowClear
               showSearch={{
                 filterOption: (input, option) =>
                   ((option?.label as string) || '').toLowerCase().includes(input.toLowerCase()),
               }}
               style={{ width: '100%' }}
-              onChange={(v) => updateFallback(record.rowKey, { childId: v })}
+              onChange={(v) => updateFallback(record.rowKey, { childId: v ?? null })}
             />
             <Button
               disabled={idx === 0}

+ 2 - 2
frontend/src/pages/inbounds/form/useInboundFallbacks.ts

@@ -39,7 +39,7 @@ export function useInboundFallbacks(dbInbound: DBInbound | null, dbInbounds: DBI
       }[])
         .map((r) => ({
           rowKey: `fb-${++fallbackKeyRef.current}`,
-          childId: r.childId,
+          childId: r.childId && r.childId > 0 ? r.childId : null,
           name: r.name || '',
           alpn: r.alpn || '',
           path: r.path || '',
@@ -52,7 +52,7 @@ export function useInboundFallbacks(dbInbound: DBInbound | null, dbInbounds: DBI
   const saveFallbacks = async (masterId: number) => {
     if (!masterId) return true;
     const payload = {
-      fallbacks: fallbacks.filter((c) => c.childId).map((c, i) => ({
+      fallbacks: fallbacks.filter((c) => c.childId || (c.dest ?? '').trim()).map((c, i) => ({
         childId: c.childId,
         name: c.name,
         alpn: c.alpn,

+ 11 - 7
web/service/fallback.go

@@ -63,12 +63,16 @@ func (s *FallbackService) SetByMaster(masterId int, items []FallbackInput) error
 			return err
 		}
 		for i, c := range items {
-			if c.ChildId <= 0 || c.ChildId == masterId {
+			childId := c.ChildId
+			if childId == masterId {
+				childId = 0
+			}
+			if childId <= 0 && strings.TrimSpace(c.Dest) == "" {
 				continue
 			}
 			row := model.InboundFallback{
 				MasterId:  masterId,
-				ChildId:   c.ChildId,
+				ChildId:   childId,
 				Name:      c.Name,
 				Alpn:      c.Alpn,
 				Path:      c.Path,
@@ -117,12 +121,12 @@ func (s *FallbackService) BuildFallbacksJSON(tx *gorm.DB, masterId int) ([]map[s
 
 	out := make([]map[string]any, 0, len(rows))
 	for _, r := range rows {
-		child, ok := byId[r.ChildId]
-		if !ok {
-			continue
-		}
-		dest := r.Dest
+		dest := strings.TrimSpace(r.Dest)
 		if dest == "" {
+			child, ok := byId[r.ChildId]
+			if !ok {
+				continue
+			}
 			listen := strings.TrimSpace(child.Listen)
 			if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
 				listen = "127.0.0.1"

+ 1 - 1
web/translation/ar-EG.json

@@ -264,7 +264,7 @@
       "localPanel": "بانل محلي",
       "fallbacks": {
         "title": "Fallbacks",
-        "help": "عند وصول اتصال إلى هذا الـ inbound لا يطابق أي عميل، يتم توجيهه إلى inbound آخر. اختر فرعًا أدناه وسيتم ملء حقول التوجيه (SNI / ALPN / Path / xver) تلقائيًا من نقل الفرع — في الغالب لا تحتاج إلى أي تعديل إضافي. يجب أن يستمع كل فرع على 127.0.0.1 مع security=none.",
+        "help": "عند وصول اتصال إلى هذا الـ inbound لا يطابق أي عميل، يتم توجيهه إلى مكان آخر. اختر inbound فرعيًا أدناه لملء حقول التوجيه (SNI / ALPN / Path / xver) تلقائيًا من نقله، أو اترك القائمة فارغة واضبط Dest مباشرةً (مثل 8080 أو 127.0.0.1:8080) للتوجيه إلى خادم خارجي مثل Nginx. يجب أن يستمع كل inbound فرعي على 127.0.0.1 مع security=none.",
         "empty": "لا توجد fallbacks بعد",
         "add": "إضافة fallback",
         "pickInbound": "اختر inbound",

+ 1 - 1
web/translation/en-US.json

@@ -264,7 +264,7 @@
       "localPanel": "Local panel",
       "fallbacks": {
         "title": "Fallbacks",
-        "help": "When a connection on this inbound does not match any client, route it to another inbound. Pick a child below and the routing fields (SNI / ALPN / path / xver) auto-fill from its transport — most setups need no further tweaking. Each child should listen on 127.0.0.1 with security=none.",
+        "help": "When a connection on this inbound does not match any client, route it elsewhere. Pick a child inbound below to auto-fill the routing fields (SNI / ALPN / path / xver) from its transport, or leave the picker empty and set Dest directly (e.g. 8080 or 127.0.0.1:8080) to route to an external server such as Nginx. Each child inbound should listen on 127.0.0.1 with security=none.",
         "empty": "No fallbacks yet",
         "add": "Add fallback",
         "pickInbound": "Pick an inbound",

+ 1 - 1
web/translation/es-ES.json

@@ -264,7 +264,7 @@
       "localPanel": "Panel local",
       "fallbacks": {
         "title": "Fallbacks",
-        "help": "Cuando una conexión en este inbound no coincide con ningún cliente, redirígela a otro inbound. Elige un hijo abajo y los campos de enrutamiento (SNI / ALPN / Path / xver) se rellenan automáticamente desde su transporte; la mayoría de las configuraciones no necesitan más ajustes. Cada hijo debe escuchar en 127.0.0.1 con security=none.",
+        "help": "Cuando una conexión en este inbound no coincide con ningún cliente, redirígela a otro lugar. Elige un inbound hijo abajo para rellenar automáticamente los campos de enrutamiento (SNI / ALPN / Path / xver) desde su transporte, o deja el selector vacío y define Dest directamente (p. ej. 8080 o 127.0.0.1:8080) para redirigir a un servidor externo como Nginx. Cada inbound hijo debe escuchar en 127.0.0.1 con security=none.",
         "empty": "Aún no hay fallbacks",
         "add": "Añadir fallback",
         "pickInbound": "Selecciona un inbound",

+ 1 - 1
web/translation/fa-IR.json

@@ -264,7 +264,7 @@
       "localPanel": "پنل لوکال",
       "fallbacks": {
         "title": "Fallbackها",
-        "help": "وقتی اتصالی روی این اینباند با هیچ کلاینتی تطبیق پیدا نمی‌کند، به یک اینباند دیگر ارجاع داده می‌شود. یک فرزند انتخاب کنید، فیلدهای مسیریابی (SNI / ALPN / Path / xver) خودکار از روی transport آن پر می‌شود — برای بیشتر تنظیمات نیازی به ویرایش نیست. هر فرزند باید روی 127.0.0.1 با security=none گوش بدهد.",
+        "help": "وقتی اتصالی روی این اینباند با هیچ کلاینتی تطبیق پیدا نمی‌کند، به جای دیگری ارجاع داده می‌شود. یک اینباند فرزند از پایین انتخاب کنید تا فیلدهای مسیریابی (SNI / ALPN / Path / xver) خودکار از روی transport آن پر شود، یا انتخاب‌گر را خالی بگذارید و مقدار Dest را مستقیم تنظیم کنید (مثلاً 8080 یا 127.0.0.1:8080) تا به یک سرور بیرونی مانند Nginx ارجاع شود. هر اینباند فرزند باید روی 127.0.0.1 با security=none گوش بدهد.",
         "empty": "هنوز فال‌بکی اضافه نشده",
         "add": "افزودن فال‌بک",
         "pickInbound": "یک اینباند انتخاب کنید",

+ 1 - 1
web/translation/id-ID.json

@@ -264,7 +264,7 @@
       "localPanel": "Panel lokal",
       "fallbacks": {
         "title": "Fallback",
-        "help": "Saat koneksi pada inbound ini tidak cocok dengan client mana pun, arahkan ke inbound lain. Pilih child di bawah dan field routing (SNI / ALPN / Path / xver) terisi otomatis dari transport-nya — sebagian besar konfigurasi tidak perlu disesuaikan lagi. Setiap child harus listen di 127.0.0.1 dengan security=none.",
+        "help": "Saat koneksi pada inbound ini tidak cocok dengan client mana pun, arahkan ke tempat lain. Pilih inbound child di bawah untuk mengisi otomatis field routing (SNI / ALPN / Path / xver) dari transport-nya, atau biarkan pemilih kosong dan atur Dest langsung (mis. 8080 atau 127.0.0.1:8080) untuk mengarahkan ke server eksternal seperti Nginx. Setiap inbound child harus listen di 127.0.0.1 dengan security=none.",
         "empty": "Belum ada fallback",
         "add": "Tambah fallback",
         "pickInbound": "Pilih inbound",

+ 1 - 1
web/translation/ja-JP.json

@@ -264,7 +264,7 @@
       "localPanel": "ローカルパネル",
       "fallbacks": {
         "title": "Fallbacks",
-        "help": "このインバウンドへの接続がどのクライアントにも一致しない場合、別のインバウンドへルーティングします。下から子インバウンドを選ぶとルーティング項目(SNI / ALPN / Path / xver)はその子のトランスポートから自動的に埋められます — ほとんどの構成で追加の調整は不要です。各子インバウンドは 127.0.0.1 で security=none をリッスンする必要があります。",
+        "help": "このインバウンドへの接続がどのクライアントにも一致しない場合、別の宛先へルーティングします。下から子インバウンドを選ぶとルーティング項目(SNI / ALPN / Path / xver)がそのトランスポートから自動的に埋められます。あるいは選択を空のままにして Dest を直接指定すると(例: 8080 または 127.0.0.1:8080)、Nginx などの外部サーバーへルーティングできます。各子インバウンドは 127.0.0.1 で security=none をリッスンする必要があります。",
         "empty": "フォールバックはまだありません",
         "add": "フォールバックを追加",
         "pickInbound": "インバウンドを選択",

+ 1 - 1
web/translation/pt-BR.json

@@ -264,7 +264,7 @@
       "localPanel": "Painel local",
       "fallbacks": {
         "title": "Fallbacks",
-        "help": "Quando uma conexão neste inbound não corresponde a nenhum cliente, redirecione-a para outro inbound. Escolha um filho abaixo e os campos de roteamento (SNI / ALPN / Path / xver) são preenchidos automaticamente a partir do transporte dele — a maioria das configurações não precisa de mais ajustes. Cada filho deve escutar em 127.0.0.1 com security=none.",
+        "help": "Quando uma conexão neste inbound não corresponde a nenhum cliente, redirecione-a para outro lugar. Escolha um inbound filho abaixo para preencher automaticamente os campos de roteamento (SNI / ALPN / Path / xver) a partir do transporte dele, ou deixe o seletor vazio e defina Dest diretamente (ex.: 8080 ou 127.0.0.1:8080) para rotear para um servidor externo como o Nginx. Cada inbound filho deve escutar em 127.0.0.1 com security=none.",
         "empty": "Ainda sem fallbacks",
         "add": "Adicionar fallback",
         "pickInbound": "Escolha um inbound",

+ 1 - 1
web/translation/ru-RU.json

@@ -264,7 +264,7 @@
       "localPanel": "Локальная панель",
       "fallbacks": {
         "title": "Fallback'и",
-        "help": "Когда соединение на этом инбаунде не совпадает ни с одним клиентом, оно перенаправляется на другой инбаунд. Выберите дочерний инбаунд ниже — поля маршрутизации (SNI / ALPN / Path / xver) заполнятся автоматически из его транспорта, для большинства конфигураций больше ничего менять не нужно. Каждый дочерний должен слушать на 127.0.0.1 с security=none.",
+        "help": "Когда соединение на этом инбаунде не совпадает ни с одним клиентом, оно перенаправляется в другое место. Выберите дочерний инбаунд ниже, чтобы поля маршрутизации (SNI / ALPN / Path / xver) заполнились автоматически из его транспорта, либо оставьте выбор пустым и задайте Dest напрямую (например, 8080 или 127.0.0.1:8080), чтобы перенаправить на внешний сервер, такой как Nginx. Каждый дочерний инбаунд должен слушать на 127.0.0.1 с security=none.",
         "empty": "Фолбэков пока нет",
         "add": "Добавить фолбэк",
         "pickInbound": "Выберите инбаунд",

+ 1 - 1
web/translation/tr-TR.json

@@ -264,7 +264,7 @@
       "localPanel": "Yerel panel",
       "fallbacks": {
         "title": "Fallback'ler",
-        "help": "Bu inbound üzerindeki bir bağlantı hiçbir client ile eşleşmediğinde, başka bir inbound'a yönlendirilir. Aşağıdan bir child seçin; yönlendirme alanları (SNI / ALPN / Path / xver) onun transport'undan otomatik dolar — çoğu kurulum için ek ayar gerekmez. Her child 127.0.0.1 üzerinde security=none ile dinlemelidir.",
+        "help": "Bu inbound üzerindeki bir bağlantı hiçbir client ile eşleşmediğinde, başka bir yere yönlendirilir. Aşağıdan bir child inbound seçerek yönlendirme alanlarını (SNI / ALPN / Path / xver) transport'undan otomatik doldurun ya da seçimi boş bırakıp Dest değerini doğrudan girin (örn. 8080 veya 127.0.0.1:8080); böylece Nginx gibi harici bir sunucuya yönlendirebilirsiniz. Her child inbound 127.0.0.1 üzerinde security=none ile dinlemelidir.",
         "empty": "Henüz fallback yok",
         "add": "Fallback ekle",
         "pickInbound": "Bir inbound seç",

+ 1 - 1
web/translation/uk-UA.json

@@ -264,7 +264,7 @@
       "localPanel": "Локальна панель",
       "fallbacks": {
         "title": "Fallback'и",
-        "help": "Коли з'єднання на цьому інбаунді не збігається з жодним клієнтом, воно перенаправляється на інший інбаунд. Оберіть дочірній інбаунд нижче — поля маршрутизації (SNI / ALPN / Path / xver) заповняться автоматично з його транспорту; для більшості налаштувань більше нічого змінювати не треба. Кожен дочірній має слухати на 127.0.0.1 з security=none.",
+        "help": "Коли з'єднання на цьому інбаунді не збігається з жодним клієнтом, воно перенаправляється в інше місце. Оберіть дочірній інбаунд нижче, щоб поля маршрутизації (SNI / ALPN / Path / xver) заповнилися автоматично з його транспорту, або залиште вибір порожнім і задайте Dest напряму (наприклад, 8080 або 127.0.0.1:8080), щоб перенаправити на зовнішній сервер, такий як Nginx. Кожен дочірній інбаунд має слухати на 127.0.0.1 з security=none.",
         "empty": "Фолбеків поки немає",
         "add": "Додати фолбек",
         "pickInbound": "Оберіть інбаунд",

+ 1 - 1
web/translation/vi-VN.json

@@ -264,7 +264,7 @@
       "localPanel": "Panel cục bộ",
       "fallbacks": {
         "title": "Fallbacks",
-        "help": "Khi một kết nối trên inbound này không khớp với client nào, nó sẽ được chuyển hướng tới inbound khác. Chọn một child bên dưới và các trường định tuyến (SNI / ALPN / Path / xver) sẽ được tự động điền từ transport của child — hầu hết cấu hình không cần chỉnh thêm. Mỗi child nên lắng nghe trên 127.0.0.1 với security=none.",
+        "help": "Khi một kết nối trên inbound này không khớp với client nào, hãy chuyển hướng nó tới nơi khác. Chọn một inbound con bên dưới để tự động điền các trường định tuyến (SNI / ALPN / Path / xver) từ transport của nó, hoặc để trống ô chọn và đặt Dest trực tiếp (ví dụ 8080 hoặc 127.0.0.1:8080) để chuyển hướng tới một máy chủ bên ngoài như Nginx. Mỗi inbound con nên lắng nghe trên 127.0.0.1 với security=none.",
         "empty": "Chưa có fallback nào",
         "add": "Thêm fallback",
         "pickInbound": "Chọn một inbound",

+ 1 - 1
web/translation/zh-CN.json

@@ -264,7 +264,7 @@
       "localPanel": "本地面板",
       "fallbacks": {
         "title": "Fallbacks",
-        "help": "当此入站的连接未匹配任何客户端时,将其路由到另一个入站。在下方选择一个子入站,路由字段(SNI / ALPN / Path / xver)会从子入站的传输方式中自动填充——大多数场景无需再调整。每个子入站应监听 127.0.0.1,security=none。",
+        "help": "当此入站的连接未匹配任何客户端时,将其路由到其他位置。在下方选择一个子入站,可从其传输方式自动填充路由字段(SNI / ALPN / Path / xver);或将选择框留空并直接设置 Dest(例如 8080 或 127.0.0.1:8080),以路由到 Nginx 等外部服务器。每个子入站应监听 127.0.0.1,security=none。",
         "empty": "暂无回落",
         "add": "添加回落",
         "pickInbound": "选择一个入站",

+ 1 - 1
web/translation/zh-TW.json

@@ -264,7 +264,7 @@
       "localPanel": "本機面板",
       "fallbacks": {
         "title": "Fallbacks",
-        "help": "當此入站的連線未匹配任何用戶時,將其路由到另一個入站。在下方選擇一個子入站,路由欄位(SNI / ALPN / Path / xver)會自動從子入站的傳輸方式填入——大多數情境不需要再調整。每個子入站應監聽 127.0.0.1,security=none。",
+        "help": "當此入站的連線未匹配任何用戶時,將其路由到其他位置。在下方選擇一個子入站,可從其傳輸方式自動填入路由欄位(SNI / ALPN / Path / xver);或將選擇框留空並直接設定 Dest(例如 8080 或 127.0.0.1:8080),以路由到 Nginx 等外部伺服器。每個子入站應監聽 127.0.0.1,security=none。",
         "empty": "尚未新增回落",
         "add": "新增回落",
         "pickInbound": "選擇一個入站",