From a2d8c98b0df39216787d2c77af6e499857510e9e Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 01:13:46 +0430 Subject: [PATCH 01/14] create and move middlewares to seperate folder --- web/middleware/domainValidator.go | 21 +++++++++++++++++++ web/middleware/redirect.go | 34 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 web/middleware/domainValidator.go create mode 100644 web/middleware/redirect.go diff --git a/web/middleware/domainValidator.go b/web/middleware/domainValidator.go new file mode 100644 index 00000000..3adb0f0f --- /dev/null +++ b/web/middleware/domainValidator.go @@ -0,0 +1,21 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func DomainValidatorMiddleware(domain string) gin.HandlerFunc { + return func(c *gin.Context) { + host := strings.Split(c.Request.Host, ":")[0] + + if host != domain { + c.AbortWithStatus(http.StatusForbidden) + return + } + + c.Next() + } +} diff --git a/web/middleware/redirect.go b/web/middleware/redirect.go new file mode 100644 index 00000000..e3dc8ada --- /dev/null +++ b/web/middleware/redirect.go @@ -0,0 +1,34 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func RedirectMiddleware(basePath string) gin.HandlerFunc { + return func(c *gin.Context) { + // Redirect from old '/xui' path to '/panel' + redirects := map[string]string{ + "panel/API": "panel/api", + "xui/API": "panel/api", + "xui": "panel", + } + + path := c.Request.URL.Path + for from, to := range redirects { + from, to = basePath+from, basePath+to + + if strings.HasPrefix(path, from) { + newPath := to + path[len(from):] + + c.Redirect(http.StatusMovedPermanently, newPath) + c.Abort() + return + } + } + + c.Next() + } +} From 8170b65db4d38002f81825ea876dd3e4c9292931 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 01:21:14 +0430 Subject: [PATCH 02/14] add an option for webDomain --- web/assets/js/model/models.js | 3 ++- web/controller/inbound.go | 2 ++ web/entity/entity.go | 1 + web/global/hashStorage.go | 2 -- web/html/xui/settings.html | 3 ++- web/service/inbound.go | 1 + web/service/setting.go | 7 ++++++- 7 files changed, 14 insertions(+), 5 deletions(-) diff --git a/web/assets/js/model/models.js b/web/assets/js/model/models.js index 9a5dcc85..e1a766dc 100644 --- a/web/assets/js/model/models.js +++ b/web/assets/js/model/models.js @@ -168,6 +168,7 @@ class AllSetting { constructor(data) { this.webListen = ""; + this.webDomain = ""; this.webPort = 2053; this.webCertFile = ""; this.webKeyFile = ""; @@ -187,7 +188,7 @@ class AllSetting { this.subEnable = false; this.subListen = ""; this.subPort = "2096"; - this.subPath = "sub/"; + this.subPath = "/sub/"; this.subDomain = ""; this.subCertFile = ""; this.subKeyFile = ""; diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 5ce58d53..815f1788 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -65,6 +65,7 @@ func (a *InboundController) getInbounds(c *gin.Context) { } jsonObj(c, inbounds, nil) } + func (a *InboundController) getInbound(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { @@ -168,6 +169,7 @@ func (a *InboundController) clearClientIps(c *gin.Context) { } jsonMsg(c, "Log Cleared", nil) } + func (a *InboundController) addInboundClient(c *gin.Context) { data := &model.Inbound{} err := c.ShouldBind(data) diff --git a/web/entity/entity.go b/web/entity/entity.go index 0bfbfd2a..d5e90108 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -28,6 +28,7 @@ type Pager struct { type AllSetting struct { WebListen string `json:"webListen" form:"webListen"` + WebDomain string `json:"webDomain" form:"webDomain"` WebPort int `json:"webPort" form:"webPort"` WebCertFile string `json:"webCertFile" form:"webCertFile"` WebKeyFile string `json:"webKeyFile" form:"webKeyFile"` diff --git a/web/global/hashStorage.go b/web/global/hashStorage.go index 9dfea169..5d8135ee 100644 --- a/web/global/hashStorage.go +++ b/web/global/hashStorage.go @@ -18,7 +18,6 @@ type HashStorage struct { sync.RWMutex Data map[string]HashEntry Expiration time.Duration - } func NewHashStorage(expiration time.Duration) *HashStorage { @@ -46,7 +45,6 @@ func (h *HashStorage) SaveHash(query string) string { return md5HashString } - func (h *HashStorage) GetValue(hash string) (string, bool) { h.RLock() defer h.RUnlock() diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html index d78533a1..e7d865f6 100644 --- a/web/html/xui/settings.html +++ b/web/html/xui/settings.html @@ -91,6 +91,7 @@ + @@ -391,9 +392,9 @@ + - diff --git a/web/service/inbound.go b/web/service/inbound.go index 6a182fcf..11522ad2 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -1185,6 +1185,7 @@ func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) } return InboundClientIps.Ips, nil } + func (s *InboundService) ClearClientIps(clientEmail string) error { db := database.GetDB() diff --git a/web/service/setting.go b/web/service/setting.go index 593b23be..677ccbb2 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -24,6 +24,7 @@ var xrayTemplateConfig string var defaultValueMap = map[string]string{ "xrayTemplateConfig": xrayTemplateConfig, "webListen": "", + "webDomain": "", "webPort": "2053", "webCertFile": "", "webKeyFile": "", @@ -44,7 +45,7 @@ var defaultValueMap = map[string]string{ "subEnable": "false", "subListen": "", "subPort": "2096", - "subPath": "sub/", + "subPath": "/sub/", "subDomain": "", "subCertFile": "", "subKeyFile": "", @@ -225,6 +226,10 @@ func (s *SettingService) GetListen() (string, error) { return s.getString("webListen") } +func (s *SettingService) GetWebDomain() (string, error) { + return s.getString("webDomain") +} + func (s *SettingService) GetTgBotToken() (string, error) { return s.getString("tgBotToken") } From ea7fe09c27da8c12e38f581fd7a4b336b4e55ad7 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 01:24:18 +0430 Subject: [PATCH 03/14] use the middlewares --- sub/sub.go | 19 +++++-------------- web/web.go | 34 +++++++++++----------------------- 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/sub/sub.go b/sub/sub.go index f7353cc2..b642f7f2 100644 --- a/sub/sub.go +++ b/sub/sub.go @@ -7,10 +7,10 @@ import ( "net" "net/http" "strconv" - "strings" "x-ui/config" "x-ui/logger" "x-ui/util/common" + "x-ui/web/middleware" "x-ui/web/network" "x-ui/web/service" @@ -58,18 +58,7 @@ func (s *Server) initRouter() (*gin.Engine, error) { } if subDomain != "" { - validateDomain := func(c *gin.Context) { - host := strings.Split(c.Request.Host, ":")[0] - - if host != subDomain { - c.AbortWithStatus(http.StatusForbidden) - return - } - - c.Next() - } - - engine.Use(validateDomain) + engine.Use(middleware.DomainValidatorMiddleware(subDomain)) } g := engine.Group(subPath) @@ -116,11 +105,13 @@ func (s *Server) Start() (err error) { if err != nil { return err } + listenAddr := net.JoinHostPort(listen, strconv.Itoa(port)) listener, err := net.Listen("tcp", listenAddr) if err != nil { return err } + if certFile != "" || keyFile != "" { cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { @@ -168,4 +159,4 @@ func (s *Server) Stop() error { func (s *Server) GetCtx() context.Context { return s.ctx -} \ No newline at end of file +} diff --git a/web/web.go b/web/web.go index 543ecf8e..3d8f0242 100644 --- a/web/web.go +++ b/web/web.go @@ -19,6 +19,7 @@ import ( "x-ui/web/controller" "x-ui/web/job" "x-ui/web/locale" + "x-ui/web/middleware" "x-ui/web/network" "x-ui/web/service" @@ -144,28 +145,6 @@ func (s *Server) getHtmlTemplate(funcMap template.FuncMap) (*template.Template, return t, nil } -func redirectMiddleware(basePath string) gin.HandlerFunc { - return func(c *gin.Context) { - // Redirect from old '/xui' path to '/panel' - path := c.Request.URL.Path - redirects := map[string]string{ - "panel/API": "panel/api", - "xui/API": "panel/api", - "xui": "panel", - } - for from, to := range redirects { - from, to = basePath+from, basePath+to - if strings.HasPrefix(path, from) { - newPath := to + path[len(from):] - c.Redirect(http.StatusMovedPermanently, newPath) - c.Abort() - return - } - } - c.Next() - } -} - func (s *Server) initRouter() (*gin.Engine, error) { if config.IsDebug() { gin.SetMode(gin.DebugMode) @@ -177,6 +156,15 @@ func (s *Server) initRouter() (*gin.Engine, error) { engine := gin.Default() + webDomain, err := s.settingService.GetWebDomain() + if err != nil { + return nil, err + } + + if webDomain != "" { + engine.Use(middleware.DomainValidatorMiddleware(webDomain)) + } + secret, err := s.settingService.GetSecret() if err != nil { return nil, err @@ -233,7 +221,7 @@ func (s *Server) initRouter() (*gin.Engine, error) { } // Apply the redirect middleware (`/xui` to `/panel`) - engine.Use(redirectMiddleware(basePath)) + engine.Use(middleware.RedirectMiddleware(basePath)) g := engine.Group(basePath) From c9461f1647f5c36f74f292208f7af105976e5c2b Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 01:26:29 +0430 Subject: [PATCH 04/14] Update translations --- web/translation/translate.en_US.toml | 2 ++ web/translation/translate.fa_IR.toml | 2 ++ web/translation/translate.ru_RU.toml | 2 ++ web/translation/translate.zh_Hans.toml | 2 ++ 4 files changed, 8 insertions(+) diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index a15bf44c..edd4ca80 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -221,6 +221,8 @@ "TGBotSettings" = "Telegram Bot Settings" "panelListeningIP" = "Panel Listening IP" "panelListeningIPDesc" = "Leave blank by default to monitor all IPs." +"panelListeningDomain" = "Panel Listening Domain" +"panelListeningDomainDesc" = "Leave blank by default to monitor all domains and IPs" "panelPort" = "Panel Port" "panelPortDesc" = "The port used to display this panel" "publicKeyPath" = "Panel Certificate Public Key File Path" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index abaa989b..b8b1b5fb 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -221,6 +221,8 @@ "TGBotSettings" = "تنظیمات ربات تلگرام" "panelListeningIP" = "محدودیت آی پی پنل" "panelListeningIPDesc" = "برای استفاده از تمام آی‌پیها به طور پیش فرض خالی بگذارید" +"panelListeningDomain" = "محدودیت دامین پنل" +"panelListeningDomainDesc" = "برای استفاده از تمام دامنه‌ها و آی‌پی‌ها به طور پیش فرض خالی بگذارید" "panelPort" = "پورت پنل" "panelPortDesc" = "پورت مورد استفاده برای نمایش این پنل" "publicKeyPath" = "مسیر فایل گواهی کلید عمومی پنل" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 3f0a18e6..a96cad1c 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -221,6 +221,8 @@ "TGBotSettings" = "Настройки Telegram бота" "panelListeningIP" = "IP адрес панели" "panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP" +"panelListeningDomain" = "Домен прослушивания панели" +"panelListeningDomainDesc" = "По умолчанию оставьте пустым, чтобы отслеживать все домены и IP-адреса" "panelPort" = "Порт панели" "panelPortDesc" = "Порт, используемый для отображения этой панели" "publicKeyPath" = "Путь к файлу публичного ключа сертификата панели" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index 60f42abc..febbe4b8 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -221,6 +221,8 @@ "TGBotSettings" = "TG提醒相关设置" "panelListeningIP" = "面板监听 IP" "panelListeningIPDesc" = "默认留空监听所有 IP" +"panelListeningDomain" = "面板监听域名" +"panelListeningDomainDesc" = "默认情况下留空以监视所有域名和 IP 地址" "panelPort" = "面板监听端口" "panelPortDesc" = "重启面板生效" "publicKeyPath" = "面板证书公钥文件路径" From fe963d91ef6c4ddb9e56709f993e8cc09f9786c4 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 01:31:56 +0430 Subject: [PATCH 05/14] some improvements --- web/controller/setting.go | 97 +++++++++++++-------------------------- 1 file changed, 31 insertions(+), 66 deletions(-) diff --git a/web/controller/setting.go b/web/controller/setting.go index 0292c46a..306563a2 100644 --- a/web/controller/setting.go +++ b/web/controller/setting.go @@ -65,77 +65,42 @@ func (a *SettingController) getDefaultJsonConfig(c *gin.Context) { } func (a *SettingController) getDefaultSettings(c *gin.Context) { - expireDiff, err := a.settingService.GetExpireDiff() - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) - return + type settingFunc func() (interface{}, error) + + settings := map[string]settingFunc{ + "expireDiff": func() (interface{}, error) { return a.settingService.GetExpireDiff() }, + "trafficDiff": func() (interface{}, error) { return a.settingService.GetTrafficDiff() }, + "defaultCert": func() (interface{}, error) { return a.settingService.GetCertFile() }, + "defaultKey": func() (interface{}, error) { return a.settingService.GetKeyFile() }, + "tgBotEnable": func() (interface{}, error) { return a.settingService.GetTgbotenabled() }, + "subEnable": func() (interface{}, error) { return a.settingService.GetSubEnable() }, + "subPort": func() (interface{}, error) { return a.settingService.GetSubPort() }, + "subPath": func() (interface{}, error) { return a.settingService.GetSubPath() }, + "subDomain": func() (interface{}, error) { return a.settingService.GetSubDomain() }, + "subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() }, + "subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() }, } - trafficDiff, err := a.settingService.GetTrafficDiff() - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) - return - } - defaultCert, err := a.settingService.GetCertFile() - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) - return - } - defaultKey, err := a.settingService.GetKeyFile() - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) - return - } - tgBotEnable, err := a.settingService.GetTgbotenabled() - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) - return - } - subEnable, err := a.settingService.GetSubEnable() - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) - return - } - subPort, err := a.settingService.GetSubPort() - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) - return - } - subPath, err := a.settingService.GetSubPath() - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) - return - } - subDomain, err := a.settingService.GetSubDomain() - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) - return - } - subKeyFile, err := a.settingService.GetSubKeyFile() - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) - return - } - subCertFile, err := a.settingService.GetSubCertFile() - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) - return + + result := make(map[string]interface{}) + + for key, fn := range settings { + value, err := fn() + if err != nil { + jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) + return + } + result[key] = value } + subTLS := false - if subKeyFile != "" || subCertFile != "" { + if result["subKeyFile"].(string) != "" || result["subCertFile"].(string) != "" { subTLS = true } - result := map[string]interface{}{ - "expireDiff": expireDiff, - "trafficDiff": trafficDiff, - "defaultCert": defaultCert, - "defaultKey": defaultKey, - "tgBotEnable": tgBotEnable, - "subEnable": subEnable, - "subPort": subPort, - "subPath": subPath, - "subDomain": subDomain, - "subTLS": subTLS, - } + result["subTLS"] = subTLS + + delete(result, "subKeyFile") + delete(result, "subCertFile") + jsonObj(c, result, nil) } From 6c26e40aea8f63b2f422c4f9fbf92c7ff1c0383c Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 01:33:45 +0430 Subject: [PATCH 06/14] fix qrModal.client is null --- web/html/common/qrcode_modal.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html index e6b7b998..57bf810d 100644 --- a/web/html/common/qrcode_modal.html +++ b/web/html/common/qrcode_modal.html @@ -94,7 +94,7 @@ } }, updated() { - if (qrModal.client.subId){ + if (qrModal.client && qrModal.client.subId){ qrModal.subId = qrModal.client.subId; this.setQrCode("qrCode-sub",this.genSubLink(qrModal.subId)); } From 1c9fc9422e304c8710b56e9719960e429e7a1878 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 01:34:15 +0430 Subject: [PATCH 07/14] add buildURL func --- web/assets/js/util/common.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/web/assets/js/util/common.js b/web/assets/js/util/common.js index 563bfd45..8e30bce7 100644 --- a/web/assets/js/util/common.js +++ b/web/assets/js/util/common.js @@ -135,3 +135,21 @@ function doAllItemsExist(array1, array2) { } return true; } + +function buildURL({ host, port, isTLS, base, path }) { + if (!host || host.length === 0) host = window.location.hostname; + if (!port || port.length === 0) port = window.location.port; + + if (isTLS === undefined) isTLS = window.location.protocol === "https:"; + + const protocol = isTLS ? "https:" : "http:"; + + port = String(port); + if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) { + port = ""; + } else { + port = `:${port}`; + } + + return `${protocol}//${host}${port}${base}${path}`; +} From b203067dfd416bd45d34cc2dbaad85ab51fcb085 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 01:47:07 +0430 Subject: [PATCH 08/14] fix urls + use the new buildURL func --- README.md | 2 +- web/html/common/qrcode_modal.html | 30 ++++++++++++---------------- web/html/xui/inbound_info_modal.html | 8 ++------ web/html/xui/inbound_modal.html | 2 +- web/html/xui/inbounds.html | 2 +- web/html/xui/settings.html | 12 +++++------ 6 files changed, 23 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 56c4b344..48f65340 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ Reference syntax: - CPU threshold notification - Threshold for Expiration time and Traffic to report in advance - Support client report menu if client's telegram username added to the user's configurations -- Support telegram traffic report searched with UID (VMESS/VLESS) or Password (TROJAN) - anonymously +- Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously - Menu based bot - Search client by email ( only admin ) - Check all inbounds diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html index 57bf810d..8edfa2de 100644 --- a/web/html/common/qrcode_modal.html +++ b/web/html/common/qrcode_modal.html @@ -68,8 +68,8 @@ qrModal: qrModal, }, methods: { - copyToClipboard(elmentId,content) { - this.qrModal.clipboard = new ClipboardJS('#'+elmentId, { + copyToClipboard(elmentId, content) { + this.qrModal.clipboard = new ClipboardJS('#' + elmentId, { text: () => content, }); this.qrModal.clipboard.on('success', () => { @@ -77,29 +77,25 @@ this.qrModal.clipboard.destroy(); }); }, - setQrCode(elmentId,content) { + setQrCode(elmentId, content) { new QRious({ - element: document.querySelector('#'+elmentId), - size: 260, - value: content, - }); + element: document.querySelector('#' + elmentId), + size: 260, + value: content, + }); }, genSubLink(subID) { - protocol = app.subSettings.tls ? "https://" : "http://"; - hostName = app.subSettings.domain === "" ? window.location.hostname : app.subSettings.domain; - subPort = app.subSettings.port; - port = (subPort === 443 && app.subSettings.tls) || (subPort === 80 && !app.subSettings.tls) ? "" : ":" + String(subPort); - subPath = app.subSettings.path; - return protocol + hostName + port + subPath + subID; + const { domain: host, port, tls: isTLS, path: base } = app.subSettings; + return buildURL({ host, port, isTLS, base, path: subID }); } }, updated() { - if (qrModal.client && qrModal.client.subId){ + if (qrModal.client && qrModal.client.subId) { qrModal.subId = qrModal.client.subId; - this.setQrCode("qrCode-sub",this.genSubLink(qrModal.subId)); + this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId)); } - qrModal.qrcodes.forEach((element,index) => { - this.setQrCode("qrCode-"+index, element.link); + qrModal.qrcodes.forEach((element, index) => { + this.setQrCode("qrCode-" + index, element.link); }); } }); diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html index b7b3436b..23bd5af1 100644 --- a/web/html/xui/inbound_info_modal.html +++ b/web/html/xui/inbound_info_modal.html @@ -253,12 +253,8 @@ infoModal.visible = false; }, genSubLink(subID) { - protocol = app.subSettings.tls ? "https://" : "http://"; - hostName = app.subSettings.domain === "" ? window.location.hostname : app.subSettings.domain; - subPort = app.subSettings.port; - port = (subPort === 443 && app.subSettings.tls) || (subPort === 80 && !app.subSettings.tls) ? "" : ":" + String(subPort); - subPath = app.subSettings.path; - return protocol + hostName + port + subPath + subID; + const { domain: host, port, tls: isTLS, path: base } = app.subSettings; + return buildURL({ host, port, isTLS, base, path: subID }); } }; diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html index 25e19473..11e6020c 100644 --- a/web/html/xui/inbound_modal.html +++ b/web/html/xui/inbound_modal.html @@ -96,7 +96,7 @@ set multiDomain(value) { if (value) { inModal.inbound.stream.tls.server = ""; - inModal.inbound.stream.tls.settings.domains = [{remark: "", domain: window.location.host.split(":")[0]}]; + inModal.inbound.stream.tls.settings.domains = [{ remark: "", domain: window.location.hostname }]; } else { inModal.inbound.stream.tls.server = ""; inModal.inbound.stream.tls.settings.domains = []; diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html index 7b9ba207..329f0f46 100644 --- a/web/html/xui/inbounds.html +++ b/web/html/xui/inbounds.html @@ -311,7 +311,7 @@ { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } }, { title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } }, { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } }, - { title: 'UID', width: 120, dataIndex: "id" }, + { title: 'UUID', width: 120, dataIndex: "id" }, ]; const innerTrojanColumns = [ diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html index e7d865f6..e4103c52 100644 --- a/web/html/xui/settings.html +++ b/web/html/xui/settings.html @@ -523,7 +523,7 @@ this.loading(false); if (msg.success) { this.user = {}; - window.location.replace(basePath + "logout") + window.location.replace(basePath + "logout"); } }, async restartPanel() { @@ -542,12 +542,10 @@ if (msg.success) { this.loading(true); await PromiseUtil.sleep(5000); - let protocol = "http://"; - if (this.allSetting.webCertFile !== "") { - protocol = "https://"; - } - const { host } = window.location; - window.location.replace(protocol + host + this.allSetting.webBasePath + "panel/settings"); + const { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting; + const isTLS = webCertFile !== "" || webKeyFile !== ""; + const url = buildURL({ host, port, isTLS, base, path: "panel/settings" }); + window.location.replace(url); } }, async fetchUserSecret() { From ef7b979d53ba502a5354dd0cfb457e6c1febe125 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 01:49:23 +0430 Subject: [PATCH 09/14] move manual list to new tab --- web/html/xui/settings.html | 44 +++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html index e4103c52..745959a2 100644 --- a/web/html/xui/settings.html +++ b/web/html/xui/settings.html @@ -307,23 +307,37 @@ - - -

- - {{ i18n "pages.settings.templates.manualListsDesc" }} -

-
- - - - - - + + + + +

+ + {{ i18n "pages.settings.templates.manualListsDesc" }} +

+
+ + + + + + + + + + + + + + + + + +
- + @@ -336,7 +350,7 @@ - + From 6ae80fc992924bb00dcea9a1a032f1bca3af4b30 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 02:31:00 +0430 Subject: [PATCH 10/14] add /id command to tgbot to get user id --- web/service/tgbot.go | 33 ++++++++++++++++++-------- web/translation/translate.en_US.toml | 5 ++-- web/translation/translate.fa_IR.toml | 5 ++-- web/translation/translate.ru_RU.toml | 5 ++-- web/translation/translate.zh_Hans.toml | 5 ++-- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/web/service/tgbot.go b/web/service/tgbot.go index e0261775..0ad7c3c6 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -188,7 +188,7 @@ func (t *Tgbot) OnReceive() { } func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin bool) { - msg := "" + msg, onlyMessage := "", false command, commandArgs := tu.ParseCommand(message.Text) @@ -204,8 +204,13 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo } msg += "\n\n" + t.I18nBot("tgbot.commands.pleaseChoose") case "status": + onlyMessage = true msg += t.I18nBot("tgbot.commands.status") + case "id": + onlyMessage = true + msg += t.I18nBot("tgbot.commands.getID", "ID=="+strconv.FormatInt(message.From.ID, 10)) case "usage": + onlyMessage = true if len(commandArgs) > 0 { if isAdmin { t.searchClient(chatId, commandArgs[0]) @@ -216,6 +221,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo msg += t.I18nBot("tgbot.commands.usage") } case "inbound": + onlyMessage = true if isAdmin && len(commandArgs) > 0 { t.searchInbound(chatId, commandArgs[0]) } else { @@ -224,6 +230,11 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo default: msg += t.I18nBot("tgbot.commands.unknown") } + + if onlyMessage { + t.SendMsgToTgbot(chatId, msg) + return + } t.SendAnswer(chatId, msg, isAdmin) } @@ -498,6 +509,7 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R if !isRunning { return } + if msg == "" { logger.Info("[tgbot] message is empty!") return @@ -723,7 +735,7 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string, tgUserID string) output := "" output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) - if (traffic.Enable) { + if traffic.Enable { output += t.I18nBot("tgbot.messages.active") if flag { output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) @@ -791,6 +803,7 @@ func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ... output := "" output += t.I18nBot("tgbot.messages.email", "Email=="+email) output += t.I18nBot("tgbot.messages.TGUser", "TelegramID=="+tgId) + output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( @@ -840,7 +853,7 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) { flag := false diff := traffic.ExpiryTime/1000 - now if traffic.ExpiryTime == 0 { - expiryTime = t.I18nBot("tgbot.unlimited") + expiryTime = t.I18nBot("tgbot.unlimited") } else if diff > 172800 || !traffic.Enable { expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") } else if traffic.ExpiryTime < 0 { @@ -860,7 +873,7 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) { output := "" output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) - if (traffic.Enable) { + if traffic.Enable { output += t.I18nBot("tgbot.messages.active") if flag { output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) @@ -918,7 +931,7 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) { t.SendMsgToTgbot(chatId, msg) return } - + now := time.Now().Unix() for _, inbound := range inbouds { info := "" @@ -958,7 +971,7 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) { output := "" output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) - if (traffic.Enable) { + if traffic.Enable { output += t.I18nBot("tgbot.messages.active") if flag { output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) @@ -998,7 +1011,7 @@ func (t *Tgbot) searchForClient(chatId int64, query string) { flag := false diff := traffic.ExpiryTime/1000 - now if traffic.ExpiryTime == 0 { - expiryTime = t.I18nBot("tgbot.unlimited") + expiryTime = t.I18nBot("tgbot.unlimited") } else if diff > 172800 || !traffic.Enable { expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") } else if traffic.ExpiryTime < 0 { @@ -1018,7 +1031,7 @@ func (t *Tgbot) searchForClient(chatId int64, query string) { output := "" output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) - if (traffic.Enable) { + if traffic.Enable { output += t.I18nBot("tgbot.messages.active") if flag { output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) @@ -1117,7 +1130,7 @@ func (t *Tgbot) getExhausted() string { for _, traffic := range exhaustedClients { expiryTime := "" flag := false - diff := (traffic.ExpiryTime - now)/1000 + diff := (traffic.ExpiryTime - now) / 1000 if traffic.ExpiryTime == 0 { expiryTime = t.I18nBot("tgbot.unlimited") } else if diff > 172800 || !traffic.Enable { @@ -1138,7 +1151,7 @@ func (t *Tgbot) getExhausted() string { } output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) - if (traffic.Enable) { + if traffic.Enable { output += t.I18nBot("tgbot.messages.active") if flag { output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index edd4ca80..401a1265 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -168,7 +168,7 @@ "setDefaultCert" = "Set cert from panel" "xtlsDesc" = "Xray core needs to be 1.7.5" "realityDesc" = "Xray core needs to be 1.8.0 or higher." -"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot )" +"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot or use '/id' command in bot )" "subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations" [pages.client] @@ -240,7 +240,7 @@ "telegramToken" = "Telegram Token" "telegramTokenDesc" = "You must get the token from the manager of Telegram bots @botfather" "telegramChatId" = "Telegram Admin Chat IDs" -"telegramChatIdDesc" = "Multiple Chat IDs separated by comma. use @userinfobot to get your Chat IDs." +"telegramChatIdDesc" = "Multiple Chat IDs separated by comma. use @userinfobot or use '/id' command in bot to get your Chat IDs." "telegramNotifyTime" = "Telegram bot notification time" "telegramNotifyTimeDesc" = "Use Crontab timing format." "tgNotifyBackup" = "Database Backup" @@ -399,6 +399,7 @@ "welcome" = "🤖 Welcome to {{ .Hostname }} management bot.\r\n" "status" = "✅ Bot is ok!" "usage" = "❗ Please provide a text to search!" +"getID" = "🆔 Your ID: {{ .ID }}" "helpAdminCommands" = "Search for a client email:\r\n/usage [Email]\r\n \r\nSearch for inbounds (with client stats):\r\n/inbound [Remark]" "helpClientCommands" = "To search for statistics, just use folowing command:\r\n \r\n/usage [UUID|Password]\r\n \r\nUse UUID for vmess/vless and Password for Trojan." diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index b8b1b5fb..841483c7 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -168,7 +168,7 @@ "setDefaultCert" = "استفاده از گواهی پنل" "xtlsDesc" = "هسته Xray باید 1.7.5 باشد" "realityDesc" = "هسته Xray باید 1.8.0 و بالاتر باشد" -"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot)" +"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot یا در ربات دستور '/id' را وارد کنید)" "subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید" [pages.client] @@ -240,7 +240,7 @@ "telegramToken" = "توکن تلگرام" "telegramTokenDesc" = "توکن را باید از مدیر بات های تلگرام دریافت کنید @botfather" "telegramChatId" = "آی دی تلگرام مدیریت" -"telegramChatIdDesc" = "از @userinfobot برای دریافت شناسه های چت خود استفاده کنید. با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. " +"telegramChatIdDesc" = "از @userinfobot یا دستور '/id' در ربات برای دریافت شناسه های چت خود استفاده کنید. با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. " "telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام" "telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید " "tgNotifyBackup" = "پشتیبان گیری از پایگاه داده" @@ -399,6 +399,7 @@ "welcome" = "🤖 به ربات مدیریت {{ .Hostname }} خوش آمدید.\r\n" "status" = "✅ ربات در حالت عادی است!" "usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!" +"getID" = "🆔 شناسه شما: {{ .ID }}" "helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n/usage [ایمیل]\r\n \r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n/inbound [توضیح]" "helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفاده کنید:\r\n \r\n/usage [UUID|رمز عبور]\r\n \r\nاز UUID برای vmess/vless و از رمز عبور برای Trojan استفاده کنید." diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index a96cad1c..45a73a97 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -168,7 +168,7 @@ "setDefaultCert" = "Установить сертификат с панели" "xtlsDesc" = "Версия Xray должна быть не ниже 1.7.5" "realityDesc" = "Версия Xray должна быть не ниже 1.8.0" -"telegramDesc" = "Используйте Telegram ID без @ или ID пользователя (вы можете получить его у @userinfobot)" +"telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)" "subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких конфигураций" [pages.client] @@ -240,7 +240,7 @@ "telegramToken" = "Токен Telegram бота" "telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather" "telegramChatId" = "Telegram ID админа бота" -"telegramChatIdDesc" = "Несколько Telegram ID, разделённых запятой. Используйте @userinfobot, чтобы получить Telegram ID" +"telegramChatIdDesc" = "Множественные идентификаторы чата, разделенные запятыми. Чтобы получить свои идентификаторы чатов, используйте @userinfobot или команду '/id' в боте." "telegramNotifyTime" = "Частота уведомлений бота Telegram" "telegramNotifyTimeDesc" = "Используйте формат времени Crontab" "tgNotifyBackup" = "Резервное копирование базы данных" @@ -399,6 +399,7 @@ "welcome" = "🤖 Добро пожаловать в бота управления {{ .Hostname }}.\r\n" "status" = "✅ Бот работает нормально!" "usage" = "❗ Пожалуйста, укажите текст для поиска!" +"getID" = "🆔 Ваш ID: {{ .ID }}" "helpAdminCommands" = "Поиск по электронной почте клиента:\r\n/usage [Email]\r\n \r\nПоиск входящих соединений (со статистикой клиента):\r\n/inbound [Remark]" "helpClientCommands" = "Для получения статистики используйте следующую команду:\r\n \r\n/usage [UUID|Password]\r\n \r\nИспользуйте UUID для vmess/vless и пароль для Trojan." diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index febbe4b8..70c24df7 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -168,7 +168,7 @@ "setDefaultCert" = "从面板设置证书" "xtlsDesc" = "Xray核心需要1.7.5" "realityDesc" = "Xray核心需要1.8.0及以上版本" -"telegramDesc" = "使用不带@的电报 ID 或聊天 ID(您可以在此处获取 @userinfobot)" +"telegramDesc" = "使用 Telegram ID,不包含 @ 符号或聊天 ID(可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)" "subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称" [pages.client] @@ -240,7 +240,7 @@ "telegramToken" = "电报机器人TOKEN" "telegramTokenDesc" = "重启面板生效" "telegramChatId" = "以逗号分隔的多个 chatID 重启面板生效" -"telegramChatIdDesc" = "多个聊天 ID 以逗号分隔。使用@userinfobot 获取您的聊天 ID。重新启动面板以应用更改。" +"telegramChatIdDesc" = "多个聊天 ID 用逗号分隔。使用 @userinfobot 或在机器人中使用'/id'命令获取您的聊天 ID。" "telegramNotifyTime" = "电报机器人通知时间" "telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效" "tgNotifyBackup" = "数据库备份" @@ -399,6 +399,7 @@ "welcome" = "🤖 欢迎来到{{ .Hostname }}管理机器人。\r\n" "status" = "✅ 机器人正常运行!" "usage" = "❗ 请输入要搜索的文本!" +"getID" = "🆔 您的ID为:{{ .ID }}" "helpAdminCommands" = "搜索客户端邮箱:\r\n/usage [Email]\r\n \r\n搜索入站连接(包含客户端统计信息):\r\n/inbound [Remark]" "helpClientCommands" = "要搜索统计信息,请使用以下命令:\r\n \r\n/usage [UUID|Password]\r\n \r\n对于vmess/vless,请使用UUID;对于Trojan,请使用密码。" From 572d912858a1cea29408e253c3cb19146e5bd0a3 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 02:57:01 +0430 Subject: [PATCH 11/14] Update README.md --- README.md | 61 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 48f65340..a4345a69 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) 3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian)** - **If you think this project is helpful to you, you may wish to give a** :star2: **Buy Me a Coffee :** @@ -24,11 +23,12 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install. # Install custom version -To install your desired version you can add the version to the end of install command. Example for ver `v1.6.0`: +To install your desired version you can add the version to the end of install command. Example for ver `v1.6.1`: ``` -bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.6.0 +bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.6.1 ``` + # SSL ``` @@ -37,8 +37,8 @@ certbot certonly --standalone --agree-tos --register-unsafely-without-email -d y certbot renew --dry-run ``` -or you can use x-ui menu then number `16` (`SSL Certificate Management`) - +You also can use `x-ui` menu then select `16. SSL Certificate Management` + # Features - System Status Monitoring @@ -57,23 +57,24 @@ or you can use x-ui menu then number `16` (`SSL Certificate Management`) - Support export/import database from panel # Manual Install & Upgrade +
Click for Manual Install details - + 1. To download the latest version of the compressed package directly to your server, run the following command: ```sh -wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-amd64.tar.gz +[[ "$(uname -m)" == "aarch64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64" +wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz ``` -Note: If your server's CPU architecture is `arm64`, modify the URL by substituting `amd64` with your respective CPU architecture. - 2. Once the compressed package is downloaded, execute the following commands to install or upgrade x-ui: ```sh +[[ "$(uname -m)" == "aarch64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64" cd /root/ rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui -tar zxvf x-ui-linux-amd64.tar.gz +tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh cp x-ui/x-ui.sh /usr/bin/x-ui cp -f x-ui/x-ui.service /etc/systemd/system/ @@ -82,14 +83,16 @@ systemctl daemon-reload systemctl enable x-ui systemctl restart x-ui ``` -Note: If your server's CPU architecture is `arm64`, modify the `amd64` in `tar zxvf x-ui-linux-amd64.tar.gz` with your respective CPU architecture. +
- + # Install with Docker +
Click for Docker details 1. Install Docker: + ```sh bash <(curl -sSL https://get.docker.com) ``` @@ -100,7 +103,7 @@ Note: If your server's CPU architecture is `arm64`, modify the `amd64` in `tar z git clone https://github.com/MHSanaei/3x-ui.git cd 3x-ui ``` - + 3. Start the Service ```sh @@ -119,12 +122,14 @@ Note: If your server's CPU architecture is `arm64`, modify the `amd64` in `tar z --name 3x-ui \ ghcr.io/mhsanaei/3x-ui:latest ``` +
- + # Default settings +
Click for Default settings details - + - Port: 2053 - username and password will be generated randomly if you skip to modify your own security(x-ui "7") - database path: /etc/x-ui/x-ui.db @@ -141,10 +146,10 @@ After you set ssl on settings
# Xray Configurations: - +
Click for Xray Configurations details - + **copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install) - [traffic](./media/configs/traffic.json) @@ -152,14 +157,14 @@ After you set ssl on settings - [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json) - [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json) - [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json) - +
- + # [WARP Configuration](https://github.com/fscarmen/warp) (Optional) - +
Click for WARP Configuration details - + If you want to use routing to WARP follow steps as below: 1. If you already installed warp, you can uninstall using below command: @@ -171,7 +176,7 @@ If you want to use routing to WARP follow steps as below: 2. Install WARP on **socks proxy mode**: ```sh - curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash + bash <(curl -sSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh) ``` 3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json) @@ -181,14 +186,14 @@ If you want to use routing to WARP follow steps as below: - Block Ads - Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP - Fix Google 403 error - +
# Telegram Bot - +
Click for Telegram Bot details - + X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html) Set the robot-related parameters in the panel background, including: @@ -223,12 +228,14 @@ Reference syntax: - Check server status - Check depleted users - Receive backup by request and in periodic reports +- Multi language bot
# API routes +
Click for API routes details - + - `/login` with `PUSH` user data: `{username: '', password: ''}` for login - `/panel/api/inbounds` base for following actions: @@ -261,6 +268,7 @@ Reference syntax:
# Environment Variables +
Click for Environment Variables details @@ -276,6 +284,7 @@ Example: ```sh XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go ``` +
# A Special Thanks To From c565a429afd8f3fb1a9aa40c8db696090b91fd54 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 03:29:59 +0430 Subject: [PATCH 12/14] fix setting --- web/controller/setting.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/controller/setting.go b/web/controller/setting.go index 306563a2..cd509293 100644 --- a/web/controller/setting.go +++ b/web/controller/setting.go @@ -93,7 +93,7 @@ func (a *SettingController) getDefaultSettings(c *gin.Context) { } subTLS := false - if result["subKeyFile"].(string) != "" || result["subCertFile"].(string) != "" { + if result["subKeyFile"] != "" || result["subCertFile"] != "" { subTLS = true } result["subTLS"] = subTLS From 2a9cb6d29ebb8078f23201a3bc2767e66a7dbc98 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 04:25:13 +0430 Subject: [PATCH 13/14] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a4345a69..9ba7e289 100644 --- a/README.md +++ b/README.md @@ -64,14 +64,16 @@ You also can use `x-ui` menu then select `16. SSL Certificate Management` 1. To download the latest version of the compressed package directly to your server, run the following command: ```sh -[[ "$(uname -m)" == "aarch64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64" +ARCH=$(uname -m) +[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64" wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz ``` 2. Once the compressed package is downloaded, execute the following commands to install or upgrade x-ui: ```sh -[[ "$(uname -m)" == "aarch64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64" +ARCH=$(uname -m) +[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64" cd /root/ rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz From d694e6eafccad246c63264714897316f671d6428 Mon Sep 17 00:00:00 2001 From: Hamidreza <70919649+hamid-gh98@users.noreply.github.com> Date: Wed, 31 May 2023 05:01:20 +0330 Subject: [PATCH 14/14] FIX tgbot adminIds --- web/service/tgbot.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/web/service/tgbot.go b/web/service/tgbot.go index 0ad7c3c6..d1a1e3fe 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -77,13 +77,15 @@ func (t *Tgbot) Start(i18nFS embed.FS) error { return err } - for _, adminId := range strings.Split(tgBotid, ",") { - id, err := strconv.Atoi(adminId) - if err != nil { - logger.Warning("Failed to get IDs from GetTgBotChatId:", err) - return err + if tgBotid != "" { + for _, adminId := range strings.Split(tgBotid, ",") { + id, err := strconv.Atoi(adminId) + if err != nil { + logger.Warning("Failed to get IDs from GetTgBotChatId:", err) + return err + } + adminIds = append(adminIds, int64(id)) } - adminIds = append(adminIds, int64(id)) } bot, err = telego.NewBot(tgBottoken)