From a6742f395aa65201d56b456f97e9db39c82910ce Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 27 Jan 2024 12:56:10 +0330 Subject: [PATCH 01/37] remove multi protocol script --- DockerInit.sh | 10 +--------- Dockerfile | 14 +++++++------ README.md | 17 ++++++++++++---- install.sh | 4 ++-- x-ui.sh | 54 +++++++++++---------------------------------------- 5 files changed, 35 insertions(+), 64 deletions(-) diff --git a/DockerInit.sh b/DockerInit.sh index bbfcb04e..a2224999 100755 --- a/DockerInit.sh +++ b/DockerInit.sh @@ -1,5 +1,4 @@ #!/bin/sh - case $1 in amd64) ARCH="64" @@ -21,28 +20,21 @@ case $1 in ARCH="arm32-v6" FNAME="armv6" ;; - armv5) - ARCH="arm32-v5" - FNAME="armv5" - ;; *) ARCH="64" FNAME="amd64" ;; esac - - mkdir -p build/bin cd build/bin - wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.7/Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip" rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat mv xray "xray-linux-${FNAME}" - wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat +cd ../../ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 951d463b..7c3e64b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,6 @@ FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder WORKDIR /app ARG TARGETARCH -ENV CGO_ENABLED=1 -ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" RUN apk --no-cache --update add \ build-base \ @@ -15,6 +13,8 @@ RUN apk --no-cache --update add \ COPY . . +ENV CGO_ENABLED=1 +ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" RUN go build -o build/x-ui main.go RUN ./DockerInit.sh "$TARGETARCH" @@ -28,11 +28,12 @@ WORKDIR /app RUN apk add --no-cache --update \ ca-certificates \ tzdata \ - fail2ban + fail2ban \ + bash -COPY --from=builder /app/build/ /app/ -COPY --from=builder /app/DockerEntrypoint.sh /app/ -COPY --from=builder /app/x-ui.sh /usr/bin/x-ui +COPY --from=builder /app/build/ /app/ +COPY --from=builder /app/DockerEntrypoint.sh /app/ +COPY --from=builder /app/x-ui /usr/bin/x-ui # Configure fail2ban RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \ @@ -47,4 +48,5 @@ RUN chmod +x \ /usr/bin/x-ui VOLUME [ "/etc/x-ui" ] +CMD [ "./x-ui" ] ENTRYPOINT [ "/app/DockerEntrypoint.sh" ] diff --git a/README.md b/README.md index 389d6d1d..ec1e5a15 100644 --- a/README.md +++ b/README.md @@ -106,10 +106,19 @@ systemctl restart x-ui update to latest version ```sh - cd 3x-ui - docker compose down - docker compose pull 3x-ui - docker compose up -d + cd 3x-ui + docker compose down + docker compose pull 3x-ui + docker compose up -d + ``` + +remove 3x-ui from docker + + ```sh + docker stop 3x-ui + docker rm 3x-ui + cd -- + rm -r 3x-ui ``` diff --git a/install.sh b/install.sh index ae489c2a..9f46d4df 100644 --- a/install.sh +++ b/install.sh @@ -123,9 +123,9 @@ config_after_install() { echo -e "${green}username:${usernameTemp}${plain}" echo -e "${green}password:${passwordTemp}${plain}" echo -e "###############################################" - echo -e "${red}if you forgot your login info,you can type x-ui and then type 7 to check after installation${plain}" + echo -e "${red}if you forgot your login info,you can type x-ui and then type 8 to check after installation${plain}" else - echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 7 to check${plain}" + echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 8 to check${plain}" fi fi /usr/local/x-ui/x-ui migrate diff --git a/x-ui.sh b/x-ui.sh index c7331e6d..ffb20f06 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -806,34 +806,6 @@ warp_cloudflare() { esac } -multi_protocol() { - echo "This script only supports Vless and Vmess. if you use another protocols, DON'T INSTALL or get backup first! " - echo -e "${green}\t1.${plain} Install Multi Protocol Script" - echo -e "${green}\t2.${plain} Uninstall" - echo -e "${green}\t3.${plain} Start Service" - echo -e "${green}\t4.${plain} Stop Service" - echo -e "${green}\t0.${plain} Back to Main Menu" - read -p "Choose an option: " choice - case "$choice" in - 0) - show_menu - ;; - 1) - bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/install.sh --ipv4) - ;; - 2) - bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/unistall.sh --ipv4) - ;; - 3) - systemctl start 3xui-multi-protocol - ;; - 4) - systemctl stop 3xui-multi-protocol - ;; - *) echo "Invalid choice" ;; - esac -} - run_speedtest() { # Check if Speedtest is already installed if ! command -v speedtest &>/dev/null; then @@ -1145,22 +1117,21 @@ show_menu() { ${green}12.${plain} Check Status ${green}13.${plain} Check Logs ———————————————— - ${green}14.${plain} Enable x-ui On System Startup - ${green}15.${plain} Disable x-ui On System Startup + ${green}14.${plain} Enable Autostart + ${green}15.${plain} Disable Autostart ———————————————— ${green}16.${plain} SSL Certificate Management ${green}17.${plain} Cloudflare SSL Certificate ${green}18.${plain} IP Limit Management ${green}19.${plain} WARP Management - ${green}20.${plain} Multi Protocol Management ———————————————— - ${green}21.${plain} Enable BBR - ${green}22.${plain} Update Geo Files - ${green}23.${plain} Active Firewall and open ports - ${green}24.${plain} Speedtest by Ookla + ${green}20.${plain} Enable BBR + ${green}21.${plain} Update Geo Files + ${green}22.${plain} Active Firewall and open ports + ${green}23.${plain} Speedtest by Ookla " show_status - echo && read -p "Please enter your selection [0-24]: " num + echo && read -p "Please enter your selection [0-23]: " num case "${num}" in 0) @@ -1224,22 +1195,19 @@ show_menu() { warp_cloudflare ;; 20) - multi_protocol - ;; - 21) enable_bbr ;; - 22) + 21) update_geo ;; - 23) + 22) open_ports ;; - 24) + 23) run_speedtest ;; *) - LOGE "Please enter the correct number [0-24]" + LOGE "Please enter the correct number [0-23]" ;; esac } From 78e1194ebb32652908aa5e74dabffba9bdb3307f Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 29 Jan 2024 23:36:03 +0330 Subject: [PATCH 02/37] bug fix - traffic limit tbbot --- web/service/tgbot.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web/service/tgbot.go b/web/service/tgbot.go index fdb5d312..4ffa7bb4 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -6,9 +6,9 @@ import ( "net" "net/url" "os" + "slices" "strconv" "strings" - "slices" "time" "x-ui/config" "x-ui/database" @@ -260,7 +260,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo msg += t.I18nBot("tgbot.commands.unknown") } - if msg != ""{ + if msg != "" { if onlyMessage { t.SendMsgToTgbot(chatId, msg) return @@ -346,7 +346,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 40")), ), tu.InlineKeyboardRow( - tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")), + tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 50")), tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")), tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 80")), ), @@ -1022,7 +1022,7 @@ func (t *Tgbot) getInboundUsages() string { } func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic, printEnabled bool, printOnline bool, printActive bool, - printDate bool, printTraffic bool, printRefreshed bool) string { + printDate bool, printTraffic bool, printRefreshed bool) string { now := time.Now().Unix() expiryTime := "" @@ -1380,7 +1380,6 @@ func (t *Tgbot) getExhausted(chatId int64) { output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients")) output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients))) output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(exhaustedCC)) - if exhaustedCC > 0 { output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.clients")) @@ -1490,7 +1489,6 @@ func (t *Tgbot) onlineClients(chatId int64, messageID ...int) { output := t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(onlinesCount)) keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("onlines_refresh")))) - if onlinesCount > 0 { var buttons []telego.InlineKeyboardButton From 9f904f8f473bb3e167c5e37b0b2eff4fb5feeb92 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 29 Jan 2024 23:36:21 +0330 Subject: [PATCH 03/37] warp - ForceIP ForceIPv6v4 to ForceIP --- web/assets/js/model/outbound.js | 2 +- web/html/xui/warp_modal.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js index 42fb50a1..7207561e 100644 --- a/web/assets/js/model/outbound.js +++ b/web/assets/js/model/outbound.js @@ -915,7 +915,7 @@ Outbound.HttpSettings = class extends CommonClass { Outbound.WireguardSettings = class extends CommonClass { constructor( mtu=1420, secretKey=Wireguard.generateKeypair().privateKey, - address=[''], workers=2, domainStrategy='ForceIPv6v4', reserved='', + address=[''], workers=2, domainStrategy='ForceIP', reserved='', peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) { super(); this.mtu = mtu; diff --git a/web/html/xui/warp_modal.html b/web/html/xui/warp_modal.html index 4e6dd4fa..38310a69 100644 --- a/web/html/xui/warp_modal.html +++ b/web/html/xui/warp_modal.html @@ -140,7 +140,7 @@ mtu: 1420, secretKey: warpModal.warpData.private_key, address: Object.values(config.interface.addresses), - domainStrategy: 'ForceIPv6v4', + domainStrategy: 'ForceIP', peers: [{ publicKey: peer.public_key, endpoint: peer.endpoint.host, From fd75cca266ce5fde64cb7500bb27ba387fcc1a9c Mon Sep 17 00:00:00 2001 From: Saeid <43953720+surbiks@users.noreply.github.com> Date: Tue, 30 Jan 2024 00:02:58 +0330 Subject: [PATCH 04/37] fix bug in edit SOCKS and HTTP outbound (#1704) --- web/assets/js/model/outbound.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js index 7207561e..7724cf89 100644 --- a/web/assets/js/model/outbound.js +++ b/web/assets/js/model/outbound.js @@ -861,13 +861,13 @@ Outbound.SocksSettings = class extends CommonClass { } static fromJson(json={}) { - servers = json.servers; + let servers = json.servers; if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}]; return new Outbound.SocksSettings( servers[0].address, servers[0].port, ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user, - ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass, + ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass, ); } @@ -891,13 +891,13 @@ Outbound.HttpSettings = class extends CommonClass { } static fromJson(json={}) { - servers = json.servers; + let servers = json.servers; if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}]; return new Outbound.HttpSettings( servers[0].address, servers[0].port, ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user, - ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass, + ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass, ); } From 9fbaede59f87dd9d8b77479a3fb295c3c882630e Mon Sep 17 00:00:00 2001 From: emirjorge <117597846+emirjorge@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:33:51 -0500 Subject: [PATCH 05/37] Fix Spanish Translation in Main tittle (#1699) * Add files via upload * Update translate.es_ES.toml --- web/translation/translate.es_ES.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index 6c817a30..ac9de9a4 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -57,7 +57,7 @@ "dashboard" = "Estado del Sistema" "inbounds" = "Entradas" "settings" = "Configuraciones" -"xray" = "Configuración Xray" +"xray" = "Ajustes Xray" "logout" = "Cerrar Sesión" "link" = "Gestionar" From 6c0775b12055e4546cb0fd86e1d8d569d886eefa Mon Sep 17 00:00:00 2001 From: Saeid <43953720+surbiks@users.noreply.github.com> Date: Tue, 30 Jan 2024 00:07:20 +0330 Subject: [PATCH 06/37] Show outbound traffic in outbounds table (#1711) * store outbound traffic in database * show outbound traffic in outbounds table * add refresh button --- database/db.go | 5 +++ database/model/model.go | 9 ++++ web/controller/xray_setting.go | 11 +++++ web/html/xui/xray.html | 48 +++++++++++++++++++- web/job/xray_traffic_job.go | 15 ++++--- web/service/inbound.go | 4 +- web/service/outbound.go | 80 ++++++++++++++++++++++++++++++++++ xray/api.go | 6 ++- xray/traffic.go | 9 ++-- 9 files changed, 172 insertions(+), 15 deletions(-) create mode 100644 web/service/outbound.go diff --git a/database/db.go b/database/db.go index 8bd0fb49..c75953f0 100644 --- a/database/db.go +++ b/database/db.go @@ -21,6 +21,7 @@ var db *gorm.DB var initializers = []func() error{ initUser, initInbound, + initOutbound, initSetting, initInboundClientIps, initClientTraffic, @@ -51,6 +52,10 @@ func initInbound() error { return db.AutoMigrate(&model.Inbound{}) } +func initOutbound() error { + return db.AutoMigrate(&model.OutboundTraffics{}) +} + func initSetting() error { return db.AutoMigrate(&model.Setting{}) } diff --git a/database/model/model.go b/database/model/model.go index e2d54436..32ab255f 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -44,6 +44,15 @@ type Inbound struct { Tag string `json:"tag" form:"tag" gorm:"unique"` Sniffing string `json:"sniffing" form:"sniffing"` } + +type OutboundTraffics struct { + Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` + Tag string `json:"tag" form:"tag" gorm:"unique"` + Up int64 `json:"up" form:"up" gorm:"default:0"` + Down int64 `json:"down" form:"down" gorm:"default:0"` + Total int64 `json:"total" form:"total" gorm:"default:0"` +} + type InboundClientIps struct { Id int `json:"id" gorm:"primaryKey;autoIncrement"` ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"` diff --git a/web/controller/xray_setting.go b/web/controller/xray_setting.go index 09e9115f..430cc77b 100644 --- a/web/controller/xray_setting.go +++ b/web/controller/xray_setting.go @@ -10,6 +10,7 @@ type XraySettingController struct { XraySettingService service.XraySettingService SettingService service.SettingService InboundService service.InboundService + OutboundService service.OutboundService XrayService service.XrayService } @@ -27,6 +28,7 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) { g.GET("/getXrayResult", a.getXrayResult) g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig) g.POST("/warp/:action", a.warp) + g.GET("/getOutboundsTraffic", a.getOutboundsTraffic) } func (a *XraySettingController) getXraySetting(c *gin.Context) { @@ -84,3 +86,12 @@ func (a *XraySettingController) warp(c *gin.Context) { jsonObj(c, resp, err) } + +func (a *XraySettingController) getOutboundsTraffic(c *gin.Context) { + outboundsTraffic, err := a.OutboundService.GetOutboundsTraffic() + if err != nil { + jsonMsg(c, "Error getting traffics", err) + return + } + jsonObj(c, outboundsTraffic, nil) +} diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html index d6f0c0f8..d00b73cc 100644 --- a/web/html/xui/xray.html +++ b/web/html/xui/xray.html @@ -341,8 +341,15 @@ - {{ i18n "pages.xray.outbound.addOutbound" }} - WARP + + + {{ i18n "pages.xray.outbound.addOutbound" }} + WARP + + + + + reality + @@ -463,6 +473,7 @@ { title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 }, { title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } }, { title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } }, + { title: '{{ i18n "pages.inbounds.traffic" }}', align: 'center', width: 50, scopedSlots: { customRender: 'traffic' } }, ]; const reverseColumns = [ @@ -483,7 +494,9 @@ oldXraySetting: '', xraySetting: '', inboundTags: [], + outboundsTraffic: [], saveBtnDisable: true, + refreshing: false, restartResult: '', isMobile: window.innerWidth <= 768, advSettings: 'xraySetting', @@ -581,6 +594,12 @@ loading(spinning = true) { this.spinning = spinning; }, + async getOutboundsTraffic() { + const msg = await HttpUtil.get("/panel/xray/getOutboundsTraffic"); + if (msg.success) { + this.outboundsTraffic = msg.obj; + } + }, async getXraySetting() { this.loading(true); const msg = await HttpUtil.post("/panel/xray/"); @@ -759,6 +778,14 @@ } return true; }, + findOutboundTraffic(o) { + for (const otraffic of this.outboundsTraffic) { + if (otraffic.tag == o.tag) { + return sizeFormat(otraffic.up) + ' / ' + sizeFormat(otraffic.down); + } + } + return sizeFormat(0) + ' / ' + sizeFormat(0); + }, findOutboundAddress(o) { serverObj = null; switch(o.protocol){ @@ -816,6 +843,22 @@ outbounds.splice(index,1); this.outboundSettings = JSON.stringify(outbounds); }, + async refreshOutboundTraffic() { + if (!this.refreshing) { + this.refreshing = true; + await this.getOutboundsTraffic(); + + data = [] + if (this.templateSettings != null) { + this.templateSettings.outbounds.forEach((o, index) => { + data.push({'key': index, ...o}); + }); + } + + this.outboundData = data; + this.refreshing = false; + } + }, addReverse(){ reverseModal.show({ title: '{{ i18n "pages.xray.outbound.addReverse"}}', @@ -949,6 +992,7 @@ async mounted() { await this.getXraySetting(); await this.getXrayResult(); + await this.getOutboundsTraffic(); while (true) { await PromiseUtil.sleep(800); this.saveBtnDisable = this.oldXraySetting === this.xraySetting; diff --git a/web/job/xray_traffic_job.go b/web/job/xray_traffic_job.go index 158930a4..c0de4428 100644 --- a/web/job/xray_traffic_job.go +++ b/web/job/xray_traffic_job.go @@ -6,8 +6,9 @@ import ( ) type XrayTrafficJob struct { - xrayService service.XrayService - inboundService service.InboundService + xrayService service.XrayService + inboundService service.InboundService + outboundService service.OutboundService } func NewXrayTrafficJob() *XrayTrafficJob { @@ -24,11 +25,15 @@ func (j *XrayTrafficJob) Run() { logger.Warning("get xray traffic failed:", err) return } - err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics) + err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics) if err != nil { - logger.Warning("add traffic failed:", err) + logger.Warning("add inbound traffic failed:", err) } - if needRestart { + err, needRestart1 := j.outboundService.AddTraffic(traffics, clientTraffics) + if err != nil { + logger.Warning("add outbound traffic failed:", err) + } + if needRestart0 || needRestart1 { j.xrayService.SetToNeedRestart() } diff --git a/web/service/inbound.go b/web/service/inbound.go index f3445101..291c0dee 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -682,7 +682,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin return needRestart, tx.Save(oldInbound).Error } -func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) { +func (s *InboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) { var err error db := database.GetDB() tx := db.Begin() @@ -694,7 +694,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff tx.Commit() } }() - err = s.addInboundTraffic(tx, inboundTraffics) + err = s.addInboundTraffic(tx, traffics) if err != nil { return err, false } diff --git a/web/service/outbound.go b/web/service/outbound.go new file mode 100644 index 00000000..dc0e0742 --- /dev/null +++ b/web/service/outbound.go @@ -0,0 +1,80 @@ +package service + +import ( + "x-ui/database" + "x-ui/database/model" + "x-ui/logger" + "x-ui/xray" + + "gorm.io/gorm" +) + +type OutboundService struct { + xrayApi xray.XrayAPI +} + +func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) { + var err error + db := database.GetDB() + tx := db.Begin() + + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + + err = s.addOutboundTraffic(tx, traffics) + if err != nil { + return err, false + } + + return nil, false +} + +func (s *OutboundService) addOutboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error { + if len(traffics) == 0 { + return nil + } + + var err error + + for _, traffic := range traffics { + if traffic.IsOutbound { + + var outbound model.OutboundTraffics + + err = tx.Model(&model.OutboundTraffics{}).Where("tag = ?", traffic.Tag). + FirstOrCreate(&outbound).Error + if err != nil { + return err + } + + outbound.Tag = traffic.Tag + outbound.Up = outbound.Up + traffic.Up + outbound.Down = outbound.Down + traffic.Down + outbound.Total = outbound.Up + outbound.Down + + err = tx.Save(&outbound).Error + if err != nil { + return err + } + } + } + return nil +} + +func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, error) { + db := database.GetDB() + var traffics []*model.OutboundTraffics + + err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error + if err != nil { + logger.Warning(err) + return nil, err + } + + return traffics, nil +} diff --git a/xray/api.go b/xray/api.go index 36b19875..1ce5afa1 100644 --- a/xray/api.go +++ b/xray/api.go @@ -213,6 +213,7 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) { continue } isInbound := matchs[1] == "inbound" + isOutbound := matchs[1] == "outbound" tag := matchs[2] isDown := matchs[3] == "downlink" if tag == "api" { @@ -221,8 +222,9 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) { traffic, ok := tagTrafficMap[tag] if !ok { traffic = &Traffic{ - IsInbound: isInbound, - Tag: tag, + IsInbound: isInbound, + IsOutbound: isOutbound, + Tag: tag, } tagTrafficMap[tag] = traffic traffics = append(traffics, traffic) diff --git a/xray/traffic.go b/xray/traffic.go index a1ef5186..7b907bae 100644 --- a/xray/traffic.go +++ b/xray/traffic.go @@ -1,8 +1,9 @@ package xray type Traffic struct { - IsInbound bool - Tag string - Up int64 - Down int64 + IsInbound bool + IsOutbound bool + Tag string + Up int64 + Down int64 } From e4567a2b247106bd375487889fe64cd5e660aa36 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 30 Jan 2024 00:15:20 +0330 Subject: [PATCH 07/37] host name for ws header req --- README.md | 56 ++++++++++++------------- web/assets/js/model/outbound.js | 6 +-- web/html/xui/form/stream/stream_ws.html | 2 +- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index ec1e5a15..b554353d 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,34 @@ To install your desired version, add the version to the end of the installation ``` bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.2 ``` + +## SSL Certificate + +
+ Click for SSL Certificate + +### Cloudflare + +The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following: + +- Cloudflare registered email +- Cloudflare Global API Key +- The domain name has been resolved to the current server through cloudflare + +**1:** Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`. + + +### Certbot +``` +apt-get install certbot -y +certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com +certbot renew --dry-run +``` + +***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.* + +
+ ## Manual Install & Upgrade
@@ -201,34 +229,6 @@ Supports a variety of different architectures and devices. Here are some of the
- -## SSL Certificate - -
- Click for SSL Certificate - -### Cloudflare - -The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following: - -- Cloudflare registered email -- Cloudflare Global API Key -- The domain name has been resolved to the current server through cloudflare - -**1:** Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`. - - -### Certbot -``` -apt-get install certbot -y -certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com -certbot renew --dry-run -``` - -***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.* - -
- ## [WARP Configuration](https://gitlab.com/fscarmen/warp)
diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js index 7724cf89..dc02d91b 100644 --- a/web/assets/js/model/outbound.js +++ b/web/assets/js/model/outbound.js @@ -914,8 +914,8 @@ Outbound.HttpSettings = class extends CommonClass { Outbound.WireguardSettings = class extends CommonClass { constructor( - mtu=1420, secretKey=Wireguard.generateKeypair().privateKey, - address=[''], workers=2, domainStrategy='ForceIP', reserved='', + mtu=1420, secretKey='', + address=[''], workers=2, domainStrategy='', reserved='', peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) { super(); this.mtu = mtu; @@ -965,7 +965,7 @@ Outbound.WireguardSettings = class extends CommonClass { }; Outbound.WireguardSettings.Peer = class extends CommonClass { - constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) { + constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) { super(); this.publicKey = publicKey; this.psk = psk; diff --git a/web/html/xui/form/stream/stream_ws.html b/web/html/xui/form/stream/stream_ws.html index 1222124c..00b64167 100644 --- a/web/html/xui/form/stream/stream_ws.html +++ b/web/html/xui/form/stream/stream_ws.html @@ -7,7 +7,7 @@ - + + + From 012775833a2d4e88ce534790e6ade9dd81def298 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 22:56:57 +0330 Subject: [PATCH 08/37] Bump github.com/nicksnyder/go-i18n/v2 from 2.3.0 to 2.4.0 (#1721) Bumps [github.com/nicksnyder/go-i18n/v2](https://github.com/nicksnyder/go-i18n) from 2.3.0 to 2.4.0. - [Release notes](https://github.com/nicksnyder/go-i18n/releases) - [Changelog](https://github.com/nicksnyder/go-i18n/blob/main/CHANGELOG.md) - [Commits](https://github.com/nicksnyder/go-i18n/compare/v2.3.0...v2.4.0) --- updated-dependencies: - dependency-name: github.com/nicksnyder/go-i18n/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d46ed1fe..41105d28 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/gin-gonic/gin v1.9.1 github.com/goccy/go-json v0.10.2 github.com/mymmrac/telego v0.28.0 - github.com/nicksnyder/go-i18n/v2 v2.3.0 + github.com/nicksnyder/go-i18n/v2 v2.4.0 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pelletier/go-toml/v2 v2.1.1 github.com/robfig/cron/v3 v3.0.1 diff --git a/go.sum b/go.sum index 8b6465d7..aa726d4a 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ github.com/mymmrac/telego v0.28.0 h1:DNXaYISeZw1J9oB81vCNdskLow8gCRRUJxufqLuH3XE github.com/mymmrac/telego v0.28.0/go.mod h1:oRperySNzJq8dRTl24+uBF1Uy7tlQGIjid/JQtHDsZg= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ= -github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= +github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= +github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= From 68a16ef0e2385b57425c7959c425a8c02bef4a11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:15:17 +0330 Subject: [PATCH 09/37] Bump docker/metadata-action from 5.5.0 to 5.5.1 (#1726) Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.5.0 to 5.5.1. - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/v5.5.0...v5.5.1) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a7a8c790..00ac9169 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -27,7 +27,7 @@ jobs: - name: Docker meta id: meta - uses: docker/metadata-action@v5.5.0 + uses: docker/metadata-action@v5.5.1 with: images: ghcr.io/${{ github.repository }} From f0e9aa0b8f1b81aef76246fb7ea449dc9236deba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:15:41 +0330 Subject: [PATCH 10/37] Bump github.com/shirou/gopsutil/v3 from 3.23.12 to 3.24.1 (#1725) Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.23.12 to 3.24.1. - [Release notes](https://github.com/shirou/gopsutil/releases) - [Commits](https://github.com/shirou/gopsutil/compare/v3.23.12...v3.24.1) --- updated-dependencies: - dependency-name: github.com/shirou/gopsutil/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 41105d28..76d5f8eb 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pelletier/go-toml/v2 v2.1.1 github.com/robfig/cron/v3 v3.0.1 - github.com/shirou/gopsutil/v3 v3.23.12 + github.com/shirou/gopsutil/v3 v3.24.1 github.com/valyala/fasthttp v1.51.0 github.com/xtls/xray-core v1.8.7 go.uber.org/atomic v1.11.0 diff --git a/go.sum b/go.sum index aa726d4a..8e8b0db6 100644 --- a/go.sum +++ b/go.sum @@ -230,8 +230,8 @@ github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJ github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= -github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI= +github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -369,7 +369,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From 98dd6bb9493c989d2947dce3beda030a331805ec Mon Sep 17 00:00:00 2001 From: Mehdi Khodayari Date: Sat, 3 Feb 2024 14:11:57 +0330 Subject: [PATCH 11/37] This modification uses a Scanner to read the file line by line, which can be more memory-efficient for large files. (#1736) --- web/job/check_client_ip_job.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go index b393e68d..ecfd5abb 100644 --- a/web/job/check_client_ip_job.go +++ b/web/job/check_client_ip_job.go @@ -1,7 +1,9 @@ package job import ( + "bufio" "encoding/json" + "io" "log" "os" "os/exec" @@ -97,12 +99,16 @@ func (j *CheckClientIpJob) processLogFile() { return } - data, err := os.ReadFile(accessLogPath) - InboundClientIps := make(map[string][]string) + file, err := os.Open(accessLogPath) j.checkError(err) + defer file.Close() + + InboundClientIps := make(map[string][]string) + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() - lines := strings.Split(string(data), "\n") - for _, line := range lines { ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`) emailRegx, _ := regexp.Compile(`email:.+`) @@ -131,6 +137,8 @@ func (j *CheckClientIpJob) processLogFile() { } } + j.checkError(scanner.Err()) + shouldCleanLog := false for clientEmail, ips := range InboundClientIps { @@ -141,7 +149,6 @@ func (j *CheckClientIpJob) processLogFile() { } else { shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips) } - } // added delay before cleaning logs to reduce chance of logging IP that already has been banned @@ -151,13 +158,17 @@ func (j *CheckClientIpJob) processLogFile() { // copy access log to persistent file logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644) j.checkError(err) - input, err := os.ReadFile(accessLogPath) - j.checkError(err) - if _, err := logAccessP.Write(input); err != nil { - j.checkError(err) - } defer logAccessP.Close() + // reopen the access log file for reading + file, err := os.Open(accessLogPath) + j.checkError(err) + defer file.Close() + + // copy access log content to persistent file + _, err = io.Copy(logAccessP, file) + j.checkError(err) + // clean access log if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil { j.checkError(err) From 6804facabc27a5dc72a8db22bbaaf2c245161323 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 3 Feb 2024 15:24:39 +0330 Subject: [PATCH 12/37] bug fix log_writer + notice log level Co-Authored-By: Alireza Ahmadi --- logger/logger.go | 10 ++++++++++ xray/log_writer.go | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/logger/logger.go b/logger/logger.go index a1386b05..ca047cbc 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -65,6 +65,16 @@ func Infof(format string, args ...interface{}) { addToBuffer("INFO", fmt.Sprintf(format, args...)) } +func Notice(args ...interface{}) { + logger.Notice(args...) + addToBuffer("NOTICE", fmt.Sprint(args...)) +} + +func Noticef(format string, args ...interface{}) { + logger.Noticef(format, args...) + addToBuffer("NOTICE", fmt.Sprintf(format, args...)) +} + func Warning(args ...interface{}) { logger.Warning(args...) addToBuffer("WARNING", fmt.Sprint(args...)) diff --git a/xray/log_writer.go b/xray/log_writer.go index 5fc6b3d1..53358ca2 100644 --- a/xray/log_writer.go +++ b/xray/log_writer.go @@ -31,7 +31,7 @@ func (lw *LogWriter) Write(m []byte) (n int, err error) { // Find level in [] startIndex := strings.Index(messageBody, "[") endIndex := strings.Index(messageBody, "]") - if startIndex != -1 && endIndex != -1 { + if startIndex != -1 && endIndex != -1 && startIndex < endIndex { level := strings.TrimSpace(messageBody[startIndex+1 : endIndex]) msgBody := "XRAY: " + strings.TrimSpace(messageBody[endIndex+1:]) From 618a5662837b0c70a88782ccb47e1cb634241bb3 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 3 Feb 2024 17:54:04 +0330 Subject: [PATCH 13/37] new - select option for loglevel & access log --- web/html/xui/xray.html | 58 ++++++++++++++++++++++++++ web/job/check_client_ip_job.go | 8 +++- web/service/config.json | 5 ++- web/translation/translate.en_US.toml | 8 +++- web/translation/translate.es_ES.toml | 4 ++ web/translation/translate.fa_IR.toml | 4 ++ web/translation/translate.ru_RU.toml | 4 ++ web/translation/translate.vi_VN.toml | 4 ++ web/translation/translate.zh_Hans.toml | 4 ++ 9 files changed, 94 insertions(+), 5 deletions(-) diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html index d00b73cc..267103cb 100644 --- a/web/html/xui/xray.html +++ b/web/html/xui/xray.html @@ -147,6 +147,40 @@ + + + + + + + + + + + + + + + + @@ -534,6 +568,8 @@ protocol: "freedom" }, routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"], + logLevel: ["none" , "debug" , "info" , "warning", "error"], + access: ["none" , "./access.log" ], settingsData: { protocols: { bittorrent: ["bittorrent"], @@ -1109,6 +1145,28 @@ this.templateSettings = newTemplateSettings; } }, + setLogLevel: { + get: function () { + if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.loglevel) return "warning"; + return this.templateSettings.log.loglevel; + }, + set: function (newValue) { + newTemplateSettings = this.templateSettings; + newTemplateSettings.log.loglevel = newValue; + this.templateSettings = newTemplateSettings; + } + }, + setAccessLog: { + get: function () { + if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "none"; + return this.templateSettings.log.access; + }, + set: function (newValue) { + newTemplateSettings = this.templateSettings; + newTemplateSettings.log.access = newValue; + this.templateSettings = newTemplateSettings; + } + }, blockedIPs: { get: function () { return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" }); diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go index ecfd5abb..77cbe256 100644 --- a/web/job/check_client_ip_job.go +++ b/web/job/check_client_ip_job.go @@ -94,8 +94,14 @@ func (j *CheckClientIpJob) checkFail2BanInstalled() { func (j *CheckClientIpJob) processLogFile() { accessLogPath := xray.GetAccessLogPath() + + if accessLogPath == "none" { + logger.Warning("Access log is set to 'none' check your Xray Configs") + return + } + if accessLogPath == "" { - logger.Warning("access.log doesn't exist in your config.json") + logger.Warning("Access log doesn't exist in your Xray Configs") return } diff --git a/web/service/config.json b/web/service/config.json index 82f7dddf..5c25ed92 100644 --- a/web/service/config.json +++ b/web/service/config.json @@ -1,7 +1,8 @@ { "log": { - "loglevel": "warning", - "error": "./error.log" + "access": "none", + "dnsLog": false, + "loglevel": "warning" }, "api": { "tag": "api", diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 7e0f26c5..6295bf15 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -309,8 +309,8 @@ "restart" = "Restart Xray" "basicTemplate" = "Basics" "advancedTemplate" = "Advanced" -"generalConfigs" = "General Strategy" -"generalConfigsDesc" = "These options will determine general strategy adjustments." +"generalConfigs" = "General" +"generalConfigsDesc" = "These options will determine general adjustments." "blockConfigs" = "Protection Shield" "blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites." "blockCountryConfigs" = "Block Country" @@ -392,6 +392,10 @@ "Routings" = "Routing Rules" "RoutingsDesc" = "The priority of each rule is important!" "completeTemplate" = "All" +"logLevel" = "Log Level" +"logLevelDesc" = "The log level for error logs, indicating the information that needs to be recorded." +"accessLog" = "Access Log" +"accessLogDesc" = "The file path for the access log. The special value 'none' disabled access logs" [pages.xray.rules] "first" = "First" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index ac9de9a4..b21fe252 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -392,6 +392,10 @@ "Routings" = "Reglas de enrutamiento" "RoutingsDesc" = "¡La prioridad de cada regla es importante!" "completeTemplate" = "Todos" +"logLevel" = "Nivel de registro" +"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse." +"accessLog" = "Registro de acceso" +"accessLogDesc" = "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros de acceso" [pages.xray.rules] "first" = "Primero" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 11b66788..e737c79b 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -392,6 +392,10 @@ "Routings" = "قوانین مسیریابی" "RoutingsDesc" = "اولویت هر قانون مهم است" "completeTemplate" = "کامل" +"logLevel" = "سطح گزارش" +"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند." +"accessLog" = "مسیر گزارش" +"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارش‌های دسترسی را غیرفعال میکند." [pages.xray.rules] "first" = "اولین" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 2e663e4c..cbd1d4f9 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -392,6 +392,10 @@ "Routings" = "Правила маршрутизации" "RoutingsDesc" = "Важен приоритет каждого правила!" "completeTemplate" = "Все" +"logLevel" = "Уровень журнала" +"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать." +"accessLog" = "Журнал доступа" +"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключило журналы доступа." [pages.xray.rules] "first" = "Первый" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index 4c40adca..f94b56b0 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -392,6 +392,10 @@ "Routings" = "Quy tắc định tuyến" "RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!" "completeTemplate" = "All" +"logLevel" = "Mức đăng nhập" +"logLevelDesc" = "Cấp độ nhật ký cho nhật ký lỗi, cho biết thông tin cần được ghi lại." +"accessLog" = "Nhật ký truy cập" +"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập. Nhật ký truy cập bị vô hiệu hóa có giá trị đặc biệt 'không'" [pages.xray.rules] "first" = "Đầu tiên" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index a8cd3aff..d64e1331 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -392,6 +392,10 @@ "Routings" = "路由规则" "RoutingsDesc" = "每条规则的优先级都很重要" "completeTemplate" = "全部" +"logLevel" = "日志级别" +"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。" +"accessLog" = "访问日志" +"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志" [pages.xray.rules] "first" = "第一个" From 9d724d34e1cf0c6cf118fb092d737d2f84d67a60 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 3 Feb 2024 18:15:47 +0330 Subject: [PATCH 14/37] fix tgbot - no warning for empty socks5 --- web/service/tgbot.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/web/service/tgbot.go b/web/service/tgbot.go index 4ffa7bb4..0b5a8158 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -115,14 +115,19 @@ func (t *Tgbot) Start(i18nFS embed.FS) error { } func (t *Tgbot) NewBot(token string, proxyUrl string) (*telego.Bot, error) { - if proxyUrl == "" || !strings.HasPrefix(proxyUrl, "socks5://") { - logger.Warning("invalid socks5 url, start with default") + if proxyUrl == "" { + // No proxy URL provided, use default instance + return telego.NewBot(token) + } + + if !strings.HasPrefix(proxyUrl, "socks5://") { + logger.Warning("Invalid socks5 URL, starting with default") return telego.NewBot(token) } _, err := url.Parse(proxyUrl) if err != nil { - logger.Warning("cant parse proxy url, use default instance for tgbot:", err) + logger.Warning("Can't parse proxy URL, using default instance for tgbot:", err) return telego.NewBot(token) } From 2a2bf531eef9ca0fdc0894e4e740ff0a613ea350 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 4 Feb 2024 01:50:14 +0330 Subject: [PATCH 15/37] Fix tgbot - document upload issue for empty ban logs --- web/service/tgbot.go | 46 +++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/web/service/tgbot.go b/web/service/tgbot.go index 0b5a8158..7bd6a179 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -1568,30 +1568,44 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) { file, err := os.Open(xray.GetIPLimitBannedPrevLogPath()) if err == nil { - document := tu.Document( - tu.ID(chatId), - tu.File(file), - ) - _, err = bot.SendDocument(document) - if err != nil { - logger.Error("Error in uploading backup: ", err) + // Check if the file is non-empty before attempting to upload + fileInfo, _ := file.Stat() + if fileInfo.Size() > 0 { + document := tu.Document( + tu.ID(chatId), + tu.File(file), + ) + _, err = bot.SendDocument(document) + if err != nil { + logger.Error("Error in uploading IPLimitBannedPrevLog: ", err) + } + } else { + logger.Warning("IPLimitBannedPrevLog file is empty, not uploading.") } + file.Close() } else { - logger.Error("Error in opening db file for backup: ", err) + logger.Error("Error in opening IPLimitBannedPrevLog file for backup: ", err) } file, err = os.Open(xray.GetIPLimitBannedLogPath()) if err == nil { - document := tu.Document( - tu.ID(chatId), - tu.File(file), - ) - _, err = bot.SendDocument(document) - if err != nil { - logger.Error("Error in uploading config.json: ", err) + // Check if the file is non-empty before attempting to upload + fileInfo, _ := file.Stat() + if fileInfo.Size() > 0 { + document := tu.Document( + tu.ID(chatId), + tu.File(file), + ) + _, err = bot.SendDocument(document) + if err != nil { + logger.Error("Error in uploading IPLimitBannedLog: ", err) + } + } else { + logger.Warning("IPLimitBannedLog file is empty, not uploading.") } + file.Close() } else { - logger.Error("Error in opening config.json file for backup: ", err) + logger.Error("Error in opening IPLimitBannedLog file for backup: ", err) } } From 12075014056d5288e845b11a46855d6cea1c578b Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 4 Feb 2024 01:51:01 +0330 Subject: [PATCH 16/37] tgbot - improve translate --- web/translation/translate.en_US.toml | 2 +- web/translation/translate.fa_IR.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 6295bf15..78c545f6 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -456,7 +456,7 @@ "wentWrong" = "❌ Something went wrong!" "noIpRecord" = "❗ No IP Record!" "noInbounds" = "❗ No inbound found!" -"unlimited" = "♾ Unlimited" +"unlimited" = "♾ Unlimited(Reset)" "add" = "Add" "month" = "Month" "months" = "Months" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index e737c79b..7abaf973 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -456,7 +456,7 @@ "wentWrong" = "❌ مشکلی رخ داده است!" "noIpRecord" = "❗ رکورد IP یافت نشد!" "noInbounds" = "❗ هیچ ورودی یافت نشد!" -"unlimited" = "♾ نامحدود" +"unlimited" = "♾ - نامحدود(ریست)" "add" = "اضافه کردن" "month" = "ماه" "months" = "ماه‌ها" From d17185025576a81bae641a0488b876ac143826df Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 4 Feb 2024 01:51:31 +0330 Subject: [PATCH 17/37] IPLimit - IPv4 Extraction Simplification --- web/job/check_client_ip_job.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go index 77cbe256..905a8cc7 100644 --- a/web/job/check_client_ip_job.go +++ b/web/job/check_client_ip_job.go @@ -115,13 +115,13 @@ func (j *CheckClientIpJob) processLogFile() { for scanner.Scan() { line := scanner.Text() - ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`) + ipRegx, _ := regexp.Compile(`(\d+\.\d+\.\d+\.\d+).* accepted`) emailRegx, _ := regexp.Compile(`email:.+`) - matchesIp := ipRegx.FindString(line) - if len(matchesIp) > 0 { - ip := string(matchesIp) - if ip == "127.0.0.1" || ip == "1.1.1.1" { + matches := ipRegx.FindStringSubmatch(line) + if len(matches) > 1 { + ip := matches[1] + if ip == "127.0.0.1" || ip == "[::1]" { continue } @@ -136,7 +136,6 @@ func (j *CheckClientIpJob) processLogFile() { continue } InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip) - } else { InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip) } From bb7b66746763f694baea981549d311320136cb1b Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 4 Feb 2024 01:51:43 +0330 Subject: [PATCH 18/37] v2.1.3 --- README.md | 11 ++++++----- config/version | 2 +- web/service/xray.go | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b554353d..6809bc2a 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.2`: +To install your desired version, add the version to the end of the installation command. e.g., ver `v2.1.3`: ``` -bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.2 +bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.3 ``` ## SSL Certificate @@ -281,13 +281,14 @@ If you want to use routing to WARP before v2.1.0 follow steps as below: 2. Select `IP Limit Management`. 3. Choose the appropriate options based on your needs. - - make sure you have access.log on your Xray Configuration + - make sure you have ./access.log on your Xray Configuration after v2.1.3 we have an option for it ```sh "log": { - "loglevel": "warning", "access": "./access.log", - "error": "./error.log" + "dnsLog": false, + "error": "./error.log", + "loglevel": "warning" }, ``` diff --git a/config/version b/config/version index 8f9174b4..abae0d9a 100644 --- a/config/version +++ b/config/version @@ -1 +1 @@ -2.1.2 \ No newline at end of file +2.1.3 \ No newline at end of file diff --git a/web/service/xray.go b/web/service/xray.go index 82d1cc3f..7cd1612c 100644 --- a/web/service/xray.go +++ b/web/service/xray.go @@ -95,7 +95,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) { if !clientTraffic.Enable { clients = RemoveIndex(clients, index-indexDecrease) indexDecrease++ - logger.Info("Remove Inbound User", c["email"], "due the expire or traffic limit") + logger.Info("Remove Inbound User ", c["email"], " due the expire or traffic limit") } From dc49304aa5d959767f771b8b161e23b5d8cd6ec4 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 4 Feb 2024 02:01:55 +0330 Subject: [PATCH 19/37] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 6809bc2a..a1c902e2 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,6 @@ If you want to use routing to WARP before v2.1.0 follow steps as below: "log": { "access": "./access.log", "dnsLog": false, - "error": "./error.log", "loglevel": "warning" }, ``` From c9ba393ce7bd7050b4d2dc8d4e3c019641a5f087 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 4 Feb 2024 13:02:28 +0330 Subject: [PATCH 20/37] xray config - statsOutbound --- web/service/config.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/service/config.json b/web/service/config.json index 5c25ed92..6cf6c3a6 100644 --- a/web/service/config.json +++ b/web/service/config.json @@ -44,7 +44,9 @@ }, "system": { "statsInboundDownlink": true, - "statsInboundUplink": true + "statsInboundUplink": true, + "statsOutboundDownlink": true, + "statsOutboundUplink": true } }, "routing": { From 222b9734caba389604fd81caa068e815bdb16dcb Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 5 Feb 2024 12:43:18 +0330 Subject: [PATCH 21/37] Lang - Indonesian #1710 Co-Authored-By: Muhamad Solihin <85750131+lihin929@users.noreply.github.com> --- web/assets/js/langs.js | 5 + web/translation/translate.id_ID.toml | 580 +++++++++++++++++++++++++++ 2 files changed, 585 insertions(+) create mode 100644 web/translation/translate.id_ID.toml diff --git a/web/assets/js/langs.js b/web/assets/js/langs.js index 59f0696f..42fa49ff 100644 --- a/web/assets/js/langs.js +++ b/web/assets/js/langs.js @@ -29,6 +29,11 @@ const supportLangs = [ value: 'es-ES', icon: '🇪🇸', }, + { + name: 'Indonesian', + value: 'id-ID', + icon: '🇮🇩', + }, ]; function getLang() { diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml new file mode 100644 index 00000000..1e5dfac1 --- /dev/null +++ b/web/translation/translate.id_ID.toml @@ -0,0 +1,580 @@ +"username" = "Nama Pengguna" +"password" = "Kata Sandi" +"login" = "Masuk" +"confirm" = "Konfirmasi" +"cancel" = "Batal" +"close" = "Tutup" +"copy" = "Salin" +"copied" = "Tersalin" +"download" = "Unduh" +"remark" = "Catatan" +"enable" = "Aktifkan" +"protocol" = "Protokol" +"search" = "Cari" +"filter" = "Filter" +"loading" = "Memuat..." +"second" = "Detik" +"minute" = "Menit" +"hour" = "Jam" +"day" = "Hari" +"check" = "Centang" +"indefinite" = "Tak Terbatas" +"unlimited" = "Tanpa Batas" +"none" = "Tidak Ada" +"qrCode" = "Kode QR" +"info" = "Informasi Lebih Lanjut" +"edit" = "Edit" +"delete" = "Hapus" +"reset" = "Reset" +"copySuccess" = "Berhasil Disalin" +"sure" = "Yakin" +"encryption" = "Enkripsi" +"transmission" = "Transmisi" +"host" = "Host" +"path" = "Jalur" +"camouflage" = "Obfuscation" +"status" = "Status" +"enabled" = "Aktif" +"disabled" = "Nonaktif" +"depleted" = "Habis" +"depletingSoon" = "Akan Habis" +"offline" = "Offline" +"online" = "Online" +"domainName" = "Nama Domain" +"monitor" = "IP Pemantauan" +"certificate" = "Sertifikat" +"fail" = "Gagal" +"success" = "Berhasil" +"getVersion" = "Dapatkan Versi" +"install" = "Instal" +"clients" = "Klien" +"usage" = "Penggunaan" +"secretToken" = "Token Rahasia" +"remained" = "Tersisa" +"security" = "Keamanan" + +[Menu] +"dashboard" = "Ikhtisar" +"inbounds" = "Masuk" +"settings" = "Pengaturan Panel" +"xray" = "Konfigurasi Xray" +"logout" = "Keluar" +"link" = "Kelola" + +[pages.login] +"title" = "Selamat Datang" +"loginAgain" = "Sesi Anda telah berakhir, harap masuk kembali" + +[pages.login.toasts] +"invalidFormData" = "Format data input tidak valid." +"emptyUsername" = "Nama Pengguna diperlukan" +"emptyPassword" = "Kata Sandi diperlukan" +"wrongUsernameOrPassword" = "Nama pengguna atau kata sandi tidak valid." +"successLogin" = "Login berhasil" + +[pages.index] +"title" = "Ikhtisar" +"memory" = "RAM" +"hard" = "Disk" +"xrayStatus" = "Status" +"stopXray" = "Stop" +"restartXray" = "Restart" +"xraySwitch" = "Versi" +"xraySwitchClick" = "Pilih versi yang ingin Anda pindah." +"xraySwitchClickDesk" = "Pilih dengan hati-hati, karena versi yang lebih lama mungkin tidak kompatibel dengan konfigurasi saat ini." +"operationHours" = "Waktu Aktif" +"systemLoad" = "Beban Sistem" +"systemLoadDesc" = "Rata-rata beban sistem selama 1, 5, dan 15 menit terakhir" +"connectionTcpCountDesc" = "Total koneksi TCP di seluruh sistem" +"connectionUdpCountDesc" = "Total koneksi UDP di seluruh sistem" +"connectionCount" = "Statistik Koneksi" +"upSpeed" = "Kecepatan unggah keseluruhan di seluruh sistem" +"downSpeed" = "Kecepatan unduh keseluruhan di seluruh sistem" +"totalSent" = "Total data terkirim di seluruh sistem sejak startup OS" +"totalReceive" = "Total data diterima di seluruh sistem sejak startup OS" +"xraySwitchVersionDialog" = "Ganti Versi Xray" +"xraySwitchVersionDialogDesc" = "Apakah Anda yakin ingin mengubah versi Xray menjadi" +"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini" +"logs" = "Log" +"config" = "Konfigurasi" +"backup" = "Cadangan & Pulihkan" +"backupTitle" = "Cadangan & Pulihkan Database" +"backupDescription" = "Disarankan untuk membuat cadangan sebelum memulihkan database." +"exportDatabase" = "Cadangkan" +"importDatabase" = "Pulihkan" + +[pages.inbounds] +"title" = "Masuk" +"totalDownUp" = "Total Terkirim/Diterima" +"totalUsage" = "Penggunaan Total" +"inboundCount" = "Total Masuk" +"operate" = "Menu" +"enable" = "Aktifkan" +"remark" = "Catatan" +"protocol" = "Protokol" +"port" = "Port" +"traffic" = "Traffic" +"details" = "Rincian" +"transportConfig" = "Transport" +"expireDate" = "Durasi" +"resetTraffic" = "Reset Traffic" +"addInbound" = "Tambahkan Masuk" +"generalActions" = "Tindakan Umum" +"create" = "Buat" +"update" = "Perbarui" +"modifyInbound" = "Ubah Masuk" +"deleteInbound" = "Hapus Masuk" +"deleteInboundContent" = "Apakah Anda yakin ingin menghapus masuk?" +"deleteClient" = "Hapus Klien" +"deleteClientContent" = "Apakah Anda yakin ingin menghapus klien?" +"resetTrafficContent" = "Apakah Anda yakin ingin mereset traffic?" +"copyLink" = "Salin URL" +"address" = "Alamat" +"network" = "Jaringan" +"destinationPort" = "Port Tujuan" +"targetAddress" = "Alamat Target" +"monitorDesc" = "Biarkan kosong untuk mendengarkan semua IP" +"meansNoLimit" = " = Unlimited. (unit: GB)" +"totalFlow" = "Total Aliran" +"leaveBlankToNeverExpire" = "Biarkan kosong untuk tidak pernah kedaluwarsa" +"noRecommendKeepDefault" = "Disarankan untuk tetap menggunakan pengaturan default" +"certificatePath" = "Path Berkas" +"certificateContent" = "Konten Berkas" +"publicKeyPath" = "Path Kunci Publik" +"publicKeyContent" = "Konten Kunci Publik" +"keyPath" = "Path Kunci Privat" +"keyContent" = "Konten Kunci Privat" +"clickOnQRcode" = "Klik pada Kode QR untuk Menyalin" +"client" = "Klien" +"export" = "Ekspor Semua URL" +"clone" = "Duplikat" +"cloneInbound" = "Duplikat" +"cloneInboundContent" = "Semua pengaturan masuk ini, kecuali Port, Listening IP, dan Klien, akan diterapkan pada duplikat." +"cloneInboundOk" = "Duplikat" +"resetAllTraffic" = "Reset Semua Traffic Masuk" +"resetAllTrafficTitle" = "Reset Semua Traffic Masuk" +"resetAllTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua masuk?" +"resetInboundClientTraffics" = "Reset Traffic Klien Masuk" +"resetInboundClientTrafficTitle" = "Reset Traffic Klien Masuk" +"resetInboundClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic klien masuk ini?" +"resetAllClientTraffics" = "Reset Traffic Semua Klien" +"resetAllClientTrafficTitle" = "Reset Traffic Semua Klien" +"resetAllClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua klien?" +"delDepletedClients" = "Hapus Klien Habis" +"delDepletedClientsTitle" = "Hapus Klien Habis" +"delDepletedClientsContent" = "Apakah Anda yakin ingin menghapus semua klien yang habis?" +"email" = "Email" +"emailDesc" = "Harap berikan alamat email yang unik." +"IPLimit" = "Batas IP" +"IPLimitDesc" = "Menonaktifkan masuk jika jumlah melebihi nilai yang ditetapkan. (0 = nonaktif)" +"IPLimitlog" = "Log IP" +"IPLimitlogDesc" = "Log histori IP. (untuk mengaktifkan masuk setelah menonaktifkan, hapus log)" +"IPLimitlogclear" = "Hapus Log" +"setDefaultCert" = "Atur Sertifikat dari Panel" +"xtlsDesc" = "Xray harus versi 1.7.5" +"realityDesc" = "Xray harus versi 1.8.0+" +"telegramDesc" = "Harap berikan ID Telegram atau obrolan tanpa menggunakan '@'. (dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)" +"subscriptionDesc" = "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien." +"info" = "Info" +"same" = "Sama" +"inboundData" = "Data Masuk" +"exportInbound" = "Ekspor Masuk" +"import" = "Impor" +"importInbound" = "Impor Masuk" + +[pages.client] +"add" = "Tambah Klien" +"edit" = "Edit Klien" +"submitAdd" = "Tambah Klien" +"submitEdit" = "Simpan Perubahan" +"clientCount" = "Jumlah Klien" +"bulk" = "Tambahkan Massal" +"method" = "Metode" +"first" = "Pertama" +"last" = "Terakhir" +"prefix" = "Awalan" +"postfix" = "Akhiran" +"delayedStart" = "Mulai saat Penggunaan Awal" +"expireDays" = "Durasi" +"days" = "Hari" +"renew" = "Perpanjang Otomatis" +"renewDesc" = "Perpanjangan otomatis setelah kedaluwarsa. (0 = nonaktif)(unit: hari)" + +[pages.inbounds.toasts] +"obtain" = "Dapatkan" + +[pages.inbounds.stream.general] +"request" = "Permintaan" +"response" = "Respons" +"name" = "Nama" +"value" = "Nilai" + +[pages.inbounds.stream.tcp] +"version" = "Versi" +"method" = "Metode" +"path" = "Path" +"status" = "Status" +"statusDescription" = "Deskripsi Status" +"requestHeader" = "Header Permintaan" +"responseHeader" = "Header Respons" + +[pages.inbounds.stream.quic] +"encryption" = "Enkripsi" + +[pages.settings] +"title" = "Pengaturan Panel" +"save" = "Simpan" +"infoDesc" = "Setiap perubahan yang dibuat di sini perlu disimpan. Harap restart panel untuk menerapkan perubahan." +"restartPanel" = "Restart Panel" +"restartPanelDesc" = "Apakah Anda yakin ingin merestart panel? Jika Anda tidak dapat mengakses panel setelah merestart, lihat info log panel di server." +"actions" = "Tindakan" +"resetDefaultConfig" = "Reset ke Default" +"panelSettings" = "Umum" +"securitySettings" = "Otentikasi" +"TGBotSettings" = "Bot Telegram" +"panelListeningIP" = "IP Pendengar" +"panelListeningIPDesc" = "Alamat IP untuk panel web. (biarkan kosong untuk mendengarkan semua IP)" +"panelListeningDomain" = "Domain Pendengar" +"panelListeningDomainDesc" = "Nama domain untuk panel web. (biarkan kosong untuk mendengarkan semua domain dan IP)" +"panelPort" = "Port Pendengar" +"panelPortDesc" = "Nomor port untuk panel web. (harus menjadi port yang tidak digunakan)" +"publicKeyPath" = "Path Kunci Publik" +"publicKeyPathDesc" = "Path berkas kunci publik untuk panel web. (dimulai dengan ‘/‘)" +"privateKeyPath" = "Path Kunci Privat" +"privateKeyPathDesc" = "Path berkas kunci privat untuk panel web. (dimulai dengan ‘/‘)" +"panelUrlPath" = "URI Path" +"panelUrlPathDesc" = "URI path untuk panel web. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)" +"pageSize" = "Ukuran Halaman" +"pageSizeDesc" = "Tentukan ukuran halaman untuk tabel masuk. (0 = nonaktif)" +"remarkModel" = "Model Catatan & Karakter Pemisah" +"datepicker" = "Jenis Kalender" +"datepickerPlaceholder" = "Pilih tanggal" +"datepickerDescription" = "Tugas terjadwal akan berjalan berdasarkan kalender ini." +"sampleRemark" = "Contoh Catatan" +"oldUsername" = "Username Saat Ini" +"currentPassword" = "Kata Sandi Saat Ini" +"newUsername" = "Username Baru" +"newPassword" = "Kata Sandi Baru" +"telegramBotEnable" = "Aktifkan Bot Telegram" +"telegramBotEnableDesc" = "Mengaktifkan bot Telegram." +"telegramToken" = "Token Telegram" +"telegramTokenDesc" = "Token bot Telegram yang diperoleh dari '@BotFather'." +"telegramProxy" = "Proxy SOCKS" +"telegramProxyDesc" = "Mengaktifkan proxy SOCKS5 untuk terhubung ke Telegram. (sesuaikan pengaturan sesuai panduan)" +"telegramChatId" = "ID Obrolan Admin" +"telegramChatIdDesc" = "ID Obrolan Admin Telegram. (dipisahkan koma)(dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)" +"telegramNotifyTime" = "Waktu Notifikasi" +"telegramNotifyTimeDesc" = "Waktu notifikasi bot Telegram yang diatur untuk laporan berkala. (gunakan format waktu crontab)" +"tgNotifyBackup" = "Cadangan Database" +"tgNotifyBackupDesc" = "Kirim berkas cadangan database dengan laporan." +"tgNotifyLogin" = "Notifikasi Login" +"tgNotifyLoginDesc" = "Dapatkan notifikasi tentang username, alamat IP, dan waktu setiap kali seseorang mencoba masuk ke panel web Anda." +"sessionMaxAge" = "Durasi Sesi" +"sessionMaxAgeDesc" = "Durasi di mana Anda dapat tetap masuk. (unit: menit)" +"expireTimeDiff" = "Notifikasi Tanggal Kedaluwarsa" +"expireTimeDiffDesc" = "Dapatkan notifikasi tentang tanggal kedaluwarsa saat mencapai ambang batas ini. (unit: hari)" +"trafficDiff" = "Notifikasi Batas Traffic" +"trafficDiffDesc" = "Dapatkan notifikasi tentang batas traffic saat mencapai ambang batas ini. (unit: GB)" +"tgNotifyCpu" = "Notifikasi Beban CPU" +"tgNotifyCpuDesc" = "Dapatkan notifikasi jika beban CPU melebihi ambang batas ini. (unit: %)" +"timeZone" = "Zone Waktu" +"timeZoneDesc" = "Tugas terjadwal akan berjalan berdasarkan zona waktu ini." +"subSettings" = "Langganan" +"subEnable" = "Aktifkan Layanan Langganan" +"subEnableDesc" = "Mengaktifkan layanan langganan." +"subListen" = "IP Pendengar" +"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)" +"subPort" = "Port Pendengar" +"subPortDesc" = "Nomor port untuk layanan langganan. (harus menjadi port yang tidak digunakan)" +"subCertPath" = "Path Kunci Publik" +"subCertPathDesc" = "Path berkas kunci publik untuk layanan langganan. (dimulai dengan ‘/‘)" +"subKeyPath" = "Path Kunci Privat" +"subKeyPathDesc" = "Path berkas kunci privat untuk layanan langganan. (dimulai dengan ‘/‘)" +"subPath" = "URI Path" +"subPathDesc" = "URI path untuk layanan langganan. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)" +"subDomain" = "Domain Pendengar" +"subDomainDesc" = "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)" +"subUpdates" = "Interval Pembaruan" +"subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)" +"subEncrypt" = "Encode" +"subEncryptDesc" = "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64." +"subShowInfo" = "Tampilkan Info Penggunaan" +"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien." +"subURI" = "URI Proxy Terbalik" +"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy." + +[pages.xray] +"title" = "Konfigurasi Xray" +"save" = "Simpan" +"restart" = "Restart Xray" +"basicTemplate" = "Dasar" +"advancedTemplate" = "Lanjutan" +"generalConfigs" = "Strategi Umum" +"generalConfigsDesc" = "Opsi ini akan menentukan penyesuaian strategi umum." +"blockConfigs" = "Pelindung" +"blockConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan protokol dan situs web yang diminta." +"blockCountryConfigs" = "Blokir Negara" +"blockCountryConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan negara yang diminta." +"directCountryConfigs" = "Langsung ke Negara" +"directCountryConfigsDesc" = "Opsi ini akan langsung meneruskan lalu lintas berdasarkan negara yang diminta." +"ipv4Configs" = "Pengalihan IPv4" +"ipv4ConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui IPv4." +"warpConfigs" = "Pengalihan WARP" +"warpConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui WARP." +"Template" = "Template Konfigurasi Xray Lanjutan" +"TemplateDesc" = "File konfigurasi Xray akhir akan dibuat berdasarkan template ini." +"FreedomStrategy" = "Strategi Protokol Freedom" +"FreedomStrategyDesc" = "Atur strategi output untuk jaringan dalam Protokol Freedom." +"RoutingStrategy" = "Strategi Pengalihan Keseluruhan" +"RoutingStrategyDesc" = "Atur strategi pengalihan lalu lintas keseluruhan untuk menyelesaikan semua permintaan." +"Torrent" = "Blokir Protokol BitTorrent" +"TorrentDesc" = "Memblokir protokol BitTorrent." +"PrivateIp" = "Blokir Koneksi ke IP Pribadi" +"PrivateIpDesc" = "Memblokir pembentukan koneksi ke rentang IP pribadi." +"Ads" = "Blokir Iklan" +"AdsDesc" = "Memblokir situs web periklanan." +"Family" = "Proteksi Keluarga" +"FamilyDesc" = "Memblokir konten dewasa dan situs web berbahaya." +"Security" = "Pelindung Keamanan" +"SecurityDesc" = "Memblokir situs web malware, phishing, dan penambang kripto." +"Speedtest" = "Blokir Speedtest" +"SpeedtestDesc" = "Memblokir pembentukan koneksi ke situs web speedtest." +"IRIp" = "Blokir Koneksi ke IP Iran" +"IRIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Iran." +"IRDomain" = "Blokir Koneksi ke Domain Iran" +"IRDomainDesc" = "Memblokir pembentukan koneksi ke domain Iran." +"ChinaIp" = "Blokir Koneksi ke IP China" +"ChinaIpDesc" = "Memblokir pembentukan koneksi ke rentang IP China." +"ChinaDomain" = "Blokir Koneksi ke Domain China" +"ChinaDomainDesc" = "Memblokir pembentukan koneksi ke domain China." +"RussiaIp" = "Blokir Koneksi ke IP Rusia" +"RussiaIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Rusia." +"RussiaDomain" = "Blokir Koneksi ke Domain Rusia" +"RussiaDomainDesc" = "Memblokir pembentukan koneksi ke domain Rusia." +"VNIp" = "Blokir Koneksi ke IP Vietnam" +"VNIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Vietnam." +"VNDomain" = "Blokir Koneksi ke Domain Vietnam" +"VNDomainDesc" = "Memblokir pembentukan koneksi ke domain Vietnam." +"DirectIRIp" = "Koneksi Langsung ke IP Iran" +"DirectIRIpDesc" = "Membentuk koneksi langsung ke rentang IP Iran." +"DirectIRDomain" = "Koneksi Langsung ke Domain Iran" +"DirectIRDomainDesc" = "Membentuk koneksi langsung ke domain Iran." +"DirectChinaIp" = "Koneksi Langsung ke IP China" +"DirectChinaIpDesc" = "Membentuk koneksi langsung ke rentang IP China." +"DirectChinaDomain" = "Koneksi Langsung ke Domain China" +"DirectChinaDomainDesc" = "Membentuk koneksi langsung ke domain China." +"DirectRussiaIp" = "Koneksi Langsung ke IP Rusia" +"DirectRussiaIpDesc" = "Membentuk koneksi langsung ke rentang IP Rusia." +"DirectRussiaDomain" = "Koneksi Langsung ke Domain Rusia" +"DirectRussiaDomainDesc" = "Membentuk koneksi langsung ke domain Rusia." +"DirectVNIp" = "Koneksi Langsung ke IP Vietnam" +"DirectVNIpDesc" = "Membentuk koneksi langsung ke rentang IP Vietnam." +"DirectVNDomain" = "Koneksi Langsung ke Domain Vietnam" +"DirectVNDomainDesc" = "Membentuk koneksi langsung ke domain Vietnam." +"GoogleIPv4" = "Google" +"GoogleIPv4Desc" = "Rute lalu lintas ke Google melalui IPv4." +"NetflixIPv4" = "Netflix" +"NetflixIPv4Desc" = "Rute lalu lintas ke Netflix melalui IPv4." +"GoogleWARP" = "Google" +"GoogleWARPDesc" = "Tambahkan pengalihan untuk Google melalui WARP." +"OpenAIWARP" = "ChatGPT" +"OpenAIWARPDesc" = "Rute lalu lintas ke ChatGPT melalui WARP." +"NetflixWARP" = "Netflix" +"NetflixWARPDesc" = "Rute lalu lintas ke Netflix melalui WARP." +"SpotifyWARP" = "Spotify" +"SpotifyWARPDesc" = "Rute lalu lintas ke Spotify melalui WARP." +"IRWARP" = "Domain Iran" +"IRWARPDesc" = "Rute lalu lintas ke domain Iran melalui WARP." +"Inbounds" = "Masuk" +"InboundsDesc" = "Menerima klien tertentu." +"Outbounds" = "Keluar" +"OutboundsDesc" = "Atur jalur lalu lintas keluar." +"Routings" = "Aturan Pengalihan" +"RoutingsDesc" = "Prioritas setiap aturan penting!" +"completeTemplate" = "Semua" +"logLevel" = "Tingkat Log" +"logLevelDesc" = "Tingkat log untuk log kesalahan, menunjukkan informasi yang perlu dicatat." +"accessLog" = "Log Akses" +"accessLogDesc" = "Jalur file untuk log akses. Nilai khusus 'tidak ada' menonaktifkan log akses" + +[pages.xray.rules] +"first" = "Pertama" +"last" = "Terakhir" +"up" = "Naik" +"down" = "Turun" +"source" = "Sumber" +"dest" = "Tujuan" +"inbound" = "Masuk" +"outbound" = "Keluar" +"info" = "Info" +"add" = "Tambahkan Aturan" +"edit" = "Edit Aturan" +"useComma" = "Item yang dipisahkan koma" + +[pages.xray.outbound] +"addOutbound" = "Tambahkan Keluar" +"addReverse" = "Tambahkan Revers" +"editOutbound" = "Edit Keluar" +"editReverse" = "Edit Revers" +"tag" = "Tag" +"tagDesc" = "Tag Unik" +"address" = "Alamat" +"reverse" = "Revers" +"domain" = "Domain" +"type" = "Tipe" +"bridge" = "Jembatan" +"portal" = "Portal" +"intercon" = "Interkoneksi" + +[pages.xray.wireguard] +"secretKey" = "Kunci Rahasia" +"publicKey" = "Kunci Publik" +"allowedIPs" = "IP yang Diizinkan" +"endpoint" = "Titik Akhir" +"psk" = "Kunci Pra-Bagi" +"domainStrategy" = "Strategi Domain" + +[pages.settings.security] +"admin" = "Admin" +"secret" = "Token Rahasia" +"loginSecurity" = "Login Aman" +"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih." +"secretToken" = "Token Rahasia" +"secretTokenDesc" = "Simpan token ini dengan aman di tempat yang aman. Token ini diperlukan untuk login dan tidak dapat dipulihkan." + +[pages.settings.toasts] +"modifySettings" = "Ubah Pengaturan" +"getSettings" = "Dapatkan Pengaturan" +"modifyUser" = "Ubah Admin" +"originalUserPassIncorrect" = "Username atau password saat ini tidak valid" +"userPassMustBeNotEmpty" = "Username dan password baru tidak boleh kosong" + +[tgbot] +"keyboardClosed" = "❌ Papan ketik kustom ditutup!" +"noResult" = "❗ Tidak ada hasil!" +"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!" +"wentWrong" = "❌ Ada yang salah!" +"noIpRecord" = "❗ Tidak ada Catatan IP!" +"noInbounds" = "❗ Tidak ada masuk ditemukan!" +"unlimited" = "♾ Tak terbatas" +"add" = "Tambah" +"month" = "Bulan" +"months" = "Bulan" +"day" = "Hari" +"days" = "Hari" +"hours" = "Jam" +"unknown" = "Tidak diketahui" +"inbounds" = "Masuk" +"clients" = "Klien" +"offline" = "🔴 Offline" +"online" = "🟢 Online" + +[tgbot.commands] +"unknown" = "❗ Perintah tidak dikenal." +"pleaseChoose" = "👇 Harap pilih:\r\n" +"help" = "🤖 Selamat datang di bot ini! Ini dirancang untuk menyediakan data tertentu dari panel web dan memungkinkan Anda melakukan modifikasi sesuai kebutuhan.\r\n\r\n" +"start" = "👋 Halo {{ .Firstname }}.\r\n" +"welcome" = "🤖 Selamat datang di {{.Hostname }} bot managemen.\r\n" +"status" = "✅ Bot dalam keadaan baik!" +"usage" = "❗ Harap berikan teks untuk mencari!" +"getID" = "🆔 ID Anda:{{.ID }}" +"helpAdminCommands" = "Untuk mencari email klien:\r\n/usage [Email]\r\n\r\nUntuk mencari masuk (dengan statistik klien):\r\n/inbound [Remark]" +"helpClientCommands" = "Untuk mencari statistik, gunakan perintah berikut:\r\n\r\n/usage [Email]" + +[tgbot.messages] +"cpuThreshold" = "🔴 Beban CPU {{ .Percent }}% melebihi batas {{ .Threshold }}%" +"selectUserFailed" = "❌ Kesalahan dalam pemilihan pengguna!" +"userSaved" = "✅ Pengguna Telegram tersimpan." +"loginSuccess" = "✅ Berhasil masuk ke panel.\r\n" +"loginFailed" = "❗️ Gagal masuk ke panel.\r\n" +"report" = "🕰 Laporan Terjadwal: {{ .RunTime }}\r\n" +"datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n" +"hostname" = "💻 Host: {{ .Hostname }}\r\n" +"version" = "🚀 Versi 3X-UI: {{ .Version }}\r\n" +"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" +"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" +"ip" = "🌐 IP: {{ .IP }}\r\n" +"ips" = "🔢 IP:\r\n{{ .IPs }}\r\n" +"serverUpTime" = "⏳ Waktu Aktif: {{ .UpTime }} {{ .Unit }}\r\n" +"serverLoad" = "📈 Beban Sistem: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" +"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" +"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" +"udpCount" = "🔸 UDP: {{ .Count }}\r\n" +"traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" +"xrayStatus" = "ℹ️ Status: {{ .State }}\r\n" +"username" = "👤 Nama Pengguna: {{ .Username }}\r\n" +"time" = "⏰ Waktu: {{ .Time }}\r\n" +"inbound" = "📍 Inbound: {{ .Remark }}\r\n" +"port" = "🔌 Port: {{ .Port }}\r\n" +"expire" = "📅 Tanggal Kadaluarsa: {{ .Time }}\r\n" +"expireIn" = "📅 Kadaluarsa Dalam: {{ .Time }}\r\n" +"active" = "💡 Aktif: {{ .Enable }}\r\n" +"enabled" = "🚨 Diaktifkan: {{ .Enable }}\r\n" +"online" = "🌐 Status Koneksi: {{ .Status }}\r\n" +"email" = "📧 Email: {{ .Email }}\r\n" +"upload" = "🔼 Unggah: ↑{{ .Upload }}\r\n" +"download" = "🔽 Unduh: ↓{{ .Download }}\r\n" +"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" +"TGUser" = "👤 Pengguna Telegram: {{ .TelegramID }}\r\n" +"exhaustedMsg" = "🚨 Habis {{ .Type }}:\r\n" +"exhaustedCount" = "🚨 Jumlah Habis {{ .Type }}:\r\n" +"onlinesCount" = "🌐 Klien Online: {{ .Count }}\r\n" +"disabled" = "🛑 Dinonaktifkan: {{ .Disabled }}\r\n" +"depleteSoon" = "🔜 Habis Sebentar: {{ .Deplete }}\r\n\r\n" +"backupTime" = "🗄 Waktu Backup: {{ .Time }}\r\n" +"refreshedOn" = "\r\n📋🔄 Diperbarui Pada: {{ .Time }}\r\n\r\n" +"yes" = "✅ Ya" +"no" = "❌ Tidak" + +[tgbot.buttons] +"closeKeyboard" = "❌ Tutup Papan Ketik" +"cancel" = "❌ Batal" +"cancelReset" = "❌ Batal Reset" +"cancelIpLimit" = "❌ Batal Batas IP" +"confirmResetTraffic" = "✅ Konfirmasi Reset Lalu Lintas?" +"confirmClearIps" = "✅ Konfirmasi Hapus IPs?" +"confirmRemoveTGUser" = "✅ Konfirmasi Hapus Pengguna Telegram?" +"confirmToggle" = "✅ Konfirmasi Aktifkan/Nonaktifkan Pengguna?" +"dbBackup" = "Dapatkan Cadangan DB" +"serverUsage" = "Penggunaan Server" +"getInbounds" = "Dapatkan Inbounds" +"depleteSoon" = "Habis Sebentar" +"clientUsage" = "Dapatkan Penggunaan" +"onlines" = "Klien Online" +"commands" = "Perintah" +"refresh" = "🔄 Perbarui" +"clearIPs" = "❌ Hapus IPs" +"removeTGUser" = "❌ Hapus Pengguna Telegram" +"selectTGUser" = "👤 Pilih Pengguna Telegram" +"selectOneTGUser" = "👤 Pilih Pengguna Telegram:" +"resetTraffic" = "📈 Reset Lalu Lintas" +"resetExpire" = "📅 Ubah Tanggal Kadaluarsa" +"ipLog" = "🔢 Log IP" +"ipLimit" = "🔢 Batas IP" +"setTGUser" = "👤 Set Pengguna Telegram" +"toggle" = "🔘 Aktifkan / Nonaktifkan" +"custom" = "🔢 Kustom" +"confirmNumber" = "✅ Konfirmasi: {{ .Num }}" +"confirmNumberAdd" = "✅ Konfirmasi menambahkan: {{ .Num }}" +"limitTraffic" = "🚧 Batas Lalu Lintas" +"getBanLogs" = "Dapatkan Log Pemblokiran" + +[tgbot.answers] +"successfulOperation" = "✅ Operasi berhasil!" +"errorOperation" = "❗ Kesalahan dalam operasi." +"getInboundsFailed" = "❌ Gagal mendapatkan inbounds." +"canceled" = "❌ {{ .Email }}: Operasi dibatalkan." +"clientRefreshSuccess" = "✅ {{ .Email }}: Klien diperbarui dengan berhasil." +"IpRefreshSuccess" = "✅ {{ .Email }}: IP diperbarui dengan berhasil." +"TGIdRefreshSuccess" = "✅ {{ .Email }}: Pengguna Telegram Klien diperbarui dengan berhasil." +"resetTrafficSuccess" = "✅ {{ .Email }}: Lalu lintas direset dengan berhasil." +"setTrafficLimitSuccess" = "✅ {{ .Email }}: Batas lalu lintas disimpan dengan berhasil." +"expireResetSuccess" = "✅ {{ .Email }}: Hari kadaluarsa direset dengan berhasil." +"resetIpSuccess" = "✅ {{ .Email }}: Batas IP {{ .Count }} disimpan dengan berhasil." +"clearIpSuccess" = "✅ {{ .Email }}: IP dihapus dengan berhasil." +"getIpLog" = "✅ {{ .Email }}: Dapatkan Log IP." +"getUserInfo" = "✅ {{ .Email }}: Dapatkan Info Pengguna Telegram." +"removedTGUserSuccess" = "✅ {{ .Email }}: Pengguna Telegram dihapus dengan berhasil." +"enableSuccess" = "✅ {{ .Email }}: Diaktifkan dengan berhasil." +"disableSuccess" = "✅ {{ .Email }}: Dinonaktifkan dengan berhasil." +"askToAddUserId" = "Konfigurasi Anda tidak ditemukan!\r\nSilakan minta admin Anda untuk menggunakan ID Telegram Anda dalam konfigurasi Anda.\r\n\r\nID Pengguna Anda: {{ .TgUserID }}" From c53cee31f5a64ed3292f977bf5a0749324eb78a2 Mon Sep 17 00:00:00 2001 From: Saeid <43953720+surbiks@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:40:49 +0330 Subject: [PATCH 22/37] Manage balancers in settings UI (#1759) * add balancer config to ui * manage balancer in rules table * fix balancer translations * fix edit button text --- web/html/xui/xray.html | 166 ++++++++++++++++++++++++- web/html/xui/xray_balancer_modal.html | 111 +++++++++++++++++ web/html/xui/xray_rule_modal.html | 23 +++- web/translation/translate.en_US.toml | 11 ++ web/translation/translate.es_ES.toml | 11 ++ web/translation/translate.fa_IR.toml | 11 ++ web/translation/translate.ru_RU.toml | 11 ++ web/translation/translate.vi_VN.toml | 11 ++ web/translation/translate.zh_Hans.toml | 11 ++ 9 files changed, 363 insertions(+), 3 deletions(-) create mode 100644 web/html/xui/xray_balancer_modal.html diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html index 267103cb..a144c766 100644 --- a/web/html/xui/xray.html +++ b/web/html/xui/xray.html @@ -327,6 +327,14 @@ [[ rule.outboundTag ]] + - {{template "form/sniffing"}} -{{end}} \ No newline at end of file +{{end}} diff --git a/web/html/xui/form/stream/external_proxy.html b/web/html/xui/form/stream/external_proxy.html index 2a072df9..9c3ed2e0 100644 --- a/web/html/xui/form/stream/external_proxy.html +++ b/web/html/xui/form/stream/external_proxy.html @@ -20,7 +20,7 @@ - - + - {{end}} diff --git a/web/html/xui/form/stream/stream_tcp.html b/web/html/xui/form/stream/stream_tcp.html index 19a09ac3..8576df8c 100644 --- a/web/html/xui/form/stream/stream_tcp.html +++ b/web/html/xui/form/stream/stream_tcp.html @@ -33,7 +33,7 @@ - + + + @@ -79,4 +79,4 @@ -{{end}} \ No newline at end of file +{{end}} diff --git a/web/html/xui/form/stream/stream_ws.html b/web/html/xui/form/stream/stream_ws.html index 00b64167..62380e94 100644 --- a/web/html/xui/form/stream/stream_ws.html +++ b/web/html/xui/form/stream/stream_ws.html @@ -7,7 +7,7 @@ - + + + diff --git a/web/html/xui/index.html b/web/html/xui/index.html index a9391952..abd3b8d0 100644 --- a/web/html/xui/index.html +++ b/web/html/xui/index.html @@ -18,6 +18,14 @@ .ant-card-dark h2 { color: hsla(0, 0%, 100%, .65); } + + .ant-tag-df { + color: rgb(0 0 0 / 80%); + } + + .dark .ant-tag-df { + color: rgb(255 255 255 / 80%); + } @@ -36,15 +44,15 @@ -
CPU: [[ cpuCoreFormat(status.cpuCores) ]]
-
Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]
+
CPU: [[ cpuCoreFormat(status.cpuCores) ]]
+
Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]
- {{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]] + {{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
@@ -56,7 +64,7 @@ :stroke-color="status.swap.color" :percent="status.swap.percent">
- Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]] + Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
@@ -64,7 +72,7 @@ :stroke-color="status.disk.color" :percent="status.disk.percent">
- {{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]] + {{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
@@ -75,25 +83,25 @@ - + - 3X-UI v{{ .cur_ver }} - Xray v[[ status.xray.version ]] - @panel3xui + 3X-UI: + v{{ .cur_ver }} + @Panel3xui - + - {{ i18n "menu.link" }}: - {{ i18n "pages.index.logs" }} - {{ i18n "pages.index.config" }} - {{ i18n "pages.index.backup" }} + {{ i18n "pages.index.operationHours" }}: + Xray [[ formatSecond(status.appStats.uptime) ]] + OS [[ formatSecond(status.uptime) ]] - + - {{ i18n "pages.index.xrayStatus" }}: - [[ status.xray.state ]] + {{ i18n "pages.index.xrayStatus" }}: + [[ status.xray.state ]] + An error occurred while running Xray @@ -106,137 +114,143 @@ {{ i18n "pages.index.stopXray" }} {{ i18n "pages.index.restartXray" }} - {{ i18n "pages.index.xraySwitch" }} + v[[ status.xray.version ]] - + - {{ i18n "pages.index.operationHours" }}: - Xray - [[ formatSecond(status.appStats.uptime) ]] - OS - [[ formatSecond(status.uptime) ]] + {{ i18n "menu.link" }}: + {{ i18n "pages.index.logs" }} + {{ i18n "pages.index.config" }} + {{ i18n "pages.index.backup" }} - + - {{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]] + {{ i18n "pages.index.systemLoad" }}: + + [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]] - + - + - {{ i18n "usage"}}: - RAM [[ sizeFormat(status.appStats.mem) ]] - - Threads [[ status.appStats.threads ]] - + {{ i18n "usage"}}: + + RAM [[ sizeFormat(status.appStats.mem) ]] + + + Threads [[ status.appStats.threads ]] + - + - - IPv4: + + IPv4 - - - - - IPv6: + + + + + IPv6 - + - + - - TCP: [[ status.tcpCount ]] + + TCP: [[ status.tcpCount ]] - + - - UDP: [[ status.udpCount ]] + + UDP: [[ status.udpCount ]] - + - + - - [[ sizeFormat(status.netIO.up) ]]/s + + + Up: [[ sizeFormat(status.netIO.up) ]]/s - + - - [[ sizeFormat(status.netIO.down) ]]/s + + + Down: [[ sizeFormat(status.netIO.down) ]]/s - + - + - - [[ sizeFormat(status.netTraffic.sent) ]] + + - + Out: [[ sizeFormat(status.netTraffic.sent) ]] + - - [[ sizeFormat(status.netTraffic.recv) ]] + + - + In: [[ sizeFormat(status.netTraffic.recv) ]] + @@ -256,7 +270,7 @@ > diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html index 0acbbfec..533553c5 100644 --- a/web/html/xui/settings.html +++ b/web/html/xui/settings.html @@ -76,15 +76,15 @@ - - - + + + {{ i18n "pages.settings.save" }} {{ i18n "pages.settings.restartPanel" }} - +