mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-11 22:00:59 +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:
+24
-24
@@ -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.
|
||||
type AllSetting struct {
|
||||
// Web server settings
|
||||
WebListen string `json:"webListen" form:"webListen"` // Web server listen IP address
|
||||
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
|
||||
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
|
||||
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)
|
||||
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)
|
||||
WebListen string `json:"webListen" form:"webListen"` // Web server listen IP address
|
||||
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
|
||||
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
|
||||
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)
|
||||
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)
|
||||
|
||||
// UI settings
|
||||
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
|
||||
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
|
||||
Datepicker string `json:"datepicker" form:"datepicker"` // Date picker format
|
||||
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
|
||||
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
|
||||
Datepicker string `json:"datepicker" form:"datepicker"` // Date picker format
|
||||
|
||||
// Telegram bot settings
|
||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"` // Enable Telegram bot notifications
|
||||
TgBotToken string `json:"tgBotToken" form:"tgBotToken"` // Telegram bot token
|
||||
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"` // Proxy URL 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
|
||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"` // Cron schedule for Telegram notifications
|
||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` // Enable database backup via Telegram
|
||||
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)
|
||||
TgLang string `json:"tgLang" form:"tgLang"` // Telegram bot language
|
||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"` // Enable Telegram bot notifications
|
||||
TgBotToken string `json:"tgBotToken" form:"tgBotToken"` // Telegram bot token
|
||||
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"` // Proxy URL 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
|
||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"` // Cron schedule for Telegram notifications
|
||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` // Enable database backup via Telegram
|
||||
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)
|
||||
TgLang string `json:"tgLang" form:"tgLang"` // Telegram bot language
|
||||
|
||||
// Security settings
|
||||
TimeLocation string `json:"timeLocation" form:"timeLocation"` // Time zone location
|
||||
|
||||
+10
-7
@@ -18,6 +18,7 @@ type FallbackInput struct {
|
||||
Name string `json:"name"`
|
||||
Alpn string `json:"alpn"`
|
||||
Path string `json:"path"`
|
||||
Dest string `json:"dest"`
|
||||
Xver int `json:"xver"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
}
|
||||
@@ -71,6 +72,7 @@ func (s *FallbackService) SetByMaster(masterId int, items []FallbackInput) error
|
||||
Name: c.Name,
|
||||
Alpn: c.Alpn,
|
||||
Path: c.Path,
|
||||
Dest: c.Dest,
|
||||
Xver: c.Xver,
|
||||
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) {
|
||||
if tx == nil {
|
||||
tx = database.GetDB()
|
||||
@@ -122,12 +121,16 @@ func (s *FallbackService) BuildFallbacksJSON(tx *gorm.DB, masterId int) ([]map[s
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
listen := strings.TrimSpace(child.Listen)
|
||||
if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
|
||||
listen = "127.0.0.1"
|
||||
dest := r.Dest
|
||||
if dest == "" {
|
||||
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{
|
||||
"dest": fmt.Sprintf("%s:%d", listen, child.Port),
|
||||
"dest": dest,
|
||||
}
|
||||
if r.Name != "" {
|
||||
entry["name"] = r.Name
|
||||
|
||||
@@ -304,7 +304,6 @@ func resolveXrayLogPaths(logCfg json_util.RawMessage) json_util.RawMessage {
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
// GetXrayTraffic fetches the current traffic statistics from the running Xray process.
|
||||
func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) {
|
||||
if !s.IsXrayRunning() {
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "إضافة fallback",
|
||||
"pickInbound": "اختر inbound",
|
||||
"matchAny": "أي",
|
||||
"destPlaceholder": "تلقائي (listen:port للفرع)",
|
||||
"rederive": "إعادة الملء من الفرع",
|
||||
"rederived": "تم إعادة الملء من الفرع",
|
||||
"editAdvanced": "تحرير حقول التوجيه",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "Add fallback",
|
||||
"pickInbound": "Pick an inbound",
|
||||
"matchAny": "any",
|
||||
"destPlaceholder": "auto (child listen:port)",
|
||||
"rederive": "Re-fill from child",
|
||||
"rederived": "Re-filled from child",
|
||||
"editAdvanced": "Edit routing fields",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "Añadir fallback",
|
||||
"pickInbound": "Selecciona un inbound",
|
||||
"matchAny": "cualquiera",
|
||||
"destPlaceholder": "automático (listen:puerto del hijo)",
|
||||
"rederive": "Rellenar desde el hijo",
|
||||
"rederived": "Rellenado desde el hijo",
|
||||
"editAdvanced": "Editar campos de enrutamiento",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "افزودن فالبک",
|
||||
"pickInbound": "یک اینباند انتخاب کنید",
|
||||
"matchAny": "همه",
|
||||
"destPlaceholder": "خودکار (listen:port فرزند)",
|
||||
"rederive": "پر کردن مجدد از فرزند",
|
||||
"rederived": "از فرزند پر شد",
|
||||
"editAdvanced": "ویرایش فیلدهای مسیریابی",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "Tambah fallback",
|
||||
"pickInbound": "Pilih inbound",
|
||||
"matchAny": "apa pun",
|
||||
"destPlaceholder": "otomatis (listen:port child)",
|
||||
"rederive": "Isi ulang dari child",
|
||||
"rederived": "Diisi ulang dari child",
|
||||
"editAdvanced": "Edit field routing",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "フォールバックを追加",
|
||||
"pickInbound": "インバウンドを選択",
|
||||
"matchAny": "任意",
|
||||
"destPlaceholder": "自動(子の listen:port)",
|
||||
"rederive": "子から再取得",
|
||||
"rederived": "子から再取得しました",
|
||||
"editAdvanced": "ルーティング項目を編集",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "Adicionar fallback",
|
||||
"pickInbound": "Escolha um inbound",
|
||||
"matchAny": "qualquer",
|
||||
"destPlaceholder": "automático (listen:porta do filho)",
|
||||
"rederive": "Preencher a partir do filho",
|
||||
"rederived": "Preenchido a partir do filho",
|
||||
"editAdvanced": "Editar campos de roteamento",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "Добавить фолбэк",
|
||||
"pickInbound": "Выберите инбаунд",
|
||||
"matchAny": "любой",
|
||||
"destPlaceholder": "авто (listen:порт дочернего)",
|
||||
"rederive": "Заполнить из дочернего",
|
||||
"rederived": "Заполнено из дочернего",
|
||||
"editAdvanced": "Изменить поля маршрутизации",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "Fallback ekle",
|
||||
"pickInbound": "Bir inbound seç",
|
||||
"matchAny": "herhangi",
|
||||
"destPlaceholder": "otomatik (child listen:port)",
|
||||
"rederive": "Child'dan yeniden doldur",
|
||||
"rederived": "Child'dan yeniden dolduruldu",
|
||||
"editAdvanced": "Yönlendirme alanlarını düzenle",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "Додати фолбек",
|
||||
"pickInbound": "Оберіть інбаунд",
|
||||
"matchAny": "будь-який",
|
||||
"destPlaceholder": "авто (listen:порт дочірнього)",
|
||||
"rederive": "Заповнити з дочірнього",
|
||||
"rederived": "Заповнено з дочірнього",
|
||||
"editAdvanced": "Редагувати поля маршрутизації",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "Thêm fallback",
|
||||
"pickInbound": "Chọn một inbound",
|
||||
"matchAny": "bất kỳ",
|
||||
"destPlaceholder": "tự động (listen:port của child)",
|
||||
"rederive": "Điền lại từ child",
|
||||
"rederived": "Đã điền lại từ child",
|
||||
"editAdvanced": "Sửa trường định tuyến",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "添加回落",
|
||||
"pickInbound": "选择一个入站",
|
||||
"matchAny": "任意",
|
||||
"destPlaceholder": "自动(子入站 listen:port)",
|
||||
"rederive": "从子入站重新填充",
|
||||
"rederived": "已从子入站重新填充",
|
||||
"editAdvanced": "编辑路由字段",
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
"add": "新增回落",
|
||||
"pickInbound": "選擇一個入站",
|
||||
"matchAny": "任何",
|
||||
"destPlaceholder": "自動(子入站 listen:port)",
|
||||
"rederive": "從子入站重新填入",
|
||||
"rederived": "已從子入站重新填入",
|
||||
"editAdvanced": "編輯路由欄位",
|
||||
|
||||
Reference in New Issue
Block a user