feat(fallbacks): add per-rule dest override

Operators can now type an explicit dest (e.g. "8443", "127.0.0.1:8443",
"/dev/shm/x.sock") on each fallback row to override the auto-resolved
child listen+port. Empty keeps the existing auto behavior.

Adds the column to inbound_fallbacks (GORM AutoMigrate), threads it
through the panel form, API docs, and translations.
This commit is contained in:
MHSanaei 2026-05-28 21:17:49 +02:00
parent 1fd2c1333c
commit 798e18b6ee
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
22 changed files with 88 additions and 58 deletions

View file

@ -41,17 +41,17 @@ type User struct {
// Inbound represents an Xray inbound configuration with traffic statistics and settings. // Inbound represents an Xray inbound configuration with traffic statistics and settings.
type Inbound struct { type Inbound struct {
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` // Unique identifier Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` // Unique identifier
UserId int `json:"-"` // Associated user ID UserId int `json:"-"` // Associated user ID
Up int64 `json:"up" form:"up"` // Upload traffic in bytes Up int64 `json:"up" form:"up"` // Upload traffic in bytes
Down int64 `json:"down" form:"down"` // Download traffic in bytes Down int64 `json:"down" form:"down"` // Download traffic in bytes
Total int64 `json:"total" form:"total"` // Total traffic limit in bytes Total int64 `json:"total" form:"total"` // Total traffic limit in bytes
Remark string `json:"remark" form:"remark"` // Human-readable remark Remark string `json:"remark" form:"remark"` // Human-readable remark
Enable bool `json:"enable" form:"enable" gorm:"index:idx_enable_traffic_reset,priority:1"` // Whether the inbound is enabled Enable bool `json:"enable" form:"enable" gorm:"index:idx_enable_traffic_reset,priority:1"` // Whether the inbound is enabled
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp
TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never;index:idx_enable_traffic_reset,priority:2" validate:"omitempty,oneof=never hourly daily weekly monthly"` // Traffic reset schedule TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never;index:idx_enable_traffic_reset,priority:2" validate:"omitempty,oneof=never hourly daily weekly monthly"` // Traffic reset schedule
LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"` // Last traffic reset timestamp LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"` // Last traffic reset timestamp
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"` // Client traffic statistics ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"` // Client traffic statistics
// Xray configuration fields // Xray configuration fields
Listen string `json:"listen" form:"listen"` Listen string `json:"listen" form:"listen"`
@ -513,11 +513,6 @@ type ClientInbound struct {
func (ClientInbound) TableName() string { return "client_inbounds" } func (ClientInbound) TableName() string { return "client_inbounds" }
// InboundFallback is one routing rule on a master inbound's
// settings.fallbacks array. The master is always a VLESS or Trojan
// inbound on TCP transport with TLS or Reality. The child is any other
// inbound — its listen+port becomes the fallback dest, with optional
// SNI/ALPN/path match criteria pulled from the same row.
type InboundFallback struct { type InboundFallback struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"` Id int `json:"id" gorm:"primaryKey;autoIncrement"`
MasterId int `json:"masterId" gorm:"index;not null;column:master_id"` MasterId int `json:"masterId" gorm:"index;not null;column:master_id"`
@ -525,6 +520,7 @@ type InboundFallback struct {
Name string `json:"name"` Name string `json:"name"`
Alpn string `json:"alpn"` Alpn string `json:"alpn"`
Path string `json:"path"` Path string `json:"path"`
Dest string `json:"dest"`
Xver int `json:"xver"` Xver int `json:"xver"`
SortOrder int `json:"sortOrder" gorm:"default:0;column:sort_order"` SortOrder int `json:"sortOrder" gorm:"default:0;column:sort_order"`
} }

View file

@ -188,4 +188,3 @@ func TestInboundClientIpsUnmarshalJSONAcceptsBothShapes(t *testing.T) {
}) })
} }
} }

View file

@ -863,7 +863,7 @@
"tags": [ "tags": [
"Inbounds" "Inbounds"
], ],
"summary": "List the fallback rules attached to a master VLESS/Trojan TCP-TLS inbound. Each rule links one child inbound (the dest) to optional SNI/ALPN/path/xver match criteria.", "summary": "List the fallback rules attached to a master VLESS/Trojan TCP-TLS inbound. Each rule links one child inbound (the dest) to optional SNI/ALPN/path/dest/xver match criteria. When dest is empty the child inbound's listen+port is used.",
"operationId": "get_panel_api_inbounds_id_fallbacks", "operationId": "get_panel_api_inbounds_id_fallbacks",
"parameters": [ "parameters": [
{ {
@ -903,6 +903,7 @@
"name": "", "name": "",
"alpn": "", "alpn": "",
"path": "/vlws", "path": "/vlws",
"dest": "",
"xver": 2, "xver": 2,
"sortOrder": 0 "sortOrder": 0
} }
@ -946,7 +947,8 @@
}, },
{ {
"childId": 12, "childId": 12,
"alpn": "h2" "alpn": "h2",
"dest": "8443"
} }
] ]
} }

View file

@ -199,12 +199,12 @@ export const sections: readonly Section[] = [
{ {
method: 'GET', method: 'GET',
path: '/panel/api/inbounds/:id/fallbacks', path: '/panel/api/inbounds/:id/fallbacks',
summary: 'List the fallback rules attached to a master VLESS/Trojan TCP-TLS inbound. Each rule links one child inbound (the dest) to optional SNI/ALPN/path/xver match criteria.', summary: 'List the fallback rules attached to a master VLESS/Trojan TCP-TLS inbound. Each rule links one child inbound (the dest) to optional SNI/ALPN/path/dest/xver match criteria. When dest is empty the child inbound\'s listen+port is used.',
params: [ params: [
{ name: 'id', in: 'path', type: 'number', desc: 'Master inbound ID.' }, { name: 'id', in: 'path', type: 'number', desc: 'Master inbound ID.' },
], ],
response: response:
'{\n "success": true,\n "obj": [\n {\n "id": 1,\n "masterId": 10,\n "childId": 11,\n "name": "",\n "alpn": "",\n "path": "/vlws",\n "xver": 2,\n "sortOrder": 0\n }\n ]\n}', '{\n "success": true,\n "obj": [\n {\n "id": 1,\n "masterId": 10,\n "childId": 11,\n "name": "",\n "alpn": "",\n "path": "/vlws",\n "dest": "",\n "xver": 2,\n "sortOrder": 0\n }\n ]\n}',
}, },
{ {
method: 'POST', method: 'POST',
@ -212,9 +212,9 @@ export const sections: readonly Section[] = [
summary: 'Replace the entire fallback list for a master inbound. Body is JSON. Triggers an Xray restart.', summary: 'Replace the entire fallback list for a master inbound. Body is JSON. Triggers an Xray restart.',
params: [ params: [
{ name: 'id', in: 'path', type: 'number', desc: 'Master inbound ID.' }, { name: 'id', in: 'path', type: 'number', desc: 'Master inbound ID.' },
{ name: 'fallbacks', in: 'body (json)', type: 'object[]', desc: 'Array of {childId, name, alpn, path, xver, sortOrder} entries.' }, { name: 'fallbacks', in: 'body (json)', type: 'object[]', desc: 'Array of {childId, name, alpn, path, dest, xver, sortOrder} entries. Leave dest empty to auto-resolve from the child inbound\'s listen+port; set it (e.g. "8443", "127.0.0.1:8443", "/dev/shm/x.sock") to override.' },
], ],
body: '{\n "fallbacks": [\n { "childId": 11, "path": "/vlws", "xver": 2 },\n { "childId": 12, "alpn": "h2" }\n ]\n}', body: '{\n "fallbacks": [\n { "childId": 11, "path": "/vlws", "xver": 2 },\n { "childId": 12, "alpn": "h2", "dest": "8443" }\n ]\n}',
response: '{\n "success": true,\n "msg": "Inbound updated"\n}', response: '{\n "success": true,\n "msg": "Inbound updated"\n}',
}, },
], ],

View file

@ -365,13 +365,21 @@ export default function InboundFormModal({
return; return;
} }
setFallbacks( setFallbacks(
(msg.obj as { childId: number; name?: string; alpn?: string; path?: string; xver?: number }[]) (msg.obj as {
childId: number;
name?: string;
alpn?: string;
path?: string;
dest?: string;
xver?: number;
}[])
.map((r) => ({ .map((r) => ({
rowKey: `fb-${++fallbackKeyRef.current}`, rowKey: `fb-${++fallbackKeyRef.current}`,
childId: r.childId, childId: r.childId,
name: r.name || '', name: r.name || '',
alpn: r.alpn || '', alpn: r.alpn || '',
path: r.path || '', path: r.path || '',
dest: r.dest || '',
xver: r.xver || 0, xver: r.xver || 0,
})), })),
); );
@ -385,6 +393,7 @@ export default function InboundFormModal({
name: c.name, name: c.name,
alpn: c.alpn, alpn: c.alpn,
path: c.path, path: c.path,
dest: c.dest,
xver: Number(c.xver) || 0, xver: Number(c.xver) || 0,
sortOrder: i, sortOrder: i,
})), })),
@ -437,6 +446,7 @@ export default function InboundFormModal({
name: '', name: '',
alpn: '', alpn: '',
path: '', path: '',
dest: '',
xver: 0, xver: 0,
}]); }]);
}; };
@ -445,11 +455,11 @@ export default function InboundFormModal({
setFallbacks((prev) => prev.map((r) => { setFallbacks((prev) => prev.map((r) => {
if (r.rowKey !== rowKey) return r; if (r.rowKey !== rowKey) return r;
// When the picker selects a new child inbound and the row hasn't // When the picker selects a new child inbound and the row hasn't
// been hand-edited yet (sni/alpn/path all blank, xver = 0), pull // been hand-edited yet (sni/alpn/path/dest all blank, xver = 0),
// the SNI/ALPN/Path defaults off that child. Operators who // pull the SNI/ALPN/Path defaults off that child. Operators who
// intentionally typed values keep them — we only fill the empties. // intentionally typed values keep them — we only fill the empties.
if (typeof patch.childId === 'number' && patch.childId !== r.childId) { if (typeof patch.childId === 'number' && patch.childId !== r.childId) {
const isPristine = !r.name && !r.alpn && !r.path && r.xver === 0; const isPristine = !r.name && !r.alpn && !r.path && !r.dest && r.xver === 0;
if (isPristine) return { ...r, ...patch, ...deriveFallbackDefaults(patch.childId) }; if (isPristine) return { ...r, ...patch, ...deriveFallbackDefaults(patch.childId) };
} }
return { ...r, ...patch }; return { ...r, ...patch };
@ -490,6 +500,7 @@ export default function InboundFormModal({
name: derived.name ?? '', name: derived.name ?? '',
alpn: derived.alpn ?? '', alpn: derived.alpn ?? '',
path: derived.path ?? '', path: derived.path ?? '',
dest: '',
xver: derived.xver ?? 0, xver: derived.xver ?? 0,
}; };
}); });
@ -1079,6 +1090,12 @@ export default function InboundFormModal({
value={record.path} value={record.path}
onChange={(e) => updateFallback(record.rowKey, { path: e.target.value })} onChange={(e) => updateFallback(record.rowKey, { path: e.target.value })}
/> />
<InputAddon>Dest</InputAddon>
<Input
placeholder={t('pages.inbounds.fallbacks.destPlaceholder') || 'auto'}
value={record.dest}
onChange={(e) => updateFallback(record.rowKey, { dest: e.target.value })}
/>
<InputAddon>xver</InputAddon> <InputAddon>xver</InputAddon>
<InputNumber <InputNumber
min={0} min={0}

View file

@ -78,6 +78,7 @@ export const FallbackRowSchema = z.object({
name: z.string().default(''), name: z.string().default(''),
alpn: z.string().default(''), alpn: z.string().default(''),
path: z.string().default(''), path: z.string().default(''),
dest: z.string().default(''),
xver: z.number().int().min(0).max(2).default(0), xver: z.number().int().min(0).max(2).default(0),
}); });
export type FallbackRow = z.infer<typeof FallbackRowSchema>; export type FallbackRow = z.infer<typeof FallbackRowSchema>;

View file

@ -21,34 +21,34 @@ type Msg struct {
// AllSetting contains all configuration settings for the 3x-ui panel including web server, Telegram bot, and subscription settings. // AllSetting contains all configuration settings for the 3x-ui panel including web server, Telegram bot, and subscription settings.
type AllSetting struct { type AllSetting struct {
// Web server settings // Web server settings
WebListen string `json:"webListen" form:"webListen"` // Web server listen IP address WebListen string `json:"webListen" form:"webListen"` // Web server listen IP address
WebDomain string `json:"webDomain" form:"webDomain"` // Web server domain for domain validation WebDomain string `json:"webDomain" form:"webDomain"` // Web server domain for domain validation
WebPort int `json:"webPort" form:"webPort" validate:"gte=1,lte=65535"` // Web server port number WebPort int `json:"webPort" form:"webPort" validate:"gte=1,lte=65535"` // Web server port number
WebCertFile string `json:"webCertFile" form:"webCertFile"` // Path to SSL certificate file for web server WebCertFile string `json:"webCertFile" form:"webCertFile"` // Path to SSL certificate file for web server
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"` // Path to SSL private key file for web server WebKeyFile string `json:"webKeyFile" form:"webKeyFile"` // Path to SSL private key file for web server
WebBasePath string `json:"webBasePath" form:"webBasePath"` // Base path for web panel URLs WebBasePath string `json:"webBasePath" form:"webBasePath"` // Base path for web panel URLs
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge" validate:"gte=0,lte=525600"` // Session maximum age in minutes (cap at one year) SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge" validate:"gte=0,lte=525600"` // Session maximum age in minutes (cap at one year)
TrustedProxyCIDRs string `json:"trustedProxyCIDRs" form:"trustedProxyCIDRs"` // Trusted reverse proxy IPs/CIDRs for forwarded headers TrustedProxyCIDRs string `json:"trustedProxyCIDRs" form:"trustedProxyCIDRs"` // Trusted reverse proxy IPs/CIDRs for forwarded headers
PanelProxy string `json:"panelProxy" form:"panelProxy"` // Proxy URL for the panel's own outbound requests (GitHub/Telegram) PanelProxy string `json:"panelProxy" form:"panelProxy"` // Proxy URL for the panel's own outbound requests (GitHub/Telegram)
// UI settings // UI settings
PageSize int `json:"pageSize" form:"pageSize" validate:"gte=1,lte=1000"` // Number of items per page in lists PageSize int `json:"pageSize" form:"pageSize" validate:"gte=1,lte=1000"` // Number of items per page in lists
ExpireDiff int `json:"expireDiff" form:"expireDiff" validate:"gte=0"` // Expiration warning threshold in days ExpireDiff int `json:"expireDiff" form:"expireDiff" validate:"gte=0"` // Expiration warning threshold in days
TrafficDiff int `json:"trafficDiff" form:"trafficDiff" validate:"gte=0,lte=100"`// Traffic warning threshold percentage TrafficDiff int `json:"trafficDiff" form:"trafficDiff" validate:"gte=0,lte=100"` // Traffic warning threshold percentage
RemarkModel string `json:"remarkModel" form:"remarkModel"` // Remark model pattern for inbounds RemarkModel string `json:"remarkModel" form:"remarkModel"` // Remark model pattern for inbounds
Datepicker string `json:"datepicker" form:"datepicker"` // Date picker format Datepicker string `json:"datepicker" form:"datepicker"` // Date picker format
// Telegram bot settings // Telegram bot settings
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"` // Enable Telegram bot notifications TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"` // Enable Telegram bot notifications
TgBotToken string `json:"tgBotToken" form:"tgBotToken"` // Telegram bot token TgBotToken string `json:"tgBotToken" form:"tgBotToken"` // Telegram bot token
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"` // Proxy URL for Telegram bot TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"` // Proxy URL for Telegram bot
TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"` // Custom API server for Telegram bot TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"` // Custom API server for Telegram bot
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"` // Telegram chat ID for notifications TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"` // Telegram chat ID for notifications
TgRunTime string `json:"tgRunTime" form:"tgRunTime"` // Cron schedule for Telegram notifications TgRunTime string `json:"tgRunTime" form:"tgRunTime"` // Cron schedule for Telegram notifications
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` // Enable database backup via Telegram TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` // Enable database backup via Telegram
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"` // Send login notifications TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"` // Send login notifications
TgCpu int `json:"tgCpu" form:"tgCpu" validate:"gte=0,lte=100"` // CPU usage threshold for alerts (percent) TgCpu int `json:"tgCpu" form:"tgCpu" validate:"gte=0,lte=100"` // CPU usage threshold for alerts (percent)
TgLang string `json:"tgLang" form:"tgLang"` // Telegram bot language TgLang string `json:"tgLang" form:"tgLang"` // Telegram bot language
// Security settings // Security settings
TimeLocation string `json:"timeLocation" form:"timeLocation"` // Time zone location TimeLocation string `json:"timeLocation" form:"timeLocation"` // Time zone location

View file

@ -18,6 +18,7 @@ type FallbackInput struct {
Name string `json:"name"` Name string `json:"name"`
Alpn string `json:"alpn"` Alpn string `json:"alpn"`
Path string `json:"path"` Path string `json:"path"`
Dest string `json:"dest"`
Xver int `json:"xver"` Xver int `json:"xver"`
SortOrder int `json:"sortOrder"` SortOrder int `json:"sortOrder"`
} }
@ -71,6 +72,7 @@ func (s *FallbackService) SetByMaster(masterId int, items []FallbackInput) error
Name: c.Name, Name: c.Name,
Alpn: c.Alpn, Alpn: c.Alpn,
Path: c.Path, Path: c.Path,
Dest: c.Dest,
Xver: c.Xver, Xver: c.Xver,
SortOrder: c.SortOrder, SortOrder: c.SortOrder,
} }
@ -85,9 +87,6 @@ func (s *FallbackService) SetByMaster(masterId int, items []FallbackInput) error
}) })
} }
// BuildFallbacksJSON resolves the master's fallback rows into Xray's
// expected settings.fallbacks shape, looking up each child's listen+port
// to fill the dest field. Returns nil when the master has no rules.
func (s *FallbackService) BuildFallbacksJSON(tx *gorm.DB, masterId int) ([]map[string]any, error) { func (s *FallbackService) BuildFallbacksJSON(tx *gorm.DB, masterId int) ([]map[string]any, error) {
if tx == nil { if tx == nil {
tx = database.GetDB() tx = database.GetDB()
@ -122,12 +121,16 @@ func (s *FallbackService) BuildFallbacksJSON(tx *gorm.DB, masterId int) ([]map[s
if !ok { if !ok {
continue continue
} }
listen := strings.TrimSpace(child.Listen) dest := r.Dest
if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" { if dest == "" {
listen = "127.0.0.1" listen := strings.TrimSpace(child.Listen)
if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
listen = "127.0.0.1"
}
dest = fmt.Sprintf("%s:%d", listen, child.Port)
} }
entry := map[string]any{ entry := map[string]any{
"dest": fmt.Sprintf("%s:%d", listen, child.Port), "dest": dest,
} }
if r.Name != "" { if r.Name != "" {
entry["name"] = r.Name entry["name"] = r.Name

View file

@ -304,7 +304,6 @@ func resolveXrayLogPaths(logCfg json_util.RawMessage) json_util.RawMessage {
return out return out
} }
// GetXrayTraffic fetches the current traffic statistics from the running Xray process. // GetXrayTraffic fetches the current traffic statistics from the running Xray process.
func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) { func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) {
if !s.IsXrayRunning() { if !s.IsXrayRunning() {

View file

@ -266,6 +266,7 @@
"add": "إضافة fallback", "add": "إضافة fallback",
"pickInbound": "اختر inbound", "pickInbound": "اختر inbound",
"matchAny": "أي", "matchAny": "أي",
"destPlaceholder": "تلقائي (listen:port للفرع)",
"rederive": "إعادة الملء من الفرع", "rederive": "إعادة الملء من الفرع",
"rederived": "تم إعادة الملء من الفرع", "rederived": "تم إعادة الملء من الفرع",
"editAdvanced": "تحرير حقول التوجيه", "editAdvanced": "تحرير حقول التوجيه",

View file

@ -266,6 +266,7 @@
"add": "Add fallback", "add": "Add fallback",
"pickInbound": "Pick an inbound", "pickInbound": "Pick an inbound",
"matchAny": "any", "matchAny": "any",
"destPlaceholder": "auto (child listen:port)",
"rederive": "Re-fill from child", "rederive": "Re-fill from child",
"rederived": "Re-filled from child", "rederived": "Re-filled from child",
"editAdvanced": "Edit routing fields", "editAdvanced": "Edit routing fields",

View file

@ -266,6 +266,7 @@
"add": "Añadir fallback", "add": "Añadir fallback",
"pickInbound": "Selecciona un inbound", "pickInbound": "Selecciona un inbound",
"matchAny": "cualquiera", "matchAny": "cualquiera",
"destPlaceholder": "automático (listen:puerto del hijo)",
"rederive": "Rellenar desde el hijo", "rederive": "Rellenar desde el hijo",
"rederived": "Rellenado desde el hijo", "rederived": "Rellenado desde el hijo",
"editAdvanced": "Editar campos de enrutamiento", "editAdvanced": "Editar campos de enrutamiento",

View file

@ -266,6 +266,7 @@
"add": "افزودن فال‌بک", "add": "افزودن فال‌بک",
"pickInbound": "یک اینباند انتخاب کنید", "pickInbound": "یک اینباند انتخاب کنید",
"matchAny": "همه", "matchAny": "همه",
"destPlaceholder": "خودکار (listen:port فرزند)",
"rederive": "پر کردن مجدد از فرزند", "rederive": "پر کردن مجدد از فرزند",
"rederived": "از فرزند پر شد", "rederived": "از فرزند پر شد",
"editAdvanced": "ویرایش فیلدهای مسیریابی", "editAdvanced": "ویرایش فیلدهای مسیریابی",

View file

@ -266,6 +266,7 @@
"add": "Tambah fallback", "add": "Tambah fallback",
"pickInbound": "Pilih inbound", "pickInbound": "Pilih inbound",
"matchAny": "apa pun", "matchAny": "apa pun",
"destPlaceholder": "otomatis (listen:port child)",
"rederive": "Isi ulang dari child", "rederive": "Isi ulang dari child",
"rederived": "Diisi ulang dari child", "rederived": "Diisi ulang dari child",
"editAdvanced": "Edit field routing", "editAdvanced": "Edit field routing",

View file

@ -266,6 +266,7 @@
"add": "フォールバックを追加", "add": "フォールバックを追加",
"pickInbound": "インバウンドを選択", "pickInbound": "インバウンドを選択",
"matchAny": "任意", "matchAny": "任意",
"destPlaceholder": "自動(子の listen:port",
"rederive": "子から再取得", "rederive": "子から再取得",
"rederived": "子から再取得しました", "rederived": "子から再取得しました",
"editAdvanced": "ルーティング項目を編集", "editAdvanced": "ルーティング項目を編集",

View file

@ -266,6 +266,7 @@
"add": "Adicionar fallback", "add": "Adicionar fallback",
"pickInbound": "Escolha um inbound", "pickInbound": "Escolha um inbound",
"matchAny": "qualquer", "matchAny": "qualquer",
"destPlaceholder": "automático (listen:porta do filho)",
"rederive": "Preencher a partir do filho", "rederive": "Preencher a partir do filho",
"rederived": "Preenchido a partir do filho", "rederived": "Preenchido a partir do filho",
"editAdvanced": "Editar campos de roteamento", "editAdvanced": "Editar campos de roteamento",

View file

@ -266,6 +266,7 @@
"add": "Добавить фолбэк", "add": "Добавить фолбэк",
"pickInbound": "Выберите инбаунд", "pickInbound": "Выберите инбаунд",
"matchAny": "любой", "matchAny": "любой",
"destPlaceholder": "авто (listen:порт дочернего)",
"rederive": "Заполнить из дочернего", "rederive": "Заполнить из дочернего",
"rederived": "Заполнено из дочернего", "rederived": "Заполнено из дочернего",
"editAdvanced": "Изменить поля маршрутизации", "editAdvanced": "Изменить поля маршрутизации",

View file

@ -266,6 +266,7 @@
"add": "Fallback ekle", "add": "Fallback ekle",
"pickInbound": "Bir inbound seç", "pickInbound": "Bir inbound seç",
"matchAny": "herhangi", "matchAny": "herhangi",
"destPlaceholder": "otomatik (child listen:port)",
"rederive": "Child'dan yeniden doldur", "rederive": "Child'dan yeniden doldur",
"rederived": "Child'dan yeniden dolduruldu", "rederived": "Child'dan yeniden dolduruldu",
"editAdvanced": "Yönlendirme alanlarını düzenle", "editAdvanced": "Yönlendirme alanlarını düzenle",

View file

@ -266,6 +266,7 @@
"add": "Додати фолбек", "add": "Додати фолбек",
"pickInbound": "Оберіть інбаунд", "pickInbound": "Оберіть інбаунд",
"matchAny": "будь-який", "matchAny": "будь-який",
"destPlaceholder": "авто (listen:порт дочірнього)",
"rederive": "Заповнити з дочірнього", "rederive": "Заповнити з дочірнього",
"rederived": "Заповнено з дочірнього", "rederived": "Заповнено з дочірнього",
"editAdvanced": "Редагувати поля маршрутизації", "editAdvanced": "Редагувати поля маршрутизації",

View file

@ -266,6 +266,7 @@
"add": "Thêm fallback", "add": "Thêm fallback",
"pickInbound": "Chọn một inbound", "pickInbound": "Chọn một inbound",
"matchAny": "bất kỳ", "matchAny": "bất kỳ",
"destPlaceholder": "tự động (listen:port của child)",
"rederive": "Điền lại từ child", "rederive": "Điền lại từ child",
"rederived": "Đã điền lại từ child", "rederived": "Đã điền lại từ child",
"editAdvanced": "Sửa trường định tuyến", "editAdvanced": "Sửa trường định tuyến",

View file

@ -266,6 +266,7 @@
"add": "添加回落", "add": "添加回落",
"pickInbound": "选择一个入站", "pickInbound": "选择一个入站",
"matchAny": "任意", "matchAny": "任意",
"destPlaceholder": "自动(子入站 listen:port",
"rederive": "从子入站重新填充", "rederive": "从子入站重新填充",
"rederived": "已从子入站重新填充", "rederived": "已从子入站重新填充",
"editAdvanced": "编辑路由字段", "editAdvanced": "编辑路由字段",

View file

@ -266,6 +266,7 @@
"add": "新增回落", "add": "新增回落",
"pickInbound": "選擇一個入站", "pickInbound": "選擇一個入站",
"matchAny": "任何", "matchAny": "任何",
"destPlaceholder": "自動(子入站 listen:port",
"rederive": "從子入站重新填入", "rederive": "從子入站重新填入",
"rederived": "已從子入站重新填入", "rederived": "已從子入站重新填入",
"editAdvanced": "編輯路由欄位", "editAdvanced": "編輯路由欄位",