diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 6afe8618..00ac9169 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -18,7 +18,7 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0
- - name: Log in to GitHub Container Registry
+ - name: Login to GHCR
uses: docker/login-action@v3.0.0
with:
registry: ghcr.io
@@ -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 }}
@@ -36,6 +36,6 @@ jobs:
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
- platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386, linux/arm/v5
+ platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
+ labels: ${{ steps.meta.outputs.labels }}
\ No newline at end of file
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..5f4a36c3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,11 +1,9 @@
# ========================================================
# Stage: Builder
# ========================================================
-FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
+FROM 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,13 @@ 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.sh /usr/bin/x-ui
# Configure fail2ban
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
@@ -47,4 +49,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 6b0beab7..a1c902e2 100644
--- a/README.md
+++ b/README.md
@@ -25,11 +25,39 @@ 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.1`:
+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.1
+bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.3
```
+
+## 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
@@ -106,10 +134,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
```
@@ -192,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)
@@ -272,13 +281,13 @@ 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,
+ "loglevel": "warning"
},
```
diff --git a/config/version b/config/version
index 7c327287..abae0d9a 100644
--- a/config/version
+++ b/config/version
@@ -1 +1 @@
-2.1.1
\ No newline at end of file
+2.1.3
\ No newline at end of file
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/go.mod b/go.mod
index 80fd416a..76d5f8eb 100644
--- a/go.mod
+++ b/go.mod
@@ -8,18 +8,18 @@ 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
- 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
golang.org/x/text v0.14.0
google.golang.org/grpc v1.61.0
gorm.io/driver/sqlite v1.5.4
- gorm.io/gorm v1.25.5
+ gorm.io/gorm v1.25.6
)
require (
diff --git a/go.sum b/go.sum
index 1663fce7..8e8b0db6 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=
@@ -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=
@@ -437,8 +436,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
-gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
-gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A=
+gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
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/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/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js
index 0191c4a4..dc02d91b 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,
);
}
@@ -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='', reserved='',
+ mtu=1420, secretKey='',
+ address=[''], workers=2, domainStrategy='', reserved='',
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
super();
this.mtu = mtu;
diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js
index b2c8a2e5..e9676252 100644
--- a/web/assets/js/model/xray.js
+++ b/web/assets/js/model/xray.js
@@ -2297,7 +2297,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
};
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
- constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], keepAlive=0) {
+ constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], keepAlive=0) {
super();
this.publicKey = publicKey;
this.psk = psk;
diff --git a/web/assets/js/util/utils.js b/web/assets/js/util/utils.js
index 48ff237d..61b322bd 100644
--- a/web/assets/js/util/utils.js
+++ b/web/assets/js/util/utils.js
@@ -83,41 +83,6 @@ class HttpUtil {
}
return msg;
}
-
- static async jsonPost(url, data) {
- let msg;
- try {
- const requestOptions = {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(data),
- };
- const resp = await fetch(url, requestOptions);
- const response = await resp.json();
-
- msg = this._respToMsg({data : response});
- } catch (e) {
- msg = new Msg(false, e.toString());
- }
- this._handleMsg(msg);
- return msg;
- }
-
- static async postWithModalJson(url, data, modal) {
- if (modal) {
- modal.loading(true);
- }
- const msg = await this.jsonPost(url, data);
- if (modal) {
- modal.loading(false);
- if (msg instanceof Msg && msg.success) {
- modal.close();
- }
- }
- return msg;
- }
}
class PromiseUtil {
diff --git a/web/controller/inbound.go b/web/controller/inbound.go
index 0aa2e2df..86da9813 100644
--- a/web/controller/inbound.go
+++ b/web/controller/inbound.go
@@ -1,7 +1,6 @@
package controller
import (
- "errors"
"encoding/json"
"fmt"
"strconv"
@@ -33,9 +32,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/clientIps/:email", a.getClientIps)
g.POST("/clearClientIps/:email", a.clearClientIps)
g.POST("/addClient", a.addInboundClient)
- g.POST("/addGroupClient", a.addGroupInboundClient)
g.POST("/:id/delClient/:clientId", a.delInboundClient)
- g.POST("/updateClients", a.updateGroupInboundClient)
g.POST("/updateClient/:clientId", a.updateInboundClient)
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
g.POST("/resetAllTraffics", a.resetAllTraffics)
@@ -163,51 +160,23 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
func (a *InboundController) addInboundClient(c *gin.Context) {
data := &model.Inbound{}
- err := c.ShouldBind(data)
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
- return
- }
+ err := c.ShouldBind(data)
+ if err != nil {
+ jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
+ return
+ }
- needRestart := true
-
- needRestart, err = a.inboundService.AddInboundClient(data)
- if err != nil {
- jsonMsg(c, "Something went wrong!", err)
- return
- }
- jsonMsg(c, "Client(s) added", nil)
- if err == nil && needRestart {
- a.xrayService.SetToNeedRestart()
- }
-}
-
-func (a *InboundController) addGroupInboundClient(c *gin.Context) {
- var requestData []model.Inbound
-
- err := c.ShouldBindJSON(&requestData)
-
- if err != nil {
- jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
- return
- }
-
- needRestart := true
-
- for _, data := range requestData {
-
- needRestart, err = a.inboundService.AddInboundClient(&data)
- if err != nil {
- jsonMsg(c, "Something went wrong!", err)
- return
- }
- }
-
- jsonMsg(c, "Client(s) added", nil)
- if err == nil && needRestart {
- a.xrayService.SetToNeedRestart()
- }
+ needRestart := true
+ needRestart, err = a.inboundService.AddInboundClient(data)
+ if err != nil {
+ jsonMsg(c, "Something went wrong!", err)
+ return
+ }
+ jsonMsg(c, "Client(s) added", nil)
+ if err == nil && needRestart {
+ a.xrayService.SetToNeedRestart()
+ }
}
func (a *InboundController) delInboundClient(c *gin.Context) {
@@ -254,56 +223,6 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
}
}
-func (a *InboundController) updateGroupInboundClient(c *gin.Context) {
- var requestData []map[string]interface{}
-
- if err := c.ShouldBindJSON(&requestData); err != nil {
- jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
- return
- }
-
- needRestart := false
-
- for _, item := range requestData {
-
- inboundMap, ok := item["inbound"].(map[string]interface{})
- if !ok {
- jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'inbound' to map"))
- return
- }
-
- clientId, ok := item["clientId"].(string)
- if !ok {
- jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'clientId' to string"))
- return
- }
-
- inboundJSON, err := json.Marshal(inboundMap)
- if err != nil {
- jsonMsg(c, "Something went wrong!", err)
- return
- }
-
- var inboundModel model.Inbound
- if err := json.Unmarshal(inboundJSON, &inboundModel); err != nil {
- jsonMsg(c, "Something went wrong!", err)
- return
- }
-
- if restart, err := a.inboundService.UpdateInboundClient(&inboundModel, clientId); err != nil {
- jsonMsg(c, "Something went wrong!", err)
- return
- } else {
- needRestart = needRestart || restart
- }
- }
-
- jsonMsg(c, "Client updated", nil)
- if needRestart {
- a.xrayService.SetToNeedRestart()
- }
-}
-
func (a *InboundController) resetClientTraffic(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
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/common/qrcode_modal.html b/web/html/common/qrcode_modal.html
index 31b3450c..3c4fd929 100644
--- a/web/html/common/qrcode_modal.html
+++ b/web/html/common/qrcode_modal.html
@@ -11,12 +11,10 @@
Subscription
- {{ i18n "pages.inbounds.client" }}
-
-
- [[ row.remark ]]
-
-
+ {{ i18n "pages.inbounds.client" }}
+
+ [[ row.remark ]]
+
@@ -29,14 +27,12 @@
qrcodes: [],
clipboard: null,
visible: false,
- isJustSub: false,
subId: '',
- show: function (title = '', dbInbound, client, isJustSub = false) {
+ show: function (title = '', dbInbound, client) {
this.title = title;
this.dbInbound = dbInbound;
this.inbound = dbInbound.toInbound();
this.client = client;
- this.isJustSub = isJustSub;
this.subId = '';
this.qrcodes = [];
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
@@ -57,9 +53,6 @@
el: '#qrcode-modal',
data: {
qrModal: qrModal,
- get isJustSub(){
- return qrModal.isJustSub
- }
},
methods: {
copyToClipboard(elmentId, content) {
diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html
index cb15e1e7..4b270607 100644
--- a/web/html/xui/client_modal.html
+++ b/web/html/xui/client_modal.html
@@ -15,16 +15,8 @@
confirmLoading: false,
title: '',
okText: '',
- group: {
- canGroup: true,
- isGroup: false,
- currentClient: null,
- inbounds: [],
- clients: [],
- editIds: []
- },
+ isEdit: false,
dbInbound: new DBInbound(),
- dbInbounds: null,
inbound: new Inbound(),
clients: [],
clientStats: [],
@@ -33,137 +25,33 @@
clientIps: null,
delayedStart: false,
ok() {
- if (clientModal.group.isGroup && clientModal.group.canGroup) {
- const currentClient = clientModal.group.currentClient;
-
- clientModal.group.clients.forEach((client, index) => {
- const { email, limitIp, totalGB, expiryTime, reset, enable, subId, tgId, flow } = currentClient;
-
- const match = email.match(/^(.*?)__/);
- const new_email = match ? match[1] : email;
-
- client.email = `${new_email}__${index + 1}`;
- client.limitIp = limitIp;
- client.totalGB = totalGB;
- client.expiryTime = expiryTime;
- client.reset = reset;
- client.enable = enable;
-
- if (subId) {
- client.subId = subId;
- }
- if (tgId) {
- client.tgId = tgId;
- }
- if (flow) {
- client.flow = flow;
- }
- });
-
- if (clientModal.isEdit) {
- ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds, clientModal.group.editIds);
- }else{
- ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds);
- }
+ if (clientModal.isEdit) {
+ ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
} else {
- if (clientModal.isEdit){
- ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
- }else{
- ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
- }
+ ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
}
},
- show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, dbInbounds = null, confirm = () => { }, isEdit = false }) {
- this.group = {
- canGroup: true,
- isGroup: false,
- currentClient: null,
- inbounds: [],
- clients: [],
- editIds: []
- }
- this.dbInbounds = dbInbounds;
+ show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) {
this.visible = true;
this.title = title;
this.okText = okText;
this.isEdit = isEdit;
- if (dbInbounds !== null && Array.isArray(dbInbounds)) {
- if (isEdit) {
- this.showProcess(dbInbound, index);
- let processSingleEdit = true
- if (this.group.canGroup){
- this.group.currentClient = this.clients[this.index]
- const response = this.getGroupInboundsClients(dbInbounds,this.group.currentClient)
- if (response.clients.length > 1){
- this.group.isGroup = true;
- this.group.inbounds = response.inbounds
- this.group.clients = response.clients
- this.group.editIds = response.editIds
- if (this.clients[index].expiryTime < 0) {
- this.delayedStart = true;
- }
- processSingleEdit = false
- }
- }
- if(processSingleEdit){
- this.singleEditClientProcess(index)
- }
- } else {
- this.group.isGroup = true;
- dbInbounds.forEach((dbInboundItem) => {
- this.showProcess(dbInboundItem);
- this.addClient(this.inbound.protocol, this.clients);
- this.group.inbounds.push(dbInboundItem.id)
- this.group.clients.push(this.clients[this.index])
- })
- this.group.currentClient = this.clients[this.index]
- }
- } else {
- this.showProcess(dbInbound, index);
- if (isEdit) {
- this.singleEditClientProcess(index)
- } else {
- this.addClient(this.inbound.protocol, this.clients);
- }
- }
- this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
- this.confirm = confirm;
- },
- showProcess(dbInbound, index = null) {
this.dbInbound = new DBInbound(dbInbound);
this.inbound = dbInbound.toInbound();
this.clients = this.inbound.clients;
this.index = index === null ? this.clients.length : index;
this.delayedStart = false;
- },
- singleEditClientProcess(index) {
- if (this.clients[index].expiryTime < 0) {
- this.delayedStart = true;
- }
- this.oldClientId = this.getClientId(this.dbInbound.protocol, this.clients[index]);
- },
- getGroupInboundsClients(dbInbounds, currentClient) {
- const response = {
- inbounds: [],
- clients: [],
- editIds: []
- }
- dbInbounds.forEach((dbInboundItem) => {
- const dbInbound = new DBInbound(dbInboundItem);
- const inbound = dbInbound.toInbound();
- const clients = inbound.clients;
- if (clients.length > 0){
- clients.forEach((client) => {
- if (client['subId'] === currentClient['subId']){
- response.inbounds.push(dbInboundItem.id)
- response.clients.push(client)
- response.editIds.push(this.getClientId(dbInbound.protocol, client))
- }
- })
+ if (isEdit) {
+ if (this.clients[index].expiryTime < 0) {
+ this.delayedStart = true;
}
- })
- return response;
- },
+ this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]);
+ } else {
+ this.addClient(this.inbound.protocol, this.clients);
+ }
+ this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
+ this.confirm = confirm;
+ },
getClientId(protocol, client) {
switch (protocol) {
case Protocols.TROJAN: return client.password;
@@ -206,18 +94,6 @@
get isEdit() {
return this.clientModal.isEdit;
},
- get isGroup() {
- return this.clientModal.group.isGroup;
- },
- get isGroupEdit() {
- return this.clientModal.group.canGroup;
- },
- set isGroupEdit(value) {
- this.clientModal.group.canGroup = value;
- if (!value){
- this.clientModal.singleEditClientProcess(this.clientModal.index)
- }
- },
get datepicker() {
return app.datepicker;
},
diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html
index 526bbb8b..63cb8aef 100644
--- a/web/html/xui/form/client.html
+++ b/web/html/xui/form/client.html
@@ -3,18 +3,6 @@
-
-
-
-
- {{ i18n "pages.inbounds.isGroupEditDesc" }}
-
- {{ i18n "pages.inbounds.isGroupEdit" }}
-
-
-
-
-
@@ -27,7 +15,7 @@
-
+
@@ -40,7 +28,7 @@
-
+
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 @@
- +
+ +
diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html
index 7f9ed740..ab42e09c 100644
--- a/web/html/xui/inbound_modal.html
+++ b/web/html/xui/inbound_modal.html
@@ -13,7 +13,6 @@
confirmLoading: false,
okText: '{{ i18n "sure" }}',
isEdit: false,
- isGroup: false,
confirm: null,
inbound: new Inbound(),
dbInbound: new DBInbound(),
@@ -61,9 +60,6 @@
get isEdit() {
return inModal.isEdit;
},
- get isGroup() {
- return inModal.isGroup;
- },
get client() {
return inModal.inbound.clients[0];
},
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index 2707fc42..c986e3fd 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -145,10 +145,6 @@
{{ i18n "pages.inbounds.delDepletedClients" }}
-
-
- {{ i18n "pages.client.groupAdd"}}
-
@@ -289,7 +285,7 @@
[[ clientEmail ]]
[[ clientCount[dbInbound.id].online.length ]]
-
+
@@ -343,7 +339,7 @@
[[ dbInbound.toInbound().stream.network ]]
tls
reality
-
+
@@ -377,7 +373,7 @@
[[ clientEmail ]]
[[ clientCount[dbInbound.id].online.length ]]
-
+
@@ -744,9 +740,6 @@
case "delDepletedClients":
this.delDepletedClients(-1)
break;
- case "addGroupClient":
- this.openGroupAddClient()
- break;
}
},
clickAction(action, dbInbound) {
@@ -890,20 +883,6 @@
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
},
- openGroupAddClient() {
- clientModal.show({
- title: '{{ i18n "pages.client.groupAdd"}}',
- okText: '{{ i18n "pages.client.submitAdd"}}',
- dbInbounds: this.dbInbounds,
- confirm: async (clients, dbInboundIds) => {
- clientModal.loading();
- await this.addGroupClient(clients, dbInboundIds);
- clientModal.close();
- await this.showQrcode(dbInboundIds[0],clients[0], true)
- },
- isEdit: false
- });
- },
openAddClient(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
clientModal.show({
@@ -914,7 +893,6 @@
clientModal.loading();
await this.addClient(clients, dbInboundId);
clientModal.close();
- await this.showQrcode(dbInboundId,clients)
},
isEdit: false
});
@@ -939,7 +917,6 @@
clientModal.show({
title: '{{ i18n "pages.client.edit"}}',
okText: '{{ i18n "pages.client.submitEdit"}}',
- dbInbounds: this.dbInbounds,
dbInbound: dbInbound,
index: index,
confirm: async (client, dbInboundId, clientId) => {
@@ -963,41 +940,14 @@
id: dbInboundId,
settings: '{"clients": [' + clients.toString() + ']}',
};
-
- await this.submit(`/panel/inbound/addClient`, data)
- },
-
- async addGroupClient(clients, dbInboundIds) {
- const data = []
- dbInboundIds.forEach((dbInboundId, index) => {
- data.push({
- id: dbInboundId,
- settings: '{"clients": [' + clients[index].toString() + ']}',
- })
- })
-
- await this.submit(`/panel/inbound/addGroupClient`, data, true)
+ await this.submit(`/panel/inbound/addClient`, data);
},
async updateClient(client, dbInboundId, clientId) {
- if (Array.isArray(client) && Array.isArray(dbInboundId) && Array.isArray(clientId)){
- const data = []
- client.forEach((client, index) => {
- data.push({
- clientId: clientId[index],
- inbound: {
- id: dbInboundId[index],
- settings: '{"clients": [' + client.toString() + ']}',
- }
- })
- })
- await this.submit(`/panel/inbound/updateClients`, data, true);
- }else{
- const data = {
- id: dbInboundId,
- settings: '{"clients": [' + client.toString() + ']}',
- };
- await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
- }
+ const data = {
+ id: dbInboundId,
+ settings: '{"clients": [' + client.toString() + ']}',
+ };
+ await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
},
resetTraffic(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
@@ -1051,8 +1001,8 @@
checkFallback(dbInbound) {
newDbInbound = new DBInbound(dbInbound);
if (dbInbound.listen.startsWith("@")){
- rootInbound = this.inbounds.find((i) =>
- i.isTcp &&
+ rootInbound = this.inbounds.find((i) =>
+ i.isTcp &&
['trojan','vless'].includes(i.protocol) &&
i.settings.fallbacks.find(f => f.dest === dbInbound.listen)
);
@@ -1068,10 +1018,10 @@
}
return newDbInbound;
},
- showQrcode(dbInboundId, client, isJustSub = false) {
+ showQrcode(dbInboundId, client) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
newDbInbound = this.checkFallback(dbInbound);
- qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client, isJustSub);
+ qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client);
},
showInfo(dbInboundId, client) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
@@ -1100,8 +1050,8 @@
await this.updateClient(clients[index], dbInboundId, clientId);
this.loading(false);
},
- async submit(url, data, isJson = false) {
- const msg = isJson ? await HttpUtil.postWithModalJson(url, data) : await HttpUtil.postWithModal(url, data);
+ async submit(url, data) {
+ const msg = await HttpUtil.postWithModal(url, data);
if (msg.success) {
await this.getDBInbounds();
}
@@ -1243,7 +1193,7 @@
value: '',
okText: '{{ i18n "pages.inbounds.import" }}',
confirm: async (dbInboundText) => {
- await this.submit('/panel/inbound/import', {data: dbInboundText});
+ await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal);
promptModal.close();
},
});
diff --git a/web/html/xui/index.html b/web/html/xui/index.html
index 3c01a29f..258d4154 100644
--- a/web/html/xui/index.html
+++ b/web/html/xui/index.html
@@ -299,12 +299,12 @@
+ :href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs.join('\n'))" download="x-ui.log">
{{ i18n "download" }} x-ui.log
-
+
0 ? this.formatLogs(logs) : "No Record...";
},
formatLogs(logs) {
let formattedLogs = '';
diff --git a/web/html/xui/warp_modal.html b/web/html/xui/warp_modal.html
index d1663df0..38310a69 100644
--- a/web/html/xui/warp_modal.html
+++ b/web/html/xui/warp_modal.html
@@ -140,6 +140,7 @@
mtu: 1420,
secretKey: warpModal.warpData.private_key,
address: Object.values(config.interface.addresses),
+ domainStrategy: 'ForceIP',
peers: [{
publicKey: peer.public_key,
endpoint: peer.endpoint.host,
diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html
index 05f5f178..267103cb 100644
--- a/web/html/xui/xray.html
+++ b/web/html/xui/xray.html
@@ -147,6 +147,40 @@
+
+
+
+
+
+
+
+ [[ s ]]
+
+
+
+
+
+
+
+
+
+
+
+ [[ s ]]
+
+
+
+
@@ -341,8 +375,15 @@
- {{ i18n "pages.xray.outbound.addOutbound" }}
- WARP
+
+
+ {{ i18n "pages.xray.outbound.addOutbound" }}
+ WARP
+
+
+
+
+
reality
+
+ [[ findOutboundTraffic(outbound) ]]
+
@@ -463,6 +507,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 +528,9 @@
oldXraySetting: '',
xraySetting: '',
inboundTags: [],
+ outboundsTraffic: [],
saveBtnDisable: true,
+ refreshing: false,
restartResult: '',
isMobile: window.innerWidth <= 768,
advSettings: 'xraySetting',
@@ -521,6 +568,8 @@
protocol: "freedom"
},
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
+ logLevel: ["none" , "debug" , "info" , "warning", "error"],
+ access: ["none" , "./access.log" ],
settingsData: {
protocols: {
bittorrent: ["bittorrent"],
@@ -569,9 +618,11 @@
familyProtectDNS: {
"servers": [
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
- "1.0.0.3"
+ "1.0.0.3",
+ "2606:4700:4700::1113",
+ "2606:4700:4700::1003"
],
- "queryStrategy": "UseIPv4"
+ "queryStrategy": "UseIP"
},
}
},
@@ -579,6 +630,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/");
@@ -757,6 +814,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){
@@ -814,6 +879,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"}}',
@@ -947,6 +1028,7 @@
async mounted() {
await this.getXraySetting();
await this.getXrayResult();
+ await this.getOutboundsTraffic();
while (true) {
await PromiseUtil.sleep(800);
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
@@ -1063,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 b393e68d..905a8cc7 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"
@@ -92,24 +94,34 @@ func (j *CheckClientIpJob) checkFail2BanInstalled() {
func (j *CheckClientIpJob) processLogFile() {
accessLogPath := xray.GetAccessLogPath()
- if accessLogPath == "" {
- logger.Warning("access.log doesn't exist in your config.json")
+
+ if accessLogPath == "none" {
+ logger.Warning("Access log is set to 'none' check your Xray Configs")
return
}
- data, err := os.ReadFile(accessLogPath)
- InboundClientIps := make(map[string][]string)
- j.checkError(err)
+ if accessLogPath == "" {
+ logger.Warning("Access log doesn't exist in your Xray Configs")
+ return
+ }
- lines := strings.Split(string(data), "\n")
- for _, line := range lines {
- ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
+ 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()
+
+ 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
}
@@ -124,13 +136,14 @@ func (j *CheckClientIpJob) processLogFile() {
continue
}
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
-
} else {
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
}
}
}
+ j.checkError(scanner.Err())
+
shouldCleanLog := false
for clientEmail, ips := range InboundClientIps {
@@ -141,7 +154,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 +163,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)
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/config.json b/web/service/config.json
index c846d590..6cf6c3a6 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",
@@ -24,6 +25,7 @@
],
"outbounds": [
{
+ "tag": "direct",
"protocol": "freedom",
"settings": {}
},
@@ -42,7 +44,9 @@
},
"system": {
"statsInboundDownlink": true,
- "statsInboundUplink": true
+ "statsInboundUplink": true,
+ "statsOutboundDownlink": true,
+ "statsOutboundUplink": true
}
},
"routing": {
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/web/service/tgbot.go b/web/service/tgbot.go
index fdb5d312..7bd6a179 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"
@@ -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)
}
@@ -260,7 +265,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 +351,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 +1027,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 +1385,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 +1494,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
@@ -1565,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)
}
}
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")
}
diff --git a/web/service/xray_setting.go b/web/service/xray_setting.go
index 1d2e696e..86bb1d7b 100644
--- a/web/service/xray_setting.go
+++ b/web/service/xray_setting.go
@@ -80,7 +80,7 @@ func (s *XraySettingService) RegWarp(secretKey string, publicKey string) (string
hostName, _ := os.Hostname()
data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName)
- url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg")
+ url := "https://api.cloudflareclient.com/v0a2158/reg"
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
if err != nil {
diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml
index 0b7ac5b2..78c545f6 100644
--- a/web/translation/translate.en_US.toml
+++ b/web/translation/translate.en_US.toml
@@ -181,12 +181,9 @@
"exportInbound" = "Export Inbound"
"import" = "Import"
"importInbound" = "Import an Inbound"
-"isGroupEdit" = "Group editing"
-"isGroupEditDesc" = "All clients with the same subscription are edited"
[pages.client]
"add" = "Add Client"
-"groupAdd" = "Add subscription user"
"edit" = "Edit Client"
"submitAdd" = "Add Client"
"submitEdit" = "Save Changes"
@@ -312,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"
@@ -395,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"
@@ -455,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.es_ES.toml b/web/translation/translate.es_ES.toml
index 926325cf..b21fe252 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"
@@ -181,12 +181,9 @@
"exportInbound" = "Exportación entrante"
"import" = "Importar"
"importInbound" = "Importar un entrante"
-"isGroupEdit" = "Edición de grupo"
-"isGroupEditDesc" = "Se editan todos los usuarios con la misma suscripción"
[pages.client]
"add" = "Agregar Cliente"
-"groupAdd" = "Agregar usuario de suscripción"
"edit" = "Editar Cliente"
"submitAdd" = "Agregar Cliente"
"submitEdit" = "Guardar Cambios"
@@ -395,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 17e6facc..7abaf973 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -181,12 +181,9 @@
"exportInbound" = "استخراج ورودی"
"import" = "افزودن"
"importInbound" = "افزودن یک ورودی"
-"isGroupEdit" = "ویرایش گروهی"
-"isGroupEditDesc" = "تمامی کاربران با سابسکریپشن یکسان ویرایش میشوند"
[pages.client]
"add" = "کاربر جدید"
-"groupAdd" = "کاربر جدید سابسکریپشن"
"edit" = "ویرایش کاربر"
"submitAdd" = "اضافه کردن"
"submitEdit" = "ذخیره تغییرات"
@@ -395,6 +392,10 @@
"Routings" = "قوانین مسیریابی"
"RoutingsDesc" = "اولویت هر قانون مهم است"
"completeTemplate" = "کامل"
+"logLevel" = "سطح گزارش"
+"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند."
+"accessLog" = "مسیر گزارش"
+"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارشهای دسترسی را غیرفعال میکند."
[pages.xray.rules]
"first" = "اولین"
@@ -455,7 +456,7 @@
"wentWrong" = "❌ مشکلی رخ داده است!"
"noIpRecord" = "❗ رکورد IP یافت نشد!"
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
-"unlimited" = "♾ نامحدود"
+"unlimited" = "♾ - نامحدود(ریست)"
"add" = "اضافه کردن"
"month" = "ماه"
"months" = "ماهها"
diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml
index c567f38b..cbd1d4f9 100644
--- a/web/translation/translate.ru_RU.toml
+++ b/web/translation/translate.ru_RU.toml
@@ -181,12 +181,9 @@
"exportInbound" = "Экспорт входящих"
"import" = "Импортировать"
"importInbound" = "Импортировать входящее сообщение"
-"isGroupEdit" = "Редактирование группы"
-"isGroupEditDesc" = "Редактируются все пользователи с одной подпиской"
[pages.client]
"add" = "Добавить пользователя"
-"groupAdd" = "Добавить пользователя подписки"
"edit" = "Редактировать пользователя"
"submitAdd" = "Добавить пользователя"
"submitEdit" = "Сохранить изменения"
@@ -395,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 5876a5b4..f94b56b0 100644
--- a/web/translation/translate.vi_VN.toml
+++ b/web/translation/translate.vi_VN.toml
@@ -181,12 +181,9 @@
"exportInbound" = "Xuất nhập khẩu"
"import" = "Nhập"
"importInbound" = "Nhập inbound"
-"isGroupEdit" = "Chỉnh sửa nhóm"
-"isGroupEditDesc" = "Tất cả người dùng có cùng đăng ký đều được chỉnh sửa"
[pages.client]
"add" = "Thêm người dùng"
-"groupAdd" = "Thêm người dùng đăng ký"
"edit" = "Chỉnh sửa người dùng"
"submitAdd" = "Thêm"
"submitEdit" = "Lưu thay đổi"
@@ -395,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 29dc8f4c..d64e1331 100644
--- a/web/translation/translate.zh_Hans.toml
+++ b/web/translation/translate.zh_Hans.toml
@@ -181,12 +181,9 @@
"exportInbound" = "出口 入境"
"import"="导入"
"importInbound" = "导入入站"
-"isGroupEdit" = "分组编辑"
-"isGroupEditDesc" = "编辑具有相同订阅的所有用户"
[pages.client]
"add" = "添加客户端"
-"groupAdd" = "添加订阅用户"
"edit" = "编辑客户端"
"submitAdd" = "添加客户端"
"submitEdit" = "保存修改"
@@ -395,6 +392,10 @@
"Routings" = "路由规则"
"RoutingsDesc" = "每条规则的优先级都很重要"
"completeTemplate" = "全部"
+"logLevel" = "日志级别"
+"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。"
+"accessLog" = "访问日志"
+"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志"
[pages.xray.rules]
"first" = "第一个"
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
}
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/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:])
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
}