mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-31 10:14:15 +00:00
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:
parent
1fd2c1333c
commit
798e18b6ee
22 changed files with 88 additions and 58 deletions
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -188,4 +188,3 @@ func TestInboundClientIpsUnmarshalJSONAcceptsBothShapes(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
dest := r.Dest
|
||||||
|
if dest == "" {
|
||||||
listen := strings.TrimSpace(child.Listen)
|
listen := strings.TrimSpace(child.Listen)
|
||||||
if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
|
if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
|
||||||
listen = "127.0.0.1"
|
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
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,7 @@
|
||||||
"add": "إضافة fallback",
|
"add": "إضافة fallback",
|
||||||
"pickInbound": "اختر inbound",
|
"pickInbound": "اختر inbound",
|
||||||
"matchAny": "أي",
|
"matchAny": "أي",
|
||||||
|
"destPlaceholder": "تلقائي (listen:port للفرع)",
|
||||||
"rederive": "إعادة الملء من الفرع",
|
"rederive": "إعادة الملء من الفرع",
|
||||||
"rederived": "تم إعادة الملء من الفرع",
|
"rederived": "تم إعادة الملء من الفرع",
|
||||||
"editAdvanced": "تحرير حقول التوجيه",
|
"editAdvanced": "تحرير حقول التوجيه",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,7 @@
|
||||||
"add": "افزودن فالبک",
|
"add": "افزودن فالبک",
|
||||||
"pickInbound": "یک اینباند انتخاب کنید",
|
"pickInbound": "یک اینباند انتخاب کنید",
|
||||||
"matchAny": "همه",
|
"matchAny": "همه",
|
||||||
|
"destPlaceholder": "خودکار (listen:port فرزند)",
|
||||||
"rederive": "پر کردن مجدد از فرزند",
|
"rederive": "پر کردن مجدد از فرزند",
|
||||||
"rederived": "از فرزند پر شد",
|
"rederived": "از فرزند پر شد",
|
||||||
"editAdvanced": "ویرایش فیلدهای مسیریابی",
|
"editAdvanced": "ویرایش فیلدهای مسیریابی",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,7 @@
|
||||||
"add": "フォールバックを追加",
|
"add": "フォールバックを追加",
|
||||||
"pickInbound": "インバウンドを選択",
|
"pickInbound": "インバウンドを選択",
|
||||||
"matchAny": "任意",
|
"matchAny": "任意",
|
||||||
|
"destPlaceholder": "自動(子の listen:port)",
|
||||||
"rederive": "子から再取得",
|
"rederive": "子から再取得",
|
||||||
"rederived": "子から再取得しました",
|
"rederived": "子から再取得しました",
|
||||||
"editAdvanced": "ルーティング項目を編集",
|
"editAdvanced": "ルーティング項目を編集",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,7 @@
|
||||||
"add": "Добавить фолбэк",
|
"add": "Добавить фолбэк",
|
||||||
"pickInbound": "Выберите инбаунд",
|
"pickInbound": "Выберите инбаунд",
|
||||||
"matchAny": "любой",
|
"matchAny": "любой",
|
||||||
|
"destPlaceholder": "авто (listen:порт дочернего)",
|
||||||
"rederive": "Заполнить из дочернего",
|
"rederive": "Заполнить из дочернего",
|
||||||
"rederived": "Заполнено из дочернего",
|
"rederived": "Заполнено из дочернего",
|
||||||
"editAdvanced": "Изменить поля маршрутизации",
|
"editAdvanced": "Изменить поля маршрутизации",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,7 @@
|
||||||
"add": "Додати фолбек",
|
"add": "Додати фолбек",
|
||||||
"pickInbound": "Оберіть інбаунд",
|
"pickInbound": "Оберіть інбаунд",
|
||||||
"matchAny": "будь-який",
|
"matchAny": "будь-який",
|
||||||
|
"destPlaceholder": "авто (listen:порт дочірнього)",
|
||||||
"rederive": "Заповнити з дочірнього",
|
"rederive": "Заповнити з дочірнього",
|
||||||
"rederived": "Заповнено з дочірнього",
|
"rederived": "Заповнено з дочірнього",
|
||||||
"editAdvanced": "Редагувати поля маршрутизації",
|
"editAdvanced": "Редагувати поля маршрутизації",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,7 @@
|
||||||
"add": "添加回落",
|
"add": "添加回落",
|
||||||
"pickInbound": "选择一个入站",
|
"pickInbound": "选择一个入站",
|
||||||
"matchAny": "任意",
|
"matchAny": "任意",
|
||||||
|
"destPlaceholder": "自动(子入站 listen:port)",
|
||||||
"rederive": "从子入站重新填充",
|
"rederive": "从子入站重新填充",
|
||||||
"rederived": "已从子入站重新填充",
|
"rederived": "已从子入站重新填充",
|
||||||
"editAdvanced": "编辑路由字段",
|
"editAdvanced": "编辑路由字段",
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,7 @@
|
||||||
"add": "新增回落",
|
"add": "新增回落",
|
||||||
"pickInbound": "選擇一個入站",
|
"pickInbound": "選擇一個入站",
|
||||||
"matchAny": "任何",
|
"matchAny": "任何",
|
||||||
|
"destPlaceholder": "自動(子入站 listen:port)",
|
||||||
"rederive": "從子入站重新填入",
|
"rederive": "從子入站重新填入",
|
||||||
"rederived": "已從子入站重新填入",
|
"rederived": "已從子入站重新填入",
|
||||||
"editAdvanced": "編輯路由欄位",
|
"editAdvanced": "編輯路由欄位",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue