diff --git a/README.md b/README.md
index 96952794..60c6c044 100644
--- a/README.md
+++ b/README.md
@@ -25,10 +25,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
## Install Custom Version
-To install your desired version, add the version to the end of the installation command. e.g., ver `v2.1.0`:
+To install your desired version, add the version to the end of the installation command. e.g., ver `v2.1.1`:
```
-bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.0
+bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.1
```
## Manual Install & Upgrade
@@ -103,6 +103,15 @@ systemctl restart x-ui
ghcr.io/mhsanaei/3x-ui:latest
```
+update to latest version
+
+ ```sh
+ cd 3x-ui
+ docker compose down
+ docker compose pull 3x-ui
+ docker compose up -d
+ ```
+
@@ -196,26 +205,6 @@ certbot renew --dry-run
-
-## Xray Configurations
-
-
- Click for Xray configurations details
-
-#### Usage
-
-**1.** Copy & paste into the Advanced Xray Configuration:
-
-- [traffic](./media/configs/traffic.json)
-- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
-- [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)
-
-***Tip:*** *You don't need to do this for a fresh install.*
-
-
-
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
@@ -223,21 +212,21 @@ certbot renew --dry-run
#### Usage
-If you want to use routing to WARP follow steps as below:
+If you want to use routing to WARP before v2.1.0 follow steps as below:
-**1.** If you already installed warp, you can uninstall using below command:
-
- ```sh
- warp u
- ```
-
-**2.** Install WARP on **SOCKS Proxy Mode**:
+**1.** Install WARP on **SOCKS Proxy Mode**:
```sh
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/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)
+**2.** If you already installed warp, you can uninstall using below command:
+
+ ```sh
+ warp u
+ ```
+
+**3.** Turn on the config you need in panel
Config Features:
diff --git a/config/version b/config/version
index eca07e4c..c212a7fe 100644
--- a/config/version
+++ b/config/version
@@ -1 +1,2 @@
-2.1.2
+2.1.1
+
diff --git a/database/model/model.go b/database/model/model.go
index c9f9c105..e2d54436 100644
--- a/database/model/model.go
+++ b/database/model/model.go
@@ -37,7 +37,7 @@ type Inbound struct {
// config part
Listen string `json:"listen" form:"listen"`
- Port int `json:"port" form:"port" gorm:"unique"`
+ Port int `json:"port" form:"port"`
Protocol Protocol `json:"protocol" form:"protocol"`
Settings string `json:"settings" form:"settings"`
StreamSettings string `json:"streamSettings" form:"streamSettings"`
diff --git a/install.sh b/install.sh
index d0118c36..8fd42ab8 100644
--- a/install.sh
+++ b/install.sh
@@ -167,6 +167,7 @@ install_x-ui() {
# Check the system's architecture and rename the file accordingly
if [[ $(arch3xui) == "armv5" || $(arch3xui) == "armv6" || $(arch3xui) == "armv7" ]]; then
+
mv bin/xray-linux-$(arch3xui) bin/xray-linux-arm
chmod +x bin/xray-linux-arm
fi
diff --git a/media/configs/traffic+block-ads+ipv4-google.json b/media/configs/traffic+block-ads+ipv4-google.json
deleted file mode 100644
index d6cf9171..00000000
--- a/media/configs/traffic+block-ads+ipv4-google.json
+++ /dev/null
@@ -1,97 +0,0 @@
-{
- "log": {
- "loglevel": "warning",
- "error": "./error.log"
- },
- "api": {
- "tag": "api",
- "services": [
- "HandlerService",
- "LoggerService",
- "StatsService"
- ]
- },
- "inbounds": [
- {
- "tag": "api",
- "listen": "127.0.0.1",
- "port": 62789,
- "protocol": "dokodemo-door",
- "settings": {
- "address": "127.0.0.1"
- }
- }
- ],
- "outbounds": [
- {
- "protocol": "freedom",
- "settings": {}
- },
- {
- "tag": "blocked",
- "protocol": "blackhole",
- "settings": {}
- },
- {
- "tag": "IPv4",
- "protocol": "freedom",
- "settings": {
- "domainStrategy": "UseIPv4"
- }
- }
- ],
- "policy": {
- "levels": {
- "0": {
- "statsUserDownlink": true,
- "statsUserUplink": true
- }
- },
- "system": {
- "statsInboundDownlink": true,
- "statsInboundUplink": true
- }
- },
- "routing": {
- "domainStrategy": "IPIfNonMatch",
- "rules": [
- {
- "type": "field",
- "inboundTag": [
- "api"
- ],
- "outboundTag": "api"
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "ip": [
- "geoip:private"
- ]
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "protocol": [
- "bittorrent"
- ]
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "domain": [
- "geosite:category-ads-all",
- "ext:geosite_IR.dat:category-ads-all"
- ]
- },
- {
- "type": "field",
- "outboundTag": "IPv4",
- "domain": [
- "geosite:google"
- ]
- }
- ]
- },
- "stats": {}
-}
\ No newline at end of file
diff --git a/media/configs/traffic+block-ads+warp.json b/media/configs/traffic+block-ads+warp.json
deleted file mode 100644
index c85cd2e9..00000000
--- a/media/configs/traffic+block-ads+warp.json
+++ /dev/null
@@ -1,105 +0,0 @@
-{
- "log": {
- "loglevel": "warning",
- "error": "./error.log"
- },
- "api": {
- "tag": "api",
- "services": [
- "HandlerService",
- "LoggerService",
- "StatsService"
- ]
- },
- "inbounds": [
- {
- "tag": "api",
- "listen": "127.0.0.1",
- "port": 62789,
- "protocol": "dokodemo-door",
- "settings": {
- "address": "127.0.0.1"
- }
- }
- ],
- "outbounds": [
- {
- "protocol": "freedom",
- "settings": {}
- },
- {
- "tag": "blocked",
- "protocol": "blackhole",
- "settings": {}
- },
- {
- "tag": "WARP",
- "protocol": "socks",
- "settings": {
- "servers": [
- {
- "address": "127.0.0.1",
- "port": 40000
- }
- ]
- }
- }
- ],
- "policy": {
- "levels": {
- "0": {
- "statsUserDownlink": true,
- "statsUserUplink": true
- }
- },
- "system": {
- "statsInboundDownlink": true,
- "statsInboundUplink": true
- }
- },
- "routing": {
- "domainStrategy": "IPIfNonMatch",
- "rules": [
- {
- "type": "field",
- "inboundTag": [
- "api"
- ],
- "outboundTag": "api"
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "ip": [
- "geoip:private"
- ]
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "protocol": [
- "bittorrent"
- ]
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "domain": [
- "geosite:category-ads-all",
- "ext:geosite_IR.dat:category-ads-all"
- ]
- },
- {
- "type": "field",
- "outboundTag": "WARP",
- "domain": [
- "geosite:spotify",
- "geosite:netflix",
- "geosite:openai",
- "geosite:google"
- ]
- }
- ]
- },
- "stats": {}
-}
\ No newline at end of file
diff --git a/media/configs/traffic+block-iran-domains.json b/media/configs/traffic+block-iran-domains.json
deleted file mode 100644
index 96de15fd..00000000
--- a/media/configs/traffic+block-iran-domains.json
+++ /dev/null
@@ -1,84 +0,0 @@
-{
- "log": {
- "loglevel": "warning",
- "error": "./error.log"
- },
- "api": {
- "tag": "api",
- "services": [
- "HandlerService",
- "LoggerService",
- "StatsService"
- ]
- },
- "inbounds": [
- {
- "tag": "api",
- "listen": "127.0.0.1",
- "port": 62789,
- "protocol": "dokodemo-door",
- "settings": {
- "address": "127.0.0.1"
- }
- }
- ],
- "outbounds": [
- {
- "protocol": "freedom",
- "settings": {}
- },
- {
- "tag": "blocked",
- "protocol": "blackhole",
- "settings": {}
- }
- ],
- "policy": {
- "levels": {
- "0": {
- "statsUserDownlink": true,
- "statsUserUplink": true
- }
- },
- "system": {
- "statsInboundDownlink": true,
- "statsInboundUplink": true
- }
- },
- "routing": {
- "domainStrategy": "IPIfNonMatch",
- "rules": [
- {
- "type": "field",
- "inboundTag": [
- "api"
- ],
- "outboundTag": "api"
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "ip": [
- "geoip:private"
- ]
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "protocol": [
- "bittorrent"
- ]
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "domain": [
- "regexp:.*\\.ir$",
- "regexp:.*\\.xn--mgba3a4f16a$",
- "ext:geosite_IR.dat:ir"
- ]
- }
- ]
- },
- "stats": {}
-}
\ No newline at end of file
diff --git a/media/configs/traffic+block-iran-ip.json b/media/configs/traffic+block-iran-ip.json
deleted file mode 100644
index 5e7d3f29..00000000
--- a/media/configs/traffic+block-iran-ip.json
+++ /dev/null
@@ -1,76 +0,0 @@
-{
- "log": {
- "loglevel": "warning",
- "error": "./error.log"
- },
- "api": {
- "tag": "api",
- "services": [
- "HandlerService",
- "LoggerService",
- "StatsService"
- ]
- },
- "inbounds": [
- {
- "tag": "api",
- "listen": "127.0.0.1",
- "port": 62789,
- "protocol": "dokodemo-door",
- "settings": {
- "address": "127.0.0.1"
- }
- }
- ],
- "outbounds": [
- {
- "protocol": "freedom",
- "settings": {}
- },
- {
- "tag": "blocked",
- "protocol": "blackhole",
- "settings": {}
- }
- ],
- "policy": {
- "levels": {
- "0": {
- "statsUserDownlink": true,
- "statsUserUplink": true
- }
- },
- "system": {
- "statsInboundDownlink": true,
- "statsInboundUplink": true
- }
- },
- "routing": {
- "domainStrategy": "IPIfNonMatch",
- "rules": [
- {
- "type": "field",
- "inboundTag": [
- "api"
- ],
- "outboundTag": "api"
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "ip": [
- "geoip:private",
- "ext:geoip_IR.dat:ir"
- ]
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "protocol": [
- "bittorrent"
- ]
- }
- ]
- },
- "stats": {}
-}
\ No newline at end of file
diff --git a/media/configs/traffic.json b/media/configs/traffic.json
deleted file mode 100644
index 63c3537e..00000000
--- a/media/configs/traffic.json
+++ /dev/null
@@ -1,65 +0,0 @@
-{
- "log": {
- "loglevel": "warning",
- "error": "./error.log"
- },
- "api": {
- "tag": "api",
- "services": ["HandlerService", "LoggerService", "StatsService"]
- },
- "inbounds": [
- {
- "tag": "api",
- "listen": "127.0.0.1",
- "port": 62789,
- "protocol": "dokodemo-door",
- "settings": {
- "address": "127.0.0.1"
- }
- }
- ],
- "outbounds": [
- {
- "protocol": "freedom",
- "settings": {}
- },
- {
- "tag": "blocked",
- "protocol": "blackhole",
- "settings": {}
- }
- ],
- "policy": {
- "levels": {
- "0": {
- "statsUserDownlink": true,
- "statsUserUplink": true
- }
- },
- "system": {
- "statsInboundDownlink": true,
- "statsInboundUplink": true
- }
- },
- "routing": {
- "domainStrategy": "IPIfNonMatch",
- "rules": [
- {
- "type": "field",
- "inboundTag": ["api"],
- "outboundTag": "api"
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "ip": ["geoip:private"]
- },
- {
- "type": "field",
- "outboundTag": "blocked",
- "protocol": ["bittorrent"]
- }
- ]
- },
- "stats": {}
-}
diff --git a/web/controller/inbound.go b/web/controller/inbound.go
index 123c486f..86da9813 100644
--- a/web/controller/inbound.go
+++ b/web/controller/inbound.go
@@ -85,7 +85,11 @@ func (a *InboundController) addInbound(c *gin.Context) {
}
user := session.GetLoginUser(c)
inbound.UserId = user.Id
- inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
+ if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
+ inbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
+ } else {
+ inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
+ }
needRestart := false
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
@@ -278,7 +282,11 @@ func (a *InboundController) importInbound(c *gin.Context) {
user := session.GetLoginUser(c)
inbound.Id = 0
inbound.UserId = user.Id
- inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
+ if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
+ inbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
+ } else {
+ inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
+ }
for index := range inbound.ClientStats {
inbound.ClientStats[index].Id = 0
diff --git a/web/html/xui/index.html b/web/html/xui/index.html
index d3357a4d..3c01a29f 100644
--- a/web/html/xui/index.html
+++ b/web/html/xui/index.html
@@ -93,7 +93,7 @@
{{ i18n "pages.index.xrayStatus" }}:
- [[ status.xray.state.toUpperCase() ]]
+ [[ status.xray.state ]]
An error occurred while running Xray
diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html
index 4be7d2b5..0acbbfec 100644
--- a/web/html/xui/settings.html
+++ b/web/html/xui/settings.html
@@ -244,7 +244,7 @@
-
+
diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html
index 54d40993..05f5f178 100644
--- a/web/html/xui/xray.html
+++ b/web/html/xui/xray.html
@@ -1122,26 +1122,6 @@
this.templateRuleSetter({ outboundTag: "warp", property: "domain", data: newValue });
}
},
- manualBlockedIPs: {
- get: function () { return JSON.stringify(this.blockedIPs, null, 2); },
- set: debounce(function (value) { this.blockedIPs = JSON.parse(value); }, 1000)
- },
- manualBlockedDomains: {
- get: function () { return JSON.stringify(this.blockedDomains, null, 2); },
- set: debounce(function (value) { this.blockedDomains = JSON.parse(value); }, 1000)
- },
- manualDirectIPs: {
- get: function () { return JSON.stringify(this.directIPs, null, 2); },
- set: debounce(function (value) { this.directIPs = JSON.parse(value); }, 1000)
- },
- manualDirectDomains: {
- get: function () { return JSON.stringify(this.directDomains, null, 2); },
- set: debounce(function (value) { this.directDomains = JSON.parse(value); }, 1000)
- },
- manualIPv4Domains: {
- get: function () { return JSON.stringify(this.ipv4Domains, null, 2); },
- set: debounce(function (value) { this.ipv4Domains = JSON.parse(value); }, 1000)
- },
torrentSettings: {
get: function () {
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
diff --git a/web/service/inbound.go b/web/service/inbound.go
index 30619791..2a4966ab 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -38,9 +38,25 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
return inbounds, nil
}
-func (s *InboundService) checkPortExist(port int, ignoreId int) (bool, error) {
+func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) {
db := database.GetDB()
- db = db.Model(model.Inbound{}).Where("port = ?", port)
+ if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
+ db = db.Model(model.Inbound{}).Where("port = ?", port)
+ } else {
+ db = db.Model(model.Inbound{}).
+ Where("port = ?", port).
+ Where(
+ db.Model(model.Inbound{}).Where(
+ "listen = ?", listen,
+ ).Or(
+ "listen = \"\"",
+ ).Or(
+ "listen = \"0.0.0.0\"",
+ ).Or(
+ "listen = \"::\"",
+ ).Or(
+ "listen = \"::0\""))
+ }
if ignoreId > 0 {
db = db.Where("id != ?", ignoreId)
}
@@ -135,7 +151,7 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
}
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, bool, error) {
- exist, err := s.checkPortExist(inbound.Port, 0)
+ exist, err := s.checkPortExist(inbound.Listen, inbound.Port, 0)
if err != nil {
return inbound, false, err
}
@@ -252,7 +268,7 @@ func (s *InboundService) GetInbound(id int) (*model.Inbound, error) {
}
func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, bool, error) {
- exist, err := s.checkPortExist(inbound.Port, inbound.Id)
+ exist, err := s.checkPortExist(inbound.Listen, inbound.Port, inbound.Id)
if err != nil {
return inbound, false, err
}
@@ -295,7 +311,12 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
oldInbound.Settings = inbound.Settings
oldInbound.StreamSettings = inbound.StreamSettings
oldInbound.Sniffing = inbound.Sniffing
- oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
+ if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
+ oldInbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
+ } else {
+ oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
+ }
+
needRestart := false
s.xrayApi.Init(p.GetAPIPort())
diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml
index d7c93c71..2f1c0544 100644
--- a/web/translation/translate.en_US.toml
+++ b/web/translation/translate.en_US.toml
@@ -54,11 +54,11 @@
"security" = "Security"
[menu]
-"dashboard" = "OVERVIEW"
-"inbounds" = "INBOUNDS"
-"settings" = "PANEL SETTINGS"
-"xray" = "XRAY CONFIGS"
-"logout" = "LOG OUT"
+"dashboard" = "Overview"
+"inbounds" = "Inbounds"
+"settings" = "Panel Settings"
+"xray" = "Xray Configs"
+"logout" = "Log Out"
"link" = "Manage"
[pages.login]
@@ -319,7 +319,7 @@
"ipv4Configs" = "IPv4 Routing"
"ipv4ConfigsDesc" = "These options will route traffic based on a specific destination via IPv4."
"warpConfigs" = "WARP Routing"
-"warpConfigsDesc" = "These options will route traffic based on a specific destination via WARP. (follow the guide on the Panel’s GitHub)"
+"warpConfigsDesc" = "These options will route traffic based on a specific destination via WARP."
"Template" = "Advanced Xray Configuration Template"
"TemplateDesc" = "The final Xray config file will be generated based on this template."
"FreedomStrategy" = "Freedom Protocol Strategy"
@@ -476,7 +476,6 @@
"helpAdminCommands" = "To search for a client email:\r\n/usage [Email]
\r\n\r\nTo search for inbounds (with client stats):\r\n/inbound [Remark]
"
"helpClientCommands" = "To search for statistics, use the following command:\r\n\r\n/usage [Email]
"
-
[tgbot.messages]
"cpuThreshold" = "🔴 CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%"
"selectUserFailed" = "❌ Error in user selection!"
@@ -522,7 +521,6 @@
"yes" = "✅ Yes"
"no" = "❌ No"
-
[tgbot.buttons]
"closeKeyboard" = "❌ Close Keyboard"
"cancel" = "❌ Cancel"
@@ -556,7 +554,6 @@
"limitTraffic" = "🚧 Traffic Limit"
"getBanLogs" = "Get Ban Logs"
-
[tgbot.answers]
"successfulOperation" = "✅ Operation successful!"
"errorOperation" = "❗ Error in operation."
diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml
index fb22b8a1..42a3a2b2 100644
--- a/web/translation/translate.es_ES.toml
+++ b/web/translation/translate.es_ES.toml
@@ -1,6 +1,6 @@
"username" = "Nombre de Usuario"
"password" = "Contraseña"
-"login" = "Acceso"
+"login" = "Acceder"
"confirm" = "Confirmar"
"cancel" = "Cancelar"
"close" = "Cerrar"
@@ -59,10 +59,10 @@
"settings" = "Configuraciones"
"xray" = "Configuración Xray"
"logout" = "Cerrar Sesión"
-"link" = "Otro"
+"link" = "Gestionar"
[pages.login]
-"title" = "Grata"
+"title" = "Bienvenido"
"loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente."
[pages.login.toasts]
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml
index 307ea1d6..3edcb4cb 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -501,8 +501,8 @@
"time" = "⏰ زمان: {{ .Time }}\r\n"
"inbound" = "📍 نامورودی: {{ .Remark }}\r\n"
"port" = "🔌 پورت: {{ .Port }}\r\n"
-"expire" = "📅 تاریخانقضا: {{ .DateTime }}\r\n \r\n"
-"expireIn" = "📅 باقیماندهتاانقضا: {{ .Time }}\r\n \r\n"
+"expire" = "📅 تاریخانقضا: {{ .Time }}\r\n\r\n"
+"expireIn" = "📅 باقیماندهتاانقضا: {{ .Time }}\r\n\r\n"
"active" = "💡 فعال: {{ .Enable }}\r\n"
"enabled" = "🚨 وضعیت: {{ .Enable }}\r\n"
"online" = "🌐 وضعیت اتصال: {{ .Status }}\r\n"
@@ -515,7 +515,7 @@
"exhaustedCount" = "🚨 تعداد {{ .Type }} بهاتمامرسیدهاست:\r\n"
"onlinesCount" = "🌐 کاربرانآنلاین: {{ .Count }}\r\n"
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
-"depleteSoon" = "🔜 بهزودیبهپایانخواهدرسید: {{ .Deplete }}\r\n \r\n"
+"depleteSoon" = "🔜 بهزودیبهپایانخواهدرسید: {{ .Deplete }}\r\n\r\n"
"backupTime" = "🗄 زمانپشتیبانگیری: {{ .Time }}\r\n"
"refreshedOn" = "\r\n📋🔄 تازهسازی شده در: {{ .Time }}\r\n\r\n"
"yes" = "✅ بله"
@@ -537,7 +537,6 @@
"clientUsage" = "دریافت آمار کاربر"
"onlines" = "کاربران آنلاین"
"commands" = "دستورات"
-
"refresh" = "🔄 تازهسازی"
"clearIPs" = "❌ پاکسازی آدرسها"
"removeTGUser" = "❌ حذف کاربر تلگرام"
diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml
index 8d91562c..d8211d8d 100644
--- a/web/translation/translate.ru_RU.toml
+++ b/web/translation/translate.ru_RU.toml
@@ -503,7 +503,7 @@
"port" = "🔌 Порт: {{ .Port }}\r\n"
"expire" = "📅 Дата окончания: {{ .Time }}\r\n"
"expireIn" = "📅 Окончание через: {{ .Time }}\r\n"
-"active" = "💡 Активен: ✅ Да\r\n"
+"active" = "💡 Активен: {{ .Enable }}\r\n"
"enabled" = "🚨 Включен: {{ .Enable }}\r\n"
"online" = "🌐 Статус соединения: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n"
diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml
index 7cb0230b..7491a70c 100644
--- a/web/translation/translate.vi_VN.toml
+++ b/web/translation/translate.vi_VN.toml
@@ -32,7 +32,7 @@
"transmission" = "Truyền tải"
"host" = "Máy chủ"
"path" = "Đường dẫn"
-"camouflage" = "camouflage"
+"camouflage" = "Ngụy trang"
"status" = "Trạng thái"
"enabled" = "Đã kích hoạt"
"disabled" = "Đã tắt"
@@ -125,8 +125,8 @@
"modifyInbound" = "Chỉnh sửa điểm vào (Inbound)"
"deleteInbound" = "Xóa điểm vào (Inbound)"
"deleteInboundContent" = "Xác nhận xóa điểm vào? (Inbound)"
-"deleteClient" = "Xóa khách hàng"
-"deleteClientContent" = "Bạn có chắc chắn muốn xóa ứng dụng khách không?"
+"deleteClient" = "Xóa người dùng"
+"deleteClientContent" = "Bạn có chắc chắn muốn xóa người dùng không?"
"resetTrafficContent" = "Xác nhận đặt lại lưu lượng?"
"copyLink" = "Sao chép liên kết"
"address" = "Địa chỉ"
@@ -145,7 +145,7 @@
"keyPath" = "Đường dẫn khóa riêng tư"
"keyContent" = "Nội dung khóa riêng tư"
"clickOnQRcode" = "Nhấn vào Mã QR để sao chép"
-"client" = "Máy Khách"
+"client" = "Người dùng"
"export" = "Xuất liên kết"
"clone" = "Sao chép"
"cloneInbound" = "Sao chép điểm vào (Inbound)"
@@ -154,15 +154,15 @@
"resetAllTraffic" = "Đặt lại lưu lượng cho tất cả điểm vào"
"resetAllTrafficTitle" = "Đặt lại lưu lượng cho tất cả điểm vào"
"resetAllTrafficContent" = "Bạn có chắc chắn muốn đặt lại lưu lượng cho tất cả điểm vào không?"
-"resetInboundClientTraffics" = "Đặt lại lưu lượng cho các client của điểm vào"
-"resetInboundClientTrafficTitle" = "Đặt lại lưu lượng cho tất cả lưu lượng của client"
-"resetInboundClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho các client của điểm vào này không?"
-"resetAllClientTraffics" = "Đặt lại lưu lượng cho tất cả client"
-"resetAllClientTrafficTitle" = "Đặt lại lưu lượng cho tất cả client"
-"resetAllClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho tất cả client không?"
-"delDepletedClients" = "Xóa các client đã cạn kiệt"
-"delDepletedClientsTitle" = "Xóa các client đã cạn kiệt"
-"delDepletedClientsContent" = "Bạn có chắc chắn muốn xóa tất cả các client đã cạn kiệt không?"
+"resetInboundClientTraffics" = "Đặt lại lưu lượng toàn bộ người dùng của điểm vào"
+"resetInboundClientTrafficTitle" = "Đặt lại lưu lượng cho toàn bộ người dùng của điểm vào"
+"resetInboundClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho các người dùng của điểm vào này không?"
+"resetAllClientTraffics" = "Đặt lại lưu lượng cho toàn bộ người dùng"
+"resetAllClientTrafficTitle" = "Đặt lại lưu lượng cho toàn bộ người dùng"
+"resetAllClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho toàn bộ người dùng không?"
+"delDepletedClients" = "Xóa các người dùng đã cạn kiệt"
+"delDepletedClientsTitle" = "Xóa các người dùng đã cạn kiệt"
+"delDepletedClientsContent" = "Bạn có chắc chắn muốn xóa toàn bộ người dùng đã cạn kiệt không?"
"email" = "Email"
"emailDesc" = "Vui lòng cung cấp một địa chỉ email duy nhất."
"IPLimit" = "Giới hạn IP"
@@ -174,20 +174,20 @@
"xtlsDesc" = "Xray core cần phiên bản 1.7.5"
"realityDesc" = "Xray core cần phiên bản 1.8.0 hoặc cao hơn."
"telegramDesc" = "Chỉ sử dụng ID trò chuyện (bạn có thể nhận được nó ở đây @userinfobot hoặc sử dụng lệnh '/id' trong bot)"
-"subscriptionDesc" = "Bạn có thể tìm liên kết đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau"
+"subscriptionDesc" = "Bạn có thể tìm liên kết gói đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau"
"info" = "Thông tin"
"same" = "Giống nhau"
"inboundData" = "Dữ liệu gửi đến"
"copyToClipboard" = "Sao chép vào bảng nhớ tạm"
"import" = "Nhập"
-"importInbound" = "Nhập hàng gửi về"
+"importInbound" = "Nhập inbound"
[pages.client]
-"add" = "Thêm Client"
-"edit" = "Chỉnh sửa Client"
-"submitAdd" = "Thêm Client"
+"add" = "Thêm người dùng"
+"edit" = "Chỉnh sửa người dùng"
+"submitAdd" = "Thêm"
"submitEdit" = "Lưu thay đổi"
-"clientCount" = "Số lượng Client"
+"clientCount" = "Số lượng người dùng"
"bulk" = "Thêm hàng loạt"
"method" = "Phương pháp"
"first" = "Đầu tiên"
@@ -212,11 +212,11 @@
[pages.inbounds.stream.tcp]
"version" = "Phiên bản"
"method" = "Phương pháp"
-"path" = "Con đường"
+"path" = "Đường dẫn"
"status" = "Trạng thái"
"statusDescription" = "Tình trạng Mô tả"
-"requestHeader" = "Tiêu đề yêu cầu"
-"responseHeader" = "Tiêu đề phản hồi"
+"requestHeader" = "Header yêu cầu"
+"responseHeader" = "Header phản hồi"
[pages.inbounds.stream.quic]
"encryption" = "Mã hóa"
@@ -225,30 +225,30 @@
"title" = "Cài đặt"
"save" = "Lưu"
"infoDesc" = "Mọi thay đổi được thực hiện ở đây cần phải được lưu. Vui lòng khởi động lại bảng điều khiển để áp dụng các thay đổi."
-"restartPanel" = "Khởi động lại Bảng điều khiển"
+"restartPanel" = "Khởi động lại bảng điều khiển"
"restartPanelDesc" = "Bạn có chắc chắn muốn khởi động lại bảng điều khiển? Nhấn OK để khởi động lại sau 3 giây. Nếu bạn không thể truy cập bảng điều khiển sau khi khởi động lại, vui lòng xem thông tin nhật ký của bảng điều khiển trên máy chủ."
"actions" = "Hành động"
-"resetDefaultConfig" = "Đặt lại Cấu hình Mặc định"
-"panelSettings" = "Cài đặt Bảng điều khiển"
-"securitySettings" = "Cài đặt Bảo mật"
-"TGBotSettings" = "Cài đặt Bot Telegram"
-"panelListeningIP" = "IP Nghe của Bảng điều khiển"
+"resetDefaultConfig" = "Đặt lại cấu hình mặc định"
+"panelSettings" = "Bảng điều khiển"
+"securitySettings" = "Bảo mật"
+"TGBotSettings" = "Bot Telegram"
+"panelListeningIP" = "IP Nghe của bảng điều khiển"
"panelListeningIPDesc" = "Mặc định để trống để nghe tất cả các IP."
-"panelListeningDomain" = "Tên miền của nghe Bảng điều khiển"
+"panelListeningDomain" = "Tên miền của nghe bảng điều khiển"
"panelListeningDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
-"panelPort" = "Cổng Bảng điều khiển"
-"panelPortDesc" = "Cổng được sử dụng để hiển thị bảng điều khiển này"
-"publicKeyPath" = "Đường dẫn tập tin khóa công khai Chứng chỉ Bảng điều khiển"
-"publicKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với."
-"privateKeyPath" = "Đường dẫn tập tin khóa riêng tư Chứng chỉ Bảng điều khiển"
-"privateKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với."
-"panelUrlPath" = "Đường dẫn gốc URL Bảng điều khiển"
-"panelUrlPathDesc" = "Phải bắt đầu bằng '/' và kết thúc bằng."
+"panelPort" = "Cổng bảng điều khiển"
+"panelPortDesc" = "Cổng được sử dụng để kết nối với bảng điều khiển này"
+"publicKeyPath" = "Đường dẫn file chứng chỉ bảng điều khiển"
+"publicKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')"
+"privateKeyPath" = "Đường dẫn file khóa của chứng chỉ bảng điều khiển"
+"privateKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')"
+"panelUrlPath" = "Đường dẫn gốc URL bảng điều khiển"
+"panelUrlPathDesc" = "Phải bắt đầu và kết thúc bằng '/'"
"pageSize" = "Kích thước phân trang"
"pageSizeDesc" = "Xác định kích thước trang cho bảng gửi đến. Đặt 0 để tắt"
"remarkModel" = "Ghi chú mô hình và ký tự phân tách"
-"datepicker" = "bảng chọn ngày"
-"datepickerDescription" = "Loại lịch chọn chỉ định ngày hết hạn"
+"datepicker" = "Kiểu lịch"
+"datepickerDescription" = "Tác vụ chạy theo lịch trình sẽ chạy theo kiểu lịch này."
"sampleRemark" = "Nhận xét mẫu"
"oldUsername" = "Tên người dùng hiện tại"
"currentPassword" = "Mật khẩu hiện tại"
@@ -268,7 +268,7 @@
"tgNotifyBackupDesc" = "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo."
"tgNotifyLogin" = "Thông báo Đăng nhập"
"tgNotifyLoginDesc" = "Hiển thị tên người dùng, địa chỉ IP và thời gian khi ai đó cố gắng đăng nhập vào bảng điều khiển của bạn."
-"sessionMaxAge" = "Tuổi tối đa của phiên"
+"sessionMaxAge" = "Thời gian tối đa của phiên"
"sessionMaxAgeDesc" = "Thời gian của phiên đăng nhập (đơn vị: phút)"
"expireTimeDiff" = "Ngưỡng hết hạn cho thông báo"
"expireTimeDiffDesc" = "Nhận thông báo về việc hết hạn tài khoản trước ngưỡng này (đơn vị: ngày)"
@@ -278,29 +278,29 @@
"tgNotifyCpuDesc" = "Nhận thông báo nếu tỷ lệ sử dụng CPU vượt quá ngưỡng này (đơn vị: %)"
"timeZone" = "Múi giờ"
"timeZoneDesc" = "Các tác vụ được lên lịch chạy theo thời gian trong múi giờ này."
-"subSettings" = "Đăng ký"
+"subSettings" = "Gói đăng ký"
"subEnable" = "Bật dịch vụ"
-"subEnableDesc" = "Tính năng đăng ký với cấu hình riêng"
+"subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng"
"subListen" = "Listening IP"
"subListenDesc" = "Mặc định để trống để nghe tất cả các IP"
-"subPort" = "Cổng Đăng ký"
+"subPort" = "Cổng gói đăng ký"
"subPortDesc" = "Số cổng dịch vụ đăng ký phải chưa được sử dụng trên máy chủ"
-"subCertPath" = "Đường dẫn tập tin khóa công khai Chứng chỉ Đăng ký"
-"subCertPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với '/'"
-"subKeyPath" = "Đường dẫn tập tin khóa riêng tư Chứng chỉ Đăng ký"
-"subKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với '/'"
-"subPath" = "Đường dẫn gốc URL Đăng ký"
-"subPathDesc" = "Phải bắt đầu bằng '/' và kết thúc bằng '/'"
+"subCertPath" = "Đường dẫn file chứng chỉ gói đăng ký"
+"subCertPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu với '/')"
+"subKeyPath" = "Đường dẫn file khóa của chứng chỉ gói đăng ký"
+"subKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu với '/')"
+"subPath" = "Đường dẫn gốc URL gói đăng ký"
+"subPathDesc" = "Phải bắt đầu và kết thúc bằng '/'"
"subDomain" = "Tên miền con"
"subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
-"subUpdates" = "Khoảng thời gian cập nhật đăng ký"
+"subUpdates" = "Khoảng thời gian cập nhật gói đăng ký"
"subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách"
"subEncrypt" = "Mã hóa cấu hình"
-"subEncryptDesc" = "Mã hóa các cấu hình được trả về trong đăng ký"
+"subEncryptDesc" = "Mã hóa các cấu hình được trả về trong gói đăng ký"
"subShowInfo" = "Hiển thị thông tin sử dụng"
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
-"subURI" = "URI proxy ngược"
-"subURIDesc" = "Thay đổi URI cơ sở của URL đăng ký để sử dụng ở phía sau proxy"
+"subURI" = "URI proxy trung gian"
+"subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian"
[pages.xray]
"title" = "Cài đặt Xray"
@@ -422,7 +422,7 @@
"intercon" = "Kết nối"
[pages.xray.wireguard]
-"secretKey" = "Chìa khoá bí mật"
+"secretKey" = "Khoá bí mật"
"publicKey" = "Khóa công khai"
"allowedIPs" = "IP được phép"
"endpoint" = "Điểm cuối"
@@ -434,8 +434,8 @@
"secret" = "Mã thông báo bí mật"
"loginSecurity" = "Bảo mật đăng nhập"
"loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng"
-"secretToken" = "Mã thông báo bí mật"
-"secretTokenDesc" = "Vui lòng sao chép và lưu trữ mã thông báo này một cách an toàn ở nơi an toàn. Mã thông báo này cần thiết để đăng nhập và không thể phục hồi từ công cụ lệnh x-ui."
+"secretToken" = "Mã bí mật"
+"secretTokenDesc" = "Vui lòng sao chép và lưu trữ mã này một cách an toàn ở nơi an toàn. Mã này cần thiết để đăng nhập và không thể phục hồi từ công cụ lệnh x-ui."
[pages.settings.toasts]
"modifySettings" = "Chỉnh sửa cài đặt "
@@ -460,7 +460,7 @@
"hours" = "Giờ"
"unknown" = "Không rõ"
"inbounds" = "Vào"
-"clients" = "Các khách hàng"
+"clients" = "Các người dùng"
"offline" = "🔴 Ngoại tuyến"
"online" = "🟢 Trực tuyến"
diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml
index 32765f01..5a46ec73 100644
--- a/web/translation/translate.zh_Hans.toml
+++ b/web/translation/translate.zh_Hans.toml
@@ -503,7 +503,7 @@
"port" = "🔌 端口:{{ .Port }}\r\n"
"expire" = "📅 过期日期:{{ .Time }}\r\n"
"expireIn" = "📅 剩余时间:{{ .Time }}\r\n"
-"active" = "💡 激活:✅\r\n"
+"active" = "💡 激活:{{ .Enable }}\r\n"
"enabled" = "🚨 已启用:{{ .Enable }}\r\n"
"online" = "🌐 连接状态:{{ .Status }}\r\n"
"email" = "📧 邮箱:{{ .Email }}\r\n"
diff --git a/x-ui.sh b/x-ui.sh
index d245a247..2807bc4f 100644
--- a/x-ui.sh
+++ b/x-ui.sh
@@ -991,6 +991,12 @@ install_iplimit() {
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1 ;;
esac
+
+ if ! command -v fail2ban-client &>/dev/null; then
+ echo -e "${red}Fail2ban installation failed.${plain}\n"
+ exit 1
+ fi
+
echo -e "${green}Fail2ban installed successfully!${plain}\n"
else
echo -e "${yellow}Fail2ban is already installed.${plain}\n"