Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
045c549937 | ||
|
|
25430b7818 | ||
|
|
6aeedac051 | ||
|
|
9c231a41d1 | ||
|
|
ba673778ea | ||
|
|
8698024b61 | ||
|
|
e8b576fa65 | ||
|
|
c49147c5db | ||
|
|
149bd0ec51 | ||
|
|
f3280b46fe | ||
|
|
0f91e4d5f6 | ||
|
|
18617afd43 | ||
|
|
731e83a7da | ||
|
|
1c1f53267a | ||
|
|
8489f5f528 | ||
|
|
872974910c | ||
|
|
147999dd88 | ||
|
|
fe22cbd0e5 | ||
|
|
1277285d08 | ||
|
|
75df8a05f1 | ||
|
|
38e1d0f94e | ||
|
|
71c1a05386 | ||
|
|
4a6bd23873 | ||
|
|
8a1d647547 | ||
|
|
fb5180a890 | ||
|
|
d2c75b94cf | ||
|
|
edfc2d8d93 | ||
|
|
dd177b19f7 | ||
|
|
ae7283fc73 | ||
|
|
51a7c56cae | ||
|
|
c812168d6e | ||
|
|
fa10ae14f7 | ||
|
|
ed53df5ef3 | ||
|
|
b541290ded | ||
|
|
792fe6d9ef | ||
|
|
eb0c1dabf1 | ||
|
|
e00c3f1823 | ||
|
|
05bc655e16 | ||
|
|
7cddada8b4 | ||
|
|
24eb36715a | ||
|
|
22cf278ce2 | ||
|
|
5ab5986bd0 | ||
|
|
f0775abc67 | ||
|
|
d424b4bc32 | ||
|
|
329889ec00 | ||
|
|
95268dbc61 | ||
|
|
9a3200c9b5 | ||
|
|
c213fb6216 | ||
|
|
dd0217b46b | ||
|
|
b805bf6222 | ||
|
|
07b9474212 | ||
|
|
bf971911d5 | ||
|
|
c46ced0c4c | ||
|
|
dd3bbbc4af | ||
|
|
a97f90d225 | ||
|
|
6066edd510 | ||
|
|
eaec9e54ad | ||
|
|
fafcb2e8e7 | ||
|
|
145ea1e6f1 | ||
|
|
4cfed17650 | ||
|
|
c2b1fb4855 | ||
|
|
09807b39aa | ||
|
|
a0ec2f3972 | ||
|
|
e4b1dc20c3 | ||
|
|
2f05d4960e | ||
|
|
e63d2644bd | ||
|
|
56e4d13179 | ||
|
|
0dd0ba717f | ||
|
|
8050330e2e | ||
|
|
65e35c1711 |
4
.github/workflows/docker.yml
vendored
@@ -11,12 +11,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v3.6.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.9.1
|
||||
uses: docker/setup-buildx-action@v2.10.0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v2.2.0
|
||||
|
||||
14
.github/workflows/release.yml
vendored
@@ -14,12 +14,12 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v3.6.0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4.0.1
|
||||
uses: actions/setup-go@v4.1.0
|
||||
with:
|
||||
go-version: 'stable'
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Install dependencies for arm64
|
||||
if: matrix.platform == 'arm64'
|
||||
@@ -47,18 +47,18 @@ jobs:
|
||||
|
||||
# Download dependencies
|
||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip
|
||||
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.4/Xray-linux-64.zip
|
||||
unzip Xray-linux-64.zip
|
||||
rm -f Xray-linux-64.zip
|
||||
else
|
||||
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
|
||||
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.4/Xray-linux-arm64-v8a.zip
|
||||
unzip Xray-linux-arm64-v8a.zip
|
||||
rm -f Xray-linux-arm64-v8a.zip
|
||||
fi
|
||||
rm -f geoip.dat geosite.dat iran.dat
|
||||
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 https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
wget https://github.com/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
mv xray xray-linux-${{ matrix.platform }}
|
||||
cd ../..
|
||||
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||
|
||||
- name: Upload
|
||||
uses: svenstaro/upload-release-action@2.6.1
|
||||
uses: svenstaro/upload-release-action@2.7.0
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Start fail2ban
|
||||
fail2ban-client -x -f start
|
||||
fail2ban-client -x start
|
||||
|
||||
# Run x-ui
|
||||
exec /app/x-ui
|
||||
|
||||
@@ -18,11 +18,11 @@ esac
|
||||
mkdir -p build/bin
|
||||
cd build/bin
|
||||
|
||||
wget "https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-${ARCH}.zip"
|
||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.4/Xray-linux-${ARCH}.zip"
|
||||
unzip "Xray-linux-${ARCH}.zip"
|
||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.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 "https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat"
|
||||
wget "https://github.com/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ========================================================
|
||||
# Stage: Builder
|
||||
# ========================================================
|
||||
FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:alpine AS builder
|
||||
WORKDIR /app
|
||||
ARG TARGETARCH
|
||||
ENV CGO_ENABLED=1
|
||||
@@ -36,7 +36,9 @@ COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
||||
# Configure fail2ban
|
||||
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
|
||||
&& cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \
|
||||
&& sed -i "s/^\[ssh\]$/&\nenabled = false/" /etc/fail2ban/jail.local
|
||||
&& sed -i "s/^\[ssh\]$/&\nenabled = false/" /etc/fail2ban/jail.local \
|
||||
&& sed -i "s/^\[sshd\]$/&\nenabled = false/" /etc/fail2ban/jail.local \
|
||||
&& sed -i "s/#allowipv6 = auto/allowipv6 = auto/g" /etc/fail2ban/fail2ban.conf
|
||||
|
||||
RUN chmod +x \
|
||||
/app/DockerEntrypoint.sh \
|
||||
|
||||
56
README.md
@@ -8,7 +8,7 @@
|
||||
[](#)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian)**
|
||||
3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian,Vietnamese)**
|
||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||
|
||||
**Buy Me a Coffee :**
|
||||
@@ -23,10 +23,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||
|
||||
# Install custom version
|
||||
|
||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.6.1`:
|
||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.7.7`:
|
||||
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.6.1
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.7.7
|
||||
```
|
||||
|
||||
# SSL
|
||||
@@ -37,7 +37,7 @@ certbot certonly --standalone --agree-tos --register-unsafely-without-email -d y
|
||||
certbot renew --dry-run
|
||||
```
|
||||
|
||||
You also can use `x-ui` menu then select `16. SSL Certificate Management`
|
||||
You also can use `x-ui` menu then select `SSL Certificate Management`
|
||||
|
||||
# Features
|
||||
|
||||
@@ -191,6 +191,27 @@ If you want to use routing to WARP follow steps as below:
|
||||
|
||||
</details>
|
||||
|
||||
# IP Limit
|
||||
|
||||
<details>
|
||||
<summary>Click for IP Limit details</summary>
|
||||
|
||||
**Note: IP Limit won't work correctly when using IP Tunnel**
|
||||
|
||||
- For versions up to `v1.6.1`:
|
||||
|
||||
- IP limit is built-in into the panel.
|
||||
|
||||
- For versions `v1.7.0` and newer:
|
||||
|
||||
- To make IP Limit work properly, you need to install fail2ban and its required files by following these steps:
|
||||
|
||||
1. Use the `x-ui` command inside the shell.
|
||||
2. Select `IP Limit Management`.
|
||||
3. Choose the appropriate options based on your needs.
|
||||
|
||||
</details>
|
||||
|
||||
# Telegram Bot
|
||||
|
||||
<details>
|
||||
@@ -233,6 +254,27 @@ Reference syntax:
|
||||
- Multi language bot
|
||||
</details>
|
||||
|
||||
# Setting up Telegram bot
|
||||
|
||||
- Start [Botfather](https://t.me/BotFather) in your Telegram account:
|
||||

|
||||
|
||||
- Create a new Bot using /newbot command: It will ask you 2 questions, A name and a username for your bot. Note that the username has to end with the word "bot".
|
||||

|
||||
|
||||
- Start the bot you've just created. You can find the link to your bot here.
|
||||

|
||||
|
||||
- Enter your panel and config Telegram bot settings like below:
|
||||

|
||||
|
||||
Enter your bot token in input field number 3.
|
||||
Enter the user ID in input field number 4. The Telegram accounts with this id will be the bot admin. (You can enter more than one, Just separate them with ,)
|
||||
|
||||
- How to get Telegram user ID? Use this [bot](https://t.me/useridinfobot), Start the bot and it will give you the Telegram user ID.
|
||||

|
||||
|
||||
|
||||
# API routes
|
||||
|
||||
<details>
|
||||
@@ -294,12 +336,18 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||
|
||||
- [alireza0](https://github.com/alireza0/)
|
||||
|
||||
# Acknowledgment
|
||||
|
||||
- [Iran Hosted Domains](https://github.com/bootmortis/iran-hosted-domains) (License: **MIT**): _A comprehensive list of Iranian domains and services that are hosted within the country._
|
||||
- [PersianBlocker](https://github.com/MasterKia/PersianBlocker) (License: **AGPLv3**): _An optimal and extensive list to block ads and trackers on Persian websites._
|
||||
|
||||
# Suggestion System
|
||||
|
||||
- Ubuntu 20.04+
|
||||
- Debian 10+
|
||||
- CentOS 8+
|
||||
- Fedora 36+
|
||||
- Arch Linux
|
||||
|
||||
# Pictures
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.7.0
|
||||
1.7.8
|
||||
69
go.mod
@@ -1,47 +1,49 @@
|
||||
module x-ui
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.0
|
||||
|
||||
require (
|
||||
github.com/Calidity/gin-sessions v1.3.1
|
||||
github.com/Workiva/go-datastructures v1.1.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/mymmrac/telego v0.25.1
|
||||
github.com/mymmrac/telego v0.26.1
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml/v2 v2.0.9
|
||||
github.com/pelletier/go-toml/v2 v2.1.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.6
|
||||
github.com/xtls/xray-core v1.8.3
|
||||
github.com/yaa110/go-persian-calendar v1.1.5
|
||||
github.com/shirou/gopsutil/v3 v3.23.7
|
||||
github.com/xtls/xray-core v1.8.4
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/text v0.11.0
|
||||
google.golang.org/grpc v1.56.2
|
||||
gorm.io/driver/sqlite v1.5.2
|
||||
gorm.io/gorm v1.25.2
|
||||
golang.org/x/text v0.12.0
|
||||
google.golang.org/grpc v1.57.0
|
||||
gorm.io/driver/sqlite v1.5.3
|
||||
gorm.io/gorm v1.25.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/bytedance/sonic v1.9.2 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/bytedance/sonic v1.10.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
github.com/fasthttp/router v1.4.19 // indirect
|
||||
github.com/fasthttp/router v1.4.20 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.15.2 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
|
||||
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/gorilla/sessions v1.2.1 // indirect
|
||||
@@ -51,47 +53,48 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.12.0 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/quic-go/quic-go v0.35.1 // indirect
|
||||
github.com/refraction-networking/utls v1.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
|
||||
github.com/quic-go/quic-go v0.38.1 // indirect
|
||||
github.com/refraction-networking/utls v1.4.3 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/sagernet/sing v0.2.7 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.2 // indirect
|
||||
github.com/sagernet/sing v0.2.9 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.4 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.48.0 // indirect
|
||||
github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983 // indirect
|
||||
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
|
||||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||
golang.org/x/tools v0.12.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
)
|
||||
|
||||
151
go.sum
@@ -22,13 +22,18 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
|
||||
github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||
github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk=
|
||||
github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -36,8 +41,8 @@ github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fp
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fasthttp/router v1.4.19 h1:RLE539IU/S4kfb4MP56zgP0TIBU9kEg0ID9GpWO0vqk=
|
||||
github.com/fasthttp/router v1.4.19/go.mod h1:+Fh3YOd8x1+he6ZS+d2iUDBH9MGGZ1xQFUor0DE9rKE=
|
||||
github.com/fasthttp/router v1.4.20 h1:yPeNxz5WxZGojzolKqiP15DTXnxZce9Drv577GBrDgU=
|
||||
github.com/fasthttp/router v1.4.20/go.mod h1:um867yNQKtERxBm+C+yzgWxjspTiQoA8z86Ec3fK/tc=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
@@ -48,6 +53,7 @@ github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXb
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
@@ -55,15 +61,18 @@ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SU
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
||||
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.15.2 h1:Ra5cll2/eF8X0Ff2+8SMD7euo2nenQ8WEpgqfy4NhHU=
|
||||
github.com/go-playground/validator/v10 v10.15.2/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
@@ -93,8 +102,8 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ=
|
||||
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@@ -123,12 +132,15 @@ github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
@@ -142,28 +154,31 @@ github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
|
||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mymmrac/telego v0.25.1 h1:tMNmrRm0YGyLS56CBi0NDHwO1ZI6V7QMgX4KWSWuT1U=
|
||||
github.com/mymmrac/telego v0.25.1/go.mod h1:nBO4SUqRV8j60JOS7trIr6bHPofwYCGJxYeqtQWgu2c=
|
||||
github.com/mymmrac/telego v0.26.1 h1:BzWWzDOkov0SMVnSF+mLJ5ChYcTqmhTIyBWBGyi51hw=
|
||||
github.com/mymmrac/telego v0.26.1/go.mod h1:kizipjY3MhxmkcGvyz8jiw/26vEKAhR2V7YTE69iqvw=
|
||||
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.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
|
||||
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
||||
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
@@ -177,14 +192,12 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo=
|
||||
github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
|
||||
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
|
||||
github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE=
|
||||
github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4=
|
||||
github.com/refraction-networking/utls v1.4.3 h1:BdWS3BSzCwWCFfMIXP3mjLAyQkdmog7diaD/OqFbAzM=
|
||||
github.com/refraction-networking/utls v1.4.3/go.mod h1:4u9V/awOSBrRw6+federGmVJQfPtemEqLBXkML1b0bo=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
@@ -192,10 +205,10 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sagernet/sing v0.2.7 h1:cOy0FfPS8q7m0aJ51wS7LRQAGc9wF+fWhHtBDj99wy8=
|
||||
github.com/sagernet/sing v0.2.7/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.2 h1:ezSdVhrmIcwDXmCZF3bOJVMuVtTQWpda+1Op+Ie2TA4=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.2/go.mod h1:JIBWG6a7orB2HxBxYElViQFLUQxFVG7DuqIj8gD7uCQ=
|
||||
github.com/sagernet/sing v0.2.9 h1:3wsTz+JG5Wzy65eZnh6AuCrD2QqcRF6Iq6f7ttmJsAo=
|
||||
github.com/sagernet/sing v0.2.9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||
@@ -203,8 +216,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.6 h1:5y46WPI9QBKBbK7EEccUPNXpJpNrvPuTD0O2zHEHT08=
|
||||
github.com/shirou/gopsutil/v3 v3.23.6/go.mod h1:j7QX50DrXYggrpN30W0Mo+I4/8U2UUIQrnrhqUeWrAU=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||
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=
|
||||
@@ -248,8 +261,9 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
@@ -266,12 +280,10 @@ github.com/valyala/fasthttp v1.48.0 h1:oJWvHb9BIZToTQS3MuQ2R3bJZiNSa2KiNdeI8A+79
|
||||
github.com/valyala/fasthttp v1.48.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983 h1:AMyzgjkh54WocjQSlCnT1LhDc/BKiUqtNOv40AkpURs=
|
||||
github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983/go.mod h1:rkuAY1S9F8eI8gDiPDYvACE8e2uwkyg8qoOTuwWov7Y=
|
||||
github.com/xtls/xray-core v1.8.3 h1:lxaVklPjLKqUU4ua4qH8SBaRcAaNHlH+LmXOx0U/Ejg=
|
||||
github.com/xtls/xray-core v1.8.3/go.mod h1:i7t4JFnq828P2+XK0XjGQ8W9x78iu+EJ7jI4l3sonIw=
|
||||
github.com/yaa110/go-persian-calendar v1.1.5 h1:EUipRRhzE6bR2NZaSyZ5BEOP46LGbUjzQgdC+Ivrbe4=
|
||||
github.com/yaa110/go-persian-calendar v1.1.5/go.mod h1:qtnmHCS9u1EiwzzSCSttGoxD5NfV9ZMzymxFCBYmqfg=
|
||||
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6 h1:T+YCYGfFdzyaKTDCdZn/hEiKvsw6yUfd+e4hze0rCUw=
|
||||
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6/go.mod h1:rkuAY1S9F8eI8gDiPDYvACE8e2uwkyg8qoOTuwWov7Y=
|
||||
github.com/xtls/xray-core v1.8.4 h1:YEoY3iLx/5zoNbt5HORG5LtPyzwICInFfoS+oPkYDIw=
|
||||
github.com/xtls/xray-core v1.8.4/go.mod h1:GGD9elFSHa4IqOArW8gzMsEksPIqK/jdNLo8RcSMfnI=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@@ -280,7 +292,11 @@ github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU=
|
||||
go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
|
||||
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
@@ -291,19 +307,19 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -318,8 +334,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -334,6 +350,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -349,13 +366,14 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -363,8 +381,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
@@ -378,8 +396,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -396,41 +414,44 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
|
||||
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc=
|
||||
gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
||||
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g=
|
||||
gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
||||
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744 h1:tE44CyJgxEGzoPtHs9GI7ddKdgEGCREQBP54AmaVM+I=
|
||||
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744/go.mod h1:lYEMhXbxgudVhALYsMQrBaUAjM3NMinh8mKL1CJv7rc=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
||||
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
|
||||
21
install.sh
@@ -53,21 +53,28 @@ elif [[ "${release}" == "debian" ]]; then
|
||||
if [[ ${os_version} -lt 10 ]]; then
|
||||
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "arch" ]]; then
|
||||
echo "OS is ArchLinux"
|
||||
|
||||
else
|
||||
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
|
||||
fi
|
||||
|
||||
install_base() {
|
||||
case "${release}" in
|
||||
centos | fedora)
|
||||
yum install -y -q wget curl tar
|
||||
;;
|
||||
*)
|
||||
apt install -y -q wget curl tar
|
||||
;;
|
||||
centos|fedora)
|
||||
yum install -y -q wget curl tar
|
||||
;;
|
||||
arch)
|
||||
pacman -Syu --noconfirm wget curl tar
|
||||
;;
|
||||
*)
|
||||
apt install -y -q wget curl tar
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
# This function will be called when user installed x-ui out of sercurity
|
||||
config_after_install() {
|
||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||
@@ -104,7 +111,6 @@ config_after_install() {
|
||||
}
|
||||
|
||||
install_x-ui() {
|
||||
systemctl stop x-ui
|
||||
cd /usr/local/
|
||||
|
||||
if [ $# == 0 ]; then
|
||||
@@ -131,6 +137,7 @@ install_x-ui() {
|
||||
fi
|
||||
|
||||
if [[ -e /usr/local/x-ui/ ]]; then
|
||||
systemctl stop x-ui
|
||||
rm /usr/local/x-ui/ -rf
|
||||
fi
|
||||
|
||||
|
||||
112
logger/logger.go
@@ -1,98 +1,118 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
logger *logging.Logger
|
||||
mu sync.Mutex
|
||||
)
|
||||
var logger *logging.Logger
|
||||
var logBuffer []struct {
|
||||
time string
|
||||
level logging.Level
|
||||
log string
|
||||
}
|
||||
|
||||
func init() {
|
||||
InitLogger(logging.INFO)
|
||||
}
|
||||
|
||||
func InitLogger(level logging.Level) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
newLogger := logging.MustGetLogger("x-ui")
|
||||
var err error
|
||||
var backend logging.Backend
|
||||
var format logging.Formatter
|
||||
ppid := os.Getppid()
|
||||
|
||||
if logger != nil {
|
||||
return
|
||||
if ppid == 1 {
|
||||
backend, err = logging.NewSyslogBackend("")
|
||||
format = logging.MustStringFormatter(
|
||||
`%{level} - %{message}`,
|
||||
)
|
||||
}
|
||||
if err != nil || ppid != 1 {
|
||||
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
||||
format = logging.MustStringFormatter(
|
||||
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
||||
)
|
||||
}
|
||||
|
||||
format := logging.MustStringFormatter(
|
||||
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
||||
)
|
||||
newLogger := logging.MustGetLogger("x-ui")
|
||||
backend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||
backendFormatter := logging.NewBackendFormatter(backend, format)
|
||||
backendLeveled := logging.AddModuleLevel(backendFormatter)
|
||||
backendLeveled.SetLevel(level, "")
|
||||
newLogger.SetBackend(logging.MultiLogger(backendLeveled))
|
||||
backendLeveled.SetLevel(level, "x-ui")
|
||||
newLogger.SetBackend(backendLeveled)
|
||||
|
||||
logger = newLogger
|
||||
}
|
||||
|
||||
func Debug(args ...interface{}) {
|
||||
if logger != nil {
|
||||
logger.Debug(args...)
|
||||
}
|
||||
logger.Debug(args...)
|
||||
addToBuffer("DEBUG", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
if logger != nil {
|
||||
logger.Debugf(format, args...)
|
||||
}
|
||||
logger.Debugf(format, args...)
|
||||
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Info(args ...interface{}) {
|
||||
if logger != nil {
|
||||
logger.Info(args...)
|
||||
}
|
||||
logger.Info(args...)
|
||||
addToBuffer("INFO", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Infof(format string, args ...interface{}) {
|
||||
if logger != nil {
|
||||
logger.Infof(format, args...)
|
||||
}
|
||||
logger.Infof(format, args...)
|
||||
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Warning(args ...interface{}) {
|
||||
if logger != nil {
|
||||
logger.Warning(args...)
|
||||
}
|
||||
logger.Warning(args...)
|
||||
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
if logger != nil {
|
||||
logger.Warningf(format, args...)
|
||||
}
|
||||
logger.Warningf(format, args...)
|
||||
addToBuffer("WARNING", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Error(args ...interface{}) {
|
||||
if logger != nil {
|
||||
logger.Error(args...)
|
||||
}
|
||||
logger.Error(args...)
|
||||
addToBuffer("ERROR", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
if logger != nil {
|
||||
logger.Errorf(format, args...)
|
||||
}
|
||||
logger.Errorf(format, args...)
|
||||
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Notice(args ...interface{}) {
|
||||
if logger != nil {
|
||||
logger.Notice(args...)
|
||||
func addToBuffer(level string, newLog string) {
|
||||
t := time.Now()
|
||||
if len(logBuffer) >= 10240 {
|
||||
logBuffer = logBuffer[1:]
|
||||
}
|
||||
|
||||
logLevel, _ := logging.LogLevel(level)
|
||||
logBuffer = append(logBuffer, struct {
|
||||
time string
|
||||
level logging.Level
|
||||
log string
|
||||
}{
|
||||
time: t.Format("2006/01/02 15:04:05"),
|
||||
level: logLevel,
|
||||
log: newLog,
|
||||
})
|
||||
}
|
||||
|
||||
func Noticef(format string, args ...interface{}) {
|
||||
if logger != nil {
|
||||
logger.Noticef(format, args...)
|
||||
func GetLogs(c int, level string) []string {
|
||||
var output []string
|
||||
logLevel, _ := logging.LogLevel(level)
|
||||
|
||||
for i := len(logBuffer) - 1; i >= 0 && len(output) <= c; i-- {
|
||||
if logBuffer[i].level <= logLevel {
|
||||
output = append(output, fmt.Sprintf("%s %s - %s", logBuffer[i].time, logBuffer[i].level, logBuffer[i].log))
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
BIN
media/1.png
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 54 KiB |
BIN
media/2.png
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 26 KiB |
BIN
media/3.png
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 70 KiB |
BIN
media/4.png
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 36 KiB |
BIN
media/5.png
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 54 KiB |
BIN
media/botfather.png
Normal file
|
After Width: | Height: | Size: 544 KiB |
BIN
media/newbot.png
Normal file
|
After Width: | Height: | Size: 455 KiB |
BIN
media/panel-bot-config.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
media/token.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
media/user-id.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
@@ -3,12 +3,14 @@ package sub
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"x-ui/web/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SUBController struct {
|
||||
subService SubService
|
||||
subService SubService
|
||||
settingService service.SettingService
|
||||
}
|
||||
|
||||
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
||||
@@ -24,9 +26,11 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
func (a *SUBController) subs(c *gin.Context) {
|
||||
subEncrypt, _ := a.settingService.GetSubEncrypt()
|
||||
subShowInfo, _ := a.settingService.GetSubShowInfo()
|
||||
subId := c.Param("subid")
|
||||
host := strings.Split(c.Request.Host, ":")[0]
|
||||
subs, headers, err := a.subService.GetSubs(subId, host)
|
||||
subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo)
|
||||
if err != nil || len(subs) == 0 {
|
||||
c.String(400, "Error!")
|
||||
} else {
|
||||
@@ -40,6 +44,10 @@ func (a *SUBController) subs(c *gin.Context) {
|
||||
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
||||
c.Writer.Header().Set("Profile-Title", headers[2])
|
||||
|
||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||
if subEncrypt {
|
||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||
} else {
|
||||
c.String(200, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,17 +14,18 @@ import (
|
||||
"x-ui/xray"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
ptime "github.com/yaa110/go-persian-calendar"
|
||||
)
|
||||
|
||||
type SubService struct {
|
||||
address string
|
||||
showInfo bool
|
||||
inboundService service.InboundService
|
||||
settingServics service.SettingService
|
||||
}
|
||||
|
||||
func (s *SubService) GetSubs(subId string, host string) ([]string, []string, error) {
|
||||
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
|
||||
s.address = host
|
||||
s.showInfo = showInfo
|
||||
var result []string
|
||||
var headers []string
|
||||
var traffic xray.ClientTraffic
|
||||
@@ -58,7 +59,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, []string, err
|
||||
}
|
||||
for _, client := range clients {
|
||||
if client.Enable && client.SubID == subId {
|
||||
link := s.getLink(inbound, client.Email, client.ExpiryTime)
|
||||
link := s.getLink(inbound, client.Email)
|
||||
result = append(result, link)
|
||||
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
|
||||
}
|
||||
@@ -124,32 +125,26 @@ func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
func (s *SubService) getLink(inbound *model.Inbound, email string, expiryTime int64) string {
|
||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||
switch inbound.Protocol {
|
||||
case "vmess":
|
||||
return s.genVmessLink(inbound, email, expiryTime)
|
||||
return s.genVmessLink(inbound, email)
|
||||
case "vless":
|
||||
return s.genVlessLink(inbound, email, expiryTime)
|
||||
return s.genVlessLink(inbound, email)
|
||||
case "trojan":
|
||||
return s.genTrojanLink(inbound, email, expiryTime)
|
||||
return s.genTrojanLink(inbound, email)
|
||||
case "shadowsocks":
|
||||
return s.genShadowsocksLink(inbound, email, expiryTime)
|
||||
return s.genShadowsocksLink(inbound, email)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *SubService) genVmessLink(inbound *model.Inbound, email string, expiryTime int64) string {
|
||||
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
if inbound.Protocol != model.VMess {
|
||||
return ""
|
||||
}
|
||||
|
||||
remainedTraffic := s.getRemainedTraffic(email)
|
||||
expiryTimeString := getExpiryTime(expiryTime)
|
||||
|
||||
remark := fmt.Sprintf("%s: %s- %s", email, remainedTraffic, expiryTimeString)
|
||||
obj := map[string]interface{}{
|
||||
"v": "2",
|
||||
"ps": remark,
|
||||
"add": s.address,
|
||||
"port": inbound.Port,
|
||||
"type": "none",
|
||||
@@ -248,7 +243,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string, expiryTi
|
||||
links := ""
|
||||
for index, d := range domains {
|
||||
domain := d.(map[string]interface{})
|
||||
obj["ps"] = remark + "-" + domain["remark"].(string)
|
||||
obj["ps"] = s.genRemark(inbound, email, domain["remark"].(string))
|
||||
obj["add"] = domain["domain"].(string)
|
||||
if index > 0 {
|
||||
links += "\n"
|
||||
@@ -259,11 +254,13 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string, expiryTi
|
||||
return links
|
||||
}
|
||||
|
||||
obj["ps"] = s.genRemark(inbound, email, "")
|
||||
|
||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||
}
|
||||
|
||||
func (s *SubService) genVlessLink(inbound *model.Inbound, email string, expiryTime int64) string {
|
||||
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
address := s.address
|
||||
if inbound.Protocol != model.VLESS {
|
||||
return ""
|
||||
@@ -456,16 +453,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string, expiryTi
|
||||
// Set the new query values on the URL
|
||||
url.RawQuery = q.Encode()
|
||||
|
||||
remainedTraffic := s.getRemainedTraffic(email)
|
||||
expiryTimeString := getExpiryTime(expiryTime)
|
||||
|
||||
remark := fmt.Sprintf("%s: %s- %s", email, remainedTraffic, expiryTimeString)
|
||||
|
||||
if len(domains) > 0 {
|
||||
links := ""
|
||||
for index, d := range domains {
|
||||
domain := d.(map[string]interface{})
|
||||
url.Fragment = remark + "-" + domain["remark"].(string)
|
||||
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
|
||||
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
||||
if index > 0 {
|
||||
links += "\n"
|
||||
@@ -474,11 +466,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string, expiryTi
|
||||
}
|
||||
return links
|
||||
}
|
||||
url.Fragment = remark
|
||||
|
||||
url.Fragment = s.genRemark(inbound, email, "")
|
||||
return url.String()
|
||||
}
|
||||
|
||||
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string, expiryTime int64) string {
|
||||
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
|
||||
address := s.address
|
||||
if inbound.Protocol != model.Trojan {
|
||||
return ""
|
||||
@@ -668,16 +661,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string, expiryT
|
||||
// Set the new query values on the URL
|
||||
url.RawQuery = q.Encode()
|
||||
|
||||
remainedTraffic := s.getRemainedTraffic(email)
|
||||
expiryTimeString := getExpiryTime(expiryTime)
|
||||
|
||||
remark := fmt.Sprintf("%s: %s- %s", email, remainedTraffic, expiryTimeString)
|
||||
|
||||
if len(domains) > 0 {
|
||||
links := ""
|
||||
for index, d := range domains {
|
||||
domain := d.(map[string]interface{})
|
||||
url.Fragment = remark + "-" + domain["remark"].(string)
|
||||
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
|
||||
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
||||
if index > 0 {
|
||||
links += "\n"
|
||||
@@ -687,11 +675,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string, expiryT
|
||||
return links
|
||||
}
|
||||
|
||||
url.Fragment = remark
|
||||
url.Fragment = s.genRemark(inbound, email, "")
|
||||
return url.String()
|
||||
}
|
||||
|
||||
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string, expiryTime int64) string {
|
||||
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
|
||||
address := s.address
|
||||
if inbound.Protocol != model.Shadowsocks {
|
||||
return ""
|
||||
@@ -756,7 +744,10 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string, ex
|
||||
}
|
||||
}
|
||||
|
||||
encPart := fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
||||
if method[0] == '2' {
|
||||
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||
}
|
||||
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
||||
url, _ := url.Parse(link)
|
||||
q := url.Query()
|
||||
@@ -767,15 +758,55 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string, ex
|
||||
|
||||
// Set the new query values on the URL
|
||||
url.RawQuery = q.Encode()
|
||||
|
||||
remainedTraffic := s.getRemainedTraffic(email)
|
||||
expiryTimeString := getExpiryTime(expiryTime)
|
||||
|
||||
remark := fmt.Sprintf("%s: %s- %s", clients[clientIndex].Email, remainedTraffic, expiryTimeString)
|
||||
url.Fragment = remark
|
||||
url.Fragment = s.genRemark(inbound, email, "")
|
||||
return url.String()
|
||||
}
|
||||
|
||||
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
|
||||
var remark []string
|
||||
if len(email) > 0 {
|
||||
if len(inbound.Remark) > 0 {
|
||||
remark = append(remark, inbound.Remark)
|
||||
}
|
||||
remark = append(remark, email)
|
||||
if len(extra) > 0 {
|
||||
remark = append(remark, extra)
|
||||
}
|
||||
} else {
|
||||
return inbound.Remark
|
||||
}
|
||||
|
||||
if s.showInfo {
|
||||
statsExist := false
|
||||
var stats xray.ClientTraffic
|
||||
for _, clientStat := range inbound.ClientStats {
|
||||
if clientStat.Email == email {
|
||||
stats = clientStat
|
||||
statsExist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Get remained days
|
||||
if statsExist {
|
||||
if !stats.Enable {
|
||||
return fmt.Sprintf("⛔️N/A-%s", strings.Join(remark, "-"))
|
||||
}
|
||||
if vol := stats.Total - (stats.Up + stats.Down); vol > 0 {
|
||||
remark = append(remark, fmt.Sprintf("%s%s", common.FormatTraffic(vol), "📊"))
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
switch exp := stats.ExpiryTime / 1000; {
|
||||
case exp > 0:
|
||||
remark = append(remark, fmt.Sprintf("%d%s⏳", (exp-now)/86400, "Days"))
|
||||
case exp < 0:
|
||||
remark = append(remark, fmt.Sprintf("%d%s⏳", exp/-86400, "Days"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(remark, " : ")
|
||||
}
|
||||
|
||||
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||
switch val := data.(type) {
|
||||
case map[string]interface{}:
|
||||
@@ -817,38 +848,3 @@ func searchHost(headers interface{}) string {
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getExpiryTime(expiryTime int64) string {
|
||||
now := time.Now().Unix()
|
||||
expiryString := ""
|
||||
|
||||
timeDifference := expiryTime/1000 - now
|
||||
|
||||
if expiryTime == 0 {
|
||||
expiryString = "♾ ⏳"
|
||||
} else if timeDifference > 172800 {
|
||||
expiryString = fmt.Sprintf("%s ⏳", ptime.Unix((expiryTime/1000), 0).Format("yy-MM-dd hh:mm"))
|
||||
} else if expiryTime < 0 {
|
||||
expiryString = fmt.Sprintf("%d ⏳", expiryTime/-86400000)
|
||||
} else {
|
||||
expiryString = fmt.Sprintf("%s %d ⏳", "ساعت", timeDifference/3600)
|
||||
}
|
||||
|
||||
return expiryString
|
||||
}
|
||||
|
||||
func (s *SubService) getRemainedTraffic(email string) string {
|
||||
traffic, err := s.inboundService.GetClientTrafficByEmail(email)
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
}
|
||||
|
||||
remainedTraffic := ""
|
||||
if traffic.Total == 0 {
|
||||
remainedTraffic = "♾ 📊"
|
||||
} else {
|
||||
remainedTraffic = fmt.Sprintf("%s%s", common.FormatTraffic(traffic.Total-(traffic.Up+traffic.Down)), "📊")
|
||||
}
|
||||
|
||||
return remainedTraffic
|
||||
}
|
||||
|
||||
@@ -262,6 +262,7 @@ body {
|
||||
.ant-card-dark .ant-collapse-content,
|
||||
.ant-card-dark .ant-calendar,
|
||||
.ant-card-dark .ant-table-placeholder,
|
||||
.ant-card-dark .ant-select-selection__choice,
|
||||
.ant-card-dark .ant-input-group-addon {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #262f3d;
|
||||
|
||||
@@ -19,6 +19,11 @@ const supportLangs = [
|
||||
value: 'ru-RU',
|
||||
icon: '🇷🇺',
|
||||
},
|
||||
{
|
||||
name: 'Tiếng Việt',
|
||||
value: 'vi-VN',
|
||||
icon: '🇻🇳',
|
||||
},
|
||||
];
|
||||
|
||||
function getLang() {
|
||||
|
||||
@@ -181,7 +181,7 @@ class AllSetting {
|
||||
this.tgBotChatId = "";
|
||||
this.tgRunTime = "@daily";
|
||||
this.tgBotBackup = false;
|
||||
this.tgBotLoginNotify = false;
|
||||
this.tgBotLoginNotify = true;
|
||||
this.tgCpu = "";
|
||||
this.tgLang = "en-US";
|
||||
this.xrayTemplateConfig = "";
|
||||
@@ -194,6 +194,8 @@ class AllSetting {
|
||||
this.subCertFile = "";
|
||||
this.subKeyFile = "";
|
||||
this.subUpdates = 0;
|
||||
this.subEncrypt = true;
|
||||
this.subShowInfo = true;
|
||||
|
||||
this.timeLocation = "Asia/Tehran";
|
||||
|
||||
|
||||
@@ -16,9 +16,12 @@ const VmessMethods = {
|
||||
};
|
||||
|
||||
const SSMethods = {
|
||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||
AES_256_GCM: 'aes-256-gcm',
|
||||
AES_128_GCM: 'aes-128-gcm',
|
||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
||||
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
||||
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||
@@ -75,9 +78,9 @@ const UTLS_FINGERPRINT = {
|
||||
};
|
||||
|
||||
const ALPN_OPTION = {
|
||||
HTTP1: "http/1.1",
|
||||
H2: "h2",
|
||||
H3: "h3",
|
||||
H2: "h2",
|
||||
HTTP1: "http/1.1",
|
||||
};
|
||||
|
||||
const SNIFFING_OPTION = {
|
||||
@@ -386,12 +389,10 @@ class HttpStreamSettings extends XrayCommonClass {
|
||||
constructor(
|
||||
path='/',
|
||||
host=[''],
|
||||
sockopt={acceptProxyProtocol: false}
|
||||
) {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host.length === 0 ? [''] : host;
|
||||
this.sockopt = sockopt;
|
||||
}
|
||||
|
||||
addHost(host) {
|
||||
@@ -403,7 +404,7 @@ class HttpStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
return new HttpStreamSettings(json.path, json.host, json.sockopt);
|
||||
return new HttpStreamSettings(json.path, json.host);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
@@ -416,15 +417,13 @@ class HttpStreamSettings extends XrayCommonClass {
|
||||
return {
|
||||
path: this.path,
|
||||
host: host,
|
||||
sockopt: this.sockopt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class QuicStreamSettings extends XrayCommonClass {
|
||||
constructor(security=VmessMethods.NONE,
|
||||
key='', type='none') {
|
||||
key=RandomUtil.randomSeq(10), type='none') {
|
||||
super();
|
||||
this.security = security;
|
||||
this.key = key;
|
||||
@@ -454,19 +453,16 @@ class GrpcStreamSettings extends XrayCommonClass {
|
||||
constructor(
|
||||
serviceName="",
|
||||
multiMode=false,
|
||||
sockopt={acceptProxyProtocol: false}
|
||||
) {
|
||||
super();
|
||||
this.serviceName = serviceName;
|
||||
this.multiMode = multiMode;
|
||||
this.sockopt = sockopt;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
return new GrpcStreamSettings(
|
||||
json.serviceName,
|
||||
json.multiMode,
|
||||
json.sockopt
|
||||
json.multiMode
|
||||
);
|
||||
}
|
||||
|
||||
@@ -474,7 +470,6 @@ class GrpcStreamSettings extends XrayCommonClass {
|
||||
return {
|
||||
serviceName: this.serviceName,
|
||||
multiMode: this.multiMode,
|
||||
sockopt: this.sockopt
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -487,7 +482,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
cipherSuites = '',
|
||||
rejectUnknownSni = false,
|
||||
certificates=[new TlsStreamSettings.Cert()],
|
||||
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||
alpn=[ALPN_OPTION.HTTP1,ALPN_OPTION.H2],
|
||||
settings=new TlsStreamSettings.Settings()) {
|
||||
super();
|
||||
this.server = serverName;
|
||||
@@ -545,13 +540,14 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='') {
|
||||
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='', ocspStapling=3600) {
|
||||
super();
|
||||
this.useFile = useFile;
|
||||
this.certFile = certificateFile;
|
||||
this.keyFile = keyFile;
|
||||
this.cert = certificate instanceof Array ? certificate.join('\n') : certificate;
|
||||
this.key = key instanceof Array ? key.join('\n') : key;
|
||||
this.ocspStapling = ocspStapling;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
@@ -559,13 +555,15 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||
return new TlsStreamSettings.Cert(
|
||||
true,
|
||||
json.certificateFile,
|
||||
json.keyFile,
|
||||
json.keyFile, '', '',
|
||||
json.ocspStapling,
|
||||
);
|
||||
} else {
|
||||
return new TlsStreamSettings.Cert(
|
||||
false, '', '',
|
||||
json.certificate.join('\n'),
|
||||
json.key.join('\n'),
|
||||
json.ocspStapling,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -575,11 +573,13 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||
return {
|
||||
certificateFile: this.certFile,
|
||||
keyFile: this.keyFile,
|
||||
ocspStapling: this.ocspStapling,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
certificate: this.cert.split('\n'),
|
||||
key: this.key.split('\n'),
|
||||
ocspStapling: this.ocspStapling,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -806,6 +806,34 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||
}
|
||||
};
|
||||
|
||||
class SockoptStreamSettings extends XrayCommonClass {
|
||||
constructor(acceptProxyProtocol = false, tcpFastOpen = false, mark = 0, tproxy="off") {
|
||||
super();
|
||||
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||
this.tcpFastOpen = tcpFastOpen;
|
||||
this.mark = mark;
|
||||
this.tproxy = tproxy;
|
||||
}
|
||||
static fromJson(json = {}) {
|
||||
if (Object.keys(json).length === 0) return undefined;
|
||||
return new SockoptStreamSettings(
|
||||
json.acceptProxyProtocol,
|
||||
json.tcpFastOpen,
|
||||
json.mark,
|
||||
json.tproxy,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
acceptProxyProtocol: this.acceptProxyProtocol,
|
||||
tcpFastOpen: this.tcpFastOpen,
|
||||
mark: this.mark,
|
||||
tproxy: this.tproxy,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class StreamSettings extends XrayCommonClass {
|
||||
constructor(network='tcp',
|
||||
security='none',
|
||||
@@ -818,6 +846,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
httpSettings=new HttpStreamSettings(),
|
||||
quicSettings=new QuicStreamSettings(),
|
||||
grpcSettings=new GrpcStreamSettings(),
|
||||
sockopt = undefined,
|
||||
) {
|
||||
super();
|
||||
this.network = network;
|
||||
@@ -831,6 +860,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
this.http = httpSettings;
|
||||
this.quic = quicSettings;
|
||||
this.grpc = grpcSettings;
|
||||
this.sockopt = sockopt;
|
||||
}
|
||||
|
||||
get isTls() {
|
||||
@@ -870,6 +900,14 @@ class StreamSettings extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
get sockoptSwitch() {
|
||||
return this.sockopt != undefined;
|
||||
}
|
||||
|
||||
set sockoptSwitch(value) {
|
||||
this.sockopt = value ? new SockoptStreamSettings() : undefined;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
|
||||
return new StreamSettings(
|
||||
@@ -884,6 +922,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
HttpStreamSettings.fromJson(json.httpSettings),
|
||||
QuicStreamSettings.fromJson(json.quicSettings),
|
||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||
SockoptStreamSettings.fromJson(json.sockopt),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -901,6 +940,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1040,7 +1080,10 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
get isSSMultiUser() {
|
||||
return [SSMethods.BLAKE3_AES_128_GCM,SSMethods.BLAKE3_AES_256_GCM].includes(this.method);
|
||||
return this.method != SSMethods.BLAKE3_CHACHA20_POLY1305;
|
||||
}
|
||||
get isSS2022(){
|
||||
return this.method.substring(0,4) === "2022";
|
||||
}
|
||||
|
||||
get serverName() {
|
||||
@@ -1067,7 +1110,7 @@ class Inbound extends XrayCommonClass {
|
||||
} else if (this.isWs) {
|
||||
return this.stream.ws.path;
|
||||
} else if (this.isH2) {
|
||||
return this.stream.http.path[0];
|
||||
return this.stream.http.path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1470,9 +1513,11 @@ class Inbound extends XrayCommonClass {
|
||||
break;
|
||||
}
|
||||
|
||||
let clientPassword = this.isSSMultiUser ? ':' + settings.shadowsockses[clientIndex].password : '';
|
||||
let password = new Array();
|
||||
if (this.isSS2022) password.push(settings.password);
|
||||
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
|
||||
|
||||
let link = `ss://${safeBase64(settings.method + ':' + settings.password + clientPassword)}@${address}:${this.port}`;
|
||||
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${this.port}`;
|
||||
const url = new URL(link);
|
||||
for (const [key, value] of params) {
|
||||
url.searchParams.set(key, value)
|
||||
@@ -1620,10 +1665,10 @@ class Inbound extends XrayCommonClass {
|
||||
JSON.parse(this.settings).clients.forEach((client,index) => {
|
||||
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
|
||||
this.stream.tls.settings.domains.forEach((domain) => {
|
||||
link += this.genLink(domain.domain, remark + '-' + client.email + '-' + domain.remark, index) + '\r\n';
|
||||
link += this.genLink(domain.domain, [remark, client.email, domain.remark].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
||||
});
|
||||
} else {
|
||||
link += this.genLink(address, remark + '-' + client.email, index) + '\r\n';
|
||||
link += this.genLink(address, [remark, client.email].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
||||
}
|
||||
});
|
||||
return link;
|
||||
@@ -1701,11 +1746,9 @@ Inbound.Settings = class extends XrayCommonClass {
|
||||
|
||||
Inbound.VmessSettings = class extends Inbound.Settings {
|
||||
constructor(protocol,
|
||||
vmesses=[new Inbound.VmessSettings.Vmess()],
|
||||
disableInsecureEncryption=false) {
|
||||
vmesses=[new Inbound.VmessSettings.Vmess()]) {
|
||||
super(protocol);
|
||||
this.vmesses = vmesses;
|
||||
this.disableInsecure = disableInsecureEncryption;
|
||||
}
|
||||
|
||||
indexOfVmessById(id) {
|
||||
@@ -1730,14 +1773,12 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
||||
return new Inbound.VmessSettings(
|
||||
Protocols.VMESS,
|
||||
json.clients.map(client => Inbound.VmessSettings.Vmess.fromJson(client)),
|
||||
ObjectUtil.isEmpty(json.disableInsecureEncryption) ? false : json.disableInsecureEncryption,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
clients: Inbound.VmessSettings.toJsonArray(this.vmesses),
|
||||
disableInsecureEncryption: this.disableInsecure,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -2097,8 +2138,9 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||
};
|
||||
|
||||
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
constructor(password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||
super();
|
||||
this.method = method;
|
||||
this.password = password;
|
||||
this.email = email;
|
||||
this.limitIp = limitIp;
|
||||
@@ -2111,6 +2153,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
method: this.method,
|
||||
password: this.password,
|
||||
email: this.email,
|
||||
limitIp: this.limitIp,
|
||||
@@ -2124,6 +2167,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new Inbound.ShadowsocksSettings.Shadowsocks(
|
||||
json.method,
|
||||
json.password,
|
||||
json.email,
|
||||
json.limitIp,
|
||||
|
||||
@@ -118,12 +118,9 @@ func (a *ServerController) restartXrayService(c *gin.Context) {
|
||||
|
||||
func (a *ServerController) getLogs(c *gin.Context) {
|
||||
count := c.Param("count")
|
||||
logLevel := c.PostForm("logLevel")
|
||||
logs, err := a.serverService.GetLogs(count, logLevel)
|
||||
if err != nil {
|
||||
jsonMsg(c, "getLogs", err)
|
||||
return
|
||||
}
|
||||
level := c.PostForm("level")
|
||||
syslog := c.PostForm("syslog")
|
||||
logs := a.serverService.GetLogs(count, level, syslog)
|
||||
jsonObj(c, logs, nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||
"subDomain": func() (interface{}, error) { return a.settingService.GetSubDomain() },
|
||||
"subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
|
||||
"subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
|
||||
"subEncrypt": func() (interface{}, error) { return a.settingService.GetSubEncrypt() },
|
||||
"subShowInfo": func() (interface{}, error) { return a.settingService.GetSubShowInfo() },
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
|
||||
@@ -55,6 +55,8 @@ type AllSetting struct {
|
||||
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||
}
|
||||
|
||||
func (s *AllSetting) CheckValid() error {
|
||||
|
||||
@@ -37,15 +37,16 @@
|
||||
this.inbound = dbInbound.toInbound();
|
||||
settings = JSON.parse(this.inbound.settings);
|
||||
this.client = settings.clients[clientIndex];
|
||||
remark = this.dbInbound.remark + ( this.client ? "-" + this.client.email : '');
|
||||
remark = [this.dbInbound.remark, ( this.client ? this.client.email : '')].filter(Boolean).join('-');
|
||||
address = this.dbInbound.address;
|
||||
this.subId = '';
|
||||
this.qrcodes = [];
|
||||
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
||||
this.qrcodes.push({
|
||||
remark: remark + "-" + domain.remark,
|
||||
link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, clientIndex)
|
||||
remark: remarkText,
|
||||
link: this.inbound.genLink(domain.domain, remarkText, clientIndex)
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -86,7 +87,7 @@
|
||||
},
|
||||
genSubLink(subID) {
|
||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||
return buildURL({ host, port, isTLS, base, path: subID });
|
||||
return buildURL({ host, port, isTLS, base, path: subID+'?name='+remark });
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
|
||||
@@ -200,21 +200,12 @@
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.delayedStart = false;
|
||||
},
|
||||
getClients(protocol, clientSettings) {
|
||||
switch (protocol) {
|
||||
case Protocols.VMESS: return clientSettings.vmesses;
|
||||
case Protocols.VLESS: return clientSettings.vlesses;
|
||||
case Protocols.TROJAN: return clientSettings.trojans;
|
||||
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
||||
default: return null;
|
||||
}
|
||||
},
|
||||
newClient(protocol) {
|
||||
switch (protocol) {
|
||||
case Protocols.VMESS: return new Inbound.VmessSettings.Vmess();
|
||||
case Protocols.VLESS: return new Inbound.VLESSSettings.VLESS();
|
||||
case Protocols.TROJAN: return new Inbound.TrojanSettings.Trojan();
|
||||
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings.Shadowsocks();
|
||||
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings.Shadowsocks(clientsBulkModal.inbound.settings.shadowsockses[0].method);
|
||||
default: return null;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
||||
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
||||
case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks());
|
||||
case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks(clients[0].method));
|
||||
default: return null;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
{{define "form/http"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "username"}}'>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
||||
<a-form-item>
|
||||
<a-row>
|
||||
<a-button type="primary" size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
||||
<a-input style="width: 45%" v-model.trim="account.user"
|
||||
addon-before='{{ i18n "username" }}'></a-input>
|
||||
<a-input style="width: 55%" v-model.trim="account.pass"
|
||||
addon-before='{{ i18n "password" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button type="primary" size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -115,7 +115,7 @@
|
||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-form-item v-if="inbound.isSS2022" label='{{ i18n "password" }}'>
|
||||
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
|
||||
@@ -6,11 +6,20 @@
|
||||
</a-form-item>
|
||||
<br>
|
||||
<template v-if="inbound.settings.auth === 'password'">
|
||||
<a-form-item label='{{ i18n "username" }}'>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
||||
<a-form-item>
|
||||
<a-row>
|
||||
<a-button type="primary" size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
||||
<a-input style="width: 45%" v-model.trim="account.user"
|
||||
addon-before='{{ i18n "username" }}'></a-input>
|
||||
<a-input style="width: 55%" v-model.trim="account.pass"
|
||||
addon-before='{{ i18n "password" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button type="primary" size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<br>
|
||||
|
||||
@@ -108,9 +108,4 @@
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-form>
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
||||
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Sniffing
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span >{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{{define "form/streamGRPC"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="AcceptProxyProtocol">
|
||||
<a-switch v-model="inbound.stream.grpc.sockopt.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item label="ServiceName">
|
||||
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
||||
</a-form-item>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{{define "form/streamHTTP"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="AcceptProxyProtocol">
|
||||
<a-switch v-model="inbound.stream.http.sockopt.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
||||
</a-form-item>
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model="inbound.stream.kcp.seed"></a-input>
|
||||
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
||||
<a-input v-model="inbound.stream.kcp.seed" style="width: 150px;" ></a-input>
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item label="MTU">
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
||||
<a-icon @click="inbound.stream.quic.key = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="inbound.stream.quic.key" style="width: 150px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
|
||||
@@ -42,4 +42,9 @@
|
||||
<template v-if="inbound.stream.network === 'grpc'">
|
||||
{{template "form/streamGRPC"}}
|
||||
</template>
|
||||
|
||||
<!-- sockopt -->
|
||||
<template>
|
||||
{{template "form/streamSockopt"}}
|
||||
</template>
|
||||
{{end}}
|
||||
46
web/html/xui/form/stream/stream_sockopt.html
Normal file
@@ -0,0 +1,46 @@
|
||||
{{define "form/streamSockopt"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="Transparent Proxy">
|
||||
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody" v-if="inbound.stream.sockoptSwitch">
|
||||
<tr>
|
||||
<td>Accept Proxy Protocol</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TCP FastOpen</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Route Mark</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>T-Proxy</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="off">OFF</a-select-option>
|
||||
<a-select-option value="redirect">Redirect</a-select-option>
|
||||
<a-select-option value="tproxy">T-Proxy</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -80,9 +80,13 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Alpn">
|
||||
<a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px">
|
||||
<a-checkbox v-for="key,value in ALPN_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
<a-select
|
||||
mode="multiple"
|
||||
style="width: 250px"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||
v-model="inbound.stream.tls.alpn">
|
||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item label="Allow insecure">
|
||||
@@ -118,6 +122,10 @@
|
||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="cert.key"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<br>
|
||||
<a-form-item label="ocspStapling">
|
||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
|
||||
|
||||
@@ -58,8 +58,19 @@
|
||||
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
|
||||
</td>
|
||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
||||
</td>
|
||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
||||
<tr>
|
||||
<td>{{ i18n "encryption" }}</td>
|
||||
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
||||
</tr><tr v-if="inbound.isSS2022">
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
||||
</tr><tr>
|
||||
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
<template v-if="infoModal.clientSettings">
|
||||
@@ -167,19 +178,7 @@
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-divider></a-divider>
|
||||
<table v-if="inbound.protocol == Protocols.SHADOWSOCKS" style="margin-bottom: 10px; width: 100%;">
|
||||
<tr>
|
||||
<th>{{ i18n "encryption" }}</th>
|
||||
<th>{{ i18n "password" }}</th>
|
||||
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||
</tr><tr>
|
||||
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
||||
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
<template v-if="inbound.protocol == Protocols.SHADOWSOCKS && !inbound.isSSMultiUser">
|
||||
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
|
||||
<a-divider>URL</a-divider>
|
||||
<a-row v-for="(link,index) in infoModal.links">
|
||||
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||
@@ -205,17 +204,19 @@
|
||||
<td><a-tag color="blue">[[ inbound.settings.followRedirect ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
<table v-if="inbound.protocol == Protocols.SOCKS" style="margin-bottom: 10px; width: 100%;">
|
||||
<table v-if="dbInbound.isSocks" style="margin-bottom: 10px; width: 100%;">
|
||||
<tr>
|
||||
<th>{{ i18n "password" }} Auth</th>
|
||||
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
||||
<th>IP</th>
|
||||
</tr><tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
||||
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
||||
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
||||
</tr><tr v-if="inbound.settings.auth == 'password'">
|
||||
</tr>
|
||||
<template v-if="inbound.settings.auth == 'password'">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>{{ i18n "username" }}</td>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
@@ -224,9 +225,9 @@
|
||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
<table v-if="inbound.protocol == Protocols.HTTP" style="margin-bottom: 10px; width: 100%;">
|
||||
</template>
|
||||
</table>
|
||||
<table v-if="dbInbound.isHTTP" style="margin-bottom: 10px; width: 100%;">
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>{{ i18n "username" }}</th>
|
||||
@@ -237,7 +238,6 @@
|
||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
</template>
|
||||
</a-modal>
|
||||
<script>
|
||||
@@ -264,14 +264,15 @@
|
||||
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
|
||||
this.isExpired = this.inbound.isExpiry(index);
|
||||
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||
remark = this.dbInbound.remark + ( this.clientSettings ? "-" + this.clientSettings.email : '');
|
||||
remark = [this.dbInbound.remark, ( this.clientSettings ? this.clientSettings.email : '')].filter(Boolean).join('-');
|
||||
address = this.dbInbound.address;
|
||||
this.links = [];
|
||||
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
||||
this.links.push({
|
||||
remark: remark + "-" + domain.remark,
|
||||
link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, index)
|
||||
remark: remarkText,
|
||||
link: this.inbound.genLink(domain.domain, remarkText, index)
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -295,7 +296,7 @@
|
||||
},
|
||||
genSubLink(subID) {
|
||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||
return buildURL({ host, port, isTLS, base, path: subID });
|
||||
return buildURL({ host, port, isTLS, base, path: subID+'?name='+remark });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -110,6 +110,15 @@
|
||||
if (this.inModal.inbound.settings.shadowsockses.length ==0){
|
||||
this.inModal.inbound.settings.shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()];
|
||||
}
|
||||
if (!this.inModal.inbound.isSS2022) {
|
||||
this.inModal.inbound.settings.shadowsockses.forEach(client => {
|
||||
client.method = this.inModal.inbound.settings.method;
|
||||
})
|
||||
} else {
|
||||
this.inModal.inbound.settings.shadowsockses.forEach(client => {
|
||||
client.method = "";
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (this.inModal.inbound.settings.shadowsockses.length > 0){
|
||||
this.inModal.inbound.settings.shadowsockses = [];
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
.ant-col-sm-24 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
tr.hideExpandIcon .ant-table-row-expand-icon {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
@@ -118,8 +121,12 @@
|
||||
</a-radio-group>
|
||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||
:data-source="searchedInbounds"
|
||||
:loading="spinning" :scroll="{ x: 1300 }"
|
||||
:loading="spinning" :scroll="{ x: 1200 }"
|
||||
:pagination="false"
|
||||
:expand-icon-as-cell="false"
|
||||
:expand-row-by-click="false"
|
||||
:expand-icon-column-index="0"
|
||||
:row-class-name="dbInbound => (dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser) ? '' : 'hideExpandIcon')"
|
||||
style="margin-top: 20px"
|
||||
@change="() => getDBInbounds()">
|
||||
<template slot="action" slot-scope="text, dbInbound">
|
||||
@@ -135,7 +142,7 @@
|
||||
<a-icon type="qrcode"></a-icon>
|
||||
{{ i18n "qrCode" }}
|
||||
</a-menu-item>
|
||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || dbInbound.toInbound().isSSMultiUser">
|
||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser)">
|
||||
<a-menu-item key="addClient">
|
||||
<a-icon type="user-add"></a-icon>
|
||||
{{ i18n "pages.client.add"}}
|
||||
@@ -255,6 +262,7 @@
|
||||
:columns="innerColumns"
|
||||
:data-source="getInboundClients(record)"
|
||||
:pagination="false"
|
||||
style="margin-left: 20px;"
|
||||
>
|
||||
{{template "client_table"}}
|
||||
</a-table>
|
||||
@@ -264,6 +272,7 @@
|
||||
:columns="innerTrojanColumns"
|
||||
:data-source="getInboundClients(record)"
|
||||
:pagination="false"
|
||||
style="margin-left: 20px;"
|
||||
>
|
||||
{{template "client_table"}}
|
||||
</a-table>
|
||||
@@ -279,20 +288,20 @@
|
||||
{{template "component/themeSwitcher" .}}
|
||||
<script>
|
||||
const columns = [{
|
||||
title: "ID",
|
||||
align: 'right',
|
||||
dataIndex: "id",
|
||||
width: 30,
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||
align: 'center',
|
||||
width: 60,
|
||||
width: 40,
|
||||
scopedSlots: { customRender: 'action' },
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.enable" }}',
|
||||
align: 'center',
|
||||
width: 40,
|
||||
scopedSlots: { customRender: 'enable' },
|
||||
}, {
|
||||
title: "ID",
|
||||
align: 'center',
|
||||
dataIndex: "id",
|
||||
width: 40,
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.remark" }}',
|
||||
align: 'center',
|
||||
@@ -340,7 +349,7 @@
|
||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
||||
{ title: 'Password', width: 170, dataIndex: "password" },
|
||||
{ title: '{{ i18n "password" }}', width: 170, dataIndex: "password" },
|
||||
];
|
||||
|
||||
const app = new Vue({
|
||||
@@ -702,7 +711,7 @@
|
||||
openEditClient(dbInboundId, client) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
clients = this.getInboundClients(dbInbound);
|
||||
index = this.findIndexOfClient(clients, client);
|
||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||
clientModal.show({
|
||||
title: '{{ i18n "pages.client.edit"}}',
|
||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||
@@ -716,9 +725,13 @@
|
||||
isEdit: true
|
||||
});
|
||||
},
|
||||
findIndexOfClient(clients, client) {
|
||||
firstKey = Object.keys(client)[0];
|
||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
||||
findIndexOfClient(protocol, clients, client) {
|
||||
switch (protocol) {
|
||||
case Protocols.TROJAN:
|
||||
case Protocols.SHADOWSOCKS:
|
||||
return clients.findIndex(item => item.password === client.password && item.email === client.email);
|
||||
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
||||
}
|
||||
},
|
||||
async addClient(clients, dbInboundId) {
|
||||
const data = {
|
||||
@@ -824,7 +837,7 @@
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
inbound = dbInbound.toInbound();
|
||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||
index = this.findIndexOfClient(clients, client);
|
||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||
clients[index].enable = !clients[index].enable;
|
||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
{{ i18n "menu.link" }}:
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(logModal.rows, logModal.logLevel)">{{ i18n "pages.index.logs" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||
</a-card>
|
||||
@@ -108,21 +108,30 @@
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.systemLoadDesc" }}
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
{{ i18n "pages.index.operationHours" }}:
|
||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||
</a-col>
|
||||
</a-row>
|
||||
{{ i18n "pages.index.operationHours" }}:
|
||||
Xray:
|
||||
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||
OS:
|
||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.systemLoadDesc" }}
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
{{ i18n "usage"}}:
|
||||
Memory [[ sizeFormat(status.appStats.mem) ]] -
|
||||
Threads [[ status.appStats.threads ]]
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
@@ -253,7 +262,7 @@
|
||||
<a-form-item label="Count">
|
||||
<a-select v-model="logModal.rows"
|
||||
style="width: 80px"
|
||||
@change="openLogs(logModal.rows, logModal.logLevel)"
|
||||
@change="openLogs()"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="10">10</a-select-option>
|
||||
<a-select-option value="20">20</a-select-option>
|
||||
@@ -262,9 +271,9 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Log Level">
|
||||
<a-select v-model="logModal.logLevel"
|
||||
<a-select v-model="logModal.level"
|
||||
style="width: 120px"
|
||||
@change="openLogs(logModal.rows, logModal.logLevel)"
|
||||
@change="openLogs()"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="debug">Debug</a-select-option>
|
||||
<a-select-option value="info">Info</a-select-option>
|
||||
@@ -273,8 +282,11 @@
|
||||
<a-select-option value="err">Error</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="SysLog">
|
||||
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<button class="ant-btn ant-btn-primary" @click="openLogs(logModal.rows, logModal.logLevel)"><a-icon type="sync"></a-icon> Reload</button>
|
||||
<button class="ant-btn ant-btn-primary" @click="openLogs()"><a-icon type="sync"></a-icon> Reload</button>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" style="margin-bottom: 10px;"
|
||||
@@ -358,6 +370,8 @@
|
||||
this.tcpCount = 0;
|
||||
this.udpCount = 0;
|
||||
this.uptime = 0;
|
||||
this.appUptime = 0;
|
||||
this.appStats = {threads: 0, mem: 0, uptime: 0};
|
||||
this.xray = { state: State.Stop, errorMsg: "", version: "", color: "" };
|
||||
|
||||
if (data == null) {
|
||||
@@ -376,6 +390,8 @@
|
||||
this.tcpCount = data.tcpCount;
|
||||
this.udpCount = data.udpCount;
|
||||
this.uptime = data.uptime;
|
||||
this.appUptime = data.appUptime;
|
||||
this.appStats = data.appStats;
|
||||
this.xray = data.xray;
|
||||
switch (this.xray.state) {
|
||||
case State.Running:
|
||||
@@ -409,11 +425,11 @@
|
||||
visible: false,
|
||||
logs: '',
|
||||
rows: 20,
|
||||
logLevel: 'info',
|
||||
show(logs, rows) {
|
||||
level: 'info',
|
||||
syslog: false,
|
||||
show(logs) {
|
||||
this.visible = true;
|
||||
this.rows = rows;
|
||||
this.logs = logs.join("\n");
|
||||
this.logs = logs? logs.join("\n"): "No Record...";
|
||||
},
|
||||
hide() {
|
||||
this.visible = false;
|
||||
@@ -514,14 +530,14 @@
|
||||
return;
|
||||
}
|
||||
},
|
||||
async openLogs(rows, logLevel) {
|
||||
async openLogs(){
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post('server/logs/' + rows, { logLevel: `${logLevel}` });
|
||||
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
logModal.show(msg.obj, rows);
|
||||
logModal.show(msg.obj);
|
||||
},
|
||||
async openConfig() {
|
||||
this.loading(true);
|
||||
|
||||
@@ -406,6 +406,8 @@
|
||||
</a-row>
|
||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort"></setting-list-item>
|
||||
|
||||
@@ -15,12 +15,11 @@ import (
|
||||
"x-ui/xray"
|
||||
)
|
||||
|
||||
type CheckClientIpJob struct {}
|
||||
type CheckClientIpJob struct{}
|
||||
|
||||
var job *CheckClientIpJob
|
||||
var disAllowedIps []string
|
||||
var ipFiles = []string{
|
||||
xray.GetBlockedIPsPath(),
|
||||
xray.GetIPLimitLogPath(),
|
||||
xray.GetIPLimitBannedLogPath(),
|
||||
xray.GetAccessPersistentLogPath(),
|
||||
@@ -32,7 +31,6 @@ func NewCheckClientIpJob() *CheckClientIpJob {
|
||||
}
|
||||
|
||||
func (j *CheckClientIpJob) Run() {
|
||||
logger.Debug("Check Client IP Job...")
|
||||
|
||||
// create files required for iplimit if not exists
|
||||
for i := 0; i < len(ipFiles); i++ {
|
||||
@@ -45,11 +43,6 @@ func (j *CheckClientIpJob) Run() {
|
||||
if j.hasLimitIp() {
|
||||
j.processLogFile()
|
||||
}
|
||||
|
||||
// write to blocked ips
|
||||
blockedIps := []byte(strings.Join(disAllowedIps, ","))
|
||||
err := os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0644)
|
||||
j.checkError(err)
|
||||
}
|
||||
|
||||
func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||
@@ -136,8 +129,8 @@ func (j *CheckClientIpJob) processLogFile() {
|
||||
|
||||
}
|
||||
|
||||
// added 3 seconds delay before cleaning logs to reduce chance of logging IP that already has been banned
|
||||
time.Sleep(time.Second * 3)
|
||||
// added delay before cleaning logs to reduce chance of logging IP that already has been banned
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
if shouldCleanLog {
|
||||
// copy access log to persistent file
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"x-ui/logger"
|
||||
"x-ui/web/service"
|
||||
)
|
||||
|
||||
type CheckInboundJob struct {
|
||||
xrayService service.XrayService
|
||||
inboundService service.InboundService
|
||||
}
|
||||
|
||||
func NewCheckInboundJob() *CheckInboundJob {
|
||||
return new(CheckInboundJob)
|
||||
}
|
||||
|
||||
func (j *CheckInboundJob) Run() {
|
||||
needRestart, count, err := j.inboundService.DisableInvalidClients()
|
||||
if err != nil {
|
||||
logger.Warning("Error in disabling invalid clients:", err)
|
||||
} else if count > 0 {
|
||||
logger.Debugf("%v clients disabled", count)
|
||||
if needRestart {
|
||||
j.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
||||
needRestart, count, err = j.inboundService.DisableInvalidInbounds()
|
||||
if err != nil {
|
||||
logger.Warning("Error in disabling invalid inbounds:", err)
|
||||
} else if count > 0 {
|
||||
logger.Debugf("%v inbounds disabled", count)
|
||||
if needRestart {
|
||||
j.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,12 @@ func (j *XrayTrafficJob) Run() {
|
||||
logger.Warning("get xray traffic failed:", err)
|
||||
return
|
||||
}
|
||||
err = j.inboundService.AddTraffic(traffics)
|
||||
err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics)
|
||||
if err != nil {
|
||||
logger.Warning("add traffic failed:", err)
|
||||
}
|
||||
|
||||
err = j.inboundService.AddClientTraffic(clientTraffics)
|
||||
if err != nil {
|
||||
logger.Warning("add client traffic failed:", err)
|
||||
if needRestart {
|
||||
j.xrayService.SetToNeedRestart()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -194,38 +194,6 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||
return inbound, needRestart, err
|
||||
}
|
||||
|
||||
func (s *InboundService) AddInbounds(inbounds []*model.Inbound) error {
|
||||
for _, inbound := range inbounds {
|
||||
exist, err := s.checkPortExist(inbound.Port, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
return common.NewError("Port already exists:", inbound.Port)
|
||||
}
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
var err error
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
for _, inbound := range inbounds {
|
||||
err = tx.Save(inbound).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) DelInbound(id int) (bool, error) {
|
||||
db := database.GetDB()
|
||||
|
||||
@@ -316,21 +284,17 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
|
||||
needRestart := false
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
err1 := s.xrayApi.DelInbound(tag)
|
||||
if err1 != nil {
|
||||
logger.Debug("Unable to delete old inbound by api:", err1)
|
||||
needRestart = true
|
||||
} else {
|
||||
if s.xrayApi.DelInbound(tag) == nil {
|
||||
logger.Debug("Old inbound deleted by api:", tag)
|
||||
|
||||
if inbound.Enable {
|
||||
inboundJson, err2 := json.MarshalIndent(oldInbound.GenXrayInboundConfig(), "", " ")
|
||||
if err2 != nil {
|
||||
logger.Debug("Unable to marshal updated inbound config:", err2)
|
||||
}
|
||||
|
||||
}
|
||||
if inbound.Enable {
|
||||
inboundJson, err2 := json.MarshalIndent(oldInbound.GenXrayInboundConfig(), "", " ")
|
||||
if err2 != nil {
|
||||
logger.Debug("Unable to marshal updated inbound config:", err2)
|
||||
needRestart = true
|
||||
} else {
|
||||
err2 = s.xrayApi.AddInbound(inboundJson)
|
||||
if err1 == nil {
|
||||
if err2 == nil {
|
||||
logger.Debug("Updated inbound added by api:", oldInbound.Tag)
|
||||
} else {
|
||||
logger.Debug("Unable to update inbound by api:", err2)
|
||||
@@ -461,15 +425,21 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
||||
if len(client.Email) > 0 {
|
||||
s.AddClientStat(tx, data.Id, &client)
|
||||
if client.Enable {
|
||||
cipher := ""
|
||||
if oldInbound.Protocol == "shadowsocks" {
|
||||
cipher = oldSettings["method"].(string)
|
||||
}
|
||||
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
|
||||
"email": client.Email,
|
||||
"id": client.ID,
|
||||
"flow": client.Flow,
|
||||
"password": client.Password,
|
||||
"cipher": cipher,
|
||||
})
|
||||
if err1 == nil {
|
||||
logger.Debug("Client added by api:", client.Email)
|
||||
} else {
|
||||
logger.Debug("Error in adding client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
@@ -536,15 +506,18 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||
return false, err
|
||||
}
|
||||
needRestart := false
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
if len(email) > 0 {
|
||||
err = s.xrayApi.RemoveUser(oldInbound.Tag, email)
|
||||
if err == nil {
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email)
|
||||
if err1 == nil {
|
||||
logger.Debug("Client deleted by api:", email)
|
||||
needRestart = false
|
||||
} else {
|
||||
logger.Debug("Unable to del client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
return needRestart, db.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
@@ -650,58 +623,40 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
}
|
||||
}
|
||||
needRestart := false
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
if len(oldEmail) > 0 {
|
||||
s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
if s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail) == nil {
|
||||
logger.Debug("Old client deleted by api:", clients[0].Email)
|
||||
}
|
||||
if clients[0].Enable {
|
||||
cipher := ""
|
||||
if oldInbound.Protocol == "shadowsocks" {
|
||||
cipher = oldSettings["method"].(string)
|
||||
}
|
||||
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
|
||||
"email": clients[0].Email,
|
||||
"id": clients[0].ID,
|
||||
"flow": clients[0].Flow,
|
||||
"password": clients[0].Password,
|
||||
"cipher": cipher,
|
||||
})
|
||||
if err1 == nil {
|
||||
logger.Debug("Client edited by api:", clients[0].Email)
|
||||
needRestart = false
|
||||
} else {
|
||||
logger.Debug("Error in adding client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
} else {
|
||||
logger.Debug("Client disabled by api:", clients[0].Email)
|
||||
needRestart = false
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
} else {
|
||||
logger.Debug("Client old email not found")
|
||||
needRestart = true
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
return needRestart, tx.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
func (s *InboundService) AddTraffic(traffics []*xray.Traffic) error {
|
||||
if len(traffics) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Update traffics in a single transaction
|
||||
err := database.GetDB().Transaction(func(tx *gorm.DB) error {
|
||||
for _, traffic := range traffics {
|
||||
if traffic.IsInbound {
|
||||
update := tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag).
|
||||
Updates(map[string]interface{}{
|
||||
"up": gorm.Expr("up + ?", traffic.Up),
|
||||
"down": gorm.Expr("down + ?", traffic.Down),
|
||||
})
|
||||
if update.Error != nil {
|
||||
return update.Error
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err error) {
|
||||
if len(traffics) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
||||
var err error
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
|
||||
@@ -712,17 +667,73 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
||||
tx.Commit()
|
||||
}
|
||||
}()
|
||||
err = s.addInboundTraffic(tx, inboundTraffics)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
err = s.addClientTraffic(tx, clientTraffics)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
needRestart1, count, err := s.disableInvalidClients(tx)
|
||||
if err != nil {
|
||||
logger.Warning("Error in disabling invalid clients:", err)
|
||||
} else if count > 0 {
|
||||
logger.Debugf("%v clients disabled", count)
|
||||
}
|
||||
|
||||
needRestart2, count, err := s.disableInvalidInbounds(tx)
|
||||
if err != nil {
|
||||
logger.Warning("Error in disabling invalid inbounds:", err)
|
||||
} else if count > 0 {
|
||||
logger.Debugf("%v inbounds disabled", count)
|
||||
}
|
||||
return nil, (needRestart1 || needRestart2)
|
||||
}
|
||||
|
||||
func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
||||
if len(traffics) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
for _, traffic := range traffics {
|
||||
if traffic.IsInbound {
|
||||
err = tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag).
|
||||
Updates(map[string]interface{}{
|
||||
"up": gorm.Expr("up + ?", traffic.Up),
|
||||
"down": gorm.Expr("down + ?", traffic.Down),
|
||||
}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
|
||||
if len(traffics) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
emails := make([]string, 0, len(traffics))
|
||||
for _, traffic := range traffics {
|
||||
emails = append(emails, traffic.Email)
|
||||
}
|
||||
dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics))
|
||||
err = db.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error
|
||||
err = tx.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Avoid empty slice error
|
||||
if len(dbClientTraffics) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
dbClientTraffics, err = s.adjustTraffics(tx, dbClientTraffics)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -798,14 +809,13 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
||||
return dbClientTraffics, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) DisableInvalidInbounds() (bool, int64, error) {
|
||||
db := database.GetDB()
|
||||
func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) {
|
||||
now := time.Now().Unix() * 1000
|
||||
needRestart := false
|
||||
|
||||
if p != nil {
|
||||
var tags []string
|
||||
err := db.Table("inbounds").
|
||||
err := tx.Table("inbounds").
|
||||
Select("inbounds.tag").
|
||||
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||
Scan(&tags).Error
|
||||
@@ -814,17 +824,18 @@ func (s *InboundService) DisableInvalidInbounds() (bool, int64, error) {
|
||||
}
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
for _, tag := range tags {
|
||||
err = s.xrayApi.DelInbound(tag)
|
||||
err1 := s.xrayApi.DelInbound(tag)
|
||||
if err == nil {
|
||||
logger.Debug("Inbound disabled by api:", tag)
|
||||
} else {
|
||||
logger.Debug("Error in disabling inbound by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
}
|
||||
|
||||
result := db.Model(model.Inbound{}).
|
||||
result := tx.Model(model.Inbound{}).
|
||||
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||
Update("enable", false)
|
||||
err := result.Error
|
||||
@@ -832,8 +843,7 @@ func (s *InboundService) DisableInvalidInbounds() (bool, int64, error) {
|
||||
return needRestart, count, err
|
||||
}
|
||||
|
||||
func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
|
||||
db := database.GetDB()
|
||||
func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error) {
|
||||
now := time.Now().Unix() * 1000
|
||||
needRestart := false
|
||||
|
||||
@@ -843,7 +853,7 @@ func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
|
||||
Email string
|
||||
}
|
||||
|
||||
err := db.Table("inbounds").
|
||||
err := tx.Table("inbounds").
|
||||
Select("inbounds.tag, client_traffics.email").
|
||||
Joins("JOIN client_traffics ON inbounds.id = client_traffics.inbound_id").
|
||||
Where("((client_traffics.total > 0 AND client_traffics.up + client_traffics.down >= client_traffics.total) OR (client_traffics.expiry_time > 0 AND client_traffics.expiry_time <= ?)) AND client_traffics.enable = ?", now, true).
|
||||
@@ -853,16 +863,17 @@ func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
|
||||
}
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
for _, result := range results {
|
||||
err = s.xrayApi.RemoveUser(result.Tag, result.Email)
|
||||
if err == nil {
|
||||
err1 := s.xrayApi.RemoveUser(result.Tag, result.Email)
|
||||
if err1 == nil {
|
||||
logger.Debug("Client disabled by api:", result.Email)
|
||||
} else {
|
||||
logger.Debug("Error in disabling client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
}
|
||||
result := db.Model(xray.ClientTraffic{}).
|
||||
result := tx.Model(xray.ClientTraffic{}).
|
||||
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||
Update("enable", false)
|
||||
err := result.Error
|
||||
@@ -1254,15 +1265,26 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e
|
||||
for _, client := range clients {
|
||||
if client.Email == clientEmail {
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
cipher := ""
|
||||
if string(inbound.Protocol) == "shadowsocks" {
|
||||
var oldSettings map[string]interface{}
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &oldSettings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
cipher = oldSettings["method"].(string)
|
||||
}
|
||||
err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]interface{}{
|
||||
"email": client.Email,
|
||||
"id": client.ID,
|
||||
"flow": client.Flow,
|
||||
"password": client.Password,
|
||||
"cipher": cipher,
|
||||
})
|
||||
if err1 == nil {
|
||||
logger.Debug("Client enabled due to reset traffic:", clientEmail)
|
||||
} else {
|
||||
logger.Debug("Error in enabling client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -76,6 +77,11 @@ type Status struct {
|
||||
IPv4 string `json:"ipv4"`
|
||||
IPv6 string `json:"ipv6"`
|
||||
} `json:"publicIP"`
|
||||
AppStats struct {
|
||||
Threads uint32 `json:"threads"`
|
||||
Mem uint64 `json:"mem"`
|
||||
Uptime uint64 `json:"uptime"`
|
||||
} `json:"appStats"`
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
@@ -219,12 +225,22 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
status.Xray.ErrorMsg = s.xrayService.GetXrayResult()
|
||||
}
|
||||
status.Xray.Version = s.xrayService.GetXrayVersion()
|
||||
var rtm runtime.MemStats
|
||||
runtime.ReadMemStats(&rtm)
|
||||
|
||||
status.AppStats.Mem = rtm.Sys
|
||||
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
||||
if p.IsRunning() {
|
||||
status.AppStats.Uptime = p.GetUptime()
|
||||
} else {
|
||||
status.AppStats.Uptime = 0
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||
url := "https://api.github.com/repos/MHSanaei/Xray-core/releases"
|
||||
url := "https://api.github.com/repos/XTLS/Xray-core/releases"
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -243,9 +259,11 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versions := make([]string, 0, len(releases))
|
||||
var versions []string
|
||||
for _, release := range releases {
|
||||
versions = append(versions, release.TagName)
|
||||
if release.TagName >= "v1.7.5" {
|
||||
versions = append(versions, release.TagName)
|
||||
}
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
@@ -289,7 +307,7 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
||||
url := fmt.Sprintf("https://github.com/MHSanaei/Xray-core/releases/download/%s/%s", version, fileName)
|
||||
url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -358,48 +376,72 @@ func (s *ServerService) UpdateXray(version string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = copyZipFile("xray", xray.GetBinaryPath())
|
||||
if err != nil {
|
||||
downloadFile := func(fileName string, url string) error {
|
||||
os.Remove(fileName)
|
||||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("download file failed: %s", resp.Status)
|
||||
}
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
return err
|
||||
}
|
||||
err = copyZipFile("geosite.dat", xray.GetGeositePath())
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
copyFiles := map[string]string{
|
||||
"xray": xray.GetBinaryPath(),
|
||||
"geosite.dat": xray.GetGeositePath(),
|
||||
"geoip.dat": xray.GetGeoipPath(),
|
||||
}
|
||||
err = copyZipFile("geoip.dat", xray.GetGeoipPath())
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
downloadFiles := map[string]string{
|
||||
xray.GetIranPath(): "https://github.com/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat",
|
||||
}
|
||||
err = copyZipFile("iran.dat", xray.GetIranPath())
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
for fileName, filePath := range copyFiles {
|
||||
err := copyZipFile(fileName, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for fileName, filePath := range downloadFiles {
|
||||
err := downloadFile(fileName, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerService) GetLogs(count string, logLevel string) ([]string, error) {
|
||||
var cmdArgs []string
|
||||
if runtime.GOOS == "linux" {
|
||||
cmdArgs = []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count}
|
||||
if logLevel != "" {
|
||||
cmdArgs = append(cmdArgs, "-p", logLevel)
|
||||
func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
|
||||
c, _ := strconv.Atoi(count)
|
||||
var lines []string
|
||||
|
||||
if syslog == "true" {
|
||||
cmdArgs := []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count, "-p", level}
|
||||
// Run the command
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return []string{"Failed to run journalctl command!"}
|
||||
}
|
||||
lines = strings.Split(out.String(), "\n")
|
||||
} else {
|
||||
return []string{"Unsupported operating system"}, nil
|
||||
lines = logger.GetLogs(c, level)
|
||||
}
|
||||
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(out.String(), "\n")
|
||||
|
||||
return lines, nil
|
||||
return lines
|
||||
}
|
||||
|
||||
func (s *ServerService) GetConfigJson() (interface{}, error) {
|
||||
@@ -417,6 +459,7 @@ func (s *ServerService) GetConfigJson() (interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jsonData, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ var defaultValueMap = map[string]string{
|
||||
"tgBotChatId": "",
|
||||
"tgRunTime": "@daily",
|
||||
"tgBotBackup": "false",
|
||||
"tgBotLoginNotify": "false",
|
||||
"tgBotLoginNotify": "true",
|
||||
"tgCpu": "0",
|
||||
"tgLang": "en-US",
|
||||
"secretEnable": "false",
|
||||
@@ -51,6 +51,8 @@ var defaultValueMap = map[string]string{
|
||||
"subCertFile": "",
|
||||
"subKeyFile": "",
|
||||
"subUpdates": "12",
|
||||
"subEncrypt": "true",
|
||||
"subShowInfo": "true",
|
||||
}
|
||||
|
||||
type SettingService struct {
|
||||
@@ -396,6 +398,14 @@ func (s *SettingService) GetSubUpdates() (int, error) {
|
||||
return s.getInt("subUpdates")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubEncrypt() (bool, error) {
|
||||
return s.getBool("subEncrypt")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubShowInfo() (bool, error) {
|
||||
return s.getBool("subShowInfo")
|
||||
}
|
||||
|
||||
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||
if err := allSetting.CheckValid(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -827,7 +827,7 @@ func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...
|
||||
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
|
||||
requestUser := telego.KeyboardButtonRequestUser{
|
||||
RequestID: int32(traffic.Id),
|
||||
UserIsBot: false,
|
||||
UserIsBot: new(bool),
|
||||
}
|
||||
keyboard := tu.Keyboard(
|
||||
tu.KeyboardRow(
|
||||
|
||||
@@ -69,7 +69,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.inboundService.DisableInvalidClients()
|
||||
s.inboundService.AddTraffic(nil, nil)
|
||||
|
||||
inbounds, err := s.inboundService.GetAllInbounds()
|
||||
if err != nil {
|
||||
@@ -116,7 +116,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
}
|
||||
}
|
||||
for key := range c {
|
||||
if key != "email" && key != "id" && key != "password" && key != "flow" {
|
||||
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "method" {
|
||||
delete(c, key)
|
||||
}
|
||||
if c["flow"] == "xtls-rprx-vision-udp443" {
|
||||
|
||||
@@ -127,7 +127,6 @@
|
||||
"network" = "Network"
|
||||
"destinationPort" = "Destination Port"
|
||||
"targetAddress" = "Target Address"
|
||||
"disableInsecureEncryption" = "Disable Insecure Encryption"
|
||||
"monitorDesc" = "Leave blank by default"
|
||||
"meansNoLimit" = "Means No Limit"
|
||||
"totalFlow" = "Total Flow"
|
||||
@@ -274,6 +273,10 @@
|
||||
"subDomainDesc" = "Leave blank by default to monitor all domains and IPs"
|
||||
"subUpdates" = "Subscription update intervals"
|
||||
"subUpdatesDesc" = "Interval hours between updates in client application"
|
||||
"subEncrypt" = "Encrypt configs"
|
||||
"subEncryptDesc" = "Encrypt the returned configs in subscription"
|
||||
"subShowInfo" = "Show usage info"
|
||||
"subShowInfoDesc" = "Show remianed traffic and date after config name"
|
||||
|
||||
[pages.settings.templates]
|
||||
"title" = "Templates"
|
||||
|
||||
@@ -127,7 +127,6 @@
|
||||
"network" = "شبکه"
|
||||
"destinationPort" = "پورت مقصد"
|
||||
"targetAddress" = "آدرس مقصد"
|
||||
"disableInsecureEncryption" = "رمزگذاری ناامن را غیرفعال کنید"
|
||||
"monitorDesc" = "به طور پیش فرض خالی بگذارید"
|
||||
"meansNoLimit" = "یعنی بدون محدودیت"
|
||||
"totalFlow" = "کل ترافیک"
|
||||
@@ -274,6 +273,10 @@
|
||||
"subDomainDesc" = "برای نظارت بر همه دامنه ها و آیپی ها به طور پیش فرض خالی بگذارید"
|
||||
"subUpdates" = "فاصله به روز رسانی های سابسکریپشن"
|
||||
"subUpdatesDesc" = "ساعت های فاصله بین به روز رسانی در برنامه کاربر"
|
||||
"subEncrypt" = "رمزگذاری کانفیگ ها"
|
||||
"subEncryptDesc" = "رمزگذاری کانفیگ های بازگشتی سابسکریپشن"
|
||||
"subShowInfo" = "نمایش اطلاعات مصرف"
|
||||
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در هر کانفیگ نمایش میدهد"
|
||||
|
||||
[pages.settings.templates]
|
||||
"title" = "الگوها"
|
||||
|
||||
@@ -127,7 +127,6 @@
|
||||
"network" = "Сеть"
|
||||
"destinationPort" = "Порт назначения"
|
||||
"targetAddress" = "Целевой адрес"
|
||||
"disableInsecureEncryption" = "Отключить небезопасное шифрование"
|
||||
"monitorDesc" = "Оставьте пустым по умолчанию"
|
||||
"meansNoLimit" = "Значит без ограничений"
|
||||
"totalFlow" = "Общий расход"
|
||||
@@ -274,6 +273,10 @@
|
||||
"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все домены и IP-адреса"
|
||||
"subUpdates" = "Интервалы обновления подписки"
|
||||
"subUpdatesDesc" = "Часовой интервал между обновлениями в клиентском приложении"
|
||||
"subEncrypt" = "Шифровать конфиги"
|
||||
"subEncryptDesc" = "Шифровать возвращенные конфиги в подписке"
|
||||
"subShowInfo" = "Показать информацию об использовании"
|
||||
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
||||
|
||||
[pages.settings.templates]
|
||||
"title" = "Шаблоны"
|
||||
|
||||
495
web/translation/translate.vi_VN.toml
Normal file
@@ -0,0 +1,495 @@
|
||||
"username" = "Tên người dùng"
|
||||
"password" = "Mật khẩu"
|
||||
"login" = "Đăng nhập"
|
||||
"confirm" = "Xác nhận"
|
||||
"cancel" = "Hủy bỏ"
|
||||
"close" = "Đóng"
|
||||
"copy" = "Sao chép"
|
||||
"copied" = "Đã sao chép"
|
||||
"download" = "Tải xuống"
|
||||
"remark" = "Ghi chú"
|
||||
"enable" = "Kích hoạt"
|
||||
"protocol" = "Giao thức"
|
||||
"search" = "Tìm kiếm"
|
||||
"filter" = "Bộ lọc"
|
||||
"loading" = "Đang tải"
|
||||
"second" = "Giây"
|
||||
"minute" = "Phút"
|
||||
"hour" = "Giờ"
|
||||
"day" = "Ngày"
|
||||
"check" = "Kiểm tra"
|
||||
"indefinite" = "Không xác định"
|
||||
"unlimited" = "Không giới hạn"
|
||||
"none" = "Không có"
|
||||
"qrCode" = "Mã QR"
|
||||
"info" = "Thông tin thêm"
|
||||
"edit" = "Chỉnh sửa"
|
||||
"delete" = "Xóa"
|
||||
"reset" = "Đặt lại"
|
||||
"copySuccess" = "Đã sao chép thành công"
|
||||
"sure" = "Chắc chắn"
|
||||
"encryption" = "Mã hóa"
|
||||
"transmission" = "Truyền tải"
|
||||
"host" = "Máy chủ"
|
||||
"path" = "Đường dẫn"
|
||||
"camouflage" = "camouflage"
|
||||
"status" = "Trạng thái"
|
||||
"enabled" = "Đã kích hoạt"
|
||||
"disabled" = "Đã tắt"
|
||||
"depleted" = "Đã cạn kiệt"
|
||||
"depletingSoon" = "Sắp cạn kiệt"
|
||||
"domainName" = "Tên miền"
|
||||
"monitor" = "Listening IP"
|
||||
"certificate" = "Chứng chỉ"
|
||||
"fail" = "Thất bại"
|
||||
"success" = "Thành công"
|
||||
"getVersion" = "Lấy phiên bản"
|
||||
"install" = "Cài đặt"
|
||||
"clients" = "Khách hàng"
|
||||
"usage" = "Sử dụng"
|
||||
"secretToken" = "secretToken"
|
||||
"remained" = "Còn lại"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "Trạng thái hệ thống"
|
||||
"inbounds" = "Inbounds"
|
||||
"settings" = "Cài đặt bảng điều khiển"
|
||||
"logout" = "Đăng xuất"
|
||||
"link" = "Khác"
|
||||
|
||||
[pages.login]
|
||||
"title" = "Đăng nhập"
|
||||
"loginAgain" = "Thời hạn đăng nhập đã hết. Vui lòng đăng nhập lại."
|
||||
|
||||
[pages.login.toasts]
|
||||
"invalidFormData" = "Dạng dữ liệu nhập không hợp lệ."
|
||||
"emptyUsername" = "Vui lòng nhập tên người dùng."
|
||||
"emptyPassword" = "Vui lòng nhập mật khẩu."
|
||||
"wrongUsernameOrPassword" = "Tên người dùng hoặc mật khẩu không đúng."
|
||||
"successLogin" = "Đăng nhập thành công."
|
||||
|
||||
[pages.index]
|
||||
"title" = "Trạng thái hệ thống"
|
||||
"memory" = "Bộ nhớ"
|
||||
"hard" = "Ổ cứng"
|
||||
"xrayStatus" = "Trạng thái của Xray"
|
||||
"stopXray" = "Dừng Xray"
|
||||
"restartXray" = "Khởi động lại Xray"
|
||||
"xraySwitch" = "Chuyển đổi phiên bản"
|
||||
"xraySwitchClick" = "Chọn phiên bản mà bạn muốn chuyển đổi sang."
|
||||
"xraySwitchClickDesk" = "Hãy lựa chọn thận trọng, vì các phiên bản cũ có thể không tương thích với các cấu hình hiện tại."
|
||||
"operationHours" = "Thời gian hoạt động"
|
||||
"systemLoad" = "Tải hệ thống"
|
||||
"systemLoadDesc" = "trung bình tải hệ thống trong 1, 5 và 15 phút qua"
|
||||
"connectionTcpCountDesc" = "Tổng số kết nối TCP trên tất cả các card mạng."
|
||||
"connectionUdpCountDesc" = "Tổng số kết nối UDP trên tất cả các card mạng."
|
||||
"connectionCount" = "Số lượng kết nối"
|
||||
"upSpeed" = "Tổng tốc độ tải lên cho tất cả các card mạng."
|
||||
"downSpeed" = "Tổng tốc độ tải xuống cho tất cả các card mạng."
|
||||
"totalSent" = "Tổng lưu lượng tải lên của tất cả các thẻ mạng kể từ khi hệ thống khởi động."
|
||||
"totalReceive" = "Tổng lưu lượng tải xuống trên tất cả các thẻ mạng kể từ khi hệ thống khởi động."
|
||||
"xraySwitchVersionDialog" = "Chuyển đổi Phiên bản Xray"
|
||||
"xraySwitchVersionDialogDesc" = "Bạn có chắc chắn muốn chuyển đổi phiên bản Xray sang"
|
||||
"dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này."
|
||||
"logs" = "Nhật ký"
|
||||
"config" = "Cấu hình"
|
||||
"backup" = "Sao lưu & Khôi phục"
|
||||
"backupTitle" = "Sao lưu & Khôi phục Cơ sở dữ liệu"
|
||||
"backupDescription" = "Hãy nhớ sao lưu trước khi nhập cơ sở dữ liệu mới."
|
||||
"exportDatabase" = "Tải về Cơ sở dữ liệu"
|
||||
"importDatabase" = "Tải lên Cơ sở dữ liệu"
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "Điểm vào (Inbounds)"
|
||||
"totalDownUp" = "Tổng tải lên/tải xuống"
|
||||
"totalUsage" = "Tổng sử dụng"
|
||||
"inboundCount" = "Số lượng điểm vào"
|
||||
"operate" = "Thao tác"
|
||||
"enable" = "Kích hoạt"
|
||||
"remark" = "Chú thích"
|
||||
"protocol" = "Giao thức"
|
||||
"port" = "Cổng"
|
||||
"traffic" = "Lưu lượng"
|
||||
"details" = "Chi tiết"
|
||||
"transportConfig" = "Giao vận"
|
||||
"expireDate" = "Ngày hết hạn"
|
||||
"resetTraffic" = "Đặt lại lưu lượng"
|
||||
"addInbound" = "Thêm điểm vào"
|
||||
"generalActions" = "Hành động chung"
|
||||
"create" = "Tạo mới"
|
||||
"update" = "Cập nhật"
|
||||
"modifyInbound" = "Chỉnh sửa điểm vào (Inbound)"
|
||||
"deleteInbound" = "Xóa điểm vào (Inbound)"
|
||||
"deleteInboundContent" = "Xác nhận xóa điểm vào? (Inbound)"
|
||||
"resetTrafficContent" = "Xác nhận đặt lại lưu lượng?"
|
||||
"copyLink" = "Sao chép liên kết"
|
||||
"address" = "Địa chỉ"
|
||||
"network" = "Mạng"
|
||||
"destinationPort" = "Cổng đích"
|
||||
"targetAddress" = "Địa chỉ mục tiêu"
|
||||
"monitorDesc" = "Mặc định để trống"
|
||||
"meansNoLimit" = "Nghĩa là không giới hạn"
|
||||
"totalFlow" = "Tổng lưu lượng"
|
||||
"leaveBlankToNeverExpire" = "Để trống để không bao giờ hết hạn"
|
||||
"noRecommendKeepDefault" = "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định"
|
||||
"certificatePath" = "Đường dẫn tập tin chứng chỉ"
|
||||
"certificateContent" = "Nội dung tập tin chứng chỉ"
|
||||
"publicKeyPath" = "Đường dẫn khóa công khai"
|
||||
"publicKeyContent" = "Nội dung khóa công khai"
|
||||
"keyPath" = "Đường dẫn khóa riêng tư"
|
||||
"keyContent" = "Nội dung khóa riêng tư"
|
||||
"clickOnQRcode" = "Nhấn vào Mã QR để sao chép"
|
||||
"client" = "Client"
|
||||
"export" = "Xuất liên kết"
|
||||
"clone" = "Sao chép"
|
||||
"cloneInbound" = "Sao chép điểm vào (Inbound)"
|
||||
"cloneInboundContent" = "Tất cả cài đặt của điểm vào này, trừ Cổng, IP nghe và Clients, sẽ được áp dụng cho bản sao."
|
||||
"cloneInboundOk" = "Sao chép"
|
||||
"resetAllTraffic" = "Đặt lại lưu lượng cho tất cả điểm vào"
|
||||
"resetAllTrafficTitle" = "Đặt lại lưu lượng cho tất cả điểm vào"
|
||||
"resetAllTrafficContent" = "Bạn có chắc chắn muốn đặt lại lưu lượng cho tất cả điểm vào không?"
|
||||
"resetInboundClientTraffics" = "Đặt lại lưu lượng cho các client của điểm vào"
|
||||
"resetInboundClientTrafficTitle" = "Đặt lại lưu lượng cho tất cả lưu lượng của client"
|
||||
"resetInboundClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho các client của điểm vào này không?"
|
||||
"resetAllClientTraffics" = "Đặt lại lưu lượng cho tất cả client"
|
||||
"resetAllClientTrafficTitle" = "Đặt lại lưu lượng cho tất cả client"
|
||||
"resetAllClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho tất cả client không?"
|
||||
"delDepletedClients" = "Xóa các client đã cạn kiệt"
|
||||
"delDepletedClientsTitle" = "Xóa các client đã cạn kiệt"
|
||||
"delDepletedClientsContent" = "Bạn có chắc chắn muốn xóa tất cả các client đã cạn kiệt không?"
|
||||
"email" = "Email"
|
||||
"emailDesc" = "Vui lòng cung cấp một địa chỉ email duy nhất."
|
||||
"IPLimit" = "Giới hạn IP"
|
||||
"IPLimitDesc" = "Vô hiệu hóa điểm vào nếu số lượng vượt quá giá trị đã nhập (nhập 0 để vô hiệu hóa giới hạn IP)."
|
||||
"IPLimitlog" = "Lịch sử IP"
|
||||
"IPLimitlogDesc" = "Lịch sử đăng nhập IP (trước khi kích hoạt điểm vào sau khi bị vô hiệu hóa bởi giới hạn IP, bạn nên xóa lịch sử)."
|
||||
"IPLimitlogclear" = "Xóa Lịch sử"
|
||||
"setDefaultCert" = "Đặt chứng chỉ từ bảng điều khiển"
|
||||
"xtlsDesc" = "Xray core cần phiên bản 1.7.5"
|
||||
"realityDesc" = "Xray core cần phiên bản 1.8.0 hoặc cao hơn."
|
||||
"telegramDesc" = "Sử dụng Telegram ID mà không cần ký hiệu @ hoặc chat IDs (bạn có thể nhận được nó ở đây @userinfobot hoặc sử dụng lệnh '/id' trong bot)"
|
||||
"subscriptionDesc" = "Bạn có thể tìm liên kết đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau"
|
||||
|
||||
[pages.client]
|
||||
"add" = "Thêm Client"
|
||||
"edit" = "Chỉnh sửa Client"
|
||||
"submitAdd" = "Thêm Client"
|
||||
"submitEdit" = "Lưu thay đổi"
|
||||
"clientCount" = "Số lượng Client"
|
||||
"bulk" = "Thêm hàng loạt"
|
||||
"method" = "Phương pháp"
|
||||
"first" = "Đầu tiên"
|
||||
"last" = "Cuối cùng"
|
||||
"prefix" = "Tiền tố"
|
||||
"postfix" = "Hậu tố"
|
||||
"delayedStart" = "Bắt đầu sau khi sử dụng lần đầu"
|
||||
"expireDays" = "Số ngày hết hạn"
|
||||
"days" = "ngày"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "Nhận"
|
||||
|
||||
[pages.inbounds.stream.general]
|
||||
"requestHeader" = "Tiêu đề yêu cầu"
|
||||
"name" = "Tên"
|
||||
"value" = "Giá trị"
|
||||
|
||||
[pages.inbounds.stream.tcp]
|
||||
"requestVersion" = "Phiên bản yêu cầu"
|
||||
"requestMethod" = "Phương thức yêu cầu"
|
||||
"requestPath" = "Đường dẫn yêu cầu"
|
||||
"responseVersion" = "Phiên bản phản hồi"
|
||||
"responseStatus" = "Trạng thái phản hồi"
|
||||
"responseStatusDescription" = "Mô tả trạng thái phản hồi"
|
||||
"responseHeader" = "Tiêu đề phản hồi"
|
||||
|
||||
[pages.inbounds.stream.quic]
|
||||
"encryption" = "Mã hóa"
|
||||
|
||||
[pages.settings]
|
||||
"title" = "Cài đặt"
|
||||
"save" = "Lưu"
|
||||
"infoDesc" = "Mọi thay đổi được thực hiện ở đây cần phải được lưu. Vui lòng khởi động lại bảng điều khiển để áp dụng các thay đổi."
|
||||
"restartPanel" = "Khởi động lại Bảng điều khiển"
|
||||
"restartPanelDesc" = "Bạn có chắc chắn muốn khởi động lại bảng điều khiển? Nhấn OK để khởi động lại sau 3 giây. Nếu bạn không thể truy cập bảng điều khiển sau khi khởi động lại, vui lòng xem thông tin nhật ký của bảng điều khiển trên máy chủ."
|
||||
"actions" = "Hành động"
|
||||
"resetDefaultConfig" = "Đặt lại Cấu hình Mặc định"
|
||||
"panelSettings" = "Cài đặt Bảng điều khiển"
|
||||
"securitySettings" = "Cài đặt Bảo mật"
|
||||
"xrayConfiguration" = "Cấu hình Xray"
|
||||
"TGBotSettings" = "Cài đặt Bot Telegram"
|
||||
"panelListeningIP" = "IP Nghe của Bảng điều khiển"
|
||||
"panelListeningIPDesc" = "Mặc định để trống để nghe tất cả các IP."
|
||||
"panelListeningDomain" = "Tên miền của nghe Bảng điều khiển"
|
||||
"panelListeningDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
|
||||
"panelPort" = "Cổng Bảng điều khiển"
|
||||
"panelPortDesc" = "Cổng được sử dụng để hiển thị bảng điều khiển này"
|
||||
"publicKeyPath" = "Đường dẫn tập tin khóa công khai Chứng chỉ Bảng điều khiển"
|
||||
"publicKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với."
|
||||
"privateKeyPath" = "Đường dẫn tập tin khóa riêng tư Chứng chỉ Bảng điều khiển"
|
||||
"privateKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với."
|
||||
"panelUrlPath" = "Đường dẫn gốc URL Bảng điều khiển"
|
||||
"panelUrlPathDesc" = "Phải bắt đầu bằng '/' và kết thúc bằng."
|
||||
"oldUsername" = "Tên người dùng hiện tại"
|
||||
"currentPassword" = "Mật khẩu hiện tại"
|
||||
"newUsername" = "Tên người dùng mới"
|
||||
"newPassword" = "Mật khẩu mới"
|
||||
"telegramBotEnable" = "Bật Bot Telegram"
|
||||
"telegramBotEnableDesc" = "Kết nối với các tính năng của bảng điều khiển này thông qua bot Telegram"
|
||||
"telegramToken" = "Token Telegram"
|
||||
"telegramTokenDesc" = "Bạn phải nhận token từ quản lý bot Telegram @botfather"
|
||||
"telegramChatId" = "Chat ID Telegram của quản trị viên"
|
||||
"telegramChatIdDesc" = "Nhiều Chat ID phân tách bằng dấu phẩy. Sử dụng @userinfobot hoặc sử dụng lệnh '/id' trong bot để lấy Chat ID của bạn."
|
||||
"telegramNotifyTime" = "Thời gian thông báo của bot Telegram"
|
||||
"telegramNotifyTimeDesc" = "Sử dụng định dạng thời gian Crontab."
|
||||
"tgNotifyBackup" = "Sao lưu Cơ sở dữ liệu"
|
||||
"tgNotifyBackupDesc" = "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo."
|
||||
"tgNotifyLogin" = "Thông báo Đăng nhập"
|
||||
"tgNotifyLoginDesc" = "Hiển thị tên người dùng, địa chỉ IP và thời gian khi ai đó cố gắng đăng nhập vào bảng điều khiển của bạn."
|
||||
"sessionMaxAge" = "Tuổi tối đa của phiên"
|
||||
"sessionMaxAgeDesc" = "Thời gian của phiên đăng nhập (đơn vị: phút)"
|
||||
"expireTimeDiff" = "Ngưỡng hết hạn cho thông báo"
|
||||
"expireTimeDiffDesc" = "Nhận thông báo về việc hết hạn tài khoản trước ngưỡng này (đơn vị: ngày)"
|
||||
"trafficDiff" = "Ngưỡng lưu lượng cho thông báo"
|
||||
"trafficDiffDesc" = "Nhận thông báo về việc cạn kiệt lưu lượng trước khi đạt đến ngưỡng này (đơn vị: GB)"
|
||||
"tgNotifyCpu" = "Ngưỡng cảnh báo tỷ lệ CPU"
|
||||
"tgNotifyCpuDesc" = "Nhận thông báo nếu tỷ lệ sử dụng CPU vượt quá ngưỡng này (đơn vị: %)"
|
||||
"timeZone" = "Múi giờ"
|
||||
"timeZoneDesc" = "Các tác vụ được lên lịch chạy theo thời gian trong múi giờ này."
|
||||
"subSettings" = "Đăng ký"
|
||||
"subEnable" = "Bật dịch vụ"
|
||||
"subEnableDesc" = "Tính năng đăng ký với cấu hình riêng"
|
||||
"subListen" = "Listening IP"
|
||||
"subListenDesc" = "Mặc định để trống để nghe tất cả các IP"
|
||||
"subPort" = "Cổng Đăng ký"
|
||||
"subPortDesc" = "Số cổng dịch vụ đăng ký phải chưa được sử dụng trên máy chủ"
|
||||
"subCertPath" = "Đường dẫn tập tin khóa công khai Chứng chỉ Đăng ký"
|
||||
"subCertPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với '/'"
|
||||
"subKeyPath" = "Đường dẫn tập tin khóa riêng tư Chứng chỉ Đăng ký"
|
||||
"subKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với '/'"
|
||||
"subPath" = "Đường dẫn gốc URL Đăng ký"
|
||||
"subPathDesc" = "Phải bắt đầu bằng '/' và kết thúc bằng '/'"
|
||||
"subDomain" = "Tên miền con"
|
||||
"subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
|
||||
"subUpdates" = "Khoảng thời gian cập nhật đăng ký"
|
||||
"subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách"
|
||||
"subEncrypt" = "Mã hóa cấu hình"
|
||||
"subEncryptDesc" = "Mã hóa các cấu hình được trả về trong đăng ký"
|
||||
"subShowInfo" = "Hiển thị thông tin sử dụng"
|
||||
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
|
||||
|
||||
[pages.settings.templates]
|
||||
"title" = "Mẫu"
|
||||
"basicTemplate" = "Mẫu Cơ bản"
|
||||
"advancedTemplate" = "Mẫu Nâng cao"
|
||||
"completeTemplate" = "Mẫu Đầy đủ"
|
||||
"generalConfigs" = "Cấu hình Chung"
|
||||
"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát."
|
||||
"blockConfigs" = "Cấu hình Chặn"
|
||||
"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể."
|
||||
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
|
||||
"blockCountryConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các tên miền quốc gia cụ thể."
|
||||
"directCountryConfigs" = "Cấu hình Kết nối Trực tiếp Quốc gia"
|
||||
"directCountryConfigsDesc" = "Những tùy chọn này sẽ kết nối người dùng trực tiếp đến các tên miền quốc gia cụ thể."
|
||||
"ipv4Configs" = "Cấu hình IPv4"
|
||||
"ipv4ConfigsDesc" = "Những tùy chọn này sẽ chỉ định kết nối đến các tên miền mục tiêu qua IPv4."
|
||||
"warpConfigs" = "Cấu hình WARP"
|
||||
"warpConfigsDesc" = "Cảnh báo: Trước khi sử dụng những tùy chọn này, hãy cài đặt WARP ở chế độ proxy socks5 trên máy chủ của bạn bằng cách làm theo các bước trên GitHub của bảng điều khiển. WARP sẽ định tuyến lưu lượng đến các trang web qua máy chủ Cloudflare."
|
||||
"xrayConfigTemplate" = "Mẫu Cấu hình Xray"
|
||||
"xrayConfigTemplateDesc" = "Tạo tệp cấu hình Xray cuối cùng dựa trên mẫu này."
|
||||
"xrayConfigFreedomStrategy" = "Cấu hình Chiến lược cho Giao thức Freedom"
|
||||
"xrayConfigFreedomStrategyDesc" = "Đặt chiến lược đầu ra của mạng trong Giao thức Freedom."
|
||||
"xrayConfigRoutingStrategy" = "Cấu hình Chiến lược Định tuyến Tên miền"
|
||||
"xrayConfigRoutingStrategyDesc" = "Đặt chiến lược định tuyến tổng thể cho việc giải quyết DNS."
|
||||
"xrayConfigTorrent" = "Cấu hình sử dụng BitTorrent"
|
||||
"xrayConfigTorrentDesc" = "Thay đổi mẫu cấu hình để tránh việc người dùng sử dụng BitTorrent."
|
||||
"xrayConfigPrivateIp" = "Cấm kết nối đến dải IP Riêng tư"
|
||||
"xrayConfigPrivateIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP riêng tư."
|
||||
"xrayConfigAds" = "Chặn Quảng cáo"
|
||||
"xrayConfigAdsDesc" = "Thay đổi mẫu cấu hình để chặn quảng cáo."
|
||||
"xrayConfigFamily" = "Chặn Phần mềm độc hại và Nội dung cho Người lớn"
|
||||
"xrayConfigFamilyDesc" = "Các trình giải quyết DNS để chặn phần mềm độc hại và nội dung cho bảo vệ gia đình."
|
||||
"xrayConfigSpeedtest" = "Chặn Trang web Speedtest"
|
||||
"xrayConfigSpeedtestDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các trang web Speedtest."
|
||||
"xrayConfigIRIp" = "Vô hiệu hóa kết nối đến dải IP của Iran"
|
||||
"xrayConfigIRIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP của Iran."
|
||||
"xrayConfigIRDomain" = "Vô hiệu hóa kết nối đến tên miền của Iran"
|
||||
"xrayConfigIRDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các tên miền của Iran."
|
||||
"xrayConfigChinaIp" = "Vô hiệu hóa kết nối đến dải IP của Trung Quốc"
|
||||
"xrayConfigChinaIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP của Trung Quốc."
|
||||
"xrayConfigChinaDomain" = "Vô hiệu hóa kết nối đến tên miền của Trung Quốc"
|
||||
"xrayConfigChinaDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các tên miền của Trung Quốc."
|
||||
"xrayConfigRussiaIp" = "Vô hiệu hóa kết nối đến dải IP của Nga"
|
||||
"xrayConfigRussiaIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP của Nga."
|
||||
"xrayConfigRussiaDomain" = "Vô hiệu hóa kết nối đến tên miền của Nga"
|
||||
"xrayConfigRussiaDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các tên miền của Nga."
|
||||
"xrayConfigDirectIRIp" = "Kết nối trực tiếp đến dải IP của Iran"
|
||||
"xrayConfigDirectIRIpDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến dải IP của Iran."
|
||||
"xrayConfigDirectIRDomain" = "Kết nối trực tiếp đến tên miền của Iran"
|
||||
"xrayConfigDirectIRDomainDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến các tên miền của Iran."
|
||||
"xrayConfigDirectChinaIp" = "Kết nối trực tiếp đến dải IP của Trung Quốc"
|
||||
"xrayConfigDirectChinaIpDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến dải IP của Trung Quốc."
|
||||
"xrayConfigDirectChinaDomain" = "Kết nối trực tiếp đến tên miền của Trung Quốc"
|
||||
"xrayConfigDirectChinaDomainDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến các tên miền của Trung Quốc."
|
||||
"xrayConfigDirectRussiaIp" = "Kết nối trực tiếp đến dải IP của Nga"
|
||||
"xrayConfigDirectRussiaIpDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến dải IP của Nga."
|
||||
"xrayConfigDirectRussiaDomain" = "Kết nối trực tiếp đến tên miền của Nga"
|
||||
"xrayConfigDirectRussiaDomainDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến các tên miền của Nga."
|
||||
"xrayConfigGoogleIPv4" = "Sử dụng IPv4 cho Google"
|
||||
"xrayConfigGoogleIPv4Desc" = "Thêm định tuyến cho Google để kết nối qua IPv4."
|
||||
"xrayConfigNetflixIPv4" = "Sử dụng IPv4 cho Netflix"
|
||||
"xrayConfigNetflixIPv4Desc" = "Thêm định tuyến cho Netflix để kết nối qua IPv4."
|
||||
"xrayConfigGoogleWARP" = "Định tuyến Google qua WARP."
|
||||
"xrayConfigGoogleWARPDesc" = "Thêm định tuyến cho Google qua WARP."
|
||||
"xrayConfigOpenAIWARP" = "Định tuyến OpenAI (ChatGPT) qua WARP."
|
||||
"xrayConfigOpenAIWARPDesc" = "Thêm định tuyến cho OpenAI (ChatGPT) qua WARP."
|
||||
"xrayConfigNetflixWARP" = "Định tuyến Netflix qua WARP."
|
||||
"xrayConfigNetflixWARPDesc" = "Thêm định tuyến cho Netflix qua WARP."
|
||||
"xrayConfigSpotifyWARP" = "Định tuyến Spotify qua WARP."
|
||||
"xrayConfigSpotifyWARPDesc" = "Thêm định tuyến cho Spotify qua WARP."
|
||||
"xrayConfigIRWARP" = "Định tuyến tên miền của Iran qua WARP."
|
||||
"xrayConfigIRWARPDesc" = "Thêm định tuyến cho các tên miền của Iran qua WARP."
|
||||
"xrayConfigInbounds" = "Cấu hình của Inbounds"
|
||||
"xrayConfigInboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể."
|
||||
"xrayConfigOutbounds" = "Cấu hình của Outbounds"
|
||||
"xrayConfigOutboundsDesc" = "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này."
|
||||
"xrayConfigRoutings" = "Cấu hình của Luật Định tuyến."
|
||||
"xrayConfigRoutingsDesc" = "Thay đổi mẫu cấu hình để xác định luật định tuyến cho máy chủ này."
|
||||
"manualLists" = "Danh sách Thủ công"
|
||||
"manualListsDesc" = "Vui lòng sử dụng định dạng mảng JSON."
|
||||
"manualBlockedIPs" = "Danh sách IP bị Chặn"
|
||||
"manualBlockedDomains" = "Danh sách Tên miền bị Chặn"
|
||||
"manualDirectIPs" = "Danh sách IP Trực tiếp"
|
||||
"manualDirectDomains" = "Danh sách Tên miền Trực tiếp"
|
||||
"manualIPv4Domains" = "Danh sách Tên miền IPv4"
|
||||
"manualWARPDomains" = "Danh sách Tên miền WARP"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "Quản trị viên"
|
||||
"secret" = "Mã thông báo bí mật"
|
||||
"loginSecurity" = "Bảo mật đăng nhập"
|
||||
"loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng"
|
||||
"secretToken" = "Mã thông báo bí mật"
|
||||
"secretTokenDesc" = "Vui lòng sao chép và lưu trữ mã thông báo này một cách an toàn ở nơi an toàn. Mã thông báo này cần thiết để đăng nhập và không thể phục hồi từ công cụ lệnh x-ui."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Chỉnh sửa cài đặt "
|
||||
"getSettings" = "Lấy cài đặt "
|
||||
"modifyUser" = "Chỉnh sửa người dùng "
|
||||
"originalUserPassIncorrect" = "Tên người dùng hoặc mật khẩu gốc không đúng"
|
||||
"userPassMustBeNotEmpty" = "Tên người dùng mới và mật khẩu mới không thể để trống"
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ Bàn phím tùy chỉnh đã đóng!"
|
||||
"noResult" = "❗ Không có kết quả!"
|
||||
"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lệnh lại!"
|
||||
"wentWrong" = "❌ Đã xảy ra lỗi!"
|
||||
"noIpRecord" = "❗ Không có bản ghi IP!"
|
||||
"noInbounds" = "❗ Không tìm thấy inbound!"
|
||||
"unlimited" = "♾ Không giới hạn"
|
||||
"month" = "Tháng"
|
||||
"months" = "Tháng"
|
||||
"day" = "Ngày"
|
||||
"days" = "Ngày"
|
||||
"hours" = "Giờ"
|
||||
"unknown" = "Không rõ"
|
||||
"inbounds" = "Inbounds"
|
||||
"clients" = "Khách hàng"
|
||||
|
||||
[tgbot.commands]
|
||||
"unknown" = "❗ Lệnh không rõ"
|
||||
"pleaseChoose" = "👇 Vui lòng chọn:\r\n"
|
||||
"help" = "🤖 Chào mừng bạn đến với bot này! Bot được thiết kế để cung cấp cho bạn dữ liệu cụ thể từ máy chủ và cho phép bạn thực hiện các thay đổi cần thiết.\r\n\r\n"
|
||||
"start" = "👋 Xin chào <i>{{ .Firstname }}</i>.\r\n"
|
||||
"welcome" = "🤖 Chào mừng đến với bot quản lý của <b>{{ .Hostname }}</b>.\r\n"
|
||||
"status" = "✅ Bot hoạt động bình thường!"
|
||||
"usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!"
|
||||
"getID" = "🆔 ID của bạn: <code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "Tìm kiếm email của khách hàng:\r\n<code>/usage [Email]</code>\r\n \r\nTìm kiếm inbounds (với thống kê của khách hàng):\r\n<code>/inbound [Ghi chú]</code>"
|
||||
"helpClientCommands" = "Để tìm kiếm thống kê, hãy sử dụng lệnh sau:\r\n \r\n<code>/usage [UUID|Mật khẩu]</code>\r\n \r\nSử dụng UUID cho vmess/vless và Mật khẩu cho Trojan."
|
||||
|
||||
[tgbot.messages]
|
||||
"cpuThreshold" = "🔴 Sử dụng CPU {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%"
|
||||
"selectUserFailed" = "❌ Lỗi khi chọn người dùng!"
|
||||
"userSaved" = "✅ Người dùng Telegram đã được lưu."
|
||||
"loginSuccess" = "✅ Đăng nhập thành công vào bảng điều khiển.\r\n"
|
||||
"loginFailed" = "❗️ Đăng nhập vào bảng điều khiển thất bại.\r\n"
|
||||
"report" = "🕰 Báo cáo định kỳ: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Ngày-Giờ: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Tên máy chủ: {{ .Hostname }}\r\n"
|
||||
"version" = "🚀 Phiên bản X-UI: {{ .Version }}\r\n"
|
||||
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
|
||||
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
|
||||
"ip" = "🌐 IP: {{ .IP }}\r\n"
|
||||
"ips" = "🔢 Các IP: \r\n{{ .IPs }}\r\n"
|
||||
"serverUpTime" = "⏳ Thời gian hoạt động của máy chủ: {{ .UpTime }} {{ .Unit }}\r\n"
|
||||
"serverLoad" = "📈 Tải máy chủ: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
||||
"serverMemory" = "📋 Bộ nhớ máy chủ: {{ .Current }}/{{ .Total }}\r\n"
|
||||
"tcpCount" = "🔹 Số lượng kết nối TCP: {{ .Count }}\r\n"
|
||||
"udpCount" = "🔸 Số lượng kết nối UDP: {{ .Count }}\r\n"
|
||||
"traffic" = "🚦 Lưu lượng: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Trạng thái Xray: {{ .State }}\r\n"
|
||||
"username" = "👤 Tên người dùng: {{ .Username }}\r\n"
|
||||
"time" = "⏰ Thời gian: {{ .Time }}\r\n"
|
||||
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
||||
"port" = "🔌 Cổng: {{ .Port }}\r\n"
|
||||
"expire" = "📅 Ngày hết hạn: {{ .Time }}\r\n"
|
||||
"expireIn" = "📅 Hết hạn sau: {{ .Time }}\r\n"
|
||||
"active" = "💡 Hoạt động: ✅ Có\r\n"
|
||||
"inactive" = "💡 Hoạt động: ❌ Không\r\n"
|
||||
"email" = "📧 Email: {{ .Email }}\r\n"
|
||||
"upload" = "🔼 Tải lên: ↑{{ .Upload }}\r\n"
|
||||
"download" = "🔽 Tải xuống: ↓{{ .Download }}\r\n"
|
||||
"total" = "📊 Tổng cộng: ↑↓{{ .UpDown }} / {{ .Total }}\r\n"
|
||||
"TGUser" = "👤 Người dùng Telegram: {{ .TelegramID }}\r\n"
|
||||
"exhaustedMsg" = "🚨 Sự cạn kiệt {{ .Type }}:\r\n"
|
||||
"exhaustedCount" = "🚨 Số lần cạn kiệt {{ .Type }}:\r\n"
|
||||
"disabled" = "🛑 Vô hiệu hóa: {{ .Disabled }}\r\n"
|
||||
"depleteSoon" = "🔜 Sắp cạn kiệt: {{ .Deplete }}\r\n \r\n"
|
||||
"backupTime" = "🗄 Thời gian sao lưu: {{ .Time }}\r\n"
|
||||
"refreshedOn" = "\r\n📋🔄 Đã cập nhật lần cuối vào: {{ .Time }}\r\n \r\n"
|
||||
|
||||
[tgbot.buttons]
|
||||
"closeKeyboard" = "❌ Đóng Bàn Phím"
|
||||
"cancel" = "❌ Hủy"
|
||||
"cancelReset" = "❌ Hủy Đặt Lại"
|
||||
"cancelIpLimit" = "❌ Hủy Giới Hạn IP"
|
||||
"confirmResetTraffic" = "✅ Xác Nhận Đặt Lại Lưu Lượng?"
|
||||
"confirmClearIps" = "✅ Xác Nhận Xóa Các IP?"
|
||||
"confirmRemoveTGUser" = "✅ Xác Nhận Xóa Người Dùng Telegram?"
|
||||
"dbBackup" = "Tải Backup DB"
|
||||
"serverUsage" = "Sử Dụng Máy Chủ"
|
||||
"getInbounds" = "Lấy Inbounds"
|
||||
"depleteSoon" = "Sắp Cạn Kiệt"
|
||||
"clientUsage" = "Lấy Sử Dụng"
|
||||
"commands" = "Lệnh"
|
||||
"refresh" = "🔄 Cập Nhật"
|
||||
"clearIPs" = "❌ Xóa IP"
|
||||
"removeTGUser" = "❌ Xóa Người Dùng Telegram"
|
||||
"selectTGUser" = "👤 Chọn Người Dùng Telegram"
|
||||
"selectOneTGUser" = "👤 Chọn một người dùng telegram:"
|
||||
"resetTraffic" = "📈 Đặt Lại Lưu Lượng"
|
||||
"resetExpire" = "📅 Đặt Lại Ngày Hết Hạn"
|
||||
"ipLog" = "🔢 Log IP"
|
||||
"ipLimit" = "🔢 Giới Hạn IP"
|
||||
"setTGUser" = "👤 Đặt Người Dùng Telegram"
|
||||
"toggle" = "🔘 Bật / Tắt"
|
||||
|
||||
[tgbot.answers]
|
||||
"errorOperation" = "❗ Lỗi Trong Quá Trình Thực Hiện."
|
||||
"getInboundsFailed" = "❌ Không Thể Lấy Được Inbounds"
|
||||
"canceled" = "❌ {{ .Email }} : Thao Tác Đã Bị Hủy."
|
||||
"clientRefreshSuccess" = "✅ {{ .Email }} : Cập Nhật Thành Công Cho Khách Hàng."
|
||||
"IpRefreshSuccess" = "✅ {{ .Email }} : Cập Nhật Thành Công Cho IPs."
|
||||
"TGIdRefreshSuccess" = "✅ {{ .Email }} : Cập Nhật Thành Công Cho Người Dùng Telegram."
|
||||
"resetTrafficSuccess" = "✅ {{ .Email }} : Đặt Lại Lưu Lượng Thành Công."
|
||||
"expireResetSuccess" = "✅ {{ .Email }} : Đặt Lại Ngày Hết Hạn Thành Công."
|
||||
"resetIpSuccess" = "✅ {{ .Email }} : Giới Hạn IP {{ .Count }} Đã Được Lưu Thành Công."
|
||||
"clearIpSuccess" = "✅ {{ .Email }} : IPs Đã Được Xóa Thành Công."
|
||||
"getIpLog" = "✅ {{ .Email }} : Lấy Log IP Thành Công."
|
||||
"getUserInfo" = "✅ {{ .Email }} : Lấy Thông Tin Người Dùng Telegram Thành Công."
|
||||
"removedTGUserSuccess" = "✅ {{ .Email }} : Người Dùng Telegram Đã Được Xóa Thành Công."
|
||||
"enableSuccess" = "✅ {{ .Email }} : Đã Bật Thành Công."
|
||||
"disableSuccess" = "✅ {{ .Email }} : Đã Tắt Thành Công."
|
||||
"askToAddUserId" = "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nID người dùng của bạn: <b>{{ .TgUserID }}</b>"
|
||||
"askToAddUserName" = "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng tên người dùng hoặc ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nTên người dùng của bạn: <b>@{{ .TgUserName }}</b>\r\n\r\nID người dùng của bạn: <b>{{ .TgUserID }}</b>"
|
||||
@@ -127,7 +127,6 @@
|
||||
"network" = "网络"
|
||||
"destinationPort" = "目标端口"
|
||||
"targetAddress" = "目标地址"
|
||||
"disableInsecureEncryption" = "禁用不安全加密"
|
||||
"monitorDesc" = "默认留空即可"
|
||||
"meansNoLimit" = "表示不限制"
|
||||
"totalFlow" = "总流量"
|
||||
@@ -274,6 +273,10 @@
|
||||
"subDomainDesc" = "留空默认监控所有域名和IP"
|
||||
"subUpdates" = "订阅更新间隔"
|
||||
"subUpdatesDesc" = "客户端应用程序更新之间的间隔时间"
|
||||
"subEncrypt" = "加密配置"
|
||||
"subEncryptDesc" = "在订阅中加密返回的配置"
|
||||
"subShowInfo" = "显示使用信息"
|
||||
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
||||
|
||||
[pages.settings.templates]
|
||||
"title" = "模板"
|
||||
|
||||
11
web/web.go
@@ -247,14 +247,11 @@ func (s *Server) startTask() {
|
||||
s.cron.AddJob("@every 10s", job.NewXrayTrafficJob())
|
||||
}()
|
||||
|
||||
// Check the inbound traffic every 30 seconds that the traffic exceeds and expires
|
||||
s.cron.AddJob("@every 30s", job.NewCheckInboundJob())
|
||||
// check client ips from log file every 10 sec
|
||||
s.cron.AddJob("@every 10s", job.NewCheckClientIpJob())
|
||||
|
||||
// check client ips from log file every 20 sec
|
||||
s.cron.AddJob("@every 20s", job.NewCheckClientIpJob())
|
||||
|
||||
// check client ips from log file every 3 day
|
||||
s.cron.AddJob("@every 3d", job.NewClearLogsJob())
|
||||
// check client ips from log file every day
|
||||
s.cron.AddJob("@daily", job.NewClearLogsJob())
|
||||
|
||||
// Make a traffic condition every day, 8:30
|
||||
var entry cron.EntryID
|
||||
|
||||
136
x-ui.sh
@@ -54,6 +54,8 @@ elif [[ "${release}" == "debian" ]]; then
|
||||
if [[ ${os_version} -lt 10 ]]; then
|
||||
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "arch" ]]; then
|
||||
echo "OS is ArchLinux"
|
||||
fi
|
||||
|
||||
|
||||
@@ -508,7 +510,7 @@ update_geo() {
|
||||
rm -f geoip.dat geosite.dat iran.dat
|
||||
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget -N https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
wget -N https://github.com/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
systemctl start x-ui
|
||||
echo -e "${green}Geosite.dat + Geoip.dat + Iran.dat have been updated successfully in bin folder '${binfolder}'!${plain}"
|
||||
before_show_menu
|
||||
@@ -646,6 +648,83 @@ ssl_cert_issue() {
|
||||
fi
|
||||
}
|
||||
|
||||
ssl_cert_issue_CF() {
|
||||
echo -E ""
|
||||
LOGD "******Instructions for use******"
|
||||
LOGI "This Acme script requires the following data:"
|
||||
LOGI "1.Cloudflare Registered e-mail"
|
||||
LOGI "2.Cloudflare Global API Key"
|
||||
LOGI "3.The domain name that has been resolved dns to the current server by Cloudflare"
|
||||
LOGI "4.The script applies for a certificate. The default installation path is /root/cert "
|
||||
confirm "Confirmed?[y/n]" "y"
|
||||
if [ $? -eq 0 ]; then
|
||||
# check for acme.sh first
|
||||
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
||||
echo "acme.sh could not be found. we will install it"
|
||||
install_acme
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "install acme failed, please check logs"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
CF_Domain=""
|
||||
CF_GlobalKey=""
|
||||
CF_AccountEmail=""
|
||||
certPath=/root/cert
|
||||
if [ ! -d "$certPath" ]; then
|
||||
mkdir $certPath
|
||||
else
|
||||
rm -rf $certPath
|
||||
mkdir $certPath
|
||||
fi
|
||||
LOGD "Please set a domain name:"
|
||||
read -p "Input your domain here:" CF_Domain
|
||||
LOGD "Your domain name is set to:${CF_Domain}"
|
||||
LOGD "Please set the API key:"
|
||||
read -p "Input your key here:" CF_GlobalKey
|
||||
LOGD "Your API key is:${CF_GlobalKey}"
|
||||
LOGD "Please set up registered email:"
|
||||
read -p "Input your email here:" CF_AccountEmail
|
||||
LOGD "Your registered email address is:${CF_AccountEmail}"
|
||||
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "Default CA, Lets'Encrypt fail, script exiting..."
|
||||
exit 1
|
||||
fi
|
||||
export CF_Key="${CF_GlobalKey}"
|
||||
export CF_Email=${CF_AccountEmail}
|
||||
~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} --log
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "Certificate issuance failed, script exiting..."
|
||||
exit 1
|
||||
else
|
||||
LOGI "Certificate issued Successfully, Installing..."
|
||||
fi
|
||||
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} --ca-file /root/cert/ca.cer \
|
||||
--cert-file /root/cert/${CF_Domain}.cer --key-file /root/cert/${CF_Domain}.key \
|
||||
--fullchain-file /root/cert/fullchain.cer
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "Certificate installation failed, script exiting..."
|
||||
exit 1
|
||||
else
|
||||
LOGI "Certificate installed Successfully,Turning on automatic updates..."
|
||||
fi
|
||||
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "Auto update setup Failed, script exiting..."
|
||||
ls -lah cert
|
||||
chmod 755 $certPath
|
||||
exit 1
|
||||
else
|
||||
LOGI "The certificate is installed and auto-renewal is turned on, Specific information is as follows"
|
||||
ls -lah cert
|
||||
chmod 755 $certPath
|
||||
fi
|
||||
else
|
||||
show_menu
|
||||
fi
|
||||
}
|
||||
|
||||
warp_cloudflare() {
|
||||
echo -e "${green}\t1.${plain} Install WARP socks5 proxy"
|
||||
echo -e "${green}\t2.${plain} Account Type (free, plus, team)"
|
||||
@@ -713,8 +792,8 @@ enabled=true
|
||||
filter=3x-ipl
|
||||
action=3x-ipl
|
||||
logpath=${iplimit_log_path}
|
||||
maxretry=3
|
||||
findtime=100
|
||||
maxretry=4
|
||||
findtime=60
|
||||
bantime=${bantime}m
|
||||
EOF
|
||||
|
||||
@@ -772,7 +851,8 @@ iplimit_main() {
|
||||
echo -e "${green}\t2.${plain} Change Ban Duration"
|
||||
echo -e "${green}\t3.${plain} Unban Everyone"
|
||||
echo -e "${green}\t4.${plain} Check Logs"
|
||||
echo -e "${green}\t5.${plain} Uninstall IP Limit"
|
||||
echo -e "${green}\t5.${plain} fail2ban status"
|
||||
echo -e "${green}\t6.${plain} Uninstall IP Limit"
|
||||
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||||
read -p "Choose an option: " choice
|
||||
case "$choice" in
|
||||
@@ -788,7 +868,7 @@ iplimit_main() {
|
||||
2)
|
||||
read -rp "Please enter new Ban Duration in Minutes [default 5]: " NUM
|
||||
if [[ $NUM =~ ^[0-9]+$ ]]; then
|
||||
create_iplimit_jail ${NUM}
|
||||
create_iplimit_jails ${NUM}
|
||||
systemctl restart fail2ban
|
||||
else
|
||||
echo -e "${red}${NUM} is not a number! Please, try again.${plain}"
|
||||
@@ -816,6 +896,10 @@ iplimit_main() {
|
||||
iplimit_main
|
||||
fi ;;
|
||||
5)
|
||||
service fail2ban status
|
||||
;;
|
||||
|
||||
6)
|
||||
remove_iplimit ;;
|
||||
*) echo "Invalid choice" ;;
|
||||
esac
|
||||
@@ -886,23 +970,19 @@ remove_iplimit(){
|
||||
echo -e "${green}IP Limit removed successfully!${plain}\n"
|
||||
before_show_menu ;;
|
||||
2)
|
||||
rm -f /etc/fail2ban/filter.d/3x-ipl.conf
|
||||
rm -f /etc/fail2ban/action.d/3x-ipl.conf
|
||||
rm -f /etc/fail2ban/jail.d/3x-ipl.conf
|
||||
rm -rf /etc/fail2ban
|
||||
systemctl stop fail2ban
|
||||
systemctl disable fail2ban
|
||||
case "${release}" in
|
||||
ubuntu|debian)
|
||||
apt remove fail2ban -y ;;
|
||||
apt-get purge fail2ban -y;;
|
||||
centos)
|
||||
yum -y remove fail2ban ;;
|
||||
yum remove fail2ban -y;;
|
||||
fedora)
|
||||
dnf -y remove fail2ban ;;
|
||||
dnf remove fail2ban -y;;
|
||||
*)
|
||||
echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n"
|
||||
exit 1 ;;
|
||||
esac
|
||||
rm -rf /etc/fail2ban
|
||||
echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n"
|
||||
before_show_menu ;;
|
||||
0)
|
||||
@@ -955,16 +1035,17 @@ show_menu() {
|
||||
${green}14.${plain} Disable x-ui On System Startup
|
||||
————————————————
|
||||
${green}15.${plain} SSL Certificate Management
|
||||
${green}16.${plain} IP Limit Management
|
||||
${green}17.${plain} WARP Management
|
||||
${green}16.${plain} Cloudflare SSL Certificate
|
||||
${green}17.${plain} IP Limit Management
|
||||
${green}18.${plain} WARP Management
|
||||
————————————————
|
||||
${green}18.${plain} Enable BBR
|
||||
${green}19.${plain} Update Geo Files
|
||||
${green}20.${plain} Active Firewall and open ports
|
||||
${green}21.${plain} Speedtest by Ookla
|
||||
${green}19.${plain} Enable BBR
|
||||
${green}20.${plain} Update Geo Files
|
||||
${green}21.${plain} Active Firewall and open ports
|
||||
${green}22.${plain} Speedtest by Ookla
|
||||
"
|
||||
show_status
|
||||
echo && read -p "Please enter your selection [0-21]: " num
|
||||
echo && read -p "Please enter your selection [0-22]: " num
|
||||
|
||||
case "${num}" in
|
||||
0)
|
||||
@@ -1016,25 +1097,28 @@ show_menu() {
|
||||
ssl_cert_issue_main
|
||||
;;
|
||||
16)
|
||||
iplimit_main
|
||||
ssl_cert_issue_CF
|
||||
;;
|
||||
17)
|
||||
warp_cloudflare
|
||||
iplimit_main
|
||||
;;
|
||||
18)
|
||||
enable_bbr
|
||||
warp_cloudflare
|
||||
;;
|
||||
19)
|
||||
update_geo
|
||||
enable_bbr
|
||||
;;
|
||||
20)
|
||||
open_ports
|
||||
update_geo
|
||||
;;
|
||||
21)
|
||||
open_ports
|
||||
;;
|
||||
22)
|
||||
run_speedtest
|
||||
;;
|
||||
*)
|
||||
LOGE "Please enter the correct number [0-21]"
|
||||
LOGE "Please enter the correct number [0-22]"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
31
xray/api.go
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
"github.com/xtls/xray-core/proxy/shadowsocks"
|
||||
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
|
||||
"github.com/xtls/xray-core/proxy/trojan"
|
||||
"github.com/xtls/xray-core/proxy/vless"
|
||||
"github.com/xtls/xray-core/proxy/vmess"
|
||||
@@ -62,10 +63,12 @@ func (x *XrayAPI) AddInbound(inbound []byte) error {
|
||||
err := json.Unmarshal(inbound, conf)
|
||||
if err != nil {
|
||||
logger.Debug("Failed to unmarshal inbound:", err)
|
||||
return err
|
||||
}
|
||||
config, err := conf.Build()
|
||||
if err != nil {
|
||||
logger.Debug("Failed to build inbound Detur:", err)
|
||||
return err
|
||||
}
|
||||
inboundConfig := command.AddInboundRequest{Inbound: config}
|
||||
|
||||
@@ -99,9 +102,31 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in
|
||||
Password: user["password"].(string),
|
||||
})
|
||||
case "shadowsocks":
|
||||
account = serial.ToTypedMessage(&shadowsocks.Account{
|
||||
Password: user["password"].(string),
|
||||
})
|
||||
var ssCipherType shadowsocks.CipherType
|
||||
switch user["cipher"].(string) {
|
||||
case "aes-128-gcm":
|
||||
ssCipherType = shadowsocks.CipherType_AES_128_GCM
|
||||
case "aes-256-gcm":
|
||||
ssCipherType = shadowsocks.CipherType_AES_256_GCM
|
||||
case "chacha20-poly1305", "chacha20-ietf-poly1305":
|
||||
ssCipherType = shadowsocks.CipherType_CHACHA20_POLY1305
|
||||
case "xchacha20-poly1305", "xchacha20-ietf-poly1305":
|
||||
ssCipherType = shadowsocks.CipherType_XCHACHA20_POLY1305
|
||||
default:
|
||||
ssCipherType = shadowsocks.CipherType_NONE
|
||||
}
|
||||
|
||||
if ssCipherType != shadowsocks.CipherType_NONE {
|
||||
account = serial.ToTypedMessage(&shadowsocks.Account{
|
||||
Password: user["password"].(string),
|
||||
CipherType: ssCipherType,
|
||||
})
|
||||
} else {
|
||||
account = serial.ToTypedMessage(&shadowsocks_2022.User{
|
||||
Key: user["password"].(string),
|
||||
Email: user["email"].(string),
|
||||
})
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
@@ -44,10 +46,6 @@ func GetIranPath() string {
|
||||
return config.GetBinFolderPath() + "/iran.dat"
|
||||
}
|
||||
|
||||
func GetBlockedIPsPath() string {
|
||||
return config.GetBinFolderPath() + "/BlockedIps"
|
||||
}
|
||||
|
||||
func GetIPLimitLogPath() string {
|
||||
return config.GetLogFolder() + "/3xipl.log"
|
||||
}
|
||||
@@ -88,7 +86,6 @@ func stopProcess(p *Process) {
|
||||
p.Stop()
|
||||
}
|
||||
|
||||
|
||||
type Process struct {
|
||||
*process
|
||||
}
|
||||
@@ -105,16 +102,18 @@ type process struct {
|
||||
version string
|
||||
apiPort int
|
||||
|
||||
config *Config
|
||||
lines *queue.Queue
|
||||
exitErr error
|
||||
config *Config
|
||||
lines *queue.Queue
|
||||
exitErr error
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func newProcess(config *Config) *process {
|
||||
return &process{
|
||||
version: "Unknown",
|
||||
config: config,
|
||||
lines: queue.New(100),
|
||||
version: "Unknown",
|
||||
config: config,
|
||||
lines: queue.New(100),
|
||||
startTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +157,10 @@ func (p *Process) GetConfig() *Config {
|
||||
return p.config
|
||||
}
|
||||
|
||||
func (p *Process) GetUptime() uint64 {
|
||||
return uint64(time.Since(p.startTime).Seconds())
|
||||
}
|
||||
|
||||
func (p *process) refreshAPIPort() {
|
||||
for _, inbound := range p.config.InboundConfigs {
|
||||
if inbound.Tag == "api" {
|
||||
@@ -203,7 +206,7 @@ func (p *process) Start() (err error) {
|
||||
return common.NewErrorf("Failed to write configuration file: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(GetBinaryPath(), "-c", configPath, "-restrictedIPsPath", GetBlockedIPsPath())
|
||||
cmd := exec.Command(GetBinaryPath(), "-c", configPath)
|
||||
p.cmd = cmd
|
||||
|
||||
stdReader, err := cmd.StdoutPipe()
|
||||
|
||||