Compare commits
107 Commits
v2.4.7
...
Group_Mana
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04c6b2722b | ||
|
|
94d651fc93 | ||
|
|
aae0cb37b7 | ||
|
|
b922d986d6 | ||
|
|
8a7cffd63f | ||
|
|
c8e8c97afc | ||
|
|
46ba4c4518 | ||
|
|
a787ab497c | ||
|
|
3be204f272 | ||
|
|
de13729a97 | ||
|
|
468eb8b908 | ||
|
|
e95a748e77 | ||
|
|
34e2d961f5 | ||
|
|
b4a1d81444 | ||
|
|
46ef506aa6 | ||
|
|
51220917c4 | ||
|
|
b34956647b | ||
|
|
5c4e2dfd39 | ||
|
|
dd4c2adb37 | ||
|
|
2dec7f48f5 | ||
|
|
04cf250a54 | ||
|
|
ac9ab828b5 | ||
|
|
4dd40f6f19 | ||
|
|
7911eeb69f | ||
|
|
6e9180a665 | ||
|
|
66fe84181b | ||
|
|
7b7eb98acb | ||
|
|
7eb5afdd8d | ||
|
|
522ccda71c | ||
|
|
f780efb430 | ||
|
|
783f1a073e | ||
|
|
a4c38ec8ae | ||
|
|
0c47771671 | ||
|
|
67920a1962 | ||
|
|
49d3957c07 | ||
|
|
ee946ceab2 | ||
|
|
b650064177 | ||
|
|
9fb9d7201e | ||
|
|
4a3b9b913d | ||
|
|
da674d44cf | ||
|
|
cc3252531b | ||
|
|
284731deeb | ||
|
|
9bc5c1d070 | ||
|
|
26a7700557 | ||
|
|
b6a919218a | ||
|
|
6cc07254e0 | ||
|
|
4ad5a5aba4 | ||
|
|
7ab8164de4 | ||
|
|
195effd177 | ||
|
|
747ad3b9c8 | ||
|
|
cf879f9527 | ||
|
|
04c658f1a0 | ||
|
|
2ab1a174db | ||
|
|
dfca2af997 | ||
|
|
0a7d15b48c | ||
|
|
518bc72f90 | ||
|
|
3c65209ce9 | ||
|
|
174535b05d | ||
|
|
fff54fe7f3 | ||
|
|
0859d230b0 | ||
|
|
02998c5467 | ||
|
|
3f38c42852 | ||
|
|
49295661fd | ||
|
|
e4301f8d93 | ||
|
|
c6586f8df2 | ||
|
|
8e81008cdc | ||
|
|
e33ad809d6 | ||
|
|
d804043a18 | ||
|
|
0fb0df7056 | ||
|
|
73e90e0eaa | ||
|
|
44ef1ac9a6 | ||
|
|
aeac7f2c8b | ||
|
|
39ef172b87 | ||
|
|
0df85cc3d9 | ||
|
|
f0f4f082ae | ||
|
|
b29bd993d4 | ||
|
|
127eaf69b6 | ||
|
|
36b0289bc6 | ||
|
|
0abd0be725 | ||
|
|
918a2b1533 | ||
|
|
88a17cd227 | ||
|
|
b60387accb | ||
|
|
049177024b | ||
|
|
9c63638af1 | ||
|
|
67dfe664a6 | ||
|
|
4efcdb3e01 | ||
|
|
ddc2cfacb9 | ||
|
|
337729529a | ||
|
|
4c4cc362b3 | ||
|
|
4e0aca16c2 | ||
|
|
749a426a71 | ||
|
|
b859327b8a | ||
|
|
3e8fc59213 | ||
|
|
462e02140d | ||
|
|
6b41df2d89 | ||
|
|
eb5ed5c0dd | ||
|
|
e0bbacf013 | ||
|
|
34af7f8bfa | ||
|
|
b569c21fec | ||
|
|
c4a5c059e3 | ||
|
|
c21ed90da0 | ||
|
|
9b58277945 | ||
|
|
5a4a42aeb8 | ||
|
|
9476472bf6 | ||
|
|
2ce9c3cc81 | ||
|
|
0a8bfc2725 | ||
|
|
c5bbb6b632 |
10
.github/dependabot.yml
vendored
@@ -1,10 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
4
.github/workflows/docker.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -31,6 +31,8 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
|
||||
30
.github/workflows/release.yml
vendored
@@ -72,7 +72,7 @@ jobs:
|
||||
export GOARCH=s390x
|
||||
export CC=s390x-linux-gnu-gcc
|
||||
fi
|
||||
go build -o xui-release -v main.go
|
||||
go build -ldflags "-w -s" -o xui-release -v main.go
|
||||
|
||||
mkdir x-ui
|
||||
cp xui-release x-ui/
|
||||
@@ -83,43 +83,43 @@ jobs:
|
||||
cd x-ui/bin
|
||||
|
||||
# Download dependencies
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v24.11.11/"
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.1.30/"
|
||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||
wget ${Xray_URL}Xray-linux-64.zip
|
||||
wget -q ${Xray_URL}Xray-linux-64.zip
|
||||
unzip Xray-linux-64.zip
|
||||
rm -f Xray-linux-64.zip
|
||||
elif [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||
wget ${Xray_URL}Xray-linux-arm64-v8a.zip
|
||||
wget -q ${Xray_URL}Xray-linux-arm64-v8a.zip
|
||||
unzip Xray-linux-arm64-v8a.zip
|
||||
rm -f Xray-linux-arm64-v8a.zip
|
||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||
wget ${Xray_URL}Xray-linux-arm32-v7a.zip
|
||||
wget -q ${Xray_URL}Xray-linux-arm32-v7a.zip
|
||||
unzip Xray-linux-arm32-v7a.zip
|
||||
rm -f Xray-linux-arm32-v7a.zip
|
||||
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
||||
wget ${Xray_URL}Xray-linux-arm32-v6.zip
|
||||
wget -q ${Xray_URL}Xray-linux-arm32-v6.zip
|
||||
unzip Xray-linux-arm32-v6.zip
|
||||
rm -f Xray-linux-arm32-v6.zip
|
||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||
wget ${Xray_URL}Xray-linux-32.zip
|
||||
wget -q ${Xray_URL}Xray-linux-32.zip
|
||||
unzip Xray-linux-32.zip
|
||||
rm -f Xray-linux-32.zip
|
||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
||||
wget ${Xray_URL}Xray-linux-arm32-v5.zip
|
||||
wget -q ${Xray_URL}Xray-linux-arm32-v5.zip
|
||||
unzip Xray-linux-arm32-v5.zip
|
||||
rm -f Xray-linux-arm32-v5.zip
|
||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
||||
wget ${Xray_URL}Xray-linux-s390x.zip
|
||||
wget -q ${Xray_URL}Xray-linux-s390x.zip
|
||||
unzip Xray-linux-s390x.zip
|
||||
rm -f Xray-linux-s390x.zip
|
||||
fi
|
||||
rm -f geoip.dat geosite.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 -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
|
||||
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
mv xray xray-linux-${{ matrix.platform }}
|
||||
cd ../..
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Start fail2ban
|
||||
fail2ban-client -x start
|
||||
[ $X_UI_ENABLE_FAIL2BAN == "true" ] && fail2ban-client -x start
|
||||
|
||||
# Run x-ui
|
||||
exec /app/x-ui
|
||||
|
||||
@@ -27,14 +27,14 @@ case $1 in
|
||||
esac
|
||||
mkdir -p build/bin
|
||||
cd build/bin
|
||||
wget "https://github.com/XTLS/Xray-core/releases/download/v24.11.11/Xray-linux-${ARCH}.zip"
|
||||
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.1.30/Xray-linux-${ARCH}.zip"
|
||||
unzip "Xray-linux-${ARCH}.zip"
|
||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||
mv xray "xray-linux-${FNAME}"
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
|
||||
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
cd ../../
|
||||
@@ -15,7 +15,7 @@ COPY . .
|
||||
|
||||
ENV CGO_ENABLED=1
|
||||
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
||||
RUN go build -o build/x-ui main.go
|
||||
RUN go build -ldflags "-w -s" -o build/x-ui main.go
|
||||
RUN ./DockerInit.sh "$TARGETARCH"
|
||||
|
||||
# ========================================================
|
||||
@@ -48,6 +48,7 @@ RUN chmod +x \
|
||||
/app/x-ui \
|
||||
/usr/bin/x-ui
|
||||
|
||||
ENV X_UI_ENABLE_FAIL2BAN="true"
|
||||
VOLUME [ "/etc/x-ui" ]
|
||||
CMD [ "./x-ui" ]
|
||||
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
[English](/README.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||
|
||||
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
||||
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
**Un Panel Web Avanzado • Construido sobre Xray Core**
|
||||
|
||||
@@ -277,14 +282,18 @@ Nuestra plataforma ofrece compatibilidad con una amplia gama de arquitecturas y
|
||||
|
||||
## Idiomas
|
||||
|
||||
- Inglés
|
||||
- Farsi
|
||||
- Chino
|
||||
- Ruso
|
||||
- Vietnamita
|
||||
- Español
|
||||
- Indonesio
|
||||
- Ucraniano
|
||||
- English (inglés)
|
||||
- Persian (persa)
|
||||
- Traditional Chinese (chino tradicional)
|
||||
- Simplified Chinese (chino simplificado)
|
||||
- Japanese (japonés)
|
||||
- Russian (ruso)
|
||||
- Vietnamese (vietnamita)
|
||||
- Spanish (español)
|
||||
- Indonesian (indonesio)
|
||||
- Ukrainian (ucraniano)
|
||||
- Turkish (turco)
|
||||
- Português (Brazil) (portugués (Brasil))
|
||||
|
||||
|
||||
## Características
|
||||
@@ -535,13 +544,34 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||
|
||||
## Vista previa
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/01-overview-dark.png">
|
||||
<img alt="3x-ui" src="./media/01-overview-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/02-inbounds-dark.png">
|
||||
<img alt="3x-ui" src="./media/02-inbounds-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/03-add-inbound-dark.png">
|
||||
<img alt="3x-ui" src="./media/03-add-inbound-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/04-add-client-dark.png">
|
||||
<img alt="3x-ui" src="./media/04-add-client-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/05-settings-dark.png">
|
||||
<img alt="3x-ui" src="./media/05-settings-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/06-configs-dark.png">
|
||||
<img alt="3x-ui" src="./media/06-configs-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/07-bot-dark.png">
|
||||
<img alt="3x-ui" src="./media/07-bot-light.png">
|
||||
</picture>
|
||||
|
||||
## Un agradecimiento especial a
|
||||
|
||||
@@ -550,8 +580,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||
## Reconocimientos
|
||||
|
||||
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Licencia: **GPL-3.0**): _Reglas de enrutamiento mejoradas de v2ray/xray y v2ray/xray-clients con dominios iraníes integrados y un enfoque en seguridad y bloqueo de anuncios._
|
||||
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _Un dominio alojado en Vietnam y una lista de bloqueo con la máxima eficiencia para vietnamitas._
|
||||
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _Este repositorio contiene reglas de enrutamiento de V2Ray actualizadas automáticamente basadas en datos de dominios y direcciones bloqueados en Rusia._
|
||||
|
||||
## Estrellas a lo largo del tiempo
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
527
README.fa_IR.md
Normal file
@@ -0,0 +1,527 @@
|
||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
||||
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
**یک پنل وب پیشرفته • ساخته شده بر پایه Xray Core**
|
||||
|
||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
> **سلب مسئولیت:** این پروژه صرفاً برای اهداف آموزشی و تحقیقاتی است. استفاده از آن برای مقاصد غیرقانونی یا در محیطهای عملیاتی ممنوع است.
|
||||
|
||||
**اگر این پروژه برای شما مفید بوده، میتوانید با دادن یک**:star2: از آن حمایت کنید.
|
||||
|
||||
<p align="left">
|
||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
||||
<img src="./media/buymeacoffe.png" alt="Image">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## نصب و ارتقا
|
||||
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
## نصب نسخههای قدیمی (توصیه نمیشود)
|
||||
|
||||
برای نصب نسخه خاصی از دستور زیر استفاده کنید. مثال برای نسخه `v1.7.9`:
|
||||
|
||||
```
|
||||
VERSION=v1.7.9 && bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/$VERSION/install.sh") $VERSION
|
||||
```
|
||||
|
||||
## گواهی SSL
|
||||
|
||||
<details>
|
||||
<summary>جزئیات گواهی SSL</summary>
|
||||
|
||||
### ACME
|
||||
|
||||
برای مدیریت گواهیهای SSL با استفاده از ACME:
|
||||
|
||||
1. اطمینان حاصل کنید دامنه شما به درستی به سرور متصل است.
|
||||
2. دستور `x-ui` را در ترمینال اجرا کرده و گزینه `مدیریت گواهی SSL` را انتخاب کنید.
|
||||
3. گزینههای زیر نمایش داده میشوند:
|
||||
|
||||
- **دریافت SSL:** دریافت گواهی SSL
|
||||
- **لغو:** لغو گواهیهای موجود
|
||||
- **تمدید اجباری:** تمدید اجباری گواهیها
|
||||
- **نمایش دامنههای موجود:** نمایش تمام دامنههای دارای گواهی
|
||||
- **تنظیم مسیر گواهی برای پنل:** تنظیم مسیر گواهی برای دامنه شما
|
||||
|
||||
### Certbot
|
||||
|
||||
نصب و استفاده از Certbot:
|
||||
|
||||
```sh
|
||||
apt-get install certbot -y
|
||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||
certbot renew --dry-run
|
||||
```
|
||||
|
||||
### Cloudflare
|
||||
|
||||
اسکریپت داخلی برای دریافت گواهی SSL از Cloudflare. نیازمند:
|
||||
|
||||
- ایمیل ثبتشده در Cloudflare
|
||||
- کلید API جهانی Cloudflare
|
||||
- دامنه باید از طریق Cloudflare به سرور متصل باشد
|
||||
|
||||
**دریافت کلید API جهانی Cloudflare:**
|
||||
|
||||
1. دستور `x-ui` را اجرا و گزینه `گواهی SSL کلادفلر` را انتخاب کنید.
|
||||
2. به لینک [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens) مراجعه کنید.
|
||||
3. روی "View Global API Key" کلیک کنید:
|
||||

|
||||
4. پس از احراز هویت، کلید API نمایش داده میشود:
|
||||

|
||||
|
||||
در هنگام استفاده، نام دامنه، ایمیل و کلید API را وارد کنید:
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
## نصب دستی و ارتقا
|
||||
|
||||
<details>
|
||||
<summary>جزئیات نصب دستی</summary>
|
||||
|
||||
#### استفاده
|
||||
|
||||
1. دریافت آخرین نسخه از سرور:
|
||||
|
||||
```sh
|
||||
ARCH=$(uname -m)
|
||||
case "${ARCH}" in
|
||||
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
||||
i*86 | x86) XUI_ARCH="386" ;;
|
||||
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
||||
armv7* | armv7) XUI_ARCH="armv7" ;;
|
||||
armv6* | armv6) XUI_ARCH="armv6" ;;
|
||||
armv5* | armv5) XUI_ARCH="armv5" ;;
|
||||
s390x) echo 's390x' ;;
|
||||
*) XUI_ARCH="amd64" ;;
|
||||
esac
|
||||
|
||||
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
|
||||
```
|
||||
|
||||
2. نصب یا ارتقا:
|
||||
|
||||
```sh
|
||||
ARCH=$(uname -m)
|
||||
case "${ARCH}" in
|
||||
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
||||
i*86 | x86) XUI_ARCH="386" ;;
|
||||
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
||||
armv7* | armv7) XUI_ARCH="armv7" ;;
|
||||
armv6* | armv6) XUI_ARCH="armv6" ;;
|
||||
armv5* | armv5) XUI_ARCH="armv5" ;;
|
||||
s390x) echo 's390x' ;;
|
||||
*) XUI_ARCH="amd64" ;;
|
||||
esac
|
||||
|
||||
cd /root/
|
||||
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
||||
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
||||
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
|
||||
cp x-ui/x-ui.sh /usr/bin/x-ui
|
||||
cp -f x-ui/x-ui.service /etc/systemd/system/
|
||||
mv x-ui/ /usr/local/
|
||||
systemctl daemon-reload
|
||||
systemctl enable x-ui
|
||||
systemctl restart x-ui
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## نصب با Docker
|
||||
|
||||
<details>
|
||||
<summary>جزئیات Docker</summary>
|
||||
|
||||
#### استفاده
|
||||
|
||||
1. **نصب Docker:**
|
||||
|
||||
```sh
|
||||
bash <(curl -sSL https://get.docker.com)
|
||||
```
|
||||
|
||||
2. **کلون پروژه:**
|
||||
|
||||
```sh
|
||||
git clone https://github.com/MHSanaei/3x-ui.git
|
||||
cd 3x-ui
|
||||
```
|
||||
|
||||
3. **راهاندازی سرویس:**
|
||||
|
||||
```sh
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
یا
|
||||
|
||||
```sh
|
||||
docker run -itd \
|
||||
-e XRAY_VMESS_AEAD_FORCED=false \
|
||||
-v $PWD/db/:/etc/x-ui/ \
|
||||
-v $PWD/cert/:/root/cert/ \
|
||||
--network=host \
|
||||
--restart=unless-stopped \
|
||||
--name 3x-ui \
|
||||
ghcr.io/mhsanaei/3x-ui:latest
|
||||
```
|
||||
|
||||
4. **بهروزرسانی:**
|
||||
|
||||
```sh
|
||||
cd 3x-ui
|
||||
docker compose down
|
||||
docker compose pull 3x-ui
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
5. **حذف:**
|
||||
|
||||
```sh
|
||||
docker stop 3x-ui
|
||||
docker rm 3x-ui
|
||||
cd --
|
||||
rm -r 3x-ui
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## تنظیمات Nginx
|
||||
<details>
|
||||
<summary>پیکربندی Reverse Proxy</summary>
|
||||
|
||||
#### Nginx Reverse Proxy
|
||||
```nginx
|
||||
location / {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Range $http_range;
|
||||
proxy_set_header If-Range $http_if_range;
|
||||
proxy_redirect off;
|
||||
proxy_pass http://127.0.0.1:2053;
|
||||
}
|
||||
```
|
||||
|
||||
#### مسیر فرعی در Nginx
|
||||
- اطمینان حاصل کنید "URI Path" در تنظیمات پنل یکسان باشد.
|
||||
- `url` در تنظیمات پنل باید با `/` پایان یابد.
|
||||
|
||||
```nginx
|
||||
location /sub {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Range $http_range;
|
||||
proxy_set_header If-Range $http_if_range;
|
||||
proxy_redirect off;
|
||||
proxy_pass http://127.0.0.1:2053;
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
## سیستمعاملهای توصیه شده
|
||||
|
||||
- Ubuntu 20.04+
|
||||
- Debian 11+
|
||||
- CentOS 8+
|
||||
- OpenEuler 22.03+
|
||||
- Fedora 36+
|
||||
- Arch Linux
|
||||
- Parch Linux
|
||||
- Manjaro
|
||||
- Armbian
|
||||
- AlmaLinux 8.0+
|
||||
- Rocky Linux 8+
|
||||
- Oracle Linux 8+
|
||||
- OpenSUSE Tubleweed
|
||||
- Amazon Linux 2023
|
||||
- Windows x64
|
||||
|
||||
## معماریها و دستگاههای پشتیبانی شده
|
||||
|
||||
<details>
|
||||
<summary>جزئیات معماریها و دستگاهها</summary>
|
||||
|
||||
- **amd64**: معماری استاندارد برای کامپیوترهای شخصی و سرورها
|
||||
- **x86 / i386**: سیستمهای دسکتاپ و لپتاپ
|
||||
- **armv8 / arm64 / aarch64**: دستگاههای موبایل و embedded مانند Raspberry Pi 4
|
||||
- **armv7 / arm / arm32**: دستگاههای قدیمی مانند Orange Pi Zero
|
||||
- **armv6 / arm / arm32**: دستگاههای بسیار قدیمی مانند Raspberry Pi 1
|
||||
- **armv5 / arm / arm32**: سیستمهای embedded قدیمی
|
||||
- **s390x**: کامپیوترهای IBM mainframe
|
||||
</details>
|
||||
|
||||
## زبانهای پشتیبانی شده
|
||||
|
||||
- انگلیسی
|
||||
- فارسی
|
||||
- چینی سنتی
|
||||
- چینی سادهشده
|
||||
- ژاپنی
|
||||
- روسی
|
||||
- ویتنامی
|
||||
- اسپانیایی
|
||||
- اندونزیایی
|
||||
- اوکراینی
|
||||
- ترکی
|
||||
- پرتغالی (برزیل)
|
||||
|
||||
## ویژگیها
|
||||
|
||||
- مانیتورینگ وضعیت سیستم
|
||||
- جستجو در بین inboundها و کلاینتها
|
||||
- تم تاریک/روشن
|
||||
- پشتیبانی از چند کاربر و پروتکل
|
||||
- پروتکلهای VMESS، VLESS، Trojan، Shadowsocks، Dokodemo-door، Socks، HTTP، WireGuard
|
||||
- پشتیبانی از XTLS شامل RPRX-Direct، Vision، REALITY
|
||||
- آمار ترافیک، محدودیت ترافیک، محدودیت زمانی
|
||||
- تنظیمات سفارشی Xray
|
||||
- پشتیبانی از HTTPS برای پنل
|
||||
- دریافت خودکار گواهی SSL
|
||||
- مسیرهای API اصلاح شده
|
||||
- پشتیبانی از تغییر تنظیمات از طریق پنل
|
||||
- امکان export/import دیتابیس
|
||||
|
||||
## تنظیمات پیشفرض پنل
|
||||
|
||||
<details>
|
||||
<summary>جزئیات تنظیمات پیشفرض</summary>
|
||||
|
||||
### نام کاربری، رمز عبور، پورت و مسیر وب
|
||||
|
||||
در صورت عدم تغییر، این موارد به صورت تصادفی ایجاد میشوند (به جز Docker).
|
||||
|
||||
**تنظیمات پیشفرض Docker:**
|
||||
- **نام کاربری:** admin
|
||||
- **رمز عبور:** admin
|
||||
- **پورت:** 2053
|
||||
|
||||
### مدیریت دیتابیس:
|
||||
|
||||
امکان Backup و Restore دیتابیس از طریق پنل.
|
||||
|
||||
- **مسیر دیتابیس:**
|
||||
- `/etc/x-ui/x-ui.db`
|
||||
|
||||
### مسیر پایه وب
|
||||
|
||||
1. **بازنشانی مسیر:**
|
||||
- اجرای دستور `x-ui`
|
||||
- انتخاب گزینه `Reset Web Base Path`
|
||||
|
||||
2. **ساخت یا تنظیم مسیر:**
|
||||
- مسیر به صورت تصادفی ساخته شده یا قابل تنظیم است
|
||||
|
||||
3. **مشاهده تنظیمات فعلی:**
|
||||
- استفاده از دستور `x-ui settings` یا `View Current Settings` در `x-ui`
|
||||
|
||||
**توصیه امنیتی:**
|
||||
- استفاده از مسیرهای طولانی و تصادفی برای افزایش امنیت
|
||||
|
||||
**مثال:**
|
||||
- `http://ip:port/*webbasepath*/panel`
|
||||
- `http://domain:port/*webbasepath*/panel`
|
||||
|
||||
</details>
|
||||
|
||||
## پیکربندی WARP
|
||||
|
||||
<details>
|
||||
<summary>جزئیات WARP</summary>
|
||||
|
||||
#### استفاده
|
||||
|
||||
**برای نسخههای `v2.1.0` و جدیدتر:**
|
||||
|
||||
WARP به صورت داخلی پشتیبانی میشود. تنها نیاز به فعالسازی در پنل است.
|
||||
|
||||
</details>
|
||||
|
||||
## محدودیت IP
|
||||
|
||||
<details>
|
||||
<summary>جزئیات محدودیت IP</summary>
|
||||
|
||||
#### استفاده
|
||||
|
||||
**توجه:** محدودیت IP در صورت استفاده از IP Tunnel کار نمیکند.
|
||||
|
||||
- **تا نسخه `v1.6.1`:**
|
||||
- محدودیت IP به صورت داخلی در پنل وجود دارد
|
||||
|
||||
**برای نسخههای `v1.7.0` و جدیدتر:**
|
||||
|
||||
برای فعالسازی نیاز به نصب `fail2ban` است:
|
||||
|
||||
1. اجرای دستور `x-ui` و انتخاب `مدیریت محدودیت IP`
|
||||
2. گزینههای موجود:
|
||||
|
||||
- **تغییر مدت زمان Ban**
|
||||
- **حذف تمام Banها**
|
||||
- **مشاهده لاگها**
|
||||
- **وضعیت Fail2ban**
|
||||
- **راهاندازی مجدد Fail2ban**
|
||||
- **حذف Fail2ban**
|
||||
|
||||
3. تنظیم مسیر `Access log` در پنل به `./access.log` و ذخیره و راهاندازی مجدد Xray
|
||||
|
||||
- **قبل از نسخه `v2.1.3`:**
|
||||
- تنظیم دستی `access.log` در تنظیمات Xray:
|
||||
|
||||
```sh
|
||||
"log": {
|
||||
"access": "./access.log",
|
||||
"dnsLog": false,
|
||||
"loglevel": "warning"
|
||||
},
|
||||
```
|
||||
|
||||
- **از نسخه `v2.1.3`:**
|
||||
- امکان تنظیم `access.log` از طریق پنل
|
||||
|
||||
</details>
|
||||
|
||||
## ربات تلگرام
|
||||
|
||||
<details>
|
||||
<summary>جزئیات ربات تلگرام</summary>
|
||||
|
||||
#### استفاده
|
||||
|
||||
ربات تلگرام برای اطلاعرسانی ترافیک، ورود به پنل، Backup دیتابیس و ... استفاده میشود. نیازمند تنظیم:
|
||||
|
||||
- توکن تلگرام
|
||||
- Chat ID ادمینها
|
||||
- زمان اطلاعرسانی (Cron syntax)
|
||||
- اطلاعرسانی انقضا
|
||||
- اطلاعرسانی ترافیک
|
||||
- Backup دیتابیس
|
||||
- اطلاعرسانی مصرف CPU
|
||||
|
||||
**سینتکس نمونه:**
|
||||
|
||||
- `30 \* \* \* \* \*` - اطلاع در ثانیه 30 هر دقیقه
|
||||
- `@hourly` - هر ساعت
|
||||
- `@daily` - هر روز
|
||||
|
||||
### ویژگیهای ربات
|
||||
|
||||
- گزارش دورهای
|
||||
- اطلاع ورود به پنل
|
||||
- اطلاع مصرف CPU
|
||||
- اطلاع پیشاز موعد انقضا و ترافیک
|
||||
- گزارش ترافیک کلاینتها
|
||||
- منوی مبتنی بر دستور
|
||||
- جستجوی کلاینت بر اساس ایمیل
|
||||
- بررسی inboundها
|
||||
- بررسی وضعیت سرور
|
||||
- دریافت Backup
|
||||
- چندزبانه
|
||||
|
||||
### راهاندازی ربات
|
||||
|
||||
- شروع [Botfather](https://t.me/BotFather) در تلگرام:
|
||||

|
||||
|
||||
- ساخت ربات جدید با دستور /newbot:
|
||||

|
||||
|
||||
- شروع ربات ساخته شده:
|
||||

|
||||
|
||||
- تنظیمات پنل:
|
||||

|
||||
|
||||
وارد کردن توکن و Chat ID (دریافت از [این ربات](https://t.me/useridinfobot)):
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
## مسیرهای API
|
||||
|
||||
<details>
|
||||
<summary>جزئیات API</summary>
|
||||
|
||||
#### استفاده
|
||||
|
||||
- [مستندات API](https://www.postman.com/hsanaei/3x-ui/collection/q1l5l0u/3x-ui)
|
||||
- `/login` با `POST` داده کاربر: `{username: '', password: ''}`
|
||||
|
||||
| Method | مسیر | عملکرد |
|
||||
| :----: | ---------------------------------- | ------------------------------------------- |
|
||||
| `GET` | `"/list"` | دریافت تمام inboundها |
|
||||
| `GET` | `"/get/:id"` | دریافت inbound بر اساس id |
|
||||
| `POST` | `"/add"` | افزودن inbound |
|
||||
| `POST` | `"/del/:id"` | حذف inbound |
|
||||
|
||||
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
|
||||
</details>
|
||||
|
||||
## متغیرهای محیطی
|
||||
|
||||
<details>
|
||||
<summary>جزئیات متغیرها</summary>
|
||||
|
||||
#### استفاده
|
||||
|
||||
| متغیر | نوع | پیشفرض |
|
||||
| ------------- | :--------------------------------------------: | :------------ |
|
||||
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
||||
| XUI_DEBUG | `boolean` | `false` |
|
||||
| XUI_BIN_FOLDER| `string` | `"bin"` |
|
||||
|
||||
مثال:
|
||||
|
||||
```sh
|
||||
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## پیشنمایش
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/01-overview-dark.png">
|
||||
<img alt="3x-ui" src="./media/01-overview-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/02-inbounds-dark.png">
|
||||
<img alt="3x-ui" src="./media/02-inbounds-light.png">
|
||||
</picture>
|
||||
|
||||
## قدردانی ویژه از
|
||||
|
||||
- [alireza0](https://github.com/alireza0/)
|
||||
|
||||
## تشکر و قدردانی
|
||||
|
||||
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (مجوز: **GPL-3.0**)
|
||||
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (مجوز: **GPL-3.0**)
|
||||
|
||||
## Stargazers over Time
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
51
README.md
@@ -1,6 +1,11 @@
|
||||
[English](/README.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||
|
||||
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
||||
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
**An Advanced Web Panel • Built on Xray Core**
|
||||
|
||||
@@ -285,9 +290,10 @@ Our platform offers compatibility with a diverse range of architectures and devi
|
||||
## Languages
|
||||
|
||||
- English
|
||||
- Farsi
|
||||
- Persian
|
||||
- Traditional Chinese
|
||||
- Simplified Chinese
|
||||
- Japanese
|
||||
- Russian
|
||||
- Vietnamese
|
||||
- Spanish
|
||||
@@ -547,13 +553,34 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||
|
||||
## Preview
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/01-overview-dark.png">
|
||||
<img alt="3x-ui" src="./media/01-overview-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/02-inbounds-dark.png">
|
||||
<img alt="3x-ui" src="./media/02-inbounds-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/03-add-inbound-dark.png">
|
||||
<img alt="3x-ui" src="./media/03-add-inbound-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/04-add-client-dark.png">
|
||||
<img alt="3x-ui" src="./media/04-add-client-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/05-settings-dark.png">
|
||||
<img alt="3x-ui" src="./media/05-settings-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/06-configs-dark.png">
|
||||
<img alt="3x-ui" src="./media/06-configs-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/07-bot-dark.png">
|
||||
<img alt="3x-ui" src="./media/07-bot-light.png">
|
||||
</picture>
|
||||
|
||||
## A Special Thanks to
|
||||
|
||||
@@ -562,8 +589,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||
## Acknowledgment
|
||||
|
||||
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
|
||||
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._
|
||||
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _This repository contains automatically updated V2Ray routing rules based on data on blocked domains and addresses in Russia._
|
||||
|
||||
## Stargazers over Time
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
@@ -1,6 +1,11 @@
|
||||
[English](/README.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||
|
||||
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
||||
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
**Продвинутая веб-панель • Построена на основе Xray Core**
|
||||
|
||||
@@ -283,16 +288,18 @@ location /sub {
|
||||
|
||||
## Языки
|
||||
|
||||
- Английский
|
||||
- Фарси
|
||||
- Китайский
|
||||
- Русский
|
||||
- Вьетнамский
|
||||
- Испанский
|
||||
- Индонезийский
|
||||
- Украинский
|
||||
- Турецкий
|
||||
- Португальский (Бразилия)
|
||||
- English (английский)
|
||||
- Persian (персидский)
|
||||
- Traditional Chinese (традиционный китайский)
|
||||
- Simplified Chinese (упрощенный китайский)
|
||||
- Japanese (японский)
|
||||
- Russian (русский)
|
||||
- Vietnamese (вьетнамский)
|
||||
- Spanish (испанский)
|
||||
- Indonesian (индонезийский)
|
||||
- Ukrainian (украинский)
|
||||
- Turkish (турецкий)
|
||||
- Português (Brazil) (португальский (Бразилия))
|
||||
|
||||
## Возможности
|
||||
|
||||
@@ -544,13 +551,34 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||
|
||||
## Предварительный Просмотр
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/01-overview-dark.png">
|
||||
<img alt="3x-ui" src="./media/01-overview-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/02-inbounds-dark.png">
|
||||
<img alt="3x-ui" src="./media/02-inbounds-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/03-add-inbound-dark.png">
|
||||
<img alt="3x-ui" src="./media/03-add-inbound-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/04-add-client-dark.png">
|
||||
<img alt="3x-ui" src="./media/04-add-client-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/05-settings-dark.png">
|
||||
<img alt="3x-ui" src="./media/05-settings-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/06-configs-dark.png">
|
||||
<img alt="3x-ui" src="./media/06-configs-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/07-bot-dark.png">
|
||||
<img alt="3x-ui" src="./media/07-bot-light.png">
|
||||
</picture>
|
||||
|
||||
## Особая благодарность
|
||||
|
||||
@@ -559,8 +587,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||
## Благодарности
|
||||
|
||||
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
|
||||
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._
|
||||
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _Этот репозиторий содержит автоматически обновляемые правила маршрутизации V2Ray на основе данных о заблокированных доменах и адресах в России._
|
||||
|
||||
## Число звёзд со временем
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
@@ -1,6 +1,11 @@
|
||||
[English](/README.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||
|
||||
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
||||
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
**一个更好的面板 • 基于Xray Core构建**
|
||||
|
||||
@@ -277,14 +282,18 @@ location /sub {
|
||||
|
||||
## Languages
|
||||
|
||||
- English(英语)
|
||||
- Farsi(伊朗语)
|
||||
- Chinese(中文)
|
||||
- Russian(俄语)
|
||||
- Vietnamese(越南语)
|
||||
- Spanish(西班牙语)
|
||||
- Indonesian (印度尼西亚语)
|
||||
- Ukrainian(乌克兰语)
|
||||
- English(英语)
|
||||
- Persian(波斯语)
|
||||
- Traditional Chinese(繁体中文)
|
||||
- Simplified Chinese(简体中文)
|
||||
- Japanese(日语)
|
||||
- Russian(俄语)
|
||||
- Vietnamese(越南语)
|
||||
- Spanish(西班牙语)
|
||||
- Indonesian(印尼语)
|
||||
- Ukrainian(乌克兰语)
|
||||
- Turkish(土耳其语)
|
||||
- Português (Brazil)(葡萄牙语(巴西))
|
||||
|
||||
|
||||
## Features
|
||||
@@ -535,13 +544,34 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||
|
||||
## 预览
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/01-overview-dark.png">
|
||||
<img alt="3x-ui" src="./media/01-overview-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/02-inbounds-dark.png">
|
||||
<img alt="3x-ui" src="./media/02-inbounds-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/03-add-inbound-dark.png">
|
||||
<img alt="3x-ui" src="./media/03-add-inbound-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/04-add-client-dark.png">
|
||||
<img alt="3x-ui" src="./media/04-add-client-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/05-settings-dark.png">
|
||||
<img alt="3x-ui" src="./media/05-settings-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/06-configs-dark.png">
|
||||
<img alt="3x-ui" src="./media/06-configs-light.png">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./media/07-bot-dark.png">
|
||||
<img alt="3x-ui" src="./media/07-bot-light.png">
|
||||
</picture>
|
||||
|
||||
## 特别感谢
|
||||
|
||||
@@ -550,8 +580,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||
## 致谢
|
||||
|
||||
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
|
||||
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._
|
||||
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _This repository contains automatically updated V2Ray routing rules based on data on blocked domains and addresses in Russia._
|
||||
|
||||
## Star趋势
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
@@ -1 +1 @@
|
||||
2.4.7
|
||||
2.5.1
|
||||
@@ -26,20 +26,35 @@ const (
|
||||
)
|
||||
|
||||
func initModels() error {
|
||||
models := []interface{}{
|
||||
// Order matters: first create tables without dependencies
|
||||
baseModels := []interface{}{
|
||||
&model.User{},
|
||||
&model.Inbound{},
|
||||
&model.OutboundTraffics{},
|
||||
&model.Setting{},
|
||||
&model.InboundClientIps{},
|
||||
&xray.ClientTraffic{},
|
||||
}
|
||||
for _, model := range models {
|
||||
|
||||
// Migrate base models
|
||||
for _, model := range baseModels {
|
||||
if err := db.AutoMigrate(model); err != nil {
|
||||
log.Printf("Error auto migrating model: %v", err)
|
||||
log.Printf("Error auto migrating base model: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Then migrate models with dependencies
|
||||
dependentModels := []interface{}{
|
||||
&model.Inbound{},
|
||||
&model.OutboundTraffics{},
|
||||
&model.InboundClientIps{},
|
||||
&xray.ClientTraffic{},
|
||||
}
|
||||
|
||||
for _, model := range dependentModels {
|
||||
if err := db.AutoMigrate(model); err != nil {
|
||||
log.Printf("Error auto migrating dependent model: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -82,9 +97,31 @@ func InitDB(dbPath string) error {
|
||||
}
|
||||
|
||||
c := &gorm.Config{
|
||||
Logger: gormLogger,
|
||||
Logger: gormLogger,
|
||||
SkipDefaultTransaction: true,
|
||||
PrepareStmt: true,
|
||||
}
|
||||
db, err = gorm.Open(sqlite.Open(dbPath), c)
|
||||
|
||||
dsn := dbPath + "?cache=shared&_journal_mode=WAL&_synchronous=NORMAL"
|
||||
db, err = gorm.Open(sqlite.Open(dsn), c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sqlDB.Exec("PRAGMA cache_size = -64000;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = sqlDB.Exec("PRAGMA temp_store = MEMORY;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = sqlDB.Exec("PRAGMA foreign_keys = ON;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -101,6 +138,11 @@ func InitDB(dbPath string) error {
|
||||
|
||||
func CloseDB() error {
|
||||
if db != nil {
|
||||
|
||||
if err := Checkpoint(); err != nil {
|
||||
log.Printf("error executing checkpoint: %v", err)
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -29,14 +29,14 @@ type User struct {
|
||||
|
||||
type Inbound struct {
|
||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
UserId int `json:"-"`
|
||||
UserId int `json:"-" gorm:"index"`
|
||||
Up int64 `json:"up" form:"up"`
|
||||
Down int64 `json:"down" form:"down"`
|
||||
Total int64 `json:"total" form:"total"`
|
||||
Remark string `json:"remark" form:"remark"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
|
||||
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id;constraint:OnDelete:CASCADE" json:"clientStats"`
|
||||
|
||||
// config part
|
||||
Listen string `json:"listen" form:"listen"`
|
||||
@@ -98,5 +98,6 @@ type Client struct {
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
TgID int64 `json:"tgId" form:"tgId"`
|
||||
SubID string `json:"subId" form:"subId"`
|
||||
Comment string `json:"comment" form:"comment"`
|
||||
Reset int `json:"reset" form:"reset"`
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ services:
|
||||
- $PWD/cert/:/root/cert/
|
||||
environment:
|
||||
XRAY_VMESS_AEAD_FORCED: "false"
|
||||
X_UI_ENABLE_FAIL2BAN: "true"
|
||||
tty: true
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
|
||||
73
go.mod
@@ -1,46 +1,45 @@
|
||||
module x-ui
|
||||
|
||||
go 1.23.3
|
||||
go 1.23.5
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/gzip v1.0.1
|
||||
github.com/gin-contrib/sessions v1.0.1
|
||||
github.com/gin-contrib/gzip v1.2.2
|
||||
github.com/gin-contrib/sessions v1.0.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/goccy/go-json v0.10.3
|
||||
github.com/mymmrac/telego v0.31.4
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.1
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/mymmrac/telego v0.32.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml/v2 v2.2.3
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.24.10
|
||||
github.com/valyala/fasthttp v1.57.0
|
||||
github.com/xtls/xray-core v1.8.25-0.20241111042233-0df2446f824d
|
||||
github.com/shirou/gopsutil/v4 v4.25.1
|
||||
github.com/valyala/fasthttp v1.58.0
|
||||
github.com/xtls/xray-core v1.8.25-0.20250130105737-0a8470cb14eb
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/text v0.20.0
|
||||
google.golang.org/grpc v1.68.0
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
golang.org/x/text v0.21.0
|
||||
google.golang.org/grpc v1.70.0
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.4 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.8 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||
github.com/cloudflare/circl v1.5.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
github.com/ebitengine/purego v0.8.1 // indirect
|
||||
github.com/fasthttp/router v1.5.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/fasthttp/router v1.5.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // 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.22.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.24.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20241101162523-b92577c0c142 // indirect
|
||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.4.0 // indirect
|
||||
@@ -58,15 +57,15 @@ require (
|
||||
github.com/mattn/go-sqlite3 v1.14.24 // 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.21.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
|
||||
github.com/pires/go-proxyproto v0.8.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.48.1 // indirect
|
||||
github.com/quic-go/quic-go v0.49.0 // indirect
|
||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/sagernet/sing v0.5.0 // indirect
|
||||
github.com/sagernet/sing v0.5.1 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
||||
@@ -83,20 +82,20 @@ require (
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.29.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
|
||||
golang.org/x/arch v0.13.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/net v0.31.0 // indirect
|
||||
golang.org/x/sync v0.9.0 // indirect
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
golang.org/x/time v0.8.0 // indirect
|
||||
golang.org/x/tools v0.27.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
169
go.sum
@@ -4,42 +4,43 @@ github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJS
|
||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/bytedance/sonic v1.12.4 h1:9Csb3c9ZJhfUWeMtpCDCq6BUoH5ogfDFLUgQ/jG+R0k=
|
||||
github.com/bytedance/sonic v1.12.4/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
|
||||
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
|
||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
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=
|
||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
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/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/fasthttp/router v1.5.2 h1:ckJCCdV7hWkkrMeId3WfEhz+4Gyyf6QPwxi/RHIMZ6I=
|
||||
github.com/fasthttp/router v1.5.2/go.mod h1:C8EY53ozOwpONyevc/V7Gr8pqnEjwnkFFqPo1alAGs0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM=
|
||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8=
|
||||
github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
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/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
||||
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
|
||||
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
|
||||
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
|
||||
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-contrib/gzip v1.2.2 h1:iUU/EYCM8ENfkjmZaVrxbjF/ZC267Iqv5S0MMCMEliI=
|
||||
github.com/gin-contrib/gzip v1.2.2/go.mod h1:C1a5cacjlDsS20cKnHlZRCPUu57D3qH6B2pV0rl+Y/s=
|
||||
github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
|
||||
github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
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=
|
||||
@@ -49,12 +50,12 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
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.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
|
||||
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
@@ -66,8 +67,10 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241101162523-b92577c0c142 h1:sAGdeJj0bnMgUNVeUpp6AYlVdCt3/GdI3pGRqsNSQLs=
|
||||
github.com/google/pprof v0.0.0-20241101162523-b92577c0c142/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 h1:wlQI2cYY0BsWmmPPAnxfQ8SDW0S3Jasn+4B8kXFxprg=
|
||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
@@ -102,21 +105,21 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
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.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mymmrac/telego v0.31.4 h1:NpiNl0P/8eydknka/k6XaaaWVj5BKMlM3Ibba63QTBU=
|
||||
github.com/mymmrac/telego v0.31.4/go.mod h1:T12js1PgbYDYznvoN05MSMuPMfWTYo7D9LKl5cPFWiI=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.1 h1:zwzjtX4uYyiaU02K5Ia3zSkpJZrByARkRB4V3YPrr0g=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.1/go.mod h1:++Pl70FR6Cki7hdzZRnEEqdc2dJt+SAGotyFg/SvZMk=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
|
||||
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
|
||||
github.com/mymmrac/telego v0.32.0 h1:4X8C1l3k+opkk86r95+eQE8DxiS2LYlR61L/G7yreDY=
|
||||
github.com/mymmrac/telego v0.32.0/go.mod h1:qS6NaRhJgcuEEBEMVCV79S2xCAuHq9O+ixwfLuRW31M=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
|
||||
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
|
||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||
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/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
@@ -131,8 +134,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA=
|
||||
github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
|
||||
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
|
||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
@@ -141,27 +144,29 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/sagernet/sing v0.5.0 h1:soo2wVwLcieKWWKIksFNK6CCAojUgAppqQVwyRYGkEM=
|
||||
github.com/sagernet/sing v0.5.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
||||
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/shirou/gopsutil/v4 v4.24.10 h1:7VOzPtfw/5YDU+jLEoBwXwxJbQetULywoSV4RYY7HkM=
|
||||
github.com/shirou/gopsutil/v4 v4.24.10/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
@@ -174,8 +179,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.57.0 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg=
|
||||
github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE=
|
||||
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
|
||||
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
|
||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||
@@ -185,54 +190,64 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM=
|
||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg=
|
||||
github.com/xtls/xray-core v1.8.25-0.20241111042233-0df2446f824d h1:00+ceMqRs5YdBMm4NLdsupYnxkP5Ghka9Lzw2ciVhgk=
|
||||
github.com/xtls/xray-core v1.8.25-0.20241111042233-0df2446f824d/go.mod h1:n8wJBW828YcDQ+9+BnAtbofXVt31VMvz4TwbZK0BaEE=
|
||||
github.com/xtls/xray-core v1.8.25-0.20250130105737-0a8470cb14eb h1:VBzDZ4XHT9FM0S3qvXp6KuTOk7mXQQm0pjOW0Neeog4=
|
||||
github.com/xtls/xray-core v1.8.25-0.20250130105737-0a8470cb14eb/go.mod h1:EKhNEDY/WIB+ylEbVv2pzUaP08W/lt0sYmJQ9FJruko=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
||||
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||
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.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
|
||||
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
|
||||
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
|
||||
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
|
||||
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
||||
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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=
|
||||
@@ -242,12 +257,12 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
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.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 h1:P+U/06iIKPQ3DLcg+zBfSCia1luZ2msPZrJ8jYDFPs0=
|
||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
|
||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
||||
39
install.sh
@@ -2,6 +2,7 @@
|
||||
|
||||
red='\033[0;31m'
|
||||
green='\033[0;32m'
|
||||
blue='\033[0;34m'
|
||||
yellow='\033[0;33m'
|
||||
plain='\033[0m'
|
||||
|
||||
@@ -260,26 +261,26 @@ install_x-ui() {
|
||||
systemctl start x-ui
|
||||
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
|
||||
echo -e ""
|
||||
echo -e "x-ui control menu usages: "
|
||||
echo -e "----------------------------------------------"
|
||||
echo -e "SUBCOMMANDS:"
|
||||
echo -e "x-ui - Admin Management Script"
|
||||
echo -e "x-ui start - Start"
|
||||
echo -e "x-ui stop - Stop"
|
||||
echo -e "x-ui restart - Restart"
|
||||
echo -e "x-ui status - Current Status"
|
||||
echo -e "x-ui settings - Current Settings"
|
||||
echo -e "x-ui enable - Enable Autostart on OS Startup"
|
||||
echo -e "x-ui disable - Disable Autostart on OS Startup"
|
||||
echo -e "x-ui log - Check logs"
|
||||
echo -e "x-ui banlog - Check Fail2ban ban logs"
|
||||
echo -e "x-ui update - Update"
|
||||
echo -e "x-ui legacy - legacy version"
|
||||
echo -e "x-ui install - Install"
|
||||
echo -e "x-ui uninstall - Uninstall"
|
||||
echo -e "----------------------------------------------"
|
||||
echo -e "┌───────────────────────────────────────────────────────┐
|
||||
│ ${blue}x-ui control menu usages (subcommands):${plain} │
|
||||
│ │
|
||||
│ ${blue}x-ui${plain} - Admin Management Script │
|
||||
│ ${blue}x-ui start${plain} - Start │
|
||||
│ ${blue}x-ui stop${plain} - Stop │
|
||||
│ ${blue}x-ui restart${plain} - Restart │
|
||||
│ ${blue}x-ui status${plain} - Current Status │
|
||||
│ ${blue}x-ui settings${plain} - Current Settings │
|
||||
│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │
|
||||
│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │
|
||||
│ ${blue}x-ui log${plain} - Check logs │
|
||||
│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
|
||||
│ ${blue}x-ui update${plain} - Update │
|
||||
│ ${blue}x-ui legacy${plain} - legacy version │
|
||||
│ ${blue}x-ui install${plain} - Install │
|
||||
│ ${blue}x-ui uninstall${plain} - Uninstall │
|
||||
└───────────────────────────────────────────────────────┘"
|
||||
}
|
||||
|
||||
echo -e "${green}Running...${plain}"
|
||||
install_base
|
||||
install_x-ui $1
|
||||
install_x-ui $1
|
||||
BIN
media/01-overview-dark.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
media/01-overview-light.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
media/02-inbounds-dark.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
media/02-inbounds-light.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
media/03-add-inbound-dark.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
media/03-add-inbound-light.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
media/04-add-client-dark.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
media/04-add-client-light.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
media/05-settings-dark.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
media/05-settings-light.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
media/06-configs-dark.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
media/06-configs-light.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
media/07-bot-dark.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
media/07-bot-light.png
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
media/1.png
|
Before Width: | Height: | Size: 59 KiB |
BIN
media/2.png
|
Before Width: | Height: | Size: 91 KiB |
BIN
media/3.png
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 226 KiB |
BIN
media/3x-ui-light.png
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
media/4.png
|
Before Width: | Height: | Size: 26 KiB |
BIN
media/5.png
|
Before Width: | Height: | Size: 71 KiB |
BIN
media/6.png
|
Before Width: | Height: | Size: 42 KiB |
BIN
media/7.png
|
Before Width: | Height: | Size: 60 KiB |
@@ -47,7 +47,7 @@
|
||||
"tag": "direct",
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"domainStrategy": "UseIP",
|
||||
"domainStrategy": "AsIs",
|
||||
"redirect": "",
|
||||
"noises": []
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ type SubJsonService struct {
|
||||
configJson map[string]interface{}
|
||||
defaultOutbounds []json_util.RawMessage
|
||||
fragment string
|
||||
noises string
|
||||
noises string
|
||||
mux string
|
||||
|
||||
inboundService service.InboundService
|
||||
@@ -61,7 +61,7 @@ func NewSubJsonService(fragment string, noises string, mux string, rules string,
|
||||
configJson: configJson,
|
||||
defaultOutbounds: defaultOutbounds,
|
||||
fragment: fragment,
|
||||
noises: noises,
|
||||
noises: noises,
|
||||
mux: mux,
|
||||
SubService: subService,
|
||||
}
|
||||
@@ -217,7 +217,7 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
||||
delete(streamSettings, "sockopt")
|
||||
|
||||
if s.fragment != "" {
|
||||
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpMptcp": true, "tcpNoDelay": true}`)
|
||||
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpMptcp": true, "penetrate": true}`)
|
||||
}
|
||||
|
||||
// remove proxy protocol
|
||||
|
||||
@@ -208,11 +208,6 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
obj["net"] = "h2"
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
obj["path"], _ = http["path"].(string)
|
||||
obj["host"] = searchHost(http)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
obj["path"] = grpc["serviceName"].(string)
|
||||
@@ -229,16 +224,16 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
case "splithttp":
|
||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
||||
obj["path"] = splithttp["path"].(string)
|
||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
obj["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
obj["host"] = host
|
||||
} else {
|
||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
obj["mode"] = splithttp["mode"].(string)
|
||||
obj["mode"] = xhttp["mode"].(string)
|
||||
}
|
||||
security, _ := stream["security"].(string)
|
||||
obj["tls"] = security
|
||||
@@ -361,10 +356,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
params["path"] = http["path"].(string)
|
||||
params["host"] = searchHost(http)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
@@ -381,16 +372,16 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "splithttp":
|
||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
||||
params["path"] = splithttp["path"].(string)
|
||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
params["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
params["mode"] = splithttp["mode"].(string)
|
||||
params["mode"] = xhttp["mode"].(string)
|
||||
}
|
||||
security, _ := stream["security"].(string)
|
||||
if security == "tls" {
|
||||
@@ -559,10 +550,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
params["path"] = http["path"].(string)
|
||||
params["host"] = searchHost(http)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
@@ -579,16 +566,16 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "splithttp":
|
||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
||||
params["path"] = splithttp["path"].(string)
|
||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
params["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
params["mode"] = splithttp["mode"].(string)
|
||||
params["mode"] = xhttp["mode"].(string)
|
||||
}
|
||||
security, _ := stream["security"].(string)
|
||||
if security == "tls" {
|
||||
@@ -757,10 +744,6 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
params["path"] = http["path"].(string)
|
||||
params["host"] = searchHost(http)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
@@ -777,16 +760,16 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "splithttp":
|
||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
||||
params["path"] = splithttp["path"].(string)
|
||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
params["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
params["mode"] = splithttp["mode"].(string)
|
||||
params["mode"] = xhttp["mode"].(string)
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
|
||||
2
web/assets/axios/axios.min.js
vendored
2
web/assets/css/custom.min.css
vendored
@@ -19,6 +19,11 @@ const supportLangs = [
|
||||
value: "zh-TW",
|
||||
icon: "🇹🇼",
|
||||
},
|
||||
{
|
||||
name: "日本語",
|
||||
value: "ja-JP",
|
||||
icon: "🇯🇵",
|
||||
},
|
||||
{
|
||||
name: "Русский",
|
||||
value: "ru-RU",
|
||||
|
||||
@@ -34,10 +34,6 @@ const TLS_VERSION_OPTION = {
|
||||
};
|
||||
|
||||
const TLS_CIPHER_OPTION = {
|
||||
RSA_AES_128_CBC: "TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||
RSA_AES_256_CBC: "TLS_RSA_WITH_AES_256_CBC_SHA",
|
||||
RSA_AES_128_GCM: "TLS_RSA_WITH_AES_128_GCM_SHA256",
|
||||
RSA_AES_256_GCM: "TLS_RSA_WITH_AES_256_GCM_SHA384",
|
||||
AES_128_GCM: "TLS_AES_128_GCM_SHA256",
|
||||
AES_256_GCM: "TLS_AES_256_GCM_SHA384",
|
||||
CHACHA20_POLY1305: "TLS_CHACHA20_POLY1305_SHA256",
|
||||
@@ -64,6 +60,7 @@ const UTLS_FINGERPRINT = {
|
||||
UTLS_QQ: "qq",
|
||||
UTLS_RANDOM: "random",
|
||||
UTLS_RANDOMIZED: "randomized",
|
||||
UTLS_UNSAFE: "unsafe",
|
||||
};
|
||||
|
||||
const ALPN_OPTION = {
|
||||
@@ -117,6 +114,7 @@ const MODE_OPTION = {
|
||||
AUTO: "auto",
|
||||
PACKET_UP: "packet-up",
|
||||
STREAM_UP: "stream-up",
|
||||
STREAM_ONE: "stream-one",
|
||||
};
|
||||
|
||||
Object.freeze(Protocols);
|
||||
@@ -378,13 +376,15 @@ class WsStreamSettings extends XrayCommonClass {
|
||||
acceptProxyProtocol = false,
|
||||
path = '/',
|
||||
host = '',
|
||||
headers = []
|
||||
headers = [],
|
||||
heartbeatPeriod = 0,
|
||||
) {
|
||||
super();
|
||||
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
this.headers = headers;
|
||||
this.heartbeatPeriod = heartbeatPeriod;
|
||||
}
|
||||
|
||||
addHeader(name, value) {
|
||||
@@ -401,6 +401,7 @@ class WsStreamSettings extends XrayCommonClass {
|
||||
json.path,
|
||||
json.host,
|
||||
XrayCommonClass.toHeaders(json.headers),
|
||||
json.heartbeatPeriod,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -410,46 +411,11 @@ class WsStreamSettings extends XrayCommonClass {
|
||||
path: this.path,
|
||||
host: this.host,
|
||||
headers: XrayCommonClass.toV2Headers(this.headers, false),
|
||||
heartbeatPeriod: this.heartbeatPeriod,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class HttpStreamSettings extends XrayCommonClass {
|
||||
constructor(
|
||||
path = '/',
|
||||
host = [''],
|
||||
) {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host.length === 0 ? [''] : host;
|
||||
}
|
||||
|
||||
addHost(host) {
|
||||
this.host.push(host);
|
||||
}
|
||||
|
||||
removeHost(index) {
|
||||
this.host.splice(index, 1);
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new HttpStreamSettings(json.path, json.host);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
let host = [];
|
||||
for (let i = 0; i < this.host.length; ++i) {
|
||||
if (!ObjectUtil.isEmpty(this.host[i])) {
|
||||
host.push(this.host[i]);
|
||||
}
|
||||
}
|
||||
return {
|
||||
path: this.path,
|
||||
host: host,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GrpcStreamSettings extends XrayCommonClass {
|
||||
constructor(
|
||||
serviceName = "",
|
||||
@@ -520,34 +486,27 @@ class HTTPUpgradeStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
class SplitHTTPStreamSettings extends XrayCommonClass {
|
||||
class xHTTPStreamSettings extends XrayCommonClass {
|
||||
constructor(
|
||||
path = '/',
|
||||
host = '',
|
||||
headers = [],
|
||||
scMaxConcurrentPosts = "100-200",
|
||||
scMaxEachPostBytes = "1000000-2000000",
|
||||
scMinPostsIntervalMs = "10-50",
|
||||
scMaxBufferedPosts = 30,
|
||||
scMaxEachPostBytes = "1000000",
|
||||
scStreamUpServerSecs = "20-80",
|
||||
noSSEHeader = false,
|
||||
xPaddingBytes = "100-1000",
|
||||
xmux = {
|
||||
maxConcurrency: "16-32",
|
||||
maxConnections: 0,
|
||||
cMaxReuseTimes: "64-128",
|
||||
cMaxLifetimeMs: 0
|
||||
},
|
||||
mode = MODE_OPTION.AUTO,
|
||||
) {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
this.headers = headers;
|
||||
this.scMaxConcurrentPosts = scMaxConcurrentPosts;
|
||||
this.scMaxBufferedPosts = scMaxBufferedPosts;
|
||||
this.scMaxEachPostBytes = scMaxEachPostBytes;
|
||||
this.scMinPostsIntervalMs = scMinPostsIntervalMs;
|
||||
this.scStreamUpServerSecs = scStreamUpServerSecs;
|
||||
this.noSSEHeader = noSSEHeader;
|
||||
this.xPaddingBytes = xPaddingBytes;
|
||||
this.xmux = xmux;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
@@ -560,16 +519,15 @@ class SplitHTTPStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new SplitHTTPStreamSettings(
|
||||
return new xHTTPStreamSettings(
|
||||
json.path,
|
||||
json.host,
|
||||
XrayCommonClass.toHeaders(json.headers),
|
||||
json.scMaxConcurrentPosts,
|
||||
json.scMaxBufferedPosts,
|
||||
json.scMaxEachPostBytes,
|
||||
json.scMinPostsIntervalMs,
|
||||
json.scStreamUpServerSecs,
|
||||
json.noSSEHeader,
|
||||
json.xPaddingBytes,
|
||||
json.xmux,
|
||||
json.mode,
|
||||
);
|
||||
}
|
||||
@@ -579,17 +537,11 @@ class SplitHTTPStreamSettings extends XrayCommonClass {
|
||||
path: this.path,
|
||||
host: this.host,
|
||||
headers: XrayCommonClass.toV2Headers(this.headers, false),
|
||||
scMaxConcurrentPosts: this.scMaxConcurrentPosts,
|
||||
scMaxBufferedPosts: this.scMaxBufferedPosts,
|
||||
scMaxEachPostBytes: this.scMaxEachPostBytes,
|
||||
scMinPostsIntervalMs: this.scMinPostsIntervalMs,
|
||||
scStreamUpServerSecs: this.scStreamUpServerSecs,
|
||||
noSSEHeader: this.noSSEHeader,
|
||||
xPaddingBytes: this.xPaddingBytes,
|
||||
xmux: {
|
||||
maxConcurrency: this.xmux.maxConcurrency,
|
||||
maxConnections: this.xmux.maxConnections,
|
||||
cMaxReuseTimes: this.xmux.cMaxReuseTimes,
|
||||
cMaxLifetimeMs: this.xmux.cMaxLifetimeMs
|
||||
},
|
||||
mode: this.mode,
|
||||
};
|
||||
}
|
||||
@@ -602,6 +554,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
maxVersion = TLS_VERSION_OPTION.TLS13,
|
||||
cipherSuites = '',
|
||||
rejectUnknownSni = false,
|
||||
serverNameToVerify = 'dns.google',
|
||||
disableSystemRoot = false,
|
||||
enableSessionResumption = false,
|
||||
certificates = [new TlsStreamSettings.Cert()],
|
||||
@@ -614,6 +567,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
this.maxVersion = maxVersion;
|
||||
this.cipherSuites = cipherSuites;
|
||||
this.rejectUnknownSni = rejectUnknownSni;
|
||||
this.serverNameToVerify = serverNameToVerify;
|
||||
this.disableSystemRoot = disableSystemRoot;
|
||||
this.enableSessionResumption = enableSessionResumption;
|
||||
this.certs = certificates;
|
||||
@@ -645,6 +599,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
json.maxVersion,
|
||||
json.cipherSuites,
|
||||
json.rejectUnknownSni,
|
||||
json.serverNameToVerify,
|
||||
json.disableSystemRoot,
|
||||
json.enableSessionResumption,
|
||||
certs,
|
||||
@@ -660,6 +615,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
maxVersion: this.maxVersion,
|
||||
cipherSuites: this.cipherSuites,
|
||||
rejectUnknownSni: this.rejectUnknownSni,
|
||||
serverNameToVerify: this.serverNameToVerify,
|
||||
disableSystemRoot: this.disableSystemRoot,
|
||||
enableSessionResumption: this.enableSessionResumption,
|
||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||
@@ -741,7 +697,10 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||
};
|
||||
|
||||
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||
constructor(allowInsecure = false, fingerprint = '') {
|
||||
constructor(
|
||||
allowInsecure = false,
|
||||
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
|
||||
) {
|
||||
super();
|
||||
this.allowInsecure = allowInsecure;
|
||||
this.fingerprint = fingerprint;
|
||||
@@ -807,7 +766,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
||||
json.maxClient,
|
||||
json.maxTimediff,
|
||||
json.shortIds,
|
||||
json.settings,
|
||||
settings,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -830,7 +789,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
||||
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||
constructor(
|
||||
publicKey = '',
|
||||
fingerprint = UTLS_FINGERPRINT.UTLS_RANDOM,
|
||||
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
|
||||
serverName = '',
|
||||
spiderX = '/'
|
||||
) {
|
||||
@@ -865,7 +824,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
||||
mark = 0,
|
||||
tproxy = "off",
|
||||
tcpMptcp = false,
|
||||
tcpNoDelay = false,
|
||||
penetrate = false,
|
||||
domainStrategy = DOMAIN_STRATEGY_OPTION.USE_IP,
|
||||
tcpMaxSeg = 1440,
|
||||
dialerProxy = "",
|
||||
@@ -883,7 +842,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
||||
this.mark = mark;
|
||||
this.tproxy = tproxy;
|
||||
this.tcpMptcp = tcpMptcp;
|
||||
this.tcpNoDelay = tcpNoDelay;
|
||||
this.penetrate = penetrate;
|
||||
this.domainStrategy = domainStrategy;
|
||||
this.tcpMaxSeg = tcpMaxSeg;
|
||||
this.dialerProxy = dialerProxy;
|
||||
@@ -904,7 +863,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
||||
json.mark,
|
||||
json.tproxy,
|
||||
json.tcpMptcp,
|
||||
json.tcpNoDelay,
|
||||
json.penetrate,
|
||||
json.domainStrategy,
|
||||
json.tcpMaxSeg,
|
||||
json.dialerProxy,
|
||||
@@ -925,7 +884,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
||||
mark: this.mark,
|
||||
tproxy: this.tproxy,
|
||||
tcpMptcp: this.tcpMptcp,
|
||||
tcpNoDelay: this.tcpNoDelay,
|
||||
penetrate: this.penetrate,
|
||||
domainStrategy: this.domainStrategy,
|
||||
tcpMaxSeg: this.tcpMaxSeg,
|
||||
dialerProxy: this.dialerProxy,
|
||||
@@ -949,10 +908,9 @@ class StreamSettings extends XrayCommonClass {
|
||||
tcpSettings = new TcpStreamSettings(),
|
||||
kcpSettings = new KcpStreamSettings(),
|
||||
wsSettings = new WsStreamSettings(),
|
||||
httpSettings = new HttpStreamSettings(),
|
||||
grpcSettings = new GrpcStreamSettings(),
|
||||
httpupgradeSettings = new HTTPUpgradeStreamSettings(),
|
||||
splithttpSettings = new SplitHTTPStreamSettings(),
|
||||
xhttpSettings = new xHTTPStreamSettings(),
|
||||
sockopt = undefined,
|
||||
) {
|
||||
super();
|
||||
@@ -964,10 +922,9 @@ class StreamSettings extends XrayCommonClass {
|
||||
this.tcp = tcpSettings;
|
||||
this.kcp = kcpSettings;
|
||||
this.ws = wsSettings;
|
||||
this.http = httpSettings;
|
||||
this.grpc = grpcSettings;
|
||||
this.httpupgrade = httpupgradeSettings;
|
||||
this.splithttp = splithttpSettings;
|
||||
this.xhttp = xhttpSettings;
|
||||
this.sockopt = sockopt;
|
||||
}
|
||||
|
||||
@@ -1014,10 +971,9 @@ class StreamSettings extends XrayCommonClass {
|
||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||
WsStreamSettings.fromJson(json.wsSettings),
|
||||
HttpStreamSettings.fromJson(json.httpSettings),
|
||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||
HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||
SplitHTTPStreamSettings.fromJson(json.splithttpSettings),
|
||||
xHTTPStreamSettings.fromJson(json.xhttpSettings),
|
||||
SockoptStreamSettings.fromJson(json.sockopt),
|
||||
);
|
||||
}
|
||||
@@ -1033,10 +989,9 @@ class StreamSettings extends XrayCommonClass {
|
||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
||||
splithttpSettings: network === 'splithttp' ? this.splithttp.toJson() : undefined,
|
||||
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
|
||||
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||
};
|
||||
}
|
||||
@@ -1165,16 +1120,12 @@ class Inbound extends XrayCommonClass {
|
||||
return this.network === "grpc";
|
||||
}
|
||||
|
||||
get isH2() {
|
||||
return this.network === "http";
|
||||
}
|
||||
|
||||
get isHttpupgrade() {
|
||||
return this.network === "httpupgrade";
|
||||
}
|
||||
|
||||
get isSplithttp() {
|
||||
return this.network === "splithttp";
|
||||
get isXHTTP() {
|
||||
return this.network === "xhttp";
|
||||
}
|
||||
|
||||
// Shadowsocks
|
||||
@@ -1213,12 +1164,10 @@ class Inbound extends XrayCommonClass {
|
||||
return this.getHeader(this.stream.tcp.request, 'host');
|
||||
} else if (this.isWs) {
|
||||
return this.stream.ws.host?.length > 0 ? this.stream.ws.host : this.getHeader(this.stream.ws, 'host');
|
||||
} else if (this.isH2) {
|
||||
return this.stream.http.host[0];
|
||||
} else if (this.isHttpupgrade) {
|
||||
return this.stream.httpupgrade.host?.length > 0 ? this.stream.httpupgrade.host : this.getHeader(this.stream.httpupgrade, 'host');
|
||||
} else if (this.isSplithttp) {
|
||||
return this.stream.splithttp.host?.length > 0 ? this.stream.splithttp.host : this.getHeader(this.stream.splithttp, 'host');
|
||||
} else if (this.isXHTTP) {
|
||||
return this.stream.xhttp.host?.length > 0 ? this.stream.xhttp.host : this.getHeader(this.stream.xhttp, 'host');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1228,12 +1177,10 @@ class Inbound extends XrayCommonClass {
|
||||
return this.stream.tcp.request.path[0];
|
||||
} else if (this.isWs) {
|
||||
return this.stream.ws.path;
|
||||
} else if (this.isH2) {
|
||||
return this.stream.http.path;
|
||||
} else if (this.isHttpupgrade) {
|
||||
return this.stream.httpupgrade.path;
|
||||
} else if (this.isSplithttp) {
|
||||
return this.stream.splithttp.path;
|
||||
} else if (this.isXHTTP) {
|
||||
return this.stream.xhttp.path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1257,7 +1204,7 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
canEnableTls() {
|
||||
if (![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
|
||||
return ["tcp", "ws", "http", "grpc", "httpupgrade", "splithttp"].includes(this.network);
|
||||
return ["tcp", "ws", "http", "grpc", "httpupgrade", "xhttp"].includes(this.network);
|
||||
}
|
||||
|
||||
//this is used for xtls-rprx-vision
|
||||
@@ -1270,7 +1217,7 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
canEnableReality() {
|
||||
if (![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
|
||||
return ["tcp", "http", "grpc", "splithttp"].includes(this.network);
|
||||
return ["tcp", "http", "grpc", "xhttp"].includes(this.network);
|
||||
}
|
||||
|
||||
canEnableStream() {
|
||||
@@ -1322,10 +1269,6 @@ class Inbound extends XrayCommonClass {
|
||||
const ws = this.stream.ws;
|
||||
obj.path = ws.path;
|
||||
obj.host = ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host');
|
||||
} else if (network === 'http') {
|
||||
obj.net = 'h2';
|
||||
obj.path = this.stream.http.path;
|
||||
obj.host = this.stream.http.host.join(',');
|
||||
} else if (network === 'grpc') {
|
||||
obj.path = this.stream.grpc.serviceName;
|
||||
obj.authority = this.stream.grpc.authority;
|
||||
@@ -1336,14 +1279,14 @@ class Inbound extends XrayCommonClass {
|
||||
const httpupgrade = this.stream.httpupgrade;
|
||||
obj.path = httpupgrade.path;
|
||||
obj.host = httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host');
|
||||
} else if (network === 'splithttp') {
|
||||
const splithttp = this.stream.splithttp;
|
||||
obj.path = splithttp.path;
|
||||
obj.host = splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host');
|
||||
obj.mode = splithttp.mode;
|
||||
} else if (network === 'xhttp') {
|
||||
const xhttp = this.stream.xhttp;
|
||||
obj.path = xhttp.path;
|
||||
obj.host = xhttp.host?.length > 0 ? xhttp.host : this.getHeader(xhttp, 'host');
|
||||
obj.mode = xhttp.mode;
|
||||
}
|
||||
|
||||
if (security === 'tls') {
|
||||
if (tls === 'tls') {
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||
obj.sni = this.stream.tls.sni;
|
||||
}
|
||||
@@ -1391,11 +1334,6 @@ class Inbound extends XrayCommonClass {
|
||||
params.set("path", ws.path);
|
||||
params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host'));
|
||||
break;
|
||||
case "http":
|
||||
const http = this.stream.http;
|
||||
params.set("path", http.path);
|
||||
params.set("host", http.host);
|
||||
break;
|
||||
case "grpc":
|
||||
const grpc = this.stream.grpc;
|
||||
params.set("serviceName", grpc.serviceName);
|
||||
@@ -1409,11 +1347,11 @@ class Inbound extends XrayCommonClass {
|
||||
params.set("path", httpupgrade.path);
|
||||
params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
|
||||
break;
|
||||
case "splithttp":
|
||||
const splithttp = this.stream.splithttp;
|
||||
params.set("path", splithttp.path);
|
||||
params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host'));
|
||||
params.set("mode", splithttp.mode);
|
||||
case "xhttp":
|
||||
const xhttp = this.stream.xhttp;
|
||||
params.set("path", xhttp.path);
|
||||
params.set("host", xhttp.host?.length > 0 ? xhttp.host : this.getHeader(xhttp, 'host'));
|
||||
params.set("mode", xhttp.mode);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1495,11 +1433,6 @@ class Inbound extends XrayCommonClass {
|
||||
params.set("path", ws.path);
|
||||
params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host'));
|
||||
break;
|
||||
case "http":
|
||||
const http = this.stream.http;
|
||||
params.set("path", http.path);
|
||||
params.set("host", http.host);
|
||||
break;
|
||||
case "grpc":
|
||||
const grpc = this.stream.grpc;
|
||||
params.set("serviceName", grpc.serviceName);
|
||||
@@ -1513,11 +1446,11 @@ class Inbound extends XrayCommonClass {
|
||||
params.set("path", httpupgrade.path);
|
||||
params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
|
||||
break;
|
||||
case "splithttp":
|
||||
const splithttp = this.stream.splithttp;
|
||||
params.set("path", splithttp.path);
|
||||
params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host'));
|
||||
params.set("mode", splithttp.mode);
|
||||
case "xhttp":
|
||||
const xhttp = this.stream.xhttp;
|
||||
params.set("path", xhttp.path);
|
||||
params.set("host", xhttp.host?.length > 0 ? xhttp.host : this.getHeader(xhttp, 'host'));
|
||||
params.set("mode", xhttp.mode);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1578,11 +1511,6 @@ class Inbound extends XrayCommonClass {
|
||||
params.set("path", ws.path);
|
||||
params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host'));
|
||||
break;
|
||||
case "http":
|
||||
const http = this.stream.http;
|
||||
params.set("path", http.path);
|
||||
params.set("host", http.host);
|
||||
break;
|
||||
case "grpc":
|
||||
const grpc = this.stream.grpc;
|
||||
params.set("serviceName", grpc.serviceName);
|
||||
@@ -1596,11 +1524,11 @@ class Inbound extends XrayCommonClass {
|
||||
params.set("path", httpupgrade.path);
|
||||
params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
|
||||
break;
|
||||
case "splithttp":
|
||||
const splithttp = this.stream.splithttp;
|
||||
params.set("path", splithttp.path);
|
||||
params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host'));
|
||||
params.set("mode", splithttp.mode);
|
||||
case "xhttp":
|
||||
const xhttp = this.stream.xhttp;
|
||||
params.set("path", xhttp.path);
|
||||
params.set("host", xhttp.host?.length > 0 ? xhttp.host : this.getHeader(xhttp, 'host'));
|
||||
params.set("mode", xhttp.mode);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1858,6 +1786,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
||||
enable = true,
|
||||
tgId = '',
|
||||
subId = RandomUtil.randomLowerAndNum(16),
|
||||
comment = '',
|
||||
reset = 0
|
||||
) {
|
||||
super();
|
||||
@@ -1870,6 +1799,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
||||
this.enable = enable;
|
||||
this.tgId = tgId;
|
||||
this.subId = subId;
|
||||
this.comment = comment;
|
||||
this.reset = reset;
|
||||
}
|
||||
|
||||
@@ -1884,6 +1814,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
||||
json.enable,
|
||||
json.tgId,
|
||||
json.subId,
|
||||
json.comment,
|
||||
json.reset,
|
||||
);
|
||||
}
|
||||
@@ -1964,6 +1895,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||
enable = true,
|
||||
tgId = '',
|
||||
subId = RandomUtil.randomLowerAndNum(16),
|
||||
comment = '',
|
||||
reset = 0
|
||||
) {
|
||||
super();
|
||||
@@ -1976,6 +1908,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||
this.enable = enable;
|
||||
this.tgId = tgId;
|
||||
this.subId = subId;
|
||||
this.comment = comment;
|
||||
this.reset = reset;
|
||||
}
|
||||
|
||||
@@ -1990,6 +1923,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||
json.enable,
|
||||
json.tgId,
|
||||
json.subId,
|
||||
json.comment,
|
||||
json.reset,
|
||||
);
|
||||
}
|
||||
@@ -2100,6 +2034,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
enable = true,
|
||||
tgId = '',
|
||||
subId = RandomUtil.randomLowerAndNum(16),
|
||||
comment = '',
|
||||
reset = 0
|
||||
) {
|
||||
super();
|
||||
@@ -2111,6 +2046,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
this.enable = enable;
|
||||
this.tgId = tgId;
|
||||
this.subId = subId;
|
||||
this.comment = comment;
|
||||
this.reset = reset;
|
||||
}
|
||||
|
||||
@@ -2124,6 +2060,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
enable: this.enable,
|
||||
tgId: this.tgId,
|
||||
subId: this.subId,
|
||||
comment: this.comment,
|
||||
reset: this.reset,
|
||||
};
|
||||
}
|
||||
@@ -2138,6 +2075,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
json.enable,
|
||||
json.tgId,
|
||||
json.subId,
|
||||
json.comment,
|
||||
json.reset,
|
||||
);
|
||||
}
|
||||
@@ -2257,6 +2195,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
enable = true,
|
||||
tgId = '',
|
||||
subId = RandomUtil.randomLowerAndNum(16),
|
||||
comment = '',
|
||||
reset = 0
|
||||
) {
|
||||
super();
|
||||
@@ -2269,6 +2208,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
this.enable = enable;
|
||||
this.tgId = tgId;
|
||||
this.subId = subId;
|
||||
this.comment = comment;
|
||||
this.reset = reset;
|
||||
}
|
||||
|
||||
@@ -2283,6 +2223,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
enable: this.enable,
|
||||
tgId: this.tgId,
|
||||
subId: this.subId,
|
||||
comment: this.comment,
|
||||
reset: this.reset,
|
||||
};
|
||||
}
|
||||
@@ -2298,6 +2239,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
json.enable,
|
||||
json.tgId,
|
||||
json.subId,
|
||||
json.comment,
|
||||
json.reset,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ const UTLS_FINGERPRINT = {
|
||||
UTLS_QQ: "qq",
|
||||
UTLS_RANDOM: "random",
|
||||
UTLS_RANDOMIZED: "randomized",
|
||||
UTLS_UNSAFE: "unsafe",
|
||||
};
|
||||
|
||||
const ALPN_OPTION = {
|
||||
@@ -81,6 +82,7 @@ const MODE_OPTION = {
|
||||
AUTO: "auto",
|
||||
PACKET_UP: "packet-up",
|
||||
STREAM_UP: "stream-up",
|
||||
STREAM_ONE: "stream-one",
|
||||
};
|
||||
|
||||
Object.freeze(Protocols);
|
||||
@@ -205,16 +207,23 @@ class KcpStreamSettings extends CommonClass {
|
||||
}
|
||||
|
||||
class WsStreamSettings extends CommonClass {
|
||||
constructor(path = '/', host = '') {
|
||||
constructor(
|
||||
path = '/',
|
||||
host = '',
|
||||
heartbeatPeriod = 0,
|
||||
|
||||
) {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
this.heartbeatPeriod = heartbeatPeriod;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new WsStreamSettings(
|
||||
json.path,
|
||||
json.host,
|
||||
json.heartbeatPeriod,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -222,63 +231,11 @@ class WsStreamSettings extends CommonClass {
|
||||
return {
|
||||
path: this.path,
|
||||
host: this.host,
|
||||
heartbeatPeriod: this.heartbeatPeriod
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class HttpStreamSettings extends CommonClass {
|
||||
constructor(path = '/', host = '') {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new HttpStreamSettings(
|
||||
json.path,
|
||||
json.host ? json.host.join(',') : '',
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
path: this.path,
|
||||
host: ObjectUtil.isEmpty(this.host) ? [''] : this.host.split(','),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class QuicStreamSettings extends CommonClass {
|
||||
constructor(
|
||||
security = 'none',
|
||||
key = '',
|
||||
type = 'none'
|
||||
) {
|
||||
super();
|
||||
this.security = security;
|
||||
this.key = key;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new QuicStreamSettings(
|
||||
json.security,
|
||||
json.key,
|
||||
json.header ? json.header.type : 'none',
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
security: this.security,
|
||||
key: this.key,
|
||||
header: {
|
||||
type: this.type,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GrpcStreamSettings extends CommonClass {
|
||||
constructor(
|
||||
serviceName = "",
|
||||
@@ -326,23 +283,39 @@ class HttpUpgradeStreamSettings extends CommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
class SplitHTTPStreamSettings extends CommonClass {
|
||||
class xHTTPStreamSettings extends CommonClass {
|
||||
constructor(
|
||||
path = '/',
|
||||
host = '',
|
||||
mode = '',
|
||||
noGRPCHeader = false,
|
||||
scMinPostsIntervalMs = "30",
|
||||
xmux = {
|
||||
maxConcurrency: "16-32",
|
||||
maxConnections: 0,
|
||||
cMaxReuseTimes: 0,
|
||||
hMaxRequestTimes: "600-900",
|
||||
hMaxReusableSecs: "1800-3000",
|
||||
hKeepAlivePeriod: 0,
|
||||
},
|
||||
) {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
this.mode = mode;
|
||||
this.noGRPCHeader = noGRPCHeader;
|
||||
this.scMinPostsIntervalMs = scMinPostsIntervalMs;
|
||||
this.xmux = xmux;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new SplitHTTPStreamSettings(
|
||||
return new xHTTPStreamSettings(
|
||||
json.path,
|
||||
json.host,
|
||||
json.mode,
|
||||
json.noGRPCHeader,
|
||||
json.scMinPostsIntervalMs,
|
||||
json.xmux
|
||||
);
|
||||
}
|
||||
|
||||
@@ -351,6 +324,16 @@ class SplitHTTPStreamSettings extends CommonClass {
|
||||
path: this.path,
|
||||
host: this.host,
|
||||
mode: this.mode,
|
||||
noGRPCHeader: this.noGRPCHeader,
|
||||
scMinPostsIntervalMs: this.scMinPostsIntervalMs,
|
||||
xmux: {
|
||||
maxConcurrency: this.xmux.maxConcurrency,
|
||||
maxConnections: this.xmux.maxConnections,
|
||||
cMaxReuseTimes: this.xmux.cMaxReuseTimes,
|
||||
hMaxRequestTimes: this.xmux.hMaxRequestTimes,
|
||||
hMaxReusableSecs: this.xmux.hMaxReusableSecs,
|
||||
hKeepAlivePeriod: this.xmux.hKeepAlivePeriod,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -428,14 +411,14 @@ class SockoptStreamSettings extends CommonClass {
|
||||
tcpFastOpen = false,
|
||||
tcpKeepAliveInterval = 0,
|
||||
tcpMptcp = false,
|
||||
tcpNoDelay = false
|
||||
penetrate = false
|
||||
) {
|
||||
super();
|
||||
this.dialerProxy = dialerProxy;
|
||||
this.tcpFastOpen = tcpFastOpen;
|
||||
this.tcpKeepAliveInterval = tcpKeepAliveInterval;
|
||||
this.tcpMptcp = tcpMptcp;
|
||||
this.tcpNoDelay = tcpNoDelay;
|
||||
this.penetrate = penetrate;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
@@ -445,7 +428,7 @@ class SockoptStreamSettings extends CommonClass {
|
||||
json.tcpFastOpen,
|
||||
json.tcpKeepAliveInterval,
|
||||
json.tcpMptcp,
|
||||
json.tcpNoDelay,
|
||||
json.penetrate,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -455,7 +438,7 @@ class SockoptStreamSettings extends CommonClass {
|
||||
tcpFastOpen: this.tcpFastOpen,
|
||||
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
|
||||
tcpMptcp: this.tcpMptcp,
|
||||
tcpNoDelay: this.tcpNoDelay,
|
||||
penetrate: this.penetrate,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -469,11 +452,9 @@ class StreamSettings extends CommonClass {
|
||||
tcpSettings = new TcpStreamSettings(),
|
||||
kcpSettings = new KcpStreamSettings(),
|
||||
wsSettings = new WsStreamSettings(),
|
||||
httpSettings = new HttpStreamSettings(),
|
||||
quicSettings = new QuicStreamSettings(),
|
||||
grpcSettings = new GrpcStreamSettings(),
|
||||
httpupgradeSettings = new HttpUpgradeStreamSettings(),
|
||||
splithttpSettings = new SplitHTTPStreamSettings(),
|
||||
xhttpSettings = new xHTTPStreamSettings(),
|
||||
sockopt = undefined,
|
||||
) {
|
||||
super();
|
||||
@@ -484,10 +465,9 @@ class StreamSettings extends CommonClass {
|
||||
this.tcp = tcpSettings;
|
||||
this.kcp = kcpSettings;
|
||||
this.ws = wsSettings;
|
||||
this.http = httpSettings;
|
||||
this.grpc = grpcSettings;
|
||||
this.httpupgrade = httpupgradeSettings;
|
||||
this.splithttp = splithttpSettings;
|
||||
this.xhttp = xhttpSettings;
|
||||
this.sockopt = sockopt;
|
||||
}
|
||||
|
||||
@@ -516,11 +496,9 @@ class StreamSettings extends CommonClass {
|
||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||
WsStreamSettings.fromJson(json.wsSettings),
|
||||
HttpStreamSettings.fromJson(json.httpSettings),
|
||||
QuicStreamSettings.fromJson(json.quicSettings),
|
||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||
SplitHTTPStreamSettings.fromJson(json.splithttpSettings),
|
||||
xHTTPStreamSettings.fromJson(json.xhttpSettings),
|
||||
SockoptStreamSettings.fromJson(json.sockopt),
|
||||
);
|
||||
}
|
||||
@@ -535,10 +513,9 @@ class StreamSettings extends CommonClass {
|
||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
||||
splithttpSettings: network === 'splithttp' ? this.splithttp.toJson() : undefined,
|
||||
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
|
||||
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||
};
|
||||
}
|
||||
@@ -576,7 +553,7 @@ class Mux extends CommonClass {
|
||||
class Outbound extends CommonClass {
|
||||
constructor(
|
||||
tag = '',
|
||||
protocol = Protocols.VMess,
|
||||
protocol = Protocols.VLESS,
|
||||
settings = null,
|
||||
streamSettings = new StreamSettings(),
|
||||
sendThrough,
|
||||
@@ -603,7 +580,7 @@ class Outbound extends CommonClass {
|
||||
|
||||
canEnableTls() {
|
||||
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
|
||||
return ["tcp", "ws", "http", "grpc", "httpupgrade", "splithttp"].includes(this.stream.network);
|
||||
return ["tcp", "ws", "http", "grpc", "httpupgrade", "xhttp"].includes(this.stream.network);
|
||||
}
|
||||
|
||||
//this is used for xtls-rprx-vision
|
||||
@@ -616,7 +593,7 @@ class Outbound extends CommonClass {
|
||||
|
||||
canEnableReality() {
|
||||
if (![Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
||||
return ["tcp", "http", "grpc", "splithttp"].includes(this.stream.network);
|
||||
return ["tcp", "http", "grpc", "xhttp"].includes(this.stream.network);
|
||||
}
|
||||
|
||||
canEnableStream() {
|
||||
@@ -714,17 +691,12 @@ class Outbound extends CommonClass {
|
||||
stream.seed = json.path;
|
||||
} else if (network === 'ws') {
|
||||
stream.ws = new WsStreamSettings(json.path, json.host);
|
||||
} else if (network === 'http' || network == 'h2') {
|
||||
stream.network = 'http'
|
||||
stream.http = new HttpStreamSettings(
|
||||
json.path,
|
||||
json.host);
|
||||
} else if (network === 'grpc') {
|
||||
stream.grpc = new GrpcStreamSettings(json.path, json.authority, json.type == 'multi');
|
||||
} else if (network === 'httpupgrade') {
|
||||
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path, json.host);
|
||||
} else if (network === 'splithttp') {
|
||||
stream.splithttp = new SplitHTTPStreamSettings(json.path, json.host, json.mode);
|
||||
} else if (network === 'xhttp') {
|
||||
stream.xhttp = new xHTTPStreamSettings(json.path, json.host, json.mode);
|
||||
}
|
||||
|
||||
if (json.tls && json.tls == 'tls') {
|
||||
@@ -749,6 +721,7 @@ class Outbound extends CommonClass {
|
||||
let headerType = url.searchParams.get('headerType') ?? undefined;
|
||||
let host = url.searchParams.get('host') ?? undefined;
|
||||
let path = url.searchParams.get('path') ?? undefined;
|
||||
let mode = url.searchParams.get('mode') ?? undefined;
|
||||
|
||||
if (type === 'tcp' || type === 'none') {
|
||||
stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path);
|
||||
@@ -758,8 +731,6 @@ class Outbound extends CommonClass {
|
||||
stream.kcp.seed = path;
|
||||
} else if (type === 'ws') {
|
||||
stream.ws = new WsStreamSettings(path, host);
|
||||
} else if (type === 'http' || type == 'h2') {
|
||||
stream.http = new HttpStreamSettings(path, host);
|
||||
} else if (type === 'grpc') {
|
||||
stream.grpc = new GrpcStreamSettings(
|
||||
url.searchParams.get('serviceName') ?? '',
|
||||
@@ -767,8 +738,8 @@ class Outbound extends CommonClass {
|
||||
url.searchParams.get('mode') == 'multi');
|
||||
} else if (type === 'httpupgrade') {
|
||||
stream.httpupgrade = new HttpUpgradeStreamSettings(path, host);
|
||||
} else if (type === 'splithttp') {
|
||||
stream.splithttp = new SplitHTTPStreamSettings(path, host, mode);
|
||||
} else if (type === 'xhttp') {
|
||||
stream.xhttp = new xHTTPStreamSettings(path, host, mode);
|
||||
}
|
||||
|
||||
if (security == 'tls') {
|
||||
|
||||
@@ -26,6 +26,7 @@ class AllSetting {
|
||||
this.xrayTemplateConfig = "";
|
||||
this.secretEnable = false;
|
||||
this.subEnable = false;
|
||||
this.subSyncEnable = true;
|
||||
this.subListen = "";
|
||||
this.subPort = 2096;
|
||||
this.subPath = "/sub/";
|
||||
@@ -43,7 +44,7 @@ class AllSetting {
|
||||
this.subJsonMux = "";
|
||||
this.subJsonRules = "";
|
||||
|
||||
this.timeLocation = "Asia/Tehran";
|
||||
this.timeLocation = "Local";
|
||||
|
||||
if (data == null) {
|
||||
return
|
||||
|
||||
@@ -70,6 +70,41 @@ class HttpUtil {
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
static async jsonPost(url, data) {
|
||||
let msg;
|
||||
try {
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
const resp = await fetch(basePath + url.replace(/^\/+|\/+$/g, ''), requestOptions);
|
||||
const response = await resp.json();
|
||||
|
||||
msg = this._respToMsg({data : response});
|
||||
} catch (e) {
|
||||
msg = new Msg(false, e.toString());
|
||||
}
|
||||
this._handleMsg(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
static async postWithModalJson(url, data, modal) {
|
||||
if (modal) {
|
||||
modal.loading(true);
|
||||
}
|
||||
const msg = await this.jsonPost(url, data);
|
||||
if (modal) {
|
||||
modal.loading(false);
|
||||
if (msg instanceof Msg && msg.success) {
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
class PromiseUtil {
|
||||
|
||||
3
web/assets/moment/moment.min.js
vendored
@@ -1,10 +1,10 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"x-ui/database/model"
|
||||
"x-ui/web/service"
|
||||
"x-ui/web/session"
|
||||
@@ -33,9 +33,13 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/clientIps/:email", a.getClientIps)
|
||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
||||
g.POST("/addClient", a.addInboundClient)
|
||||
g.POST("/addGroupClient", a.addGroupInboundClient)
|
||||
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
||||
g.POST("/delGroupClients", a.delGroupClients)
|
||||
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
||||
g.POST("/updateClients", a.updateGroupInboundClient)
|
||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||
g.POST("/resetGroupClientTraffic", a.resetGroupClientTraffic)
|
||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||
@@ -190,6 +194,34 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) addGroupInboundClient(c *gin.Context) {
|
||||
var requestData []model.Inbound
|
||||
|
||||
err := c.ShouldBindJSON(&requestData)
|
||||
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
|
||||
needRestart := true
|
||||
|
||||
for _, data := range requestData {
|
||||
|
||||
needRestart, err = a.inboundService.AddInboundClient(&data)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
jsonMsg(c, "Client(s) added", nil)
|
||||
if err == nil && needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
@@ -211,6 +243,38 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) delGroupClients(c *gin.Context) {
|
||||
var requestData []struct {
|
||||
InboundID int `json:"inboundId"`
|
||||
ClientID string `json:"clientId"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&requestData); err != nil {
|
||||
jsonMsg(c, "Invalid request data", err)
|
||||
return
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
|
||||
for _, req := range requestData {
|
||||
needRestartTmp, err := a.inboundService.DelInboundClient(req.InboundID, req.ClientID)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Failed to delete client", err)
|
||||
return
|
||||
}
|
||||
|
||||
if needRestartTmp {
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
|
||||
jsonMsg(c, "Clients deleted successfully", nil)
|
||||
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||
clientId := c.Param("clientId")
|
||||
|
||||
@@ -234,6 +298,56 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) updateGroupInboundClient(c *gin.Context) {
|
||||
var requestData []map[string]interface{}
|
||||
|
||||
if err := c.ShouldBindJSON(&requestData); err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
|
||||
for _, item := range requestData {
|
||||
|
||||
inboundMap, ok := item["inbound"].(map[string]interface{})
|
||||
if !ok {
|
||||
jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'inbound' to map"))
|
||||
return
|
||||
}
|
||||
|
||||
clientId, ok := item["clientId"].(string)
|
||||
if !ok {
|
||||
jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'clientId' to string"))
|
||||
return
|
||||
}
|
||||
|
||||
inboundJSON, err := json.Marshal(inboundMap)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
|
||||
var inboundModel model.Inbound
|
||||
if err := json.Unmarshal(inboundJSON, &inboundModel); err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
|
||||
if restart, err := a.inboundService.UpdateInboundClient(&inboundModel, clientId); err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
} else {
|
||||
needRestart = needRestart || restart
|
||||
}
|
||||
}
|
||||
|
||||
jsonMsg(c, "Client updated", nil)
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
@@ -253,6 +367,44 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) resetGroupClientTraffic(c *gin.Context) {
|
||||
var requestData []struct {
|
||||
InboundID int `json:"inboundId"` // Map JSON "inboundId" to struct field "InboundID"
|
||||
Email string `json:"email"` // Map JSON "email" to struct field "Email"
|
||||
}
|
||||
|
||||
// Parse JSON body directly using ShouldBindJSON
|
||||
if err := c.ShouldBindJSON(&requestData); err != nil {
|
||||
jsonMsg(c, "Invalid request data", err)
|
||||
return
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
|
||||
// Process each request data
|
||||
for _, req := range requestData {
|
||||
needRestartTmp, err := a.inboundService.ResetClientTraffic(req.InboundID, req.Email)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Failed to reset client traffic", err)
|
||||
return
|
||||
}
|
||||
|
||||
// If any request requires a restart, set needRestart to true
|
||||
if needRestartTmp {
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
|
||||
// Send response back to the client
|
||||
jsonMsg(c, "Traffic reset for all clients", nil)
|
||||
|
||||
// Restart the service if required
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
||||
err := a.inboundService.ResetAllTraffics()
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"x-ui/web/service"
|
||||
"x-ui/web/session"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -49,8 +50,8 @@ func (a *IndexController) index(c *gin.Context) {
|
||||
|
||||
func (a *IndexController) login(c *gin.Context) {
|
||||
var form LoginForm
|
||||
err := c.ShouldBind(&form)
|
||||
if err != nil {
|
||||
|
||||
if err := c.ShouldBind(&form); err != nil {
|
||||
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
||||
return
|
||||
}
|
||||
@@ -68,29 +69,31 @@ func (a *IndexController) login(c *gin.Context) {
|
||||
safeUser := template.HTMLEscapeString(form.Username)
|
||||
safePass := template.HTMLEscapeString(form.Password)
|
||||
safeSecret := template.HTMLEscapeString(form.LoginSecret)
|
||||
|
||||
if user == nil {
|
||||
logger.Warningf("wrong username or password or secret: \"%s\" \"%s\" \"%s\"", safeUser, safePass, safeSecret)
|
||||
logger.Warningf("wrong username: \"%s\", password: \"%s\", secret: \"%s\", IP: \"%s\"", safeUser, safePass, safeSecret, getRemoteIp(c))
|
||||
a.tgbot.UserLoginNotify(safeUser, safePass, getRemoteIp(c), timeStr, 0)
|
||||
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||
return
|
||||
} else {
|
||||
logger.Infof("%s logged in successfully, Ip Address: %s\n", safeUser, getRemoteIp(c))
|
||||
a.tgbot.UserLoginNotify(safeUser, ``, getRemoteIp(c), timeStr, 1)
|
||||
}
|
||||
|
||||
logger.Infof("%s logged in successfully, Ip Address: %s\n", safeUser, getRemoteIp(c))
|
||||
a.tgbot.UserLoginNotify(safeUser, ``, getRemoteIp(c), timeStr, 1)
|
||||
|
||||
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
||||
if err != nil {
|
||||
logger.Warning("Unable to get session's max age from DB")
|
||||
}
|
||||
|
||||
err = session.SetMaxAge(c, sessionMaxAge*60)
|
||||
if err != nil {
|
||||
logger.Warning("Unable to set session's max age")
|
||||
session.SetMaxAge(c, sessionMaxAge*60)
|
||||
session.SetLoginUser(c, user)
|
||||
if err := sessions.Default(c).Save(); err != nil {
|
||||
logger.Warning("Unable to save session: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = session.SetLoginUser(c, user)
|
||||
logger.Infof("%s logged in successfully", user.Username)
|
||||
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
|
||||
logger.Infof("%s logged in successfully", safeUser)
|
||||
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), nil)
|
||||
}
|
||||
|
||||
func (a *IndexController) logout(c *gin.Context) {
|
||||
@@ -99,6 +102,9 @@ func (a *IndexController) logout(c *gin.Context) {
|
||||
logger.Infof("%s logged out successfully", user.Username)
|
||||
}
|
||||
session.ClearSession(c)
|
||||
if err := sessions.Default(c).Save(); err != nil {
|
||||
logger.Warning("Unable to save session after clearing:", err)
|
||||
}
|
||||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ type AllSetting struct {
|
||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||
SubSyncEnable bool `json:"subSyncEnable" form:"subSyncEnable"`
|
||||
SubListen string `json:"subListen" form:"subListen"`
|
||||
SubPort int `json:"subPort" form:"subPort"`
|
||||
SubPath string `json:"subPath" form:"subPath"`
|
||||
|
||||
@@ -23,13 +23,15 @@
|
||||
</tr-qr-bg>
|
||||
</tr-qr-box>
|
||||
</template>
|
||||
<template v-for="(row, index) in qrModal.qrcodes">
|
||||
<tr-qr-box class="qr-box">
|
||||
<a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
|
||||
<tr-qr-bg class="qr-bg">
|
||||
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas>
|
||||
</tr-qr-bg>
|
||||
</tr-qr-box>
|
||||
<template v-if="!isJustSub">
|
||||
<template v-for="(row, index) in qrModal.qrcodes">
|
||||
<tr-qr-box class="qr-box">
|
||||
<a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
|
||||
<tr-qr-bg class="qr-bg">
|
||||
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas>
|
||||
</tr-qr-bg>
|
||||
</tr-qr-box>
|
||||
</template>
|
||||
</template>
|
||||
</tr-qr-modal>
|
||||
</a-modal>
|
||||
@@ -43,12 +45,14 @@
|
||||
qrcodes: [],
|
||||
clipboard: null,
|
||||
visible: false,
|
||||
isJustSub: false,
|
||||
subId: '',
|
||||
show: function(title = '', dbInbound, client) {
|
||||
show: function(title = '', dbInbound, client, isJustSub = false) {
|
||||
this.title = title;
|
||||
this.dbInbound = dbInbound;
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.client = client;
|
||||
this.isJustSub = isJustSub;
|
||||
this.subId = '';
|
||||
this.qrcodes = [];
|
||||
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
||||
@@ -76,7 +80,9 @@
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#qrcode-modal',
|
||||
data: {
|
||||
qrModal: qrModal,
|
||||
qrModal: qrModal,get isJustSub(){
|
||||
return qrModal.isJustSub
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
copyToClipboard(elementId, content) {
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
|
||||
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||
<a-input-number v-model.number="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
|
||||
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||
<a-input-number v-model.number="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
|
||||
<a-input v-model.trim="clientsBulkModal.emailPrefix"></a-input>
|
||||
@@ -26,7 +26,7 @@
|
||||
<a-input v-model.trim="clientsBulkModal.emailPostfix"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
|
||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||
<a-input-number v-model.number="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "security" }}' v-if="inbound.protocol === Protocols.VMESS">
|
||||
<a-select v-model="clientsBulkModal.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
@@ -61,7 +61,7 @@
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number style="width: 50%" v-model="clientsBulkModal.tgId" min="0"></a-input-number>
|
||||
<a-input-number style="width: 50%" v-model.number="clientsBulkModal.tgId" min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="app.ipLimitEnable">
|
||||
<template slot="label">
|
||||
@@ -73,7 +73,7 @@
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model="clientsBulkModal.limitIp" min="0"></a-input-number>
|
||||
<a-input-number v-model.number="clientsBulkModal.limitIp" min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
@@ -85,7 +85,7 @@
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||
<a-input-number v-model.number="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||
|
||||
@@ -16,7 +16,16 @@
|
||||
title: '',
|
||||
okText: '',
|
||||
isEdit: false,
|
||||
group: {
|
||||
canGroup: true,
|
||||
isGroup: false,
|
||||
currentClient: null,
|
||||
inbounds: [],
|
||||
clients: [],
|
||||
editIds: []
|
||||
},
|
||||
dbInbound: new DBInbound(),
|
||||
dbInbounds: null,
|
||||
inbound: new Inbound(),
|
||||
clients: [],
|
||||
clientStats: [],
|
||||
@@ -25,33 +34,126 @@
|
||||
clientIps: null,
|
||||
delayedStart: false,
|
||||
ok() {
|
||||
if (clientModal.isEdit) {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
||||
if (app.subSettings.enable && clientModal.group.isGroup && clientModal.group.canGroup) {
|
||||
const currentClient = clientModal.group.currentClient;
|
||||
const { limitIp, comment, totalGB, expiryTime, reset, enable, subId, tgId, flow } = currentClient;
|
||||
const uniqueEmails = clientModalApp.makeGroupEmailsUnique(clientModal.dbInbounds, currentClient.email, clientModal.group.clients);
|
||||
|
||||
clientModal.group.clients.forEach((client, index) => {
|
||||
client.email = uniqueEmails[index];
|
||||
client.limitIp = limitIp;
|
||||
client.comment = comment;
|
||||
client.totalGB = totalGB;
|
||||
client.expiryTime = expiryTime;
|
||||
client.reset = reset;
|
||||
client.enable = enable;
|
||||
|
||||
if (subId) {
|
||||
client.subId = subId;
|
||||
}
|
||||
if (tgId) {
|
||||
client.tgId = tgId;
|
||||
}
|
||||
if (flow) {
|
||||
client.flow = flow;
|
||||
}
|
||||
});
|
||||
|
||||
if (clientModal.isEdit) {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds, clientModal.group.editIds);
|
||||
} else {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds);
|
||||
}
|
||||
} else {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||
if (clientModal.isEdit) {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
||||
} else {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) {
|
||||
show({
|
||||
title = '',
|
||||
okText = '{{ i18n "sure" }}',
|
||||
index = null,
|
||||
dbInbound = null,
|
||||
dbInbounds = null,
|
||||
confirm = () => {
|
||||
},
|
||||
isEdit = false
|
||||
}) {
|
||||
this.group = {
|
||||
canGroup: true,
|
||||
isGroup: false,
|
||||
currentClient: null,
|
||||
inbounds: [],
|
||||
clients: [],
|
||||
editIds: []
|
||||
}
|
||||
this.dbInbounds = dbInbounds;
|
||||
this.visible = true;
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.isEdit = isEdit;
|
||||
this.dbInbound = new DBInbound(dbInbound);
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.clients = this.inbound.clients;
|
||||
this.index = index === null ? this.clients.length : index;
|
||||
this.delayedStart = false;
|
||||
if (isEdit) {
|
||||
if (this.clients[index].expiryTime < 0) {
|
||||
this.delayedStart = true;
|
||||
if (app.subSettings.enable && dbInbounds !== null && Array.isArray(dbInbounds)) {
|
||||
if (isEdit) {
|
||||
this.showProcess(dbInbound, index);
|
||||
let processSingleEdit = true
|
||||
if (this.group.canGroup) {
|
||||
this.group.currentClient = this.clients[this.index]
|
||||
const response = app.getSubGroupClients(dbInbounds, this.group.currentClient)
|
||||
if (response.clients.length > 1) {
|
||||
this.group.isGroup = true;
|
||||
this.group.inbounds = response.inbounds
|
||||
this.group.clients = response.clients
|
||||
this.group.editIds = response.editIds
|
||||
if (this.clients[index].expiryTime < 0) {
|
||||
this.delayedStart = true;
|
||||
}
|
||||
processSingleEdit = false
|
||||
}
|
||||
}
|
||||
if (processSingleEdit) {
|
||||
this.singleEditClientProcess(index)
|
||||
}
|
||||
} else {
|
||||
this.group.isGroup = true;
|
||||
dbInbounds.forEach((dbInboundItem) => {
|
||||
this.showProcess(dbInboundItem);
|
||||
if (this.dbInbound.isMultiUser()) {
|
||||
this.addClient(this.inbound.protocol, this.clients);
|
||||
this.group.inbounds.push(dbInboundItem.id)
|
||||
this.group.clients.push(this.clients[this.index])
|
||||
}
|
||||
})
|
||||
this.group.currentClient = this.clients[this.index]
|
||||
}
|
||||
this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||
} else {
|
||||
this.addClient(this.inbound.protocol, this.clients);
|
||||
this.showProcess(dbInbound, index);
|
||||
if (isEdit) {
|
||||
this.singleEditClientProcess(index)
|
||||
} else {
|
||||
this.addClient(this.inbound.protocol, this.clients);
|
||||
}
|
||||
}
|
||||
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
|
||||
this.confirm = confirm;
|
||||
},
|
||||
},
|
||||
showProcess(dbInbound, index = null) {
|
||||
this.dbInbound = new DBInbound(dbInbound);
|
||||
this.inbound = dbInbound.toInbound();
|
||||
if (this.dbInbound.isMultiUser()) {
|
||||
this.clients = this.inbound.clients;
|
||||
this.index = index === null ? this.clients.length : index;
|
||||
this.delayedStart = false;
|
||||
}
|
||||
},
|
||||
singleEditClientProcess(index) {
|
||||
if (this.clients[index].expiryTime < 0) {
|
||||
this.delayedStart = true;
|
||||
}
|
||||
this.oldClientId = this.getClientId(this.dbInbound.protocol, this.clients[index]);
|
||||
},
|
||||
getClientId(protocol, client) {
|
||||
switch (protocol) {
|
||||
case Protocols.TROJAN: return client.password;
|
||||
@@ -72,7 +174,7 @@
|
||||
clientModal.visible = false;
|
||||
clientModal.loading(false);
|
||||
},
|
||||
loading(loading=true) {
|
||||
loading(loading = true) {
|
||||
clientModal.confirmLoading = loading;
|
||||
},
|
||||
};
|
||||
@@ -94,6 +196,18 @@
|
||||
get isEdit() {
|
||||
return this.clientModal.isEdit;
|
||||
},
|
||||
get isGroup() {
|
||||
return this.clientModal.group.isGroup;
|
||||
},
|
||||
get isGroupEdit() {
|
||||
return this.clientModal.group.canGroup;
|
||||
},
|
||||
set isGroupEdit(value) {
|
||||
this.clientModal.group.canGroup = value;
|
||||
if (!value) {
|
||||
this.clientModal.singleEditClientProcess(this.clientModal.index)
|
||||
}
|
||||
},
|
||||
get datepicker() {
|
||||
return app.datepicker;
|
||||
},
|
||||
@@ -120,6 +234,36 @@
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
makeGroupEmailsUnique(dbInbounds, baseEmail, groupClients) {
|
||||
// Extract the base part of the email (before the "__" if present)
|
||||
const match = baseEmail.match(/^(.*?)__/);
|
||||
const base = match ? match[1] : baseEmail;
|
||||
|
||||
// Generate initial emails for each client in the group
|
||||
const generatedEmails = groupClients.map((_, index) => `${base}__${index + 1}`);
|
||||
|
||||
// Function to check if an email already exists in dbInbounds but belongs to a different subId
|
||||
const isDuplicate = (emailToCheck, clientSubId) => {
|
||||
return dbInbounds.some((dbInbound) => {
|
||||
const settings = JSON.parse(dbInbound.settings);
|
||||
const clients = settings && settings.clients ? settings.clients : [];
|
||||
return clients.some(client => client.email === emailToCheck && client.subId !== clientSubId);
|
||||
});
|
||||
};
|
||||
|
||||
// Check if any of the generated emails are duplicates
|
||||
const hasDuplicates = generatedEmails.some((email, index) => {
|
||||
return isDuplicate(email, groupClients[index].subId);
|
||||
});
|
||||
|
||||
// If duplicates exist, add a random string to the base email to ensure uniqueness
|
||||
if (hasDuplicates) {
|
||||
const randomString = `-${RandomUtil.randomLowerAndNum(4)}`;
|
||||
return groupClients.map((_, index) => `${base}${randomString}__${index + 1}`);
|
||||
}
|
||||
|
||||
return generatedEmails;
|
||||
},
|
||||
async getDBClientIps(email) {
|
||||
const msg = await HttpUtil.post(`/panel/inbound/clientIps/${email}`);
|
||||
if (!msg.success) {
|
||||
@@ -147,7 +291,22 @@
|
||||
} catch (error) {
|
||||
}
|
||||
},
|
||||
resetClientTraffic(email, dbInboundId, iconElement) {
|
||||
async resetClientTrafficHandler(client, dbInboundId, clients = []) {
|
||||
if (clients.length > 0) {
|
||||
const resetRequests = clients
|
||||
.filter(client => {
|
||||
const inbound = clientModal.dbInbounds.find(inbound => inbound.id === client.inboundId);
|
||||
return inbound && app.hasClientStats(inbound, client.email);
|
||||
}).map(client => ({ inboundId: client.inboundId, email: client.email}));
|
||||
|
||||
return HttpUtil.postWithModalJson('/panel/inbound/resetGroupClientTraffic', resetRequests, null)
|
||||
} else {
|
||||
return HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email)
|
||||
}
|
||||
},
|
||||
resetClientTraffic(client, dbInboundId, iconElement) {
|
||||
const subGroup = app.subSettings.enable && clientModal.group.isGroup && clientModal.group.canGroup && clientModal.dbInbounds && clientModal.dbInbounds.length > 0 ? app.getSubGroupClients(clientModal.dbInbounds, client) : [];
|
||||
const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : [];
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||
@@ -156,8 +315,8 @@
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: async () => {
|
||||
iconElement.disabled = true;
|
||||
const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
|
||||
if (msg.success) {
|
||||
const msg = await this.resetClientTrafficHandler(client, dbInboundId, clients);
|
||||
if (msg && msg.success) {
|
||||
this.clientModal.clientStats.up = 0;
|
||||
this.clientModal.clientStats.down = 0;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.fakedns.poolSize" }}'>
|
||||
<a-input-number style="width: 100%;" type="number" min="1" v-model.trim="fakednsModal.fakeDns.poolSize"></a-input-number>
|
||||
<a-input-number v-model.number="fakednsModal.fakeDns.poolSize" :min="1"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
||||
<a-switch v-model="client.enable"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="isEdit && app.subSettings.enable && isGroup">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.client.isGroupEditDesc" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.client.isGroupEdit" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-switch v-model="isGroupEdit"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
@@ -66,7 +78,10 @@
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number style="width: 50%" v-model="client.tgId" min="0"></a-input-number>
|
||||
<a-input-number style="width: 50%" v-model.number="client.tgId" min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email" label='{{ i18n "comment" }}'>
|
||||
<a-input v-model.trim="client.comment"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="app.ipLimitEnable">
|
||||
<template slot="label">
|
||||
@@ -78,7 +93,7 @@
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||
<a-input-number v-model.number="client.limitIp" min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="app.ipLimitEnable && client.limitIp > 0 && client.email && isEdit">
|
||||
<template slot="label">
|
||||
@@ -120,7 +135,7 @@
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||
<a-input-number v-model.number="client._totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
|
||||
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
|
||||
@@ -131,7 +146,7 @@
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||
<a-icon type="retweet"
|
||||
@click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
|
||||
@click="resetClientTraffic(client,clientStats.inboundId,$event.target)"
|
||||
v-if="client.email.length > 0"></a-icon>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
|
||||
<a-input-number v-model.number="dbInbound.totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
|
||||
@@ -270,10 +270,9 @@
|
||||
<a-select-option value="tcp">TCP (RAW)</a-select-option>
|
||||
<a-select-option value="kcp">mKCP</a-select-option>
|
||||
<a-select-option value="ws">WebSocket</a-select-option>
|
||||
<a-select-option value="http">HTTP</a-select-option>
|
||||
<a-select-option value="grpc">gRPC</a-select-option>
|
||||
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
||||
<a-select-option value="splithttp">SplitHTTP (XHTTP)</a-select-option>
|
||||
<a-select-option value="xhttp">XHTTP</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<template v-if="outbound.stream.network === 'tcp'">
|
||||
@@ -337,15 +336,8 @@
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- http -->
|
||||
<template v-if="outbound.stream.network === 'http'">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model.trim="outbound.stream.http.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="outbound.stream.http.path"></a-input>
|
||||
<a-form-item label='Heartbeat Period'>
|
||||
<a-input-number v-model.number="outbound.stream.ws.heartbeatPeriod" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
@@ -372,19 +364,43 @@
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- splithttp -->
|
||||
<template v-if="outbound.stream.network === 'splithttp'">
|
||||
<!-- xhttp -->
|
||||
<template v-if="outbound.stream.network === 'xhttp'">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model="outbound.stream.splithttp.host"></a-input>
|
||||
<a-input v-model="outbound.stream.xhttp.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="outbound.stream.splithttp.path"></a-input>
|
||||
<a-input v-model.trim="outbound.stream.xhttp.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Mode'>
|
||||
<a-select v-model="outbound.stream.splithttp.mode" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select v-model="outbound.stream.xhttp.mode" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="No gRPC Header" v-if="outbound.stream.xhttp.mode === 'stream-up' || outbound.stream.xhttp.mode === 'stream-one'">
|
||||
<a-switch v-model="outbound.stream.xhttp.noGRPCHeader"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Min Upload Interval (Ms)" v-if="outbound.stream.xhttp.mode === 'packet-up'">
|
||||
<a-input v-model.trim="outbound.stream.xhttp.scMinPostsIntervalMs"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Concurrency" v-if="!outbound.stream.xhttp.xmux.maxConnections">
|
||||
<a-input v-model="outbound.stream.xhttp.xmux.maxConcurrency"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Connections" v-if="!outbound.stream.xhttp.xmux.maxConcurrency">
|
||||
<a-input v-model="outbound.stream.xhttp.xmux.maxConnections"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Reuse Times">
|
||||
<a-input v-model="outbound.stream.xhttp.xmux.cMaxReuseTimes"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Request Times">
|
||||
<a-input v-model="outbound.stream.xhttp.xmux.hMaxRequestTimes"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Reusable Secs">
|
||||
<a-input v-model="outbound.stream.xhttp.xmux.hMaxReusableSecs"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Keep Alive Period'>
|
||||
<a-input-number v-model.number="outbound.stream.xhttp.xmux.hKeepAlivePeriod"></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -453,13 +469,13 @@
|
||||
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Keep Alive Interval">
|
||||
<a-input-number v-model="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||
<a-input-number v-model.number="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="Multipath TCP">
|
||||
<a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP No-Delay" v-if="outbound.stream.sockopt.tcpMptcp">
|
||||
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch>
|
||||
<a-form-item label="Penetrate">
|
||||
<a-switch v-model="outbound.stream.sockopt.penetrate"></a-switch>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
@@ -470,10 +486,10 @@
|
||||
</a-form-item>
|
||||
<template v-if="outbound.mux.enabled">
|
||||
<a-form-item label="Concurrency">
|
||||
<a-input-number v-model="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
|
||||
<a-input-number v-model.number="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="xudp Concurrency">
|
||||
<a-input-number v-model="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
|
||||
<a-input-number v-model.number="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="xudp UDP 443">
|
||||
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</table>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||
<template v-if="inbound.isTcp">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="Fallbacks">
|
||||
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
|
||||
@@ -42,7 +42,7 @@
|
||||
<a-input v-model="fallback.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='xVer'>
|
||||
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-divider style="margin:5px 0;"></a-divider>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</table>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||
<template v-if="inbound.isTcp">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="Fallbacks">
|
||||
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
|
||||
@@ -42,7 +42,7 @@
|
||||
<a-input v-model="fallback.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='xVer'>
|
||||
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-divider style="margin:5px 0;"></a-divider>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{{define "form/streamHTTP"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">{{ i18n "host" }}
|
||||
<a-button icon="plus" size="small" @click="inbound.stream.http.addHost()"></a-button>
|
||||
</template>
|
||||
<template v-for="(host, index) in inbound.stream.http.host">
|
||||
<a-input v-model.trim="inbound.stream.http.host[index]">
|
||||
<a-button icon="minus" size="small" slot="addonAfter" @click="inbound.stream.http.removeHost(index)" v-if="inbound.stream.http.host.length>1"></a-button>
|
||||
</a-input>
|
||||
</template>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -10,7 +10,7 @@
|
||||
<a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||
<a-button icon="plus" size="small" @click="inbound.stream.httpupgrade.addHeader('host', '')"></a-button>
|
||||
<a-button icon="plus" size="small" @click="inbound.stream.httpupgrade.addHeader('', '')"></a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers">
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
<a-select-option value="tcp">TCP (RAW)</a-select-option>
|
||||
<a-select-option value="kcp">mKCP</a-select-option>
|
||||
<a-select-option value="ws">WebSocket</a-select-option>
|
||||
<a-select-option value="http">HTTP</a-select-option>
|
||||
<a-select-option value="grpc">gRPC</a-select-option>
|
||||
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
||||
<a-select-option value="splithttp">SplitHTTP (XHTTP)</a-select-option>
|
||||
<a-select-option value="xhttp">XHTTP</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -30,11 +29,6 @@
|
||||
{{template "form/streamWS"}}
|
||||
</template>
|
||||
|
||||
<!-- http -->
|
||||
<template v-if="inbound.stream.network === 'http'">
|
||||
{{template "form/streamHTTP"}}
|
||||
</template>
|
||||
|
||||
<!-- grpc -->
|
||||
<template v-if="inbound.stream.network === 'grpc'">
|
||||
{{template "form/streamGRPC"}}
|
||||
@@ -45,9 +39,9 @@
|
||||
{{template "form/streamHTTPUpgrade"}}
|
||||
</template>
|
||||
|
||||
<!-- splithttp -->
|
||||
<template v-if="inbound.stream.network === 'splithttp'">
|
||||
{{template "form/streamSplitHTTP"}}
|
||||
<!-- xhttp -->
|
||||
<template v-if="inbound.stream.network === 'xhttp'">
|
||||
{{template "form/streamXHTTP"}}
|
||||
</template>
|
||||
|
||||
<!-- sockopt -->
|
||||
|
||||
@@ -6,22 +6,22 @@
|
||||
</a-form-item>
|
||||
<template v-if="inbound.stream.sockoptSwitch">
|
||||
<a-form-item label="Route Mark">
|
||||
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Keep Alive Interval">
|
||||
<a-input-number v-model="inbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Keep Alive Idle">
|
||||
<a-input-number v-model="inbound.stream.sockopt.tcpKeepAliveIdle" :min="0"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveIdle" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Max Seg">
|
||||
<a-input-number v-model="inbound.stream.sockopt.tcpMaxSeg" :min="0"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.tcpMaxSeg" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP User Timeout">
|
||||
<a-input-number v-model="inbound.stream.sockopt.tcpUserTimeout" :min="0"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.tcpUserTimeout" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Window Clamp">
|
||||
<a-input-number v-model="inbound.stream.sockopt.tcpWindowClamp" :min="0"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.tcpWindowClamp" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="Proxy Protocol">
|
||||
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||
@@ -32,8 +32,8 @@
|
||||
<a-form-item label="Multipath TCP">
|
||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP No-Delay" v-if="inbound.stream.sockopt.tcpMptcp">
|
||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpNoDelay"></a-switch>
|
||||
<a-form-item label="Penetrate">
|
||||
<a-switch v-model.trim="inbound.stream.sockopt.penetrate"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="V6 Only">
|
||||
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
{{define "form/streamSplitHTTP"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model.trim="inbound.stream.splithttp.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.splithttp.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||
<a-button icon="plus" size="small" @click="inbound.stream.splithttp.addHeader('host', '')"></a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.splithttp.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<a-button slot="addonAfter" size="small"
|
||||
@click="inbound.stream.splithttp.removeHeader(index)">-</a-button>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label='Mode'>
|
||||
<a-select v-model="inbound.stream.splithttp.mode" style="width: 50%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Concurrent Upload">
|
||||
<a-input v-model.trim="inbound.stream.splithttp.scMaxConcurrentPosts"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Upload Size (Byte)">
|
||||
<a-input v-model.trim="inbound.stream.splithttp.scMaxEachPostBytes"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Min Upload Interval (Ms)">
|
||||
<a-input v-model.trim="inbound.stream.splithttp.scMinPostsIntervalMs"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Padding Bytes">
|
||||
<a-input v-model.trim="inbound.stream.splithttp.xPaddingBytes"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="No SSE Header">
|
||||
<a-switch v-model="inbound.stream.splithttp.noSSEHeader"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Concurrency" v-if="!inbound.stream.splithttp.xmux.maxConnections">
|
||||
<a-input v-model="inbound.stream.splithttp.xmux.maxConcurrency"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Connections" v-if="!inbound.stream.splithttp.xmux.maxConcurrency">
|
||||
<a-input v-model="inbound.stream.splithttp.xmux.maxConnections"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Reuse Times">
|
||||
<a-input v-model="inbound.stream.splithttp.xmux.cMaxReuseTimes"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Lifetime (ms)">
|
||||
<a-input v-model="inbound.stream.splithttp.xmux.cMaxLifetimeMs"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -9,8 +9,11 @@
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Heartbeat Period'>
|
||||
<a-input-number v-model.number="inbound.stream.ws.heartbeatPeriod" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||
<a-button icon="plus" size="small" @click="inbound.stream.ws.addHeader('host', '')"></a-button>
|
||||
<a-button icon="plus" size="small" @click="inbound.stream.ws.addHeader('', '')"></a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||
|
||||
46
web/html/xui/form/stream/stream_xhttp.html
Normal file
@@ -0,0 +1,46 @@
|
||||
{{define "form/streamXHTTP"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model.trim="inbound.stream.xhttp.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.xhttp.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||
<a-button icon="plus" size="small" @click="inbound.stream.xhttp.addHeader('', '')"></a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.xhttp.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.xhttp.removeHeader(index)"></a-button>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label='Mode'>
|
||||
<a-select v-model="inbound.stream.xhttp.mode" style="width: 50%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Buffered Upload" v-if="inbound.stream.xhttp.mode === 'packet-up'">
|
||||
<a-input-number v-model.number="inbound.stream.xhttp.scMaxBufferedPosts"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Upload Size (Byte)" v-if="inbound.stream.xhttp.mode === 'packet-up'">
|
||||
<a-input v-model.trim="inbound.stream.xhttp.scMaxEachPostBytes"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Stream-Up Server" v-if="inbound.stream.xhttp.mode === 'stream-up'">
|
||||
<a-input v-model.trim="inbound.stream.xhttp.scStreamUpServerSecs"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Padding Bytes">
|
||||
<a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="No SSE Header">
|
||||
<a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -57,6 +57,9 @@
|
||||
<a-form-item label="Session Resumption">
|
||||
<a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Server Name To Verify">
|
||||
<a-input v-model.trim="inbound.stream.tls.serverNameToVerify"></a-input>
|
||||
</a-form-item>
|
||||
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||
<a-form-item label='{{ i18n "certificate" }}'>
|
||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<template slot="title">{{ i18n "info" }}</template>
|
||||
<a-icon style="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<a-tooltip v-if="hasClientStats(record, client.email)">
|
||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)" title='{{ i18n "pages.inbounds.resetTrafficContent"}}' :overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "reset"}}' cancel-text='{{ i18n "cancel"}}'>
|
||||
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: var(--color-primary-100)' : 'color: var(--color-primary-100)'"></a-icon>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<a-tag color="green">[[ inbound.network ]]</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade || inbound.isSplithttp">
|
||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isHttpupgrade || inbound.isXHTTP">
|
||||
<tr>
|
||||
<td>{{ i18n "host" }}</td>
|
||||
<td v-if="inbound.host">
|
||||
@@ -58,11 +58,11 @@
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-if="inbound.isSplithttp">
|
||||
<template v-if="inbound.isXHTTP">
|
||||
<tr>
|
||||
<td>Mode</td>
|
||||
<td>
|
||||
<a-tag>[[ inbound.stream.splithttp.mode ]]</a-tag>
|
||||
<a-tag>[[ inbound.stream.xhttp.mode ]]</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
@@ -185,6 +185,33 @@
|
||||
<a-tag>↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="infoModal.clientSettings.comment">
|
||||
<td>{{ i18n "comment" }}</td>
|
||||
<td>
|
||||
<a-tooltip :title="[[ infoModal.clientSettings.comment ]]">
|
||||
<a-tag class="info-large-tag">[[ infoModal.clientSettings.comment ]]</a-tag>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="app.ipLimitEnable">
|
||||
<td>{{ i18n "pages.inbounds.IPLimit" }}</td>
|
||||
<td>
|
||||
<a-tag>[[ infoModal.clientSettings.limitIp ]]</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="app.ipLimitEnable">
|
||||
<td>{{ i18n "pages.inbounds.IPLimitlog" }}</td>
|
||||
<td>
|
||||
<a-tag>[[ infoModal.clientIps ]]</a-tag>
|
||||
<a-icon type="sync" :spin="refreshing" @click="refreshIPs" style="margin: 0 5px;"></a-icon>
|
||||
<a-tooltip :title="[[ dbInbound.address ]]">
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
||||
</template>
|
||||
<a-icon type="delete" @click="clearClientIps"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="display: inline-table; margin-block: 10px; width: 100%; text-align: center;">
|
||||
<tr>
|
||||
@@ -400,7 +427,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<tr-info-row v-for="(link,index) in infoModal.links" class="tr-info-row">
|
||||
<tr-info-row class="tr-info-row">
|
||||
<tr-info-title class="tr-info-title">
|
||||
<a-tag color="blue">Config</a-tag>
|
||||
<a-tooltip title='{{ i18n "copy" }}'>
|
||||
@@ -417,6 +444,18 @@
|
||||
</template>
|
||||
</a-modal>
|
||||
<script>
|
||||
function refreshIPs(email) {
|
||||
return HttpUtil.post(`/panel/inbound/clientIps/${email}`).then((msg) => {
|
||||
if (msg.success) {
|
||||
try {
|
||||
return JSON.parse(msg.obj).join(', ');
|
||||
} catch (e) {
|
||||
return msg.obj;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const infoModal = {
|
||||
visible: false,
|
||||
inbound: new Inbound(),
|
||||
@@ -431,6 +470,7 @@
|
||||
isExpired: false,
|
||||
subLink: '',
|
||||
subJsonLink: '',
|
||||
clientIps: '',
|
||||
show(dbInbound, index) {
|
||||
this.index = index;
|
||||
this.inbound = dbInbound.toInbound();
|
||||
@@ -438,6 +478,12 @@
|
||||
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
||||
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index) : this.dbInbound.isExpiry;
|
||||
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||
|
||||
if (app.ipLimitEnable && this.clientSettings.limitIp) {
|
||||
refreshIPs(this.clientStats.email).then((ips) => {
|
||||
this.clientIps = ips;
|
||||
})
|
||||
}
|
||||
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
||||
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
|
||||
} else {
|
||||
@@ -466,6 +512,7 @@
|
||||
el: '#inbound-info-modal',
|
||||
data: {
|
||||
infoModal,
|
||||
refreshing: false,
|
||||
get dbInbound() {
|
||||
return this.infoModal.dbInbound;
|
||||
},
|
||||
@@ -502,6 +549,26 @@
|
||||
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down;
|
||||
return remained > 0 ? sizeFormat(remained) : '-';
|
||||
},
|
||||
refreshIPs() {
|
||||
this.refreshing = true;
|
||||
refreshIPs(this.infoModal.clientStats.email)
|
||||
.then((ips) => {
|
||||
this.infoModal.clientIps = ips;
|
||||
})
|
||||
.finally(() => {
|
||||
this.refreshing = false;
|
||||
});
|
||||
},
|
||||
clearClientIps() {
|
||||
HttpUtil.post(`/panel/inbound/clearClientIps/${this.infoModal.clientStats.email}`)
|
||||
.then((msg) => {
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
this.infoModal.clientIps = 'No IP Record';
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
confirmLoading: false,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
isEdit: false,
|
||||
isGroup: false,
|
||||
confirm: null,
|
||||
inbound: new Inbound(),
|
||||
dbInbound: new DBInbound(),
|
||||
@@ -61,6 +62,9 @@
|
||||
get isEdit() {
|
||||
return inModal.isEdit;
|
||||
},
|
||||
get isGroup() {
|
||||
return inModal.isGroup;
|
||||
},
|
||||
get client() {
|
||||
return inModal.inbound.clients[0];
|
||||
},
|
||||
|
||||
@@ -224,6 +224,10 @@
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="subSettings.enable && dbInbounds.length > 0" key="addGroupClient">
|
||||
<a-icon type="usergroup-add"></a-icon>
|
||||
{{ i18n "pages.client.groupAdd"}}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</a-col>
|
||||
@@ -859,6 +863,9 @@
|
||||
case "delDepletedClients":
|
||||
this.delDepletedClients(-1)
|
||||
break;
|
||||
case "addGroupClient":
|
||||
this.openGroupAddClient()
|
||||
break;
|
||||
}
|
||||
},
|
||||
clickAction(action, dbInbound) {
|
||||
@@ -1004,6 +1011,21 @@
|
||||
|
||||
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
||||
},
|
||||
openGroupAddClient() {
|
||||
clientModal.show({
|
||||
title: '{{ i18n "pages.client.groupAdd"}}',
|
||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||
dbInbounds: this.dbInbounds,
|
||||
confirm: async (clients, dbInboundIds) => {
|
||||
await this.addGroupClient(clients, dbInboundIds, clientModal).then((res) => {
|
||||
if(res){
|
||||
this.showQrcode(dbInboundIds[0],clients[0], true)
|
||||
}
|
||||
});
|
||||
},
|
||||
isEdit: false
|
||||
});
|
||||
},
|
||||
openAddClient(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
clientModal.show({
|
||||
@@ -1011,7 +1033,11 @@
|
||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||
dbInbound: dbInbound,
|
||||
confirm: async (clients, dbInboundId) => {
|
||||
await this.addClient(clients, dbInboundId, clientModal);
|
||||
await this.addClient(clients, dbInboundId, clientModal).then((res) => {
|
||||
if(res){
|
||||
this.showQrcode(dbInboundId,clients)
|
||||
}
|
||||
});
|
||||
},
|
||||
isEdit: false
|
||||
});
|
||||
@@ -1034,6 +1060,7 @@
|
||||
clientModal.show({
|
||||
title: '{{ i18n "pages.client.edit"}}',
|
||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||
dbInbounds: this.dbInbounds,
|
||||
dbInbound: dbInbound,
|
||||
index: index,
|
||||
confirm: async (client, dbInboundId, clientId) => {
|
||||
@@ -1059,12 +1086,36 @@
|
||||
};
|
||||
await this.submit(`/panel/inbound/addClient`, data, modal);
|
||||
},
|
||||
async addGroupClient(clients, dbInboundIds, modal) {
|
||||
const data = []
|
||||
dbInboundIds.forEach((dbInboundId, index) => {
|
||||
data.push({
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + clients[index].toString() + ']}',
|
||||
})
|
||||
})
|
||||
return await this.submit(`/panel/inbound/addGroupClient`, data, modal, true)
|
||||
},
|
||||
async updateClient(client, dbInboundId, clientId) {
|
||||
const data = {
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + client.toString() + ']}',
|
||||
};
|
||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
|
||||
if (Array.isArray(client) && Array.isArray(dbInboundId) && Array.isArray(clientId)){
|
||||
const data = []
|
||||
client.forEach((client, index) => {
|
||||
data.push({
|
||||
clientId: clientId[index],
|
||||
inbound: {
|
||||
id: dbInboundId[index],
|
||||
settings: '{"clients": [' + client.toString() + ']}',
|
||||
}
|
||||
})
|
||||
})
|
||||
await this.submit(`/panel/inbound/updateClients`, data, clientModal, true);
|
||||
}else{
|
||||
const data = {
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + client.toString() + ']}',
|
||||
};
|
||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
|
||||
}
|
||||
},
|
||||
resetTraffic(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
@@ -1092,22 +1143,100 @@
|
||||
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
||||
});
|
||||
},
|
||||
delClient(dbInboundId, client,confirmation = true) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
clientId = this.getClientId(dbInbound.protocol, client);
|
||||
async delClientHandler(dbInboundId, currentClient, clients = []) {
|
||||
if (clients.length > 0) {
|
||||
const deleteRequestData = [];
|
||||
|
||||
for (const client of clients) {
|
||||
const dbInbound = this.dbInbounds.find(inbound => inbound.id === client.inboundId);
|
||||
if (dbInbound) {
|
||||
const inbound = dbInbound.toInbound();
|
||||
if (inbound && inbound.clients && inbound.clients.length === 1) {
|
||||
let newClient = Inbound.Settings.getSettings(inbound.protocol).toString();
|
||||
newClient = JSON.parse(newClient);
|
||||
newClient = newClient && newClient.clients && newClient.clients.length > 0 ? JSON.stringify(newClient.clients[0], null, 2) : null;
|
||||
if (newClient) {
|
||||
const data = {
|
||||
id: client.inboundId,
|
||||
settings: '{"clients": [' + newClient + ']}',
|
||||
};
|
||||
await this.submit(`/panel/inbound/addClient`, data, null);
|
||||
}
|
||||
}
|
||||
|
||||
deleteRequestData.push({
|
||||
inboundId: client.inboundId,
|
||||
clientId: client.clientId,
|
||||
});
|
||||
}
|
||||
}
|
||||
await this.submit('/panel/inbound/delGroupClients', deleteRequestData, null, true);
|
||||
} else {
|
||||
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
const clientId = this.getClientId(dbInbound.protocol, currentClient);
|
||||
await this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`);
|
||||
}
|
||||
},
|
||||
delClient(dbInboundId, currentClient, confirmation = true) {
|
||||
const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, currentClient) : [];
|
||||
const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : [];
|
||||
if (confirmation){
|
||||
const clientEmails = clients.length > 0 ? clients.map(item => item.email) : currentClient.email
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
|
||||
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + clientEmails,
|
||||
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
||||
class: themeSwitcher.currentTheme,
|
||||
okText: '{{ i18n "delete"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
|
||||
onOk: () => this.delClientHandler(dbInboundId, currentClient, clients),
|
||||
});
|
||||
} else {
|
||||
this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`);
|
||||
this.delClientHandler(dbInboundId, currentClient, clients)
|
||||
}
|
||||
},
|
||||
getSubGroupClients(dbInbounds, currentClient) {
|
||||
const response = {
|
||||
inbounds: [],
|
||||
clients: [],
|
||||
editIds: [],
|
||||
};
|
||||
|
||||
if (!Array.isArray(dbInbounds) || dbInbounds.length === 0) {
|
||||
return response;
|
||||
}
|
||||
if (!currentClient || !currentClient.subId) {
|
||||
return response;
|
||||
}
|
||||
|
||||
dbInbounds.forEach((dbInboundItem) => {
|
||||
try {
|
||||
const dbInbound = new DBInbound(dbInboundItem);
|
||||
if (!dbInbound) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inbound = dbInbound.toInbound();
|
||||
if (!inbound || !Array.isArray(inbound.clients)) {
|
||||
return;
|
||||
}
|
||||
|
||||
inbound.clients.forEach((client) => {
|
||||
if (client.subId === currentClient.subId) {
|
||||
client.inboundId = dbInboundItem.id;
|
||||
client.clientId = this.getClientId(dbInbound.protocol, client);
|
||||
|
||||
response.inbounds.push(dbInboundItem.id);
|
||||
response.clients.push(client);
|
||||
response.editIds.push(client.clientId);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error processing dbInboundItem:", dbInboundItem, error);
|
||||
}
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
getClientId(protocol, client) {
|
||||
switch (protocol) {
|
||||
case Protocols.TROJAN: return client.password;
|
||||
@@ -1135,10 +1264,10 @@
|
||||
}
|
||||
return newDbInbound;
|
||||
},
|
||||
showQrcode(dbInboundId, client) {
|
||||
showQrcode(dbInboundId, client, isJustSub = false) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
newDbInbound = this.checkFallback(dbInbound);
|
||||
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client);
|
||||
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client, isJustSub);
|
||||
},
|
||||
showInfo(dbInboundId, client) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
@@ -1158,36 +1287,61 @@
|
||||
},
|
||||
async switchEnableClient(dbInboundId, client) {
|
||||
this.loading()
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
inbound = dbInbound.toInbound();
|
||||
clients = inbound.clients;
|
||||
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);
|
||||
const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, client) : [];
|
||||
if (subGroup && subGroup.clients && subGroup.clients.length > 0){
|
||||
await this.updateClient(subGroup.clients.map(item => {
|
||||
item.enable = !item.enable
|
||||
return item
|
||||
}), subGroup.inbounds, subGroup.editIds);
|
||||
}else{
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
inbound = dbInbound.toInbound();
|
||||
clients = inbound.clients;
|
||||
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);
|
||||
}
|
||||
this.loading(false);
|
||||
},
|
||||
async submit(url, data, modal) {
|
||||
const msg = await HttpUtil.postWithModal(url, data, modal);
|
||||
async submit(url, data, model, isJson = false) {
|
||||
const msg = isJson ? await HttpUtil.postWithModalJson(url, data, model) : await HttpUtil.postWithModal(url, data, model);
|
||||
if (msg.success) {
|
||||
await this.getDBInbounds();
|
||||
}
|
||||
return msg
|
||||
},
|
||||
getInboundClients(dbInbound) {
|
||||
return dbInbound.toInbound().clients;
|
||||
},
|
||||
resetClientTrafficHandler(client, dbInboundId, clients = []) {
|
||||
if (clients.length > 0){
|
||||
const resetRequests = clients
|
||||
.filter(client => {
|
||||
const inbound = this.dbInbounds.find(inbound => inbound.id === client.inboundId);
|
||||
return inbound && this.hasClientStats(inbound, client.email);
|
||||
}).map(client => ({ inboundId: client.inboundId, email: client.email}));
|
||||
|
||||
this.submit('/panel/inbound/resetGroupClientTraffic', resetRequests, null, true)
|
||||
}else {
|
||||
this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
|
||||
}
|
||||
},
|
||||
resetClientTraffic(client, dbInboundId, confirmation = true) {
|
||||
const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, client) : [];
|
||||
const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : [];
|
||||
if (confirmation){
|
||||
const clientEmails = clients.length > 0 ? clients.map(item => item.email) : client.email
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + client.email,
|
||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + clientEmails,
|
||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||
class: themeSwitcher.currentTheme,
|
||||
okText: '{{ i18n "reset"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
||||
onOk: () => this.resetClientTrafficHandler(client, dbInboundId, clients),
|
||||
})
|
||||
} else {
|
||||
this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
|
||||
this.resetClientTrafficHandler(client, dbInboundId, clients);
|
||||
}
|
||||
},
|
||||
resetAllTraffic() {
|
||||
@@ -1223,6 +1377,10 @@
|
||||
isExpiry(dbInbound, index) {
|
||||
return dbInbound.toInbound().isExpiry(index);
|
||||
},
|
||||
hasClientStats(dbInbound, email) {
|
||||
if (email.length == 0) return 0;
|
||||
return !!dbInbound.clientStats.find(stats => stats.email === email);
|
||||
},
|
||||
getUpStats(dbInbound, email) {
|
||||
if (email.length == 0) return 0;
|
||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||
@@ -1310,7 +1468,7 @@
|
||||
if (clients != null){
|
||||
clients.forEach(c => {
|
||||
if (c.subId && c.subId.length>0){
|
||||
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
|
||||
subLinks.push(this.subSettings.subURI + c.subId)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1337,7 +1495,7 @@
|
||||
if (clients != null){
|
||||
clients.forEach(c => {
|
||||
if (c.subId && c.subId.length>0){
|
||||
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
|
||||
subLinks.push(this.subSettings.subURI + c.subId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -268,6 +268,7 @@
|
||||
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}'>
|
||||
<a-list item-layout="horizontal">
|
||||
<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.subSyncEnable"}}' desc='{{ i18n "pages.settings.subSyncEnableDesc"}}' v-model="allSetting.subSyncEnable"></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>
|
||||
@@ -381,14 +382,40 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-collapse v-if="enableDirect" style="margin-top: 14px;">
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.directips"}}'>
|
||||
<a-list-item style="padding: 10px 20px">
|
||||
<a-checkbox-group v-model="directIPs" :options="IPsOptions"></a-checkbox-group>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.direct"}}'>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 0 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta
|
||||
title='{{ i18n "pages.xray.directips" }}'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-select mode="tags" style="width: 100%"
|
||||
v-model="directIPs"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in directIPsOptions"> [[ p.label ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.directdomains"}}'>
|
||||
<a-list-item style="padding: 10px 20px">
|
||||
<a-checkbox-group v-model="directDomains" :options="DomainsOptions"></a-checkbox-group>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 0 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta
|
||||
title='{{ i18n "pages.xray.directdomains" }}'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-select mode="tags" style="width: 100%"
|
||||
v-model="directDomains"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in diretDomainsOptions"> [[ p.label ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
@@ -439,7 +466,7 @@
|
||||
sockopt: {
|
||||
tcpKeepAliveIdle: 100,
|
||||
tcpMptcp: true,
|
||||
tcpNoDelay: true
|
||||
penetrate: true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -476,25 +503,26 @@
|
||||
]
|
||||
},
|
||||
],
|
||||
IPsOptions: [
|
||||
{ label: 'Private IP', value: 'private' },
|
||||
{ label: '🇮🇷 Iran', value: 'ir' },
|
||||
{ label: '🇨🇳 China', value: 'cn' },
|
||||
{ label: '🇷🇺 Russia', value: 'ru' },
|
||||
{ label: '🇻🇳 Vietnam', value: 'vn' },
|
||||
{ label: '🇪🇸 Spain', value: 'es' },
|
||||
{ label: '🇮🇩 Indonesia', value: 'id' },
|
||||
{ label: '🇺🇦 Ukraine', value: 'ua' },
|
||||
{ label: '🇹🇷 Türkiye', value: 'tr' },
|
||||
{ label: '🇧🇷 Brazil', value: 'br' },
|
||||
directIPsOptions: [
|
||||
{ label: 'Private IP', value: 'geoip:private' },
|
||||
{ label: '🇮🇷 Iran', value: 'geoip:ir' },
|
||||
{ label: '🇨🇳 China', value: 'geoip:cn' },
|
||||
{ label: '🇷🇺 Russia', value: 'geoip:ru' },
|
||||
{ label: '🇻🇳 Vietnam', value: 'geoip:vn' },
|
||||
{ label: '🇪🇸 Spain', value: 'geoip:es' },
|
||||
{ label: '🇮🇩 Indonesia', value: 'geoip:id' },
|
||||
{ label: '🇺🇦 Ukraine', value: 'geoip:ua' },
|
||||
{ label: '🇹🇷 Türkiye', value: 'geoip:tr' },
|
||||
{ label: '🇧🇷 Brazil', value: 'geoip:br' },
|
||||
],
|
||||
DomainsOptions: [
|
||||
{ label: '🇮🇷 Iran', value: 'ir' },
|
||||
{ label: '🇨🇳 China', value: 'cn' },
|
||||
{ label: '🇷🇺 Russia', value: 'ru' },
|
||||
{ label: 'Apple', value: 'apple' },
|
||||
{ label: 'Meta', value: 'meta' },
|
||||
{ label: 'Google', value: 'google' },
|
||||
diretDomainsOptions: [
|
||||
{ label: 'Private DNS', value: 'geosite:private' },
|
||||
{ label: '🇮🇷 Iran', value: 'geosite:category-ir' },
|
||||
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
||||
{ label: '🇷🇺 Russia', value: 'geosite:category-ru' },
|
||||
{ label: 'Apple', value: 'geosite:apple' },
|
||||
{ label: 'Meta', value: 'geosite:meta' },
|
||||
{ label: 'Google', value: 'geosite:google' },
|
||||
],
|
||||
get remarkModel() {
|
||||
rm = this.allSetting.remarkModel;
|
||||
@@ -752,7 +780,7 @@
|
||||
const rules = JSON.parse(this.allSetting.subJsonRules);
|
||||
if (!Array.isArray(rules)) return [];
|
||||
const ipRule = rules.find(r => r.ip);
|
||||
return ipRule?.ip.map(d => d.replace("geoip:", "")) ?? [];
|
||||
return ipRule?.ip ?? [];
|
||||
},
|
||||
set: function (v) {
|
||||
let rules = JSON.parse(this.allSetting.subJsonRules);
|
||||
@@ -766,7 +794,7 @@
|
||||
|
||||
rules[ruleIndex].ip = [];
|
||||
v.forEach(d => {
|
||||
rules[ruleIndex].ip.push("geoip:" + d);
|
||||
rules[ruleIndex].ip.push(d);
|
||||
});
|
||||
}
|
||||
this.allSetting.subJsonRules = JSON.stringify(rules);
|
||||
@@ -778,34 +806,18 @@
|
||||
const rules = JSON.parse(this.allSetting.subJsonRules);
|
||||
if (!Array.isArray(rules)) return [];
|
||||
const domainRule = rules.find(r => r.domain);
|
||||
return domainRule?.domain.map(d => {
|
||||
if (d.startsWith("geosite:category-")) {
|
||||
return d.replace("geosite:category-", "");
|
||||
}
|
||||
return d.replace("geosite:", "");
|
||||
})
|
||||
?? [];
|
||||
return domainRule?.domain ?? [];
|
||||
},
|
||||
set: function (v) {
|
||||
let rules = JSON.parse(this.allSetting.subJsonRules);
|
||||
if (!Array.isArray(rules)) return;
|
||||
|
||||
if (v.length == 0) {
|
||||
rules = rules.filter(r => !r.domain);
|
||||
} else {
|
||||
let ruleIndex = rules.findIndex(r => r.domain);
|
||||
if (ruleIndex == -1) ruleIndex = rules.push(this.defaultRules[0]) - 1;
|
||||
|
||||
rules[ruleIndex].domain = [];
|
||||
v.forEach(d => {
|
||||
let category = '';
|
||||
if (["cn", "apple", "meta", "google"].includes(d)) {
|
||||
category = "";
|
||||
} else if (["ru", "ir"].includes(d)) {
|
||||
category = "category-";
|
||||
}
|
||||
rules[ruleIndex].domain.push("geosite:" + category + d);
|
||||
});
|
||||
rules[ruleIndex].domain = v;
|
||||
}
|
||||
this.allSetting.subJsonRules = JSON.stringify(rules);
|
||||
}
|
||||
@@ -838,4 +850,4 @@
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -143,6 +143,7 @@
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.outboundTraffic"}}' desc='{{ i18n "pages.xray.outboundTrafficDesc"}}' v-model="outboundTraffic"></setting-list-item>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.logConfigs" }}'>
|
||||
@@ -901,8 +902,8 @@
|
||||
{ label: 'Private IPs', value: 'geoip:private' },
|
||||
{ label: '🇮🇷 Iran', value: 'ext:geoip_IR.dat:ir' },
|
||||
{ label: '🇨🇳 China', value: 'geoip:cn' },
|
||||
{ label: '🇷🇺 Russia', value: 'geoip:ru' },
|
||||
{ label: '🇻🇳 Vietnam', value: 'ext:geoip_VN.dat:vn' },
|
||||
{ label: '🇷🇺 Russia', value: 'ext:geoip_RU.dat:ru' },
|
||||
{ label: '🇻🇳 Vietnam', value: 'geoip:vn' },
|
||||
{ label: '🇪🇸 Spain', value: 'geoip:es' },
|
||||
{ label: '🇮🇩 Indonesia', value: 'geoip:id' },
|
||||
{ label: '🇺🇦 Ukraine', value: 'geoip:ua' },
|
||||
@@ -915,15 +916,16 @@
|
||||
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
|
||||
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
||||
{ label: '🇨🇳 .cn', value: 'regexp:.*\\.cn$' },
|
||||
{ label: '🇷🇺 Russia', value: 'geosite:category-ru' },
|
||||
{ label: '🇷🇺 .ru', value: 'regexp:.*\\.ru' },
|
||||
{ label: '🇻🇳 Vietnam', value: 'ext:geosite_VN.dat:vn' },
|
||||
{ label: '🇷🇺 Russia', value: 'ext:geosite_RU.dat:ru-available-only-inside' },
|
||||
{ label: '🇷🇺 .ru', value: 'regexp:.*\\.ru$' },
|
||||
{ label: '🇷🇺 .su', value: 'regexp:.*\\.su$' },
|
||||
{ label: '🇷🇺 .рф', value: 'regexp:.*\\.xn--p1ai$' },
|
||||
{ label: '🇻🇳 .vn', value: 'regexp:.*\\.vn$' },
|
||||
],
|
||||
BlockDomainsOptions: [
|
||||
{ label: 'Ads All', value: 'geosite:category-ads-all' },
|
||||
{ label: 'Ads IR 🇮🇷', value: 'ext:geosite_IR.dat:category-ads-all' },
|
||||
{ label: 'Ads VN 🇻🇳', value: 'ext:geosite_VN.dat:ads' },
|
||||
{ label: 'Ads RU 🇷🇺', value: 'ext:geosite_RU.dat:category-ads-all' },
|
||||
{ label: 'Malware 🇮🇷', value: 'ext:geosite_IR.dat:malware' },
|
||||
{ label: 'Phishing 🇮🇷', value: 'ext:geosite_IR.dat:phishing' },
|
||||
{ label: 'Cryptominers 🇮🇷', value: 'ext:geosite_IR.dat:cryptominers' },
|
||||
@@ -932,9 +934,10 @@
|
||||
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
|
||||
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
||||
{ label: '🇨🇳 .cn', value: 'regexp:.*\\.cn$' },
|
||||
{ label: '🇷🇺 Russia', value: 'geosite:category-ru' },
|
||||
{ label: '🇷🇺 Russia', value: 'ext:geosite_RU.dat:ru-available-only-inside' },
|
||||
{ label: '🇷🇺 .ru', value: 'regexp:.*\\.ru' },
|
||||
{ label: '🇻🇳 Vietnam', value: 'ext:geosite_VN.dat:vn' },
|
||||
{ label: '🇷🇺 .su', value: 'regexp:.*\\.su$' },
|
||||
{ label: '🇷🇺 .рф', value: 'regexp:.*\\.xn--p1ai$' },
|
||||
{ label: '🇻🇳 .vn', value: 'regexp:.*\\.vn$' },
|
||||
],
|
||||
ServicesOptions: [
|
||||
@@ -1821,6 +1824,18 @@
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
outboundTraffic: {
|
||||
get: function () {
|
||||
if (!this.templateSettings || !this.templateSettings.policy.system || !this.templateSettings.policy.system.statsOutboundDownlink) return false;
|
||||
return this.templateSettings.policy.system.statsOutboundDownlink;
|
||||
},
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
newTemplateSettings.policy.system.statsOutboundDownlink = newValue;
|
||||
newTemplateSettings.policy.system.statsOutboundUplink = newValue;
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
maskAddressLog: {
|
||||
get: function () {
|
||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.maskAddress) return "";
|
||||
|
||||
@@ -106,7 +106,7 @@ func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||
|
||||
func (j *CheckClientIpJob) processLogFile() bool {
|
||||
|
||||
ipRegex := regexp.MustCompile(`from \[?([0-9a-fA-F:.]+)\]?:\d+ accepted`)
|
||||
ipRegex := regexp.MustCompile(`from (?:tcp:|udp:)?\[?([0-9a-fA-F\.:]+)\]?:\d+ accepted`)
|
||||
emailRegex := regexp.MustCompile(`email: (.+)$`)
|
||||
|
||||
accessLogPath, _ := xray.GetAccessLogPath()
|
||||
@@ -151,13 +151,13 @@ func (j *CheckClientIpJob) processLogFile() bool {
|
||||
}
|
||||
sort.Strings(ips)
|
||||
|
||||
inboundClientIps, err := j.getInboundClientIps(email)
|
||||
clientIpsRecord, err := j.getInboundClientIps(email)
|
||||
if err != nil {
|
||||
j.addInboundClientIps(email, ips)
|
||||
continue
|
||||
}
|
||||
|
||||
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, email, ips) || shouldCleanLog
|
||||
shouldCleanLog = j.updateInboundClientIps(clientIpsRecord, email, ips) || shouldCleanLog
|
||||
}
|
||||
|
||||
return shouldCleanLog
|
||||
@@ -309,12 +309,12 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
||||
|
||||
func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds *model.Inbound
|
||||
inbound := &model.Inbound{}
|
||||
|
||||
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error
|
||||
err := db.Model(&model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").First(inbound).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return inbounds, nil
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
178
web/job/client_traffic_sync_job.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/xray"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SyncClientTrafficJob struct {
|
||||
subClientsCollection map[string][]string
|
||||
}
|
||||
|
||||
func NewClientTrafficSyncJob() *SyncClientTrafficJob {
|
||||
return new(SyncClientTrafficJob)
|
||||
}
|
||||
func (j *SyncClientTrafficJob) Run() {
|
||||
// Step 1: Group clients by SubID
|
||||
subClientsCollection := j.collectClientsGroupedBySubId()
|
||||
|
||||
// Step 2: Sync client traffics for each SubID group
|
||||
for subId, emails := range subClientsCollection {
|
||||
err := j.syncClientTraffics(map[string][]string{subId: emails})
|
||||
if err != nil {
|
||||
logger.Error("Failed to sync traffics for SubID ", subId, ": ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collectClientsGroupedBySubId groups clients by their SubIDs
|
||||
func (j *SyncClientTrafficJob) collectClientsGroupedBySubId() map[string][]string {
|
||||
db := database.GetDB()
|
||||
result := make(map[string][]string)
|
||||
|
||||
// Fetch all inbounds
|
||||
var inbounds []*model.Inbound
|
||||
if err := db.Model(&model.Inbound{}).Find(&inbounds).Error; err != nil {
|
||||
logger.Error("Error fetching inbounds: ", err)
|
||||
return result // Return empty map on error
|
||||
}
|
||||
|
||||
// Process each inbound
|
||||
for _, inbound := range inbounds {
|
||||
if inbound.Settings == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
settingsMap, err := parseSettings(inbound.Settings, uint(inbound.Id))
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
clients, ok := settingsMap["clients"].([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
processClients(clients, result)
|
||||
}
|
||||
|
||||
// Remove SubIDs with one or fewer emails
|
||||
filterSingleEmailSubIDs(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// parseSettings unmarshals the JSON settings and returns it as a map
|
||||
func parseSettings(settings string, inboundID uint) (map[string]interface{}, error) {
|
||||
if !json.Valid([]byte(settings)) {
|
||||
return nil, fmt.Errorf("Invalid JSON format in Settings for inbound ID %d", inboundID)
|
||||
}
|
||||
|
||||
var tempData map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(settings), &tempData); err != nil {
|
||||
return nil, fmt.Errorf("Error unmarshalling settings for inbound ID %d: %v", inboundID, err)
|
||||
}
|
||||
|
||||
return tempData, nil
|
||||
}
|
||||
|
||||
// processClients extracts SubID and email from the clients and populates the result map
|
||||
func processClients(clients []interface{}, result map[string][]string) {
|
||||
for _, client := range clients {
|
||||
clientMap, ok := client.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
subId, ok := clientMap["subId"].(string)
|
||||
if !ok || subId == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
email, ok := clientMap["email"].(string)
|
||||
if !ok || email == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
result[subId] = append(result[subId], email)
|
||||
}
|
||||
}
|
||||
|
||||
// filterSingleEmailSubIDs removes SubIDs with one or fewer emails from the result map
|
||||
func filterSingleEmailSubIDs(result map[string][]string) {
|
||||
for subId, emails := range result {
|
||||
if len(emails) <= 1 {
|
||||
delete(result, subId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// syncClientTraffics synchronizes traffic data for each SubID group
|
||||
func (j *SyncClientTrafficJob) syncClientTraffics(result map[string][]string) error {
|
||||
for subId, emails := range result {
|
||||
db := database.GetDB()
|
||||
|
||||
// Step 1: Calculate maxUp and maxDown (outside transaction)
|
||||
var maxUp, maxDown int64
|
||||
err := calculateMaxTraffic(db, emails, &maxUp, &maxDown)
|
||||
if err != nil {
|
||||
logger.Error("Failed to calculate max traffic for SubID ", subId, ": ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Step 2: Update traffic data with retry mechanism
|
||||
err = retryOperation(func() error {
|
||||
return updateTraffic(db, emails, maxUp, maxDown)
|
||||
}, 5, 100*time.Millisecond)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Failed to update client traffics for SubID ", subId, ": ", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// calculateMaxTraffic calculates max up and down traffic for a group of emails
|
||||
func calculateMaxTraffic(db *gorm.DB, emails []string, maxUp, maxDown *int64) error {
|
||||
return db.Model(&xray.ClientTraffic{}).
|
||||
Where("email IN ?", emails).
|
||||
Select("MAX(up) AS max_up, MAX(down) AS max_down").
|
||||
Row().
|
||||
Scan(maxUp, maxDown)
|
||||
}
|
||||
|
||||
// updateTraffic updates the traffic data in the database within a transaction
|
||||
func updateTraffic(db *gorm.DB, emails []string, maxUp, maxDown int64) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
return tx.Model(&xray.ClientTraffic{}).
|
||||
Where("email IN ?", emails).
|
||||
Updates(map[string]interface{}{
|
||||
"up": maxUp,
|
||||
"down": maxDown,
|
||||
}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// retryOperation retries an operation multiple times with a delay
|
||||
func retryOperation(operation func() error, maxRetries int, delay time.Duration) error {
|
||||
var err error
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
err = operation()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
logger.Info(fmt.Sprintf("Retry %d/%d failed: %v", i+1, maxRetries, err))
|
||||
time.Sleep(delay)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"tag": "direct",
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"domainStrategy": "UseIP",
|
||||
"domainStrategy": "AsIs",
|
||||
"redirect": "",
|
||||
"noises": []
|
||||
}
|
||||
@@ -51,8 +51,8 @@
|
||||
"system": {
|
||||
"statsInboundDownlink": true,
|
||||
"statsInboundUplink": true,
|
||||
"statsOutboundDownlink": true,
|
||||
"statsOutboundUplink": true
|
||||
"statsOutboundDownlink": false,
|
||||
"statsOutboundUplink": false
|
||||
}
|
||||
},
|
||||
"routing": {
|
||||
|
||||
@@ -588,8 +588,12 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||
logger.Debug("Client deleted by api:", email)
|
||||
needRestart = false
|
||||
} else {
|
||||
logger.Debug("Unable to del client by api:", err1)
|
||||
needRestart = true
|
||||
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", email)) {
|
||||
logger.Debug("User is already deleted. Nothing to do more...")
|
||||
} else {
|
||||
logger.Debug("Error in deleting client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
}
|
||||
@@ -713,10 +717,14 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
if oldClients[clientIndex].Enable {
|
||||
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
|
||||
if err1 == nil {
|
||||
logger.Debug("Old client deleted by api:", clients[0].Email)
|
||||
logger.Debug("Old client deleted by api:", oldEmail)
|
||||
} else {
|
||||
logger.Debug("Error in deleting client by api:", err1)
|
||||
needRestart = true
|
||||
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", oldEmail)) {
|
||||
logger.Debug("User is already deleted. Nothing to do more...")
|
||||
} else {
|
||||
logger.Debug("Error in deleting client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if clients[0].Enable {
|
||||
@@ -1037,12 +1045,8 @@ func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error
|
||||
if err1 == nil {
|
||||
logger.Debug("Inbound disabled by api:", tag)
|
||||
} else {
|
||||
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", tag)) {
|
||||
logger.Debug("User is already disabled. Nothing to do more...")
|
||||
} else {
|
||||
logger.Debug("Error in disabling client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
logger.Debug("Error in disabling inbound by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
|
||||
@@ -286,8 +286,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||
}
|
||||
|
||||
if (major == 1 && minor == 8 && patch == 24) ||
|
||||
(major == 24 && ((minor > 11) || (minor == 11 && patch >= 11))) ||
|
||||
(major > 24) {
|
||||
(major >= 25) {
|
||||
versions = append(versions, release.TagName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ var defaultValueMap = map[string]string{
|
||||
"expireDiff": "0",
|
||||
"trafficDiff": "0",
|
||||
"remarkModel": "-ieo",
|
||||
"timeLocation": "Asia/Tehran",
|
||||
"timeLocation": "Local",
|
||||
"tgBotEnable": "false",
|
||||
"tgBotToken": "",
|
||||
"tgBotProxy": "",
|
||||
@@ -50,6 +50,7 @@ var defaultValueMap = map[string]string{
|
||||
"tgLang": "en-US",
|
||||
"secretEnable": "false",
|
||||
"subEnable": "false",
|
||||
"subSyncEnable": "true",
|
||||
"subListen": "",
|
||||
"subPort": "2096",
|
||||
"subPath": "/sub/",
|
||||
@@ -416,6 +417,14 @@ func (s *SettingService) GetSubEnable() (bool, error) {
|
||||
return s.getBool("subEnable")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubSyncEnable() (bool, error) {
|
||||
return s.getBool("subSyncEnable")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetSubSyncEnable(value bool) error {
|
||||
return s.setBool("subSyncEnable", value)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubListen() (string, error) {
|
||||
return s.getString("subListen")
|
||||
}
|
||||
@@ -544,6 +553,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
|
||||
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() },
|
||||
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
||||
"subSyncEnable": func() (interface{}, error) { return s.GetSubSyncEnable() },
|
||||
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
||||
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
|
||||
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
)
|
||||
|
||||
type WarpService struct {
|
||||
@@ -150,13 +151,23 @@ func (s *WarpService) SetWarpLicense(license string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal(buffer.Bytes(), &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if response["success"] == false {
|
||||
errorArr, _ := response["errors"].([]interface{})
|
||||
errorObj := errorArr[0].(map[string]interface{})
|
||||
return "", common.NewError(errorObj["code"], errorObj["message"])
|
||||
}
|
||||
|
||||
warpData["license_key"] = license
|
||||
newWarpData, err := json.MarshalIndent(warpData, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s.SettingService.SetWarp(string(newWarpData))
|
||||
println(string(newWarpData))
|
||||
|
||||
return string(newWarpData), nil
|
||||
}
|
||||
|
||||
@@ -10,38 +10,41 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
loginUser = "LOGIN_USER"
|
||||
defaultPath = "/"
|
||||
loginUserKey = "LOGIN_USER"
|
||||
defaultPath = "/"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(model.User{})
|
||||
}
|
||||
|
||||
func SetLoginUser(c *gin.Context, user *model.User) error {
|
||||
func SetLoginUser(c *gin.Context, user *model.User) {
|
||||
if user == nil {
|
||||
return
|
||||
}
|
||||
s := sessions.Default(c)
|
||||
s.Set(loginUser, user)
|
||||
return s.Save()
|
||||
s.Set(loginUserKey, *user)
|
||||
}
|
||||
|
||||
func SetMaxAge(c *gin.Context, maxAge int) error {
|
||||
func SetMaxAge(c *gin.Context, maxAge int) {
|
||||
s := sessions.Default(c)
|
||||
s.Options(sessions.Options{
|
||||
Path: defaultPath,
|
||||
MaxAge: maxAge,
|
||||
HttpOnly: true,
|
||||
})
|
||||
return s.Save()
|
||||
}
|
||||
|
||||
func GetLoginUser(c *gin.Context) *model.User {
|
||||
s := sessions.Default(c)
|
||||
obj := s.Get(loginUser)
|
||||
obj := s.Get(loginUserKey)
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
user, ok := obj.(model.User)
|
||||
if !ok {
|
||||
|
||||
s.Delete(loginUserKey)
|
||||
return nil
|
||||
}
|
||||
return &user
|
||||
@@ -51,7 +54,7 @@ func IsLogin(c *gin.Context) bool {
|
||||
return GetLoginUser(c) != nil
|
||||
}
|
||||
|
||||
func ClearSession(c *gin.Context) error {
|
||||
func ClearSession(c *gin.Context) {
|
||||
s := sessions.Default(c)
|
||||
s.Clear()
|
||||
s.Options(sessions.Options{
|
||||
@@ -59,5 +62,4 @@ func ClearSession(c *gin.Context) error {
|
||||
MaxAge: -1,
|
||||
HttpOnly: true,
|
||||
})
|
||||
return s.Save()
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"monitor" = "Listen IP"
|
||||
"certificate" = "Digital Certificate"
|
||||
"fail" = "Failed"
|
||||
"comment" = "Comment"
|
||||
"success" = "Successfully"
|
||||
"getVersion" = "Get Version"
|
||||
"install" = "Install"
|
||||
@@ -189,6 +190,9 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Add Client"
|
||||
"groupAdd" = "Add subscription user"
|
||||
"isGroupEdit" = "Group editing"
|
||||
"isGroupEditDesc" = "All clients with the same subscription are edited"
|
||||
"edit" = "Edit Client"
|
||||
"submitAdd" = "Add Client"
|
||||
"submitEdit" = "Save Changes"
|
||||
@@ -286,6 +290,8 @@
|
||||
"subSettings" = "Subscription"
|
||||
"subEnable" = "Enable Subscription Service"
|
||||
"subEnableDesc" = "Enables the subscription service."
|
||||
"subSyncEnable" = "Enable Subscription Sync"
|
||||
"subSyncEnableDesc" = "Traffic from clients with the same subscription will be synchronized every 10 seconds."
|
||||
"subListen" = "Listen IP"
|
||||
"subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)"
|
||||
"subPort" = "Listen Port"
|
||||
@@ -367,6 +373,8 @@
|
||||
"errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs"
|
||||
"dnsLog" = "DNS Log"
|
||||
"dnsLogDesc" = "Whether to enable DNS query logs"
|
||||
"outboundTraffic" = "Outbounds Traffic"
|
||||
"outboundTrafficDesc" = "Whether to enable outbound traffic"
|
||||
"maskAddress" = "Mask Address"
|
||||
"maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log."
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"monitor" = "Listening IP"
|
||||
"certificate" = "Certificado Digital"
|
||||
"fail" = "Falló"
|
||||
"comment" = "Comentario"
|
||||
"success" = "Éxito"
|
||||
"getVersion" = "Obtener versión"
|
||||
"install" = "Instalar"
|
||||
@@ -189,6 +190,7 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Agregar Cliente"
|
||||
"groupAdd" = "Agregar usuario de suscripción"
|
||||
"edit" = "Editar Cliente"
|
||||
"submitAdd" = "Agregar Cliente"
|
||||
"submitEdit" = "Guardar Cambios"
|
||||
@@ -286,6 +288,8 @@
|
||||
"subSettings" = "Suscripción"
|
||||
"subEnable" = "Habilitar Servicio"
|
||||
"subEnableDesc" = "Función de suscripción con configuración separada."
|
||||
"subSyncEnable" = "Habilitar sincronización de suscripciones"
|
||||
"subSyncEnableDesc" = "El tráfico de los clientes con la misma suscripción se sincronizará cada 10 segundos."
|
||||
"subListen" = "Listening IP"
|
||||
"subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs."
|
||||
"subPort" = "Puerto de Suscripción"
|
||||
@@ -367,6 +371,8 @@
|
||||
"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'none' desactiva los registros de errores."
|
||||
"dnsLog" = "Registro DNS"
|
||||
"dnsLogDesc" = "Si habilitar los registros de consulta DNS"
|
||||
"outboundTraffic" = "Tráfico saliente"
|
||||
"outboundTrafficDesc" = "Si se debe habilitar el tráfico saliente"
|
||||
"maskAddress" = "Enmascarar Dirección"
|
||||
"maskAddressDesc" = "Máscara de dirección IP, cuando se habilita, reemplazará automáticamente la dirección IP que aparece en el registro."
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"monitor" = "آیپی اتصال"
|
||||
"certificate" = "گواهی دیجیتال"
|
||||
"fail" = "ناموفق"
|
||||
"comment" = "توضیحات"
|
||||
"success" = "موفق"
|
||||
"getVersion" = "دریافت نسخه"
|
||||
"install" = "نصب"
|
||||
@@ -189,6 +190,9 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "کاربر جدید"
|
||||
"groupAdd" = "افزودن کاربر سابسکریپشن"
|
||||
"isGroupEdit" = "ویرایش گروهی"
|
||||
"isGroupEditDesc" = "همه کاربران با سابسکریپشن یکسان ویرایش می شوند"
|
||||
"edit" = "ویرایش کاربر"
|
||||
"submitAdd" = "اضافه کردن"
|
||||
"submitEdit" = "ذخیره تغییرات"
|
||||
@@ -286,6 +290,8 @@
|
||||
"subSettings" = "سابسکریپشن"
|
||||
"subEnable" = "فعالسازی سرویس سابسکریپشن"
|
||||
"subEnableDesc" = "سرویس سابسکریپشن را فعالمیکند"
|
||||
"subSyncEnable" = "فعالسازی همگام سازی سابسکریپشن"
|
||||
"subSyncEnableDesc" = "ترافیک کلاینت هایی که سابسکریپشن یکسان دارند هر ۱۰ ثانیه همگام میشوند."
|
||||
"subListen" = "آدرس آیپی"
|
||||
"subListenDesc" = "آدرس آیپی برای سرویس سابسکریپشن. برای گوش دادن بهتمام آیپیها خالیبگذارید"
|
||||
"subPort" = "پورت"
|
||||
@@ -367,6 +373,8 @@
|
||||
"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند"
|
||||
"dnsLog" = "گزارش DNS"
|
||||
"dnsLogDesc" = "آیا ثبتهای درخواست DNS را فعال کنید"
|
||||
"outboundTraffic" = "ترافیک خروجی"
|
||||
"outboundTrafficDesc" = "فعال کردن ترافیک خروجی"
|
||||
"maskAddress" = "پنهان کردن آدرس"
|
||||
"maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال میشود، به طور خودکار آدرس IP که در لاگ ظاهر میشود را جایگزین میکند."
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"monitor" = "IP Pemantauan"
|
||||
"certificate" = "Sertifikat Digital"
|
||||
"fail" = "Gagal"
|
||||
"comment" = "Komentar"
|
||||
"success" = "Berhasil"
|
||||
"getVersion" = "Dapatkan Versi"
|
||||
"install" = "Instal"
|
||||
@@ -189,6 +190,9 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Tambah Klien"
|
||||
"groupAdd" = "Tambahkan pengguna langganan"
|
||||
"isGroupEdit" = "Pengeditan grup"
|
||||
"isGroupEditDesc" = "Semua klien dengan langganan yang sama akan diedit"
|
||||
"edit" = "Edit Klien"
|
||||
"submitAdd" = "Tambah Klien"
|
||||
"submitEdit" = "Simpan Perubahan"
|
||||
@@ -286,6 +290,8 @@
|
||||
"subSettings" = "Langganan"
|
||||
"subEnable" = "Aktifkan Layanan Langganan"
|
||||
"subEnableDesc" = "Mengaktifkan layanan langganan."
|
||||
"subSyncEnable" = "Aktifkan Sinkronisasi Langganan"
|
||||
"subSyncEnableDesc" = "Lalu lintas dari klien dengan langganan yang sama akan disinkronkan setiap 10 detik."
|
||||
"subListen" = "IP Pendengar"
|
||||
"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
|
||||
"subPort" = "Port Pendengar"
|
||||
@@ -367,6 +373,8 @@
|
||||
"errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan"
|
||||
"dnsLog" = "Log DNS"
|
||||
"dnsLogDesc" = "Apakah akan mengaktifkan log kueri DNS"
|
||||
"outboundTraffic" = "Lalu Lintas Keluar"
|
||||
"outboundTrafficDesc" = "Apakah mengaktifkan lalu lintas keluar"
|
||||
"maskAddress" = "Alamat Masker"
|
||||
"maskAddressDesc" = "Masker alamat IP, ketika diaktifkan, akan secara otomatis mengganti alamat IP yang muncul di log."
|
||||
|
||||
|
||||
603
web/translation/translate.ja_JP.toml
Normal file
@@ -0,0 +1,603 @@
|
||||
"username" = "ユーザー名"
|
||||
"password" = "パスワード"
|
||||
"login" = "ログイン"
|
||||
"confirm" = "確認"
|
||||
"cancel" = "キャンセル"
|
||||
"close" = "閉じる"
|
||||
"copy" = "コピー"
|
||||
"copied" = "コピー済み"
|
||||
"download" = "ダウンロード"
|
||||
"remark" = "備考"
|
||||
"enable" = "有効化"
|
||||
"protocol" = "プロトコル"
|
||||
"search" = "検索"
|
||||
"filter" = "フィルター"
|
||||
"loading" = "読み込み中..."
|
||||
"second" = "秒"
|
||||
"minute" = "分"
|
||||
"hour" = "時間"
|
||||
"day" = "日"
|
||||
"check" = "確認"
|
||||
"indefinite" = "無期限"
|
||||
"unlimited" = "無制限"
|
||||
"none" = "なし"
|
||||
"qrCode" = "QRコード"
|
||||
"info" = "詳細情報"
|
||||
"edit" = "編集"
|
||||
"delete" = "削除"
|
||||
"reset" = "リセット"
|
||||
"copySuccess" = "コピー成功"
|
||||
"sure" = "確定"
|
||||
"encryption" = "暗号化"
|
||||
"transmission" = "伝送"
|
||||
"host" = "ホスト"
|
||||
"path" = "パス"
|
||||
"camouflage" = "偽装"
|
||||
"status" = "ステータス"
|
||||
"enabled" = "有効"
|
||||
"disabled" = "無効"
|
||||
"depleted" = "消耗済み"
|
||||
"depletingSoon" = "間もなく消耗"
|
||||
"offline" = "オフライン"
|
||||
"online" = "オンライン"
|
||||
"domainName" = "ドメイン名"
|
||||
"monitor" = "監視"
|
||||
"certificate" = "証明書"
|
||||
"fail" = "失敗"
|
||||
"comment" = "コメント"
|
||||
"success" = "成功"
|
||||
"getVersion" = "バージョン取得"
|
||||
"install" = "インストール"
|
||||
"clients" = "クライアント"
|
||||
"usage" = "利用状況"
|
||||
"secretToken" = "シークレットトークン"
|
||||
"remained" = "残り"
|
||||
"security" = "セキュリティ"
|
||||
"secAlertTitle" = "セキュリティアラート"
|
||||
"secAlertSsl" = "この接続は安全ではありません。TLSを有効にしてデータ保護を行うまで、機密情報を入力しないでください。"
|
||||
"secAlertConf" = "一部の設定は脆弱です。潜在的な脆弱性を防ぐために、セキュリティプロトコルを強化することをお勧めします。"
|
||||
"secAlertSSL" = "セキュアな接続がありません。データ保護のためにTLS証明書をインストールしてください。"
|
||||
"secAlertPanelPort" = "デフォルトのポートにはセキュリティリスクがあります。ランダムなポートまたは特定のポートを設定してください。"
|
||||
"secAlertPanelURI" = "デフォルトのURIパスは安全ではありません。複雑なURIパスを設定してください。"
|
||||
"secAlertSubURI" = "サブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。"
|
||||
"secAlertSubJsonURI" = "JSONサブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "ダッシュボード"
|
||||
"inbounds" = "インバウンド一覧"
|
||||
"settings" = "パネル設定"
|
||||
"xray" = "Xray設定"
|
||||
"logout" = "ログアウト"
|
||||
"link" = "リンク管理"
|
||||
|
||||
[pages.login]
|
||||
"hello" = "こんにちは"
|
||||
"title" = "ようこそ"
|
||||
"loginAgain" = "ログインセッションが切れました。再度ログインしてください。"
|
||||
|
||||
[pages.login.toasts]
|
||||
"invalidFormData" = "データ形式エラー"
|
||||
"emptyUsername" = "ユーザー名を入力してください"
|
||||
"emptyPassword" = "パスワードを入力してください"
|
||||
"wrongUsernameOrPassword" = "ユーザー名またはパスワードが間違っています"
|
||||
"successLogin" = "ログイン成功"
|
||||
|
||||
[pages.index]
|
||||
"title" = "システムステータス"
|
||||
"memory" = "メモリ"
|
||||
"hard" = "ハードディスク"
|
||||
"xrayStatus" = "Xray"
|
||||
"stopXray" = "停止"
|
||||
"restartXray" = "再起動"
|
||||
"xraySwitch" = "バージョン"
|
||||
"xraySwitchClick" = "切り替えるバージョンを選択してください"
|
||||
"xraySwitchClickDesk" = "慎重に選択してください。古いバージョンは現在の設定と互換性がない可能性があります。"
|
||||
"operationHours" = "システム稼働時間"
|
||||
"systemLoad" = "システム負荷"
|
||||
"systemLoadDesc" = "過去1、5、15分間のシステム平均負荷"
|
||||
"connectionTcpCountDesc" = "システム内のすべてのTCP接続数"
|
||||
"connectionUdpCountDesc" = "システム内のすべてのUDP接続数"
|
||||
"connectionCount" = "接続数"
|
||||
"upSpeed" = "総アップロード速度"
|
||||
"downSpeed" = "総ダウンロード速度"
|
||||
"totalSent" = "システム起動以降の送信データ量"
|
||||
"totalReceive" = "システム起動以降の受信データ量"
|
||||
"xraySwitchVersionDialog" = "Xrayバージョン切り替え"
|
||||
"xraySwitchVersionDialogDesc" = "Xrayのバージョンを切り替えますか?"
|
||||
"dontRefresh" = "インストール中、このページをリロードしないでください"
|
||||
"logs" = "ログ"
|
||||
"config" = "設定"
|
||||
"backup" = "バックアップと復元"
|
||||
"backupTitle" = "データベースのバックアップと復元"
|
||||
"backupDescription" = "データベースを復元する前にバックアップすることをお勧めします"
|
||||
"exportDatabase" = "バックアップ"
|
||||
"importDatabase" = "復元"
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "インバウンド一覧"
|
||||
"totalDownUp" = "総アップロード / ダウンロード"
|
||||
"totalUsage" = "総使用量"
|
||||
"inboundCount" = "インバウンド数"
|
||||
"operate" = "メニュー"
|
||||
"enable" = "有効化"
|
||||
"remark" = "備考"
|
||||
"protocol" = "プロトコル"
|
||||
"port" = "ポート"
|
||||
"traffic" = "トラフィック"
|
||||
"details" = "詳細情報"
|
||||
"transportConfig" = "トランスポート設定"
|
||||
"expireDate" = "有効期限"
|
||||
"resetTraffic" = "トラフィックリセット"
|
||||
"addInbound" = "インバウンド追加"
|
||||
"generalActions" = "一般操作"
|
||||
"create" = "追加"
|
||||
"update" = "更新"
|
||||
"modifyInbound" = "インバウンド修正"
|
||||
"deleteInbound" = "インバウンド削除"
|
||||
"deleteInboundContent" = "インバウンドを削除してもよろしいですか?"
|
||||
"deleteClient" = "クライアント削除"
|
||||
"deleteClientContent" = "クライアントを削除してもよろしいですか?"
|
||||
"resetTrafficContent" = "トラフィックをリセットしてもよろしいですか?"
|
||||
"copyLink" = "リンクをコピー"
|
||||
"address" = "アドレス"
|
||||
"network" = "ネットワーク"
|
||||
"destinationPort" = "宛先ポート"
|
||||
"targetAddress" = "宛先アドレス"
|
||||
"monitorDesc" = "空白にするとすべてのIPを監視"
|
||||
"meansNoLimit" = "= 無制限(単位:GB)"
|
||||
"totalFlow" = "総トラフィック"
|
||||
"leaveBlankToNeverExpire" = "空白にすると期限なし"
|
||||
"noRecommendKeepDefault" = "デフォルト値を保持することをお勧めします"
|
||||
"certificatePath" = "ファイルパス"
|
||||
"certificateContent" = "ファイル内容"
|
||||
"publicKey" = "公開鍵"
|
||||
"privatekey" = "秘密鍵"
|
||||
"clickOnQRcode" = "QRコードをクリックしてコピー"
|
||||
"client" = "クライアント"
|
||||
"export" = "リンクエクスポート"
|
||||
"clone" = "複製"
|
||||
"cloneInbound" = "複製"
|
||||
"cloneInboundContent" = "このインバウンドルールは、ポート(Port)、リスニングIP(Listening IP)、クライアント(Clients)を除くすべての設定がクローンされます"
|
||||
"cloneInboundOk" = "クローン作成"
|
||||
"resetAllTraffic" = "すべてのインバウンドトラフィックをリセット"
|
||||
"resetAllTrafficTitle" = "すべてのインバウンドトラフィックをリセット"
|
||||
"resetAllTrafficContent" = "すべてのインバウンドトラフィックをリセットしてもよろしいですか?"
|
||||
"resetInboundClientTraffics" = "クライアントトラフィックをリセット"
|
||||
"resetInboundClientTrafficTitle" = "すべてのクライアントトラフィックをリセット"
|
||||
"resetInboundClientTrafficContent" = "このインバウンドクライアントのすべてのトラフィックをリセットしてもよろしいですか?"
|
||||
"resetAllClientTraffics" = "すべてのクライアントトラフィックをリセット"
|
||||
"resetAllClientTrafficTitle" = "すべてのクライアントトラフィックをリセット"
|
||||
"resetAllClientTrafficContent" = "すべてのクライアントのトラフィックをリセットしてもよろしいですか?"
|
||||
"delDepletedClients" = "トラフィックが尽きたクライアントを削除"
|
||||
"delDepletedClientsTitle" = "トラフィックが尽きたクライアントを削除"
|
||||
"delDepletedClientsContent" = "トラフィックが尽きたすべてのクライアントを削除してもよろしいですか?"
|
||||
"email" = "メールアドレス"
|
||||
"emailDesc" = "メールアドレスは一意でなければなりません"
|
||||
"IPLimit" = "IP制限"
|
||||
"IPLimitDesc" = "設定値を超えるとインバウンドトラフィックが無効になります。(0 = 無効)"
|
||||
"IPLimitlog" = "IPログ"
|
||||
"IPLimitlogDesc" = "IP履歴ログ(無効なインバウンドトラフィックを有効にするには、ログをクリアしてください)"
|
||||
"IPLimitlogclear" = "ログをクリア"
|
||||
"setDefaultCert" = "パネル設定から証明書を設定"
|
||||
"telegramDesc" = "TelegramチャットIDを提供してください。(ボットで'/id'コマンドを使用)または(@userinfobot)"
|
||||
"subscriptionDesc" = "サブスクリプションURLを見つけるには、“詳細情報”に移動してください。また、複数のクライアントに同じ名前を使用することができます。"
|
||||
"info" = "情報"
|
||||
"same" = "同じ"
|
||||
"inboundData" = "インバウンドデータ"
|
||||
"exportInbound" = "インバウンドルールをエクスポート"
|
||||
"import" = "インポート"
|
||||
"importInbound" = "インバウンドルールをインポート"
|
||||
|
||||
[pages.client]
|
||||
"add" = "クライアント追加"
|
||||
"groupAdd" = "サブスクリプション ユーザーの追加"
|
||||
"isGroupEdit" = "グループの編集"
|
||||
"isGroupEditDesc" = "同じサブスクリプションを持つすべてのクライアントが編集されます"
|
||||
"edit" = "クライアント編集"
|
||||
"submitAdd" = "クライアント追加"
|
||||
"submitEdit" = "変更を保存"
|
||||
"clientCount" = "クライアント数"
|
||||
"bulk" = "一括作成"
|
||||
"method" = "方法"
|
||||
"first" = "最初"
|
||||
"last" = "最後"
|
||||
"prefix" = "プレフィックス"
|
||||
"postfix" = "サフィックス"
|
||||
"delayedStart" = "初回使用後に開始"
|
||||
"expireDays" = "期間"
|
||||
"days" = "日"
|
||||
"renew" = "自動更新"
|
||||
"renewDesc" = "期限が切れた後に自動更新。(0 = 無効)(単位:日)"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "取得"
|
||||
|
||||
[pages.inbounds.stream.general]
|
||||
"request" = "リクエスト"
|
||||
"response" = "レスポンス"
|
||||
"name" = "名前"
|
||||
"value" = "値"
|
||||
|
||||
[pages.inbounds.stream.tcp]
|
||||
"version" = "バージョン"
|
||||
"method" = "方法"
|
||||
"path" = "パス"
|
||||
"status" = "ステータス"
|
||||
"statusDescription" = "ステータス説明"
|
||||
"requestHeader" = "リクエストヘッダー"
|
||||
"responseHeader" = "レスポンスヘッダー"
|
||||
|
||||
[pages.settings]
|
||||
"title" = "パネル設定"
|
||||
"save" = "保存"
|
||||
"infoDesc" = "ここでのすべての変更は、保存してパネルを再起動する必要があります"
|
||||
"restartPanel" = "パネル再起動"
|
||||
"restartPanelDesc" = "パネルを再起動してもよろしいですか?再起動後にパネルにアクセスできない場合は、サーバーでパネルログを確認してください"
|
||||
"actions" = "操作"
|
||||
"resetDefaultConfig" = "デフォルト設定にリセット"
|
||||
"panelSettings" = "一般"
|
||||
"securitySettings" = "セキュリティ設定"
|
||||
"TGBotSettings" = "Telegramボット設定"
|
||||
"panelListeningIP" = "パネル監視IP"
|
||||
"panelListeningIPDesc" = "デフォルトではすべてのIPを監視する"
|
||||
"panelListeningDomain" = "パネル監視ドメイン"
|
||||
"panelListeningDomainDesc" = "デフォルトで空白の場合、すべてのドメインとIPアドレスを監視する"
|
||||
"panelPort" = "パネル監視ポート"
|
||||
"panelPortDesc" = "再起動で有効"
|
||||
"publicKeyPath" = "パネル証明書公開鍵ファイルパス"
|
||||
"publicKeyPathDesc" = "'/'で始まる絶対パスを入力"
|
||||
"privateKeyPath" = "パネル証明書秘密鍵ファイルパス"
|
||||
"privateKeyPathDesc" = "'/'で始まる絶対パスを入力"
|
||||
"panelUrlPath" = "パネルURLルートパス"
|
||||
"panelUrlPathDesc" = "'/'で始まり、'/'で終わる必要があります"
|
||||
"pageSize" = "ページサイズ"
|
||||
"pageSizeDesc" = "インバウンドテーブルのページサイズを定義します。0を設定すると無効化されます"
|
||||
"remarkModel" = "備考モデルと区切り記号"
|
||||
"datepicker" = "日付ピッカー"
|
||||
"datepickerPlaceholder" = "日付を選択"
|
||||
"datepickerDescription" = "日付選択カレンダーで有効期限を指定する"
|
||||
"sampleRemark" = "備考の例"
|
||||
"oldUsername" = "旧ユーザー名"
|
||||
"currentPassword" = "旧パスワード"
|
||||
"newUsername" = "新しいユーザー名"
|
||||
"newPassword" = "新しいパスワード"
|
||||
"telegramBotEnable" = "Telegramボットを有効にする"
|
||||
"telegramBotEnableDesc" = "Telegramボット機能を有効にする"
|
||||
"telegramToken" = "Telegramボットトークン"
|
||||
"telegramTokenDesc" = "'@BotFather'から取得したTelegramボットトークン"
|
||||
"telegramProxy" = "SOCKS5プロキシ"
|
||||
"telegramProxyDesc" = "SOCKS5プロキシを有効にしてTelegramに接続する(ガイドに従って設定を調整)"
|
||||
"telegramAPIServer" = "Telegram APIサーバー"
|
||||
"telegramAPIServerDesc" = "使用するTelegram APIサーバー。空白の場合はデフォルトサーバーを使用する"
|
||||
"telegramChatId" = "管理者チャットID"
|
||||
"telegramChatIdDesc" = "Telegram管理者チャットID(複数の場合はカンマで区切る)@userinfobotで取得するか、ボットで'/id'コマンドを使用して取得する"
|
||||
"telegramNotifyTime" = "通知時間"
|
||||
"telegramNotifyTimeDesc" = "定期的なTelegramボット通知時間を設定する(crontab時間形式を使用)"
|
||||
"tgNotifyBackup" = "データベースバックアップ"
|
||||
"tgNotifyBackupDesc" = "レポート付きのデータベースバックアップファイルを送信"
|
||||
"tgNotifyLogin" = "ログイン通知"
|
||||
"tgNotifyLoginDesc" = "誰かがパネルにログインしようとしたときに、ユーザー名、IPアドレス、時間を表示する"
|
||||
"sessionMaxAge" = "セッション期間"
|
||||
"sessionMaxAgeDesc" = "ログイン状態を保持する期間(単位:分)"
|
||||
"expireTimeDiff" = "有効期限通知のしきい値"
|
||||
"expireTimeDiffDesc" = "このしきい値に達した場合、有効期限に関する通知を受け取る(単位:日)"
|
||||
"trafficDiff" = "トラフィック消耗しきい値"
|
||||
"trafficDiffDesc" = "このしきい値に達した場合、トラフィック消耗に関する通知を受け取る(単位:GB)"
|
||||
"tgNotifyCpu" = "CPU負荷通知しきい値"
|
||||
"tgNotifyCpuDesc" = "CPU負荷がこのしきい値を超えた場合、通知を受け取る(単位:%)"
|
||||
"timeZone" = "タイムゾーン"
|
||||
"timeZoneDesc" = "定時タスクはこのタイムゾーンの時間に従って実行される"
|
||||
"subSettings" = "サブスクリプション設定"
|
||||
"subEnable" = "サブスクリプションサービスを有効にする"
|
||||
"subEnableDesc" = "サブスクリプションサービス機能を有効にする"
|
||||
"subSyncEnable" = "サブスクリプション同期を有効にする"
|
||||
"subSyncEnableDesc" = "同じサブスクリプションを持つクライアントからのトラフィックは 10 秒ごとに同期されます。"
|
||||
"subListen" = "監視IP"
|
||||
"subListenDesc" = "サブスクリプションサービスが監視するIPアドレス(空白にするとすべてのIPを監視)"
|
||||
"subPort" = "監視ポート"
|
||||
"subPortDesc" = "サブスクリプションサービスが監視するポート番号(使用されていないポートである必要があります)"
|
||||
"subCertPath" = "公開鍵パス"
|
||||
"subCertPathDesc" = "サブスクリプションサービスで使用する公開鍵ファイルのパス('/'で始まる)"
|
||||
"subKeyPath" = "秘密鍵パス"
|
||||
"subKeyPathDesc" = "サブスクリプションサービスで使用する秘密鍵ファイルのパス('/'で始まる)"
|
||||
"subPath" = "URIパス"
|
||||
"subPathDesc" = "サブスクリプションサービスで使用するURIパス('/'で始まり、'/'で終わる)"
|
||||
"subDomain" = "監視ドメイン"
|
||||
"subDomainDesc" = "サブスクリプションサービスが監視するドメイン(空白にするとすべてのドメインとIPを監視)"
|
||||
"subUpdates" = "更新間隔"
|
||||
"subUpdatesDesc" = "クライアントアプリケーションでサブスクリプションURLの更新間隔(単位:時間)"
|
||||
"subEncrypt" = "エンコード"
|
||||
"subEncryptDesc" = "サブスクリプションサービスが返す内容をBase64エンコードする"
|
||||
"subShowInfo" = "利用情報を表示"
|
||||
"subShowInfoDesc" = "クライアントアプリで残りのトラフィックと日付情報を表示する"
|
||||
"subURI" = "リバースプロキシURI"
|
||||
"subURIDesc" = "プロキシ後ろのサブスクリプションURLのURIパスに使用する"
|
||||
"fragment" = "フラグメント"
|
||||
"fragmentDesc" = "TLS helloパケットのフラグメントを有効にする"
|
||||
"fragmentSett" = "設定"
|
||||
"noisesDesc" = "Noisesを有効にする"
|
||||
"noisesSett" = "Noises設定"
|
||||
"mux" = "マルチプレクサ"
|
||||
"muxDesc" = "確立されたストリーム内で複数の独立したストリームを伝送する"
|
||||
"muxSett" = "マルチプレクサ設定"
|
||||
"direct" = "直接接続"
|
||||
"directDesc" = "特定の国のドメインまたはIP範囲に直接接続する"
|
||||
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray 設定"
|
||||
"save" = "保存"
|
||||
"restart" = "Xray 再起動"
|
||||
"basicTemplate" = "基本設定"
|
||||
"advancedTemplate" = "高度な設定"
|
||||
"generalConfigs" = "一般設定"
|
||||
"generalConfigsDesc" = "これらのオプションは一般設定を決定します"
|
||||
"logConfigs" = "ログ"
|
||||
"logConfigsDesc" = "ログはサーバーのパフォーマンスに影響を与える可能性があるため、必要な場合にのみ有効にすることをお勧めします"
|
||||
"blockConfigs" = "防御フィルター"
|
||||
"blockConfigsDesc" = "これらのオプションは、特定のプロトコルやウェブサイトへのユーザー接続をブロックします"
|
||||
"basicRouting" = "基本ルーティング"
|
||||
"blockConnectionsConfigsDesc" = "これらのオプションにより、特定のリクエスト元の国に基づいてトラフィックをブロックします。"
|
||||
"directConnectionsConfigsDesc" = "直接接続により、特定のトラフィックが他のサーバーを経由しないようにします。"
|
||||
"blockips" = "IPをブロック"
|
||||
"blockdomains" = "ドメインをブロック"
|
||||
"directips" = "直接IP"
|
||||
"directdomains" = "直接ドメイン"
|
||||
"ipv4Routing" = "IPv4 ルーティング"
|
||||
"ipv4RoutingDesc" = "このオプションはIPv4のみを介してターゲットドメインへルーティングします"
|
||||
"warpRouting" = "WARP ルーティング"
|
||||
"warpRoutingDesc" = "注意:これらのオプションを使用する前に、パネルのGitHubの手順に従って、サーバーにsocks5プロキシモードでWARPをインストールしてください。WARPはCloudflareサーバー経由でトラフィックをウェブサイトにルーティングします。"
|
||||
"Template" = "高度なXray設定テンプレート"
|
||||
"TemplateDesc" = "最終的なXray設定ファイルはこのテンプレートに基づいて生成されます"
|
||||
"FreedomStrategy" = "Freedom プロトコル戦略"
|
||||
"FreedomStrategyDesc" = "Freedomプロトコル内のネットワークの出力戦略を設定する"
|
||||
"RoutingStrategy" = "ルーティングドメイン戦略設定"
|
||||
"RoutingStrategyDesc" = "DNS解決の全体的なルーティング戦略を設定する"
|
||||
"Torrent" = "BitTorrent プロトコルをブロック"
|
||||
"TorrentDesc" = "BitTorrentの使用を禁止する"
|
||||
"Family" = "ファミリー保護"
|
||||
"FamilyDesc" = "アダルトコンテンツや悪意のあるサイトをブロックする"
|
||||
"Inbounds" = "インバウンドルール"
|
||||
"InboundsDesc" = "特定のクライアントからのトラフィックを受け入れる"
|
||||
"Outbounds" = "アウトバウンドルール"
|
||||
"Balancers" = "負荷分散"
|
||||
"OutboundsDesc" = "アウトバウンドトラフィックの送信方法を設定する"
|
||||
"Routings" = "ルーティングルール"
|
||||
"RoutingsDesc" = "各ルールの優先順位が重要です"
|
||||
"completeTemplate" = "すべて"
|
||||
"logLevel" = "ログレベル"
|
||||
"logLevelDesc" = "エラーログのレベルを指定し、記録する情報を示します"
|
||||
"accessLog" = "アクセスログ"
|
||||
"accessLogDesc" = "アクセスログのファイルパス。特殊値 'none' はアクセスログを無効にします"
|
||||
"errorLog" = "エラーログ"
|
||||
"errorLogDesc" = "エラーログのファイルパス。特殊値 'none' はエラーログを無効にします"
|
||||
"dnsLog" = "DNS ログ"
|
||||
"dnsLogDesc" = "DNSクエリのログを有効にするかどうか"
|
||||
"outboundTraffic" = "アウトバウンドトラフィック"
|
||||
"outboundTrafficDesc" = "アウトバウンドトラフィックを有効にするかどうか"
|
||||
"maskAddress" = "アドレスをマスク"
|
||||
"maskAddressDesc" = "IPアドレスをマスクし、有効にするとログに表示されるIPアドレスを自動的に置き換えます"
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "最初"
|
||||
"last" = "最後"
|
||||
"up" = "上へ"
|
||||
"down" = "下へ"
|
||||
"source" = "ソース"
|
||||
"dest" = "宛先アドレス"
|
||||
"inbound" = "インバウンド"
|
||||
"outbound" = "アウトバウンド"
|
||||
"balancer" = "負荷分散"
|
||||
"info" = "情報"
|
||||
"add" = "ルール追加"
|
||||
"edit" = "ルール編集"
|
||||
"useComma" = "カンマ区切りの項目"
|
||||
|
||||
[pages.xray.outbound]
|
||||
"addOutbound" = "アウトバウンド追加"
|
||||
"addReverse" = "リバース追加"
|
||||
"editOutbound" = "アウトバウンド編集"
|
||||
"editReverse" = "リバース編集"
|
||||
"tag" = "タグ"
|
||||
"tagDesc" = "一意のタグ"
|
||||
"address" = "アドレス"
|
||||
"reverse" = "リバース"
|
||||
"domain" = "ドメイン"
|
||||
"type" = "タイプ"
|
||||
"bridge" = "ブリッジ"
|
||||
"portal" = "ポータル"
|
||||
"intercon" = "インターコネクション"
|
||||
"settings" = "設定"
|
||||
"accountInfo" = "アカウント情報"
|
||||
"outboundStatus" = "アウトバウンドステータス"
|
||||
"sendThrough" = "送信経路"
|
||||
|
||||
[pages.xray.balancer]
|
||||
"addBalancer" = "負荷分散追加"
|
||||
"editBalancer" = "負荷分散編集"
|
||||
"balancerStrategy" = "戦略"
|
||||
"balancerSelectors" = "セレクター"
|
||||
"tag" = "タグ"
|
||||
"tagDesc" = "一意のタグ"
|
||||
"balancerDesc" = "balancerTagとoutboundTagは同時に使用できません。同時に使用された場合、outboundTagのみが有効になります。"
|
||||
|
||||
[pages.xray.wireguard]
|
||||
"secretKey" = "シークレットキー"
|
||||
"publicKey" = "公開鍵"
|
||||
"allowedIPs" = "許可されたIP"
|
||||
"endpoint" = "エンドポイント"
|
||||
"psk" = "共有キー"
|
||||
"domainStrategy" = "ドメイン戦略"
|
||||
|
||||
[pages.xray.dns]
|
||||
"enable" = "DNSを有効にする"
|
||||
"enableDesc" = "組み込みDNSサーバーを有効にする"
|
||||
"tag" = "DNSインバウンドタグ"
|
||||
"tagDesc" = "このタグはルーティングルールでインバウンドタグとして使用できます"
|
||||
"strategy" = "クエリ戦略"
|
||||
"strategyDesc" = "ドメイン名解決の全体的な戦略"
|
||||
"add" = "サーバー追加"
|
||||
"edit" = "サーバー編集"
|
||||
"domains" = "ドメイン"
|
||||
"expectIPs" = "期待されるIP"
|
||||
|
||||
[pages.xray.fakedns]
|
||||
"add" = "フェイクDNS追加"
|
||||
"edit" = "フェイクDNS編集"
|
||||
"ipPool" = "IPプールサブネット"
|
||||
"poolSize" = "プールサイズ"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "管理者"
|
||||
"secret" = "セキュリティトークン"
|
||||
"loginSecurity" = "ログインセキュリティ"
|
||||
"loginSecurityDesc" = "追加の認証を追加してセキュリティを向上させる"
|
||||
"secretToken" = "セキュリティトークン"
|
||||
"secretTokenDesc" = "このトークンを安全な場所に保管してください。このトークンはログインに使用され、紛失すると回復できません。"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "設定を変更"
|
||||
"getSettings" = "設定を取得"
|
||||
"modifyUser" = "管理者を変更"
|
||||
"originalUserPassIncorrect" = "旧ユーザー名または旧パスワードが間違っています"
|
||||
"userPassMustBeNotEmpty" = "新しいユーザー名と新しいパスワードは空にできません"
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ カスタムキーボードが閉じられました!"
|
||||
"noResult" = "❗ 結果がありません!"
|
||||
"noQuery" = "❌ クエリが見つかりませんでした!もう一度コマンドを使用してください!"
|
||||
"wentWrong" = "❌ 問題が発生しました!"
|
||||
"noIpRecord" = "❗ IP記録がありません!"
|
||||
"noInbounds" = "❗ インバウンド接続が見つかりません!"
|
||||
"unlimited" = "♾ 無制限"
|
||||
"add" = "追加"
|
||||
"month" = "月"
|
||||
"months" = "月"
|
||||
"day" = "日"
|
||||
"days" = "日"
|
||||
"hours" = "時間"
|
||||
"unknown" = "不明"
|
||||
"inbounds" = "インバウンド接続"
|
||||
"clients" = "クライアント"
|
||||
"offline" = "🔴 オフライン"
|
||||
"online" = "🟢 オンライン"
|
||||
|
||||
[tgbot.commands]
|
||||
"unknown" = "❗ 不明なコマンド"
|
||||
"pleaseChoose" = "👇 選択してください:\r\n"
|
||||
"help" = "🤖 このボットをご利用いただきありがとうございます!サーバーから特定のデータを提供し、必要な変更を行うことができます。\r\n\r\n"
|
||||
"start" = "👋 こんにちは、<i>{{ .Firstname }}</i>。\r\n"
|
||||
"welcome" = "🤖 <b>{{ .Hostname }}</b> 管理ボットへようこそ。\r\n"
|
||||
"status" = "✅ ボットは正常に動作しています!"
|
||||
"usage" = "❗ 検索するテキストを入力してください!"
|
||||
"getID" = "🆔 あなたのIDは:<code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "Xray Coreを再起動するには:\r\n<code>/restart force</code>\r\n\r\nクライアントの電子メールを検索するには:\r\n<code>/usage [電子メール]</code>\r\n\r\nインバウンド(クライアントの統計情報を含む)を検索するには:\r\n<code>/inbound [備考]</code>\r\n\r\nTelegramチャットID:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "統計情報を検索するには、次のコマンドを使用してください:\r\n<code>/usage [電子メール]</code>\r\n\r\nTelegramチャットID:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartSuccess" = "✅ 操作成功!"
|
||||
"restartFailed" = "❗ 操作エラー。\r\n\r\n<code>エラー: {{ .Error }}</code>"
|
||||
"xrayNotRunning" = "❗ Xray Core は動作していません。"
|
||||
|
||||
[tgbot.messages]
|
||||
"cpuThreshold" = "🔴 CPU使用率は{{ .Percent }}%、しきい値{{ .Threshold }}%を超えました"
|
||||
"selectUserFailed" = "❌ ユーザーの選択に失敗しました!"
|
||||
"userSaved" = "✅ Telegramユーザーが保存されました。"
|
||||
"loginSuccess" = "✅ パネルに正常にログインしました。\r\n"
|
||||
"loginFailed" = "❗️ パネルのログインに失敗しました。\r\n"
|
||||
"report" = "🕰 定期報告:{{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ 日時:{{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 ホスト名:{{ .Hostname }}\r\n"
|
||||
"version" = "🚀 X-UI バージョン:{{ .Version }}\r\n"
|
||||
"xrayVersion" = "📡 Xray バージョン: {{ .XrayVersion }}\r\n"
|
||||
"ipv6" = "🌐 IPv6:{{ .IPv6 }}\r\n"
|
||||
"ipv4" = "🌐 IPv4:{{ .IPv4 }}\r\n"
|
||||
"ip" = "🌐 IP:{{ .IP }}\r\n"
|
||||
"ips" = "🔢 IPアドレス:\r\n{{ .IPs }}\r\n"
|
||||
"serverUpTime" = "⏳ サーバー稼働時間:{{ .UpTime }} {{ .Unit }}\r\n"
|
||||
"serverLoad" = "📈 サーバー負荷:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
||||
"serverMemory" = "📋 サーバーメモリ:{{ .Current }}/{{ .Total }}\r\n"
|
||||
"tcpCount" = "🔹 TCP接続数:{{ .Count }}\r\n"
|
||||
"udpCount" = "🔸 UDP接続数:{{ .Count }}\r\n"
|
||||
"traffic" = "🚦 トラフィック:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Xrayステータス:{{ .State }}\r\n"
|
||||
"username" = "👤 ユーザー名:{{ .Username }}\r\n"
|
||||
"password" = "👤 パスワード: {{ .Password }}\r\n"
|
||||
"time" = "⏰ 時間:{{ .Time }}\r\n"
|
||||
"inbound" = "📍 インバウンド:{{ .Remark }}\r\n"
|
||||
"port" = "🔌 ポート:{{ .Port }}\r\n"
|
||||
"expire" = "📅 有効期限:{{ .Time }}\r\n"
|
||||
"expireIn" = "📅 残り時間:{{ .Time }}\r\n"
|
||||
"active" = "💡 有効:{{ .Enable }}\r\n"
|
||||
"enabled" = "🚨 有効化済み:{{ .Enable }}\r\n"
|
||||
"online" = "🌐 接続ステータス:{{ .Status }}\r\n"
|
||||
"email" = "📧 メール:{{ .Email }}\r\n"
|
||||
"upload" = "🔼 アップロード↑:{{ .Upload }}\r\n"
|
||||
"download" = "🔽 ダウンロード↓:{{ .Download }}\r\n"
|
||||
"total" = "📊 合計:{{ .UpDown }} / {{ .Total }}\r\n"
|
||||
"TGUser" = "👤 Telegramユーザー:{{ .TelegramID }}\r\n"
|
||||
"exhaustedMsg" = "🚨 消耗済みの {{ .Type }}:\r\n"
|
||||
"exhaustedCount" = "🚨 消耗済みの {{ .Type }} 数量:\r\n"
|
||||
"onlinesCount" = "🌐 オンラインクライアント:{{ .Count }}\r\n"
|
||||
"disabled" = "🛑 無効化:{{ .Disabled }}\r\n"
|
||||
"depleteSoon" = "🔜 間もなく消耗:{{ .Deplete }}\r\n\r\n"
|
||||
"backupTime" = "🗄 バックアップ時間:{{ .Time }}\r\n"
|
||||
"refreshedOn" = "\r\n📋🔄 更新時間:{{ .Time }}\r\n\r\n"
|
||||
"yes" = "✅ はい"
|
||||
"no" = "❌ いいえ"
|
||||
|
||||
[tgbot.buttons]
|
||||
"closeKeyboard" = "❌ キーボードを閉じる"
|
||||
"cancel" = "❌ キャンセル"
|
||||
"cancelReset" = "❌ リセットをキャンセル"
|
||||
"cancelIpLimit" = "❌ IP制限をキャンセル"
|
||||
"confirmResetTraffic" = "✅ トラフィックをリセットしますか?"
|
||||
"confirmClearIps" = "✅ IPをクリアしますか?"
|
||||
"confirmRemoveTGUser" = "✅ Telegramユーザーを削除しますか?"
|
||||
"confirmToggle" = "✅ ユーザーを有効/無効にしますか?"
|
||||
"dbBackup" = "データベースバックアップを取得"
|
||||
"serverUsage" = "サーバーの使用状況"
|
||||
"getInbounds" = "インバウンド情報を取得"
|
||||
"depleteSoon" = "間もなく消耗"
|
||||
"clientUsage" = "使用状況を取得"
|
||||
"onlines" = "オンラインクライアント"
|
||||
"commands" = "コマンド"
|
||||
"refresh" = "🔄 更新"
|
||||
"clearIPs" = "❌ IPをクリア"
|
||||
"removeTGUser" = "❌ Telegramユーザーを削除"
|
||||
"selectTGUser" = "👤 Telegramユーザーを選択"
|
||||
"selectOneTGUser" = "👤 1人のTelegramユーザーを選択:"
|
||||
"resetTraffic" = "📈 トラフィックをリセット"
|
||||
"resetExpire" = "📅 有効期限を変更"
|
||||
"ipLog" = "🔢 IPログ"
|
||||
"ipLimit" = "🔢 IP制限"
|
||||
"setTGUser" = "👤 Telegramユーザーを設定"
|
||||
"toggle" = "🔘 有効/無効"
|
||||
"custom" = "🔢 カスタム"
|
||||
"confirmNumber" = "✅ 確認: {{ .Num }}"
|
||||
"confirmNumberAdd" = "✅ 追加を確認:{{ .Num }}"
|
||||
"limitTraffic" = "🚧 トラフィック制限"
|
||||
"getBanLogs" = "禁止ログ"
|
||||
"allClients" = "すべてのクライアント"
|
||||
|
||||
[tgbot.answers]
|
||||
"successfulOperation" = "✅ 成功!"
|
||||
"errorOperation" = "❗ 操作エラー。"
|
||||
"getInboundsFailed" = "❌ インバウンド情報の取得に失敗しました。"
|
||||
"getClientsFailed" = "❌ クライアントの取得に失敗しました。"
|
||||
"canceled" = "❌ {{ .Email }}:操作がキャンセルされました。"
|
||||
"clientRefreshSuccess" = "✅ {{ .Email }}:クライアントが正常に更新されました。"
|
||||
"IpRefreshSuccess" = "✅ {{ .Email }}:IPが正常に更新されました。"
|
||||
"TGIdRefreshSuccess" = "✅ {{ .Email }}:クライアントのTelegramユーザーが正常に更新されました。"
|
||||
"resetTrafficSuccess" = "✅ {{ .Email }}:トラフィックが正常にリセットされました。"
|
||||
"setTrafficLimitSuccess" = "✅ {{ .Email }}:トラフィック制限が正常に保存されました。"
|
||||
"expireResetSuccess" = "✅ {{ .Email }}:有効期限の日数が正常にリセットされました。"
|
||||
"resetIpSuccess" = "✅ {{ .Email }}:IP制限数が正常に保存されました:{{ .Count }}。"
|
||||
"clearIpSuccess" = "✅ {{ .Email }}:IPが正常にクリアされました。"
|
||||
"getIpLog" = "✅ {{ .Email }}:IPログの取得。"
|
||||
"getUserInfo" = "✅ {{ .Email }}:Telegramユーザー情報の取得。"
|
||||
"removedTGUserSuccess" = "✅ {{ .Email }}:Telegramユーザーが正常に削除されました。"
|
||||
"enableSuccess" = "✅ {{ .Email }}:正常に有効化されました。"
|
||||
"disableSuccess" = "✅ {{ .Email }}:正常に無効化されました。"
|
||||
"askToAddUserId" = "設定が見つかりませんでした!\r\n管理者に問い合わせて、設定にTelegramユーザーのChatIDを使用してください。\r\n\r\nあなたのユーザーChatID:<code>{{ .TgUserID }}</code>"
|
||||
"chooseClient" = "インバウンド {{ .Inbound }} のクライアントを選択"
|
||||
"chooseInbound" = "インバウンドを選択"
|
||||
@@ -44,6 +44,7 @@
|
||||
"monitor" = "IP de Escuta"
|
||||
"certificate" = "Certificado Digital"
|
||||
"fail" = "Falhou"
|
||||
"comment" = "Comentário"
|
||||
"success" = "Com Sucesso"
|
||||
"getVersion" = "Obter Versão"
|
||||
"install" = "Instalar"
|
||||
@@ -189,6 +190,9 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Adicionar Cliente"
|
||||
"groupAdd" = "Adicionar usuário de assinatura"
|
||||
"isGroupEdit" = "Edição de grupo"
|
||||
"isGroupEditDesc" = "Todos os clientes com a mesma assinatura são editados"
|
||||
"edit" = "Editar Cliente"
|
||||
"submitAdd" = "Adicionar Cliente"
|
||||
"submitEdit" = "Salvar Alterações"
|
||||
@@ -286,6 +290,8 @@
|
||||
"subSettings" = "Assinatura"
|
||||
"subEnable" = "Ativar Serviço de Assinatura"
|
||||
"subEnableDesc" = "Ativa o serviço de assinatura."
|
||||
"subSyncEnable" = "Habilitar sincronização de assinatura"
|
||||
"subSyncEnableDesc" = "O tráfego de clientes com a mesma assinatura será sincronizado a cada 10 segundos."
|
||||
"subListen" = "IP de Escuta"
|
||||
"subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)"
|
||||
"subPort" = "Porta de Escuta"
|
||||
@@ -367,6 +373,8 @@
|
||||
"errorLogDesc" = "O caminho do arquivo para o log de erros. O valor especial 'none' desativa os logs de erro."
|
||||
"dnsLog" = "Log DNS"
|
||||
"dnsLogDesc" = "Se ativar logs de consulta DNS"
|
||||
"outboundTraffic" = "Tráfego de saída"
|
||||
"outboundTrafficDesc" = "Se deve habilitar o tráfego de saída"
|
||||
"maskAddress" = "Mascarar Endereço"
|
||||
"maskAddressDesc" = "Máscara de endereço IP, quando ativado, substitui automaticamente o endereço IP que aparece no log."
|
||||
|
||||
|
||||
@@ -41,16 +41,17 @@
|
||||
"offline" = "Офлайн"
|
||||
"online" = "Онлайн"
|
||||
"domainName" = "Домен"
|
||||
"monitor" = "Порт IP"
|
||||
"monitor" = "Слушать IP"
|
||||
"certificate" = "Цифровой сертификат"
|
||||
"fail" = "Неудачно"
|
||||
"comment" = "Комментарий"
|
||||
"success" = "Успешно"
|
||||
"getVersion" = "Узнать версию"
|
||||
"install" = "Установка"
|
||||
"clients" = "Клиенты"
|
||||
"usage" = "Использование"
|
||||
"secretToken" = "Секретный токен"
|
||||
"remained" = "остались"
|
||||
"remained" = "Остаток"
|
||||
"security" = "Безопасность"
|
||||
"secAlertTitle" = "Предупреждение системы безопасности"
|
||||
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
|
||||
@@ -78,7 +79,7 @@
|
||||
"invalidFormData" = "Недопустимый формат данных"
|
||||
"emptyUsername" = "Введите имя пользователя"
|
||||
"emptyPassword" = "Введите пароль"
|
||||
"wrongUsernameOrPassword" = "Неверное имя пользователя или пароль"
|
||||
"wrongUsernameOrPassword" = "Неверное имя пользователя, пароль или секретный токен."
|
||||
"successLogin" = "Успешный вход"
|
||||
|
||||
[pages.index]
|
||||
@@ -108,7 +109,7 @@
|
||||
"config" = "Конфигурация"
|
||||
"backup" = "Бэкап и восстановление"
|
||||
"backupTitle" = "База данных бэкапа и восстановления"
|
||||
"backupDescription" = "Не забудьте сделать резервную копию перед импортом новой базы данных"
|
||||
"backupDescription" = "Рекомендуется сделать резервную копию перед восстановлением базы данных."
|
||||
"exportDatabase" = "Экспорт базы данных"
|
||||
"importDatabase" = "Импорт базы данных"
|
||||
|
||||
@@ -146,7 +147,7 @@
|
||||
"meansNoLimit" = "= Без ограничений (значение: ГБ)"
|
||||
"totalFlow" = "Общий расход"
|
||||
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы не истекало"
|
||||
"noRecommendKeepDefault" = "Не рекомендуется оставлять настройки по умолчанию"
|
||||
"noRecommendKeepDefault" = "Рекомендуется оставить настройки по умолчанию"
|
||||
"certificatePath" = "Путь к файлу"
|
||||
"certificateContent" = "Содержимое файла"
|
||||
"publicKey" = "Публичный ключ"
|
||||
@@ -189,6 +190,9 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Добавить пользователя"
|
||||
"groupAdd" = "Добавить пользователя подписки"
|
||||
"isGroupEdit" = "Групповое редактирование"
|
||||
"isGroupEditDesc" = "Все клиенты с одинаковой подпиской редактируются"
|
||||
"edit" = "Редактировать пользователя"
|
||||
"submitAdd" = "Добавить пользователя"
|
||||
"submitEdit" = "Сохранить изменения"
|
||||
@@ -286,6 +290,8 @@
|
||||
"subSettings" = "Подписка"
|
||||
"subEnable" = "Включить службу"
|
||||
"subEnableDesc" = "Функция подписки с отдельной конфигурацией"
|
||||
"subSyncEnable" = "Включить синхронизацию подписки"
|
||||
"subSyncEnableDesc" = "Трафик от клиентов с одинаковой подпиской будет синхронизироваться каждые 10 секунд."
|
||||
"subListen" = "Прослушивание IP"
|
||||
"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса"
|
||||
"subPort" = "Порт подписки"
|
||||
@@ -367,6 +373,8 @@
|
||||
"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок."
|
||||
"dnsLog" = "DNS Журнал"
|
||||
"dnsLogDesc" = "Включить логи запросов DNS"
|
||||
"outboundTraffic" = "Исходящий трафик"
|
||||
"outboundTrafficDesc" = "Включить исходящий трафик"
|
||||
"maskAddress" = "Маскировать Адрес"
|
||||
"maskAddressDesc" = "Маска IP-адреса, при активации автоматически заменяет IP-адрес, который появляется в логе."
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"monitor" = "Dinleme IP"
|
||||
"certificate" = "Dijital Sertifika"
|
||||
"fail" = "Başarısız"
|
||||
"comment" = "Yorum"
|
||||
"success" = "Başarılı"
|
||||
"getVersion" = "Sürümü Al"
|
||||
"install" = "Yükle"
|
||||
@@ -189,6 +190,9 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Müşteri Ekle"
|
||||
"groupAdd" = "Abonelik kullanıcısı ekle"
|
||||
"isGroupEdit" = "Grup düzenleme"
|
||||
"isGroupEditDesc" = "Aynı aboneliğe sahip tüm istemciler düzenlendi"
|
||||
"edit" = "Müşteriyi Düzenle"
|
||||
"submitAdd" = "Müşteri Ekle"
|
||||
"submitEdit" = "Değişiklikleri Kaydet"
|
||||
@@ -286,6 +290,8 @@
|
||||
"subSettings" = "Abonelik"
|
||||
"subEnable" = "Abonelik Hizmetini Etkinleştir"
|
||||
"subEnableDesc" = "Abonelik hizmetini etkinleştirir."
|
||||
"subSyncEnable" = "Abonelik Senkronizasyonunu Etkinleştir"
|
||||
"subSyncEnableDesc" = "Aynı aboneliğe sahip istemcilerden gelen trafik her 10 saniyede bir senkronize edilecektir."
|
||||
"subListen" = "Dinleme IP"
|
||||
"subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)"
|
||||
"subPort" = "Dinleme Portu"
|
||||
@@ -367,6 +373,8 @@
|
||||
"errorLogDesc" = "Hata günlüğü için dosya yolu. 'none' özel değeri hata günlüklerini devre dışı bırakır"
|
||||
"dnsLog" = "DNS Günlüğü"
|
||||
"dnsLogDesc" = "DNS sorgu günlüklerini etkinleştirin"
|
||||
"outboundTraffic" = "Gidenler trafiği"
|
||||
"outboundTrafficDesc" = "Çıkış trafiğini etkinleştirip etkinleştirmeyeceğiniz"
|
||||
"maskAddress" = "Adres Maskesi"
|
||||
"maskAddressDesc" = "IP adresi maskesi, etkinleştirildiğinde, günlükte görünen IP adresini otomatik olarak değiştirecektir."
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"monitor" = "Слухати IP"
|
||||
"certificate" = "Цифровий сертифікат"
|
||||
"fail" = "Помилка"
|
||||
"comment" = "Коментар"
|
||||
"success" = "Успішно"
|
||||
"getVersion" = "Отримати версію"
|
||||
"install" = "Встановити"
|
||||
@@ -189,6 +190,9 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Додати клієнта"
|
||||
"groupAdd" = "Додати підписаного користувача"
|
||||
"isGroupEdit" = "Редагування групи"
|
||||
"isGroupEditDesc" = "Всі клієнти з однаковою підпискою редагуються"
|
||||
"edit" = "Редагувати клієнта"
|
||||
"submitAdd" = "Додати клієнта"
|
||||
"submitEdit" = "Зберегти зміни"
|
||||
@@ -286,6 +290,8 @@
|
||||
"subSettings" = "Підписка"
|
||||
"subEnable" = "Увімкнути службу підписки"
|
||||
"subEnableDesc" = "Вмикає службу підписки."
|
||||
"subSyncEnable" = "Увімкнути синхронізацію підписки"
|
||||
"subSyncEnableDesc" = "Трафік від клієнтів з однаковою підпискою буде синхронізовано кожні 10 секунд."
|
||||
"subListen" = "Слухати IP"
|
||||
"subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)"
|
||||
"subPort" = "Слухати порт"
|
||||
@@ -367,6 +373,8 @@
|
||||
"errorLogDesc" = "Шлях до файлу журналу помилок. Спеціальне значення 'none' вимикає журнали помилок"
|
||||
"dnsLog" = "Журнал DNS"
|
||||
"dnsLogDesc" = "Чи включити журнали запитів DNS"
|
||||
"outboundTraffic" = "Вихідний трафік"
|
||||
"outboundTrafficDesc" = "Чи потрібно увімкнути вихідний трафік"
|
||||
"maskAddress" = "Маскувати Адресу"
|
||||
"maskAddressDesc" = "Маска IP-адреси, при активації автоматично замінює IP-адресу, яка з'являється у журналі."
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"monitor" = "Listening IP"
|
||||
"certificate" = "Chứng chỉ số"
|
||||
"fail" = "Thất bại"
|
||||
"comment" = "Bình luận"
|
||||
"success" = "Thành công"
|
||||
"getVersion" = "Lấy phiên bản"
|
||||
"install" = "Cài đặt"
|
||||
@@ -189,6 +190,9 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Thêm người dùng"
|
||||
"groupAdd" = "Thêm người dùng đăng ký"
|
||||
"isGroupEdit" = "Chỉnh sửa nhóm"
|
||||
"isGroupEditDesc" = "Tất cả người dùng có cùng đăng ký đều có thể được chỉnh sửa"
|
||||
"edit" = "Chỉnh sửa người dùng"
|
||||
"submitAdd" = "Thêm"
|
||||
"submitEdit" = "Lưu thay đổi"
|
||||
@@ -286,6 +290,8 @@
|
||||
"subSettings" = "Gói đăng ký"
|
||||
"subEnable" = "Bật dịch vụ"
|
||||
"subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng"
|
||||
"subSyncEnable" = "Bật đồng bộ đăng ký"
|
||||
"subSyncEnableDesc" = "Lưu lượng truy cập từ các máy khách có cùng đăng ký sẽ được đồng bộ sau mỗi 10 giây."
|
||||
"subListen" = "Listening IP"
|
||||
"subListenDesc" = "Mặc định để trống để nghe tất cả các IP"
|
||||
"subPort" = "Cổng gói đăng ký"
|
||||
@@ -367,6 +373,8 @@
|
||||
"errorLogDesc" = "Đường dẫn tệp cho nhật ký lỗi. Nhật ký lỗi bị vô hiệu hóa có giá trị đặc biệt 'không'"
|
||||
"dnsLog" = "Nhật ký DNS"
|
||||
"dnsLogDesc" = "Có bật nhật ký truy vấn DNS không"
|
||||
"outboundTraffic" = "Lưu lượng đi ra"
|
||||
"outboundTrafficDesc" = "Có nên bật lưu lượng ra không"
|
||||
"maskAddress" = "Ẩn Địa Chỉ"
|
||||
"maskAddressDesc" = "Mặt nạ địa chỉ IP, khi được bật, sẽ tự động thay thế địa chỉ IP xuất hiện trong nhật ký."
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"monitor" = "监听"
|
||||
"certificate" = "数字证书"
|
||||
"fail" = "失败"
|
||||
"comment" = "评论"
|
||||
"success" = "成功"
|
||||
"getVersion" = "获取版本"
|
||||
"install" = "安装"
|
||||
@@ -189,6 +190,9 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "添加客户端"
|
||||
"groupAdd" = "添加订阅用户"
|
||||
"isGroupEdit" = "群组编辑"
|
||||
"isGroupEditDesc" = "所有具有相同订阅的客户端均被编辑"
|
||||
"edit" = "编辑客户端"
|
||||
"submitAdd" = "添加客户端"
|
||||
"submitEdit" = "保存修改"
|
||||
@@ -286,6 +290,8 @@
|
||||
"subSettings" = "订阅设置"
|
||||
"subEnable" = "启用订阅服务"
|
||||
"subEnableDesc" = "启用订阅服务功能"
|
||||
"subSyncEnable" = "启用订阅同步"
|
||||
"subSyncEnableDesc" = "具有相同订阅的客户端的流量将每 10 秒同步一次。"
|
||||
"subListen" = "监听 IP"
|
||||
"subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP)"
|
||||
"subPort" = "监听端口"
|
||||
@@ -367,6 +373,8 @@
|
||||
"errorLogDesc" = "错误日志的文件路径。特殊值 'none' 禁用错误日志"
|
||||
"dnsLog" = "DNS 日志"
|
||||
"dnsLogDesc" = "是否启用 DNS 查询日志"
|
||||
"outboundTraffic" = "出站流量"
|
||||
"outboundTrafficDesc" = "是否启用出站流量"
|
||||
"maskAddress" = "隐藏地址"
|
||||
"maskAddressDesc" = "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。"
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"monitor" = "監聽"
|
||||
"certificate" = "憑證"
|
||||
"fail" = "失敗"
|
||||
"comment" = "評論"
|
||||
"success" = "成功"
|
||||
"getVersion" = "獲取版本"
|
||||
"install" = "安裝"
|
||||
@@ -189,6 +190,9 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "新增客戶端"
|
||||
"groupAdd" = "新增訂閱使用者"
|
||||
"isGroupEdit" = "群組編輯"
|
||||
"isGroupEditDesc" = "所有具有相同訂閱的用戶端都被編輯"
|
||||
"edit" = "編輯客戶端"
|
||||
"submitAdd" = "新增客戶端"
|
||||
"submitEdit" = "儲存修改"
|
||||
@@ -286,6 +290,8 @@
|
||||
"subSettings" = "訂閱設定"
|
||||
"subEnable" = "啟用訂閱服務"
|
||||
"subEnableDesc" = "啟用訂閱服務功能"
|
||||
"subSyncEnable" = "啟用訂閱同步"
|
||||
"subSyncEnableDesc" = "來自具有相同訂閱的客戶端的流量將每 10 秒同步一次。"
|
||||
"subListen" = "監聽 IP"
|
||||
"subListenDesc" = "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP)"
|
||||
"subPort" = "監聽埠"
|
||||
@@ -367,6 +373,8 @@
|
||||
"errorLogDesc" = "錯誤日誌的檔案路徑。特殊值 'none' 禁用錯誤日誌"
|
||||
"dnsLog" = "DNS 日誌"
|
||||
"dnsLogDesc" = "是否啟用 DNS 查詢日誌"
|
||||
"outboundTraffic" = "出站流量"
|
||||
"outboundTrafficDesc" = "是否啟用出站流量"
|
||||
"maskAddress" = "隱藏地址"
|
||||
"maskAddressDesc" = "IP 地址掩碼,啟用時會自動替換日誌中出現的 IP 地址。"
|
||||
|
||||
|
||||
@@ -260,6 +260,13 @@ func (s *Server) startTask() {
|
||||
s.cron.AddJob("@every 10s", job.NewXrayTrafficJob())
|
||||
}()
|
||||
|
||||
isSubEnable, err1 := s.settingService.GetSubEnable()
|
||||
isSubSyncEnable, err2 := s.settingService.GetSubSyncEnable()
|
||||
if err1 == nil && err2 == nil && isSubEnable && isSubSyncEnable {
|
||||
// Sync the client traffic with the same SubId every 10 seconds
|
||||
s.cron.AddJob("@every 10s", job.NewClientTrafficSyncJob())
|
||||
}
|
||||
|
||||
// check client ips from log file every 10 sec
|
||||
s.cron.AddJob("@every 10s", job.NewCheckClientIpJob())
|
||||
|
||||
|
||||
406
x-ui.sh
@@ -2,6 +2,7 @@
|
||||
|
||||
red='\033[0;31m'
|
||||
green='\033[0;32m'
|
||||
blue='\033[0;34m'
|
||||
yellow='\033[0;33m'
|
||||
plain='\033[0m'
|
||||
|
||||
@@ -682,10 +683,13 @@ show_xray_status() {
|
||||
}
|
||||
|
||||
firewall_menu() {
|
||||
echo -e "${green}\t1.${plain} Install Firewall & open ports"
|
||||
echo -e "${green}\t2.${plain} Allowed List"
|
||||
echo -e "${green}\t3.${plain} Delete Ports from List"
|
||||
echo -e "${green}\t4.${plain} Disable Firewall"
|
||||
echo -e "${green}\t1.${plain} ${green}Install${plain} Firewall"
|
||||
echo -e "${green}\t2.${plain} Port List [numbered]"
|
||||
echo -e "${green}\t3.${plain} ${green}Open${plain} Ports"
|
||||
echo -e "${green}\t4.${plain} ${red}Delete${plain} Ports from List"
|
||||
echo -e "${green}\t5.${plain} ${green}Enable${plain} Firewall"
|
||||
echo -e "${green}\t6.${plain} ${red}Disable${plain} Firewall"
|
||||
echo -e "${green}\t7.${plain} Firewall Status"
|
||||
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||||
read -p "Choose an option: " choice
|
||||
case "$choice" in
|
||||
@@ -693,19 +697,31 @@ firewall_menu() {
|
||||
show_menu
|
||||
;;
|
||||
1)
|
||||
open_ports
|
||||
install_firewall
|
||||
firewall_menu
|
||||
;;
|
||||
2)
|
||||
sudo ufw status
|
||||
ufw status numbered
|
||||
firewall_menu
|
||||
;;
|
||||
3)
|
||||
delete_ports
|
||||
open_ports
|
||||
firewall_menu
|
||||
;;
|
||||
4)
|
||||
sudo ufw disable
|
||||
delete_ports
|
||||
firewall_menu
|
||||
;;
|
||||
5)
|
||||
ufw enable
|
||||
firewall_menu
|
||||
;;
|
||||
6)
|
||||
ufw disable
|
||||
firewall_menu
|
||||
;;
|
||||
7)
|
||||
ufw status verbose
|
||||
firewall_menu
|
||||
;;
|
||||
*)
|
||||
@@ -715,7 +731,7 @@ firewall_menu() {
|
||||
esac
|
||||
}
|
||||
|
||||
open_ports() {
|
||||
install_firewall() {
|
||||
if ! command -v ufw &>/dev/null; then
|
||||
echo "ufw firewall is not installed. Installing now..."
|
||||
apt-get update
|
||||
@@ -733,13 +749,16 @@ open_ports() {
|
||||
ufw allow ssh
|
||||
ufw allow http
|
||||
ufw allow https
|
||||
ufw allow 2053/tcp
|
||||
ufw allow 2053/tcp #webPort
|
||||
ufw allow 2096/tcp #subport
|
||||
|
||||
# Enable the firewall
|
||||
ufw --force enable
|
||||
fi
|
||||
}
|
||||
|
||||
# Prompt the user to enter a list of ports
|
||||
open_ports() {
|
||||
# Prompt the user to enter the ports they want to open
|
||||
read -p "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports
|
||||
|
||||
# Check if the input is valid
|
||||
@@ -755,70 +774,113 @@ open_ports() {
|
||||
# Split the range into start and end ports
|
||||
start_port=$(echo $port | cut -d'-' -f1)
|
||||
end_port=$(echo $port | cut -d'-' -f2)
|
||||
# Open the port range
|
||||
ufw allow $start_port:$end_port/tcp
|
||||
ufw allow $start_port:$end_port/udp
|
||||
else
|
||||
# Open the single port
|
||||
ufw allow "$port"
|
||||
fi
|
||||
done
|
||||
|
||||
# Confirm that the ports are open
|
||||
echo "The following ports are now open:"
|
||||
ufw status | grep "ALLOW" | grep -Eo "[0-9]+(/[a-z]+)?"
|
||||
|
||||
echo "Firewall status:"
|
||||
ufw status verbose
|
||||
# Confirm that the ports are opened
|
||||
echo "Opened the specified ports:"
|
||||
for port in "${PORT_LIST[@]}"; do
|
||||
if [[ $port == *-* ]]; then
|
||||
start_port=$(echo $port | cut -d'-' -f1)
|
||||
end_port=$(echo $port | cut -d'-' -f2)
|
||||
# Check if the port range has been successfully opened
|
||||
(ufw status | grep -q "$start_port:$end_port") && echo "$start_port-$end_port"
|
||||
else
|
||||
# Check if the individual port has been successfully opened
|
||||
(ufw status | grep -q "$port") && echo "$port"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
delete_ports() {
|
||||
# Prompt the user to enter the ports they want to delete
|
||||
read -p "Enter the ports you want to delete (e.g. 80,443,2053 or range 400-500): " ports
|
||||
# Display current rules with numbers
|
||||
echo "Current UFW rules:"
|
||||
ufw status numbered
|
||||
|
||||
# Check if the input is valid
|
||||
if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
|
||||
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2
|
||||
# Ask the user how they want to delete rules
|
||||
echo "Do you want to delete rules by:"
|
||||
echo "1) Rule numbers"
|
||||
echo "2) Ports"
|
||||
read -p "Enter your choice (1 or 2): " choice
|
||||
|
||||
if [[ $choice -eq 1 ]]; then
|
||||
# Deleting by rule numbers
|
||||
read -p "Enter the rule numbers you want to delete (1, 2, etc.): " rule_numbers
|
||||
|
||||
# Validate the input
|
||||
if ! [[ $rule_numbers =~ ^([0-9]+)(,[0-9]+)*$ ]]; then
|
||||
echo "Error: Invalid input. Please enter a comma-separated list of rule numbers." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Split numbers into an array
|
||||
IFS=',' read -ra RULE_NUMBERS <<<"$rule_numbers"
|
||||
for rule_number in "${RULE_NUMBERS[@]}"; do
|
||||
# Delete the rule by number
|
||||
ufw delete "$rule_number" || echo "Failed to delete rule number $rule_number"
|
||||
done
|
||||
|
||||
echo "Selected rules have been deleted."
|
||||
|
||||
elif [[ $choice -eq 2 ]]; then
|
||||
# Deleting by ports
|
||||
read -p "Enter the ports you want to delete (e.g. 80,443,2053 or range 400-500): " ports
|
||||
|
||||
# Validate the input
|
||||
if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
|
||||
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Split ports into an array
|
||||
IFS=',' read -ra PORT_LIST <<<"$ports"
|
||||
for port in "${PORT_LIST[@]}"; do
|
||||
if [[ $port == *-* ]]; then
|
||||
# Split the port range
|
||||
start_port=$(echo $port | cut -d'-' -f1)
|
||||
end_port=$(echo $port | cut -d'-' -f2)
|
||||
# Delete the port range
|
||||
ufw delete allow $start_port:$end_port/tcp
|
||||
ufw delete allow $start_port:$end_port/udp
|
||||
else
|
||||
# Delete a single port
|
||||
ufw delete allow "$port"
|
||||
fi
|
||||
done
|
||||
|
||||
# Confirmation of deletion
|
||||
echo "Deleted the specified ports:"
|
||||
for port in "${PORT_LIST[@]}"; do
|
||||
if [[ $port == *-* ]]; then
|
||||
start_port=$(echo $port | cut -d'-' -f1)
|
||||
end_port=$(echo $port | cut -d'-' -f2)
|
||||
# Check if the port range has been deleted
|
||||
(ufw status | grep -q "$start_port:$end_port") || echo "$start_port-$end_port"
|
||||
else
|
||||
# Check if the individual port has been deleted
|
||||
(ufw status | grep -q "$port") || echo "$port"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "${red}Error:${plain} Invalid choice. Please enter 1 or 2." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Delete the specified ports using ufw
|
||||
IFS=',' read -ra PORT_LIST <<<"$ports"
|
||||
for port in "${PORT_LIST[@]}"; do
|
||||
if [[ $port == *-* ]]; then
|
||||
# Split the range into start and end ports
|
||||
start_port=$(echo $port | cut -d'-' -f1)
|
||||
end_port=$(echo $port | cut -d'-' -f2)
|
||||
# Delete the port range
|
||||
ufw delete allow $start_port:$end_port/tcp
|
||||
ufw delete allow $start_port:$end_port/udp
|
||||
else
|
||||
ufw delete allow "$port"
|
||||
fi
|
||||
done
|
||||
|
||||
# Confirm that the ports are deleted
|
||||
|
||||
echo "Deleted the specified ports:"
|
||||
for port in "${PORT_LIST[@]}"; do
|
||||
if [[ $port == *-* ]]; then
|
||||
start_port=$(echo $port | cut -d'-' -f1)
|
||||
end_port=$(echo $port | cut -d'-' -f2)
|
||||
# Check if the port range has been successfully deleted
|
||||
(ufw status | grep -q "$start_port:$end_port") || echo "$start_port-$end_port"
|
||||
else
|
||||
# Check if the individual port has been successfully deleted
|
||||
(ufw status | grep -q "$port") || echo "$port"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
update_geo() {
|
||||
echo -e "${green}\t1.${plain} Loyalsoldier (geoip.dat, geosite.dat)"
|
||||
echo -e "${green}\t2.${plain} chocolate4u (geoip_IR.dat, geosite_IR.dat)"
|
||||
echo -e "${green}\t3.${plain} vuong2023 (geoip_VN.dat, geosite_VN.dat)"
|
||||
echo -e "${green}\t3.${plain} runetfreedom (geoip_RU.dat, geosite_RU.dat)"
|
||||
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||||
read -p "Choose an option: " choice
|
||||
|
||||
systemctl stop x-ui
|
||||
cd /usr/local/x-ui/bin
|
||||
|
||||
case "$choice" in
|
||||
@@ -826,6 +888,7 @@ update_geo() {
|
||||
show_menu
|
||||
;;
|
||||
1)
|
||||
systemctl stop x-ui
|
||||
rm -f geoip.dat geosite.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
|
||||
@@ -833,6 +896,7 @@ update_geo() {
|
||||
restart
|
||||
;;
|
||||
2)
|
||||
systemctl stop x-ui
|
||||
rm -f geoip_IR.dat geosite_IR.dat
|
||||
wget -O geoip_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -O geosite_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
@@ -840,10 +904,11 @@ update_geo() {
|
||||
restart
|
||||
;;
|
||||
3)
|
||||
rm -f geoip_VN.dat geosite_VN.dat
|
||||
wget -O geoip_VN.dat -N https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -O geosite_VN.dat -N https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
|
||||
echo -e "${green}vuong2023 datasets have been updated successfully!${plain}"
|
||||
systemctl stop x-ui
|
||||
rm -f geoip_RU.dat geosite_RU.dat
|
||||
wget -O geoip_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget -O geosite_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
echo -e "${green}runetfreedom datasets have been updated successfully!${plain}"
|
||||
restart
|
||||
;;
|
||||
*)
|
||||
@@ -852,7 +917,6 @@ update_geo() {
|
||||
;;
|
||||
esac
|
||||
|
||||
systemctl start x-ui
|
||||
before_show_menu
|
||||
}
|
||||
|
||||
@@ -1183,8 +1247,14 @@ ssl_cert_issue_CF() {
|
||||
fi
|
||||
|
||||
# Install the certificate
|
||||
mkdir -p ${certPath}/${CF_Domain}
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "Failed to create directory: ${certPath}/${CF_Domain}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} \
|
||||
--cert-file ${certPath}/${CF_Domain}/fullchain.pem \
|
||||
--fullchain-file ${certPath}/${CF_Domain}/fullchain.pem \
|
||||
--key-file ${certPath}/${CF_Domain}/privkey.pem
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
@@ -1232,40 +1302,47 @@ ssl_cert_issue_CF() {
|
||||
run_speedtest() {
|
||||
# Check if Speedtest is already installed
|
||||
if ! command -v speedtest &>/dev/null; then
|
||||
# If not installed, install it
|
||||
local pkg_manager=""
|
||||
local speedtest_install_script=""
|
||||
|
||||
if command -v dnf &>/dev/null; then
|
||||
pkg_manager="dnf"
|
||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
|
||||
elif command -v yum &>/dev/null; then
|
||||
pkg_manager="yum"
|
||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
|
||||
elif command -v apt-get &>/dev/null; then
|
||||
pkg_manager="apt-get"
|
||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
|
||||
elif command -v apt &>/dev/null; then
|
||||
pkg_manager="apt"
|
||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
|
||||
fi
|
||||
|
||||
if [[ -z $pkg_manager ]]; then
|
||||
echo "Error: Package manager not found. You may need to install Speedtest manually."
|
||||
return 1
|
||||
# If not installed, determine installation method
|
||||
if command -v snap &>/dev/null; then
|
||||
# Use snap to install Speedtest
|
||||
echo "Installing Speedtest using snap..."
|
||||
snap install speedtest
|
||||
else
|
||||
curl -s $speedtest_install_script | bash
|
||||
$pkg_manager install -y speedtest
|
||||
# Fallback to using package managers
|
||||
local pkg_manager=""
|
||||
local speedtest_install_script=""
|
||||
|
||||
if command -v dnf &>/dev/null; then
|
||||
pkg_manager="dnf"
|
||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
|
||||
elif command -v yum &>/dev/null; then
|
||||
pkg_manager="yum"
|
||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
|
||||
elif command -v apt-get &>/dev/null; then
|
||||
pkg_manager="apt-get"
|
||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
|
||||
elif command -v apt &>/dev/null; then
|
||||
pkg_manager="apt"
|
||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
|
||||
fi
|
||||
|
||||
if [[ -z $pkg_manager ]]; then
|
||||
echo "Error: Package manager not found. You may need to install Speedtest manually."
|
||||
return 1
|
||||
else
|
||||
echo "Installing Speedtest using $pkg_manager..."
|
||||
curl -s $speedtest_install_script | bash
|
||||
$pkg_manager install -y speedtest
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run Speedtest
|
||||
speedtest
|
||||
}
|
||||
|
||||
create_iplimit_jails() {
|
||||
# Use default bantime if not passed => 15 minutes
|
||||
local bantime="${1:-15}"
|
||||
# Use default bantime if not passed => 30 minutes
|
||||
local bantime="${1:-30}"
|
||||
|
||||
# Uncomment 'allowipv6 = auto' in fail2ban.conf
|
||||
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
|
||||
@@ -1280,7 +1357,7 @@ create_iplimit_jails() {
|
||||
enabled=true
|
||||
backend=auto
|
||||
filter=3x-ipl
|
||||
action = %(known/action)s[name=%(__name__)s, protocol="%(protocol)s", chain="%(chain)s"]
|
||||
action=3x-ipl
|
||||
logpath=${iplimit_log_path}
|
||||
maxretry=2
|
||||
findtime=32
|
||||
@@ -1296,7 +1373,7 @@ EOF
|
||||
|
||||
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
|
||||
[INCLUDES]
|
||||
before = iptables-common.conf
|
||||
before = iptables-allports.conf
|
||||
|
||||
[Definition]
|
||||
actionstart = <iptables> -N f2b-<name>
|
||||
@@ -1316,8 +1393,6 @@ actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
|
||||
|
||||
[Init]
|
||||
# Use default settings from iptables-common.conf
|
||||
# This will automatically handle both IPv4 and IPv6
|
||||
name = default
|
||||
protocol = tcp
|
||||
chain = INPUT
|
||||
@@ -1341,15 +1416,22 @@ iplimit_remove_conflicts() {
|
||||
done
|
||||
}
|
||||
|
||||
ip_validation() {
|
||||
ipv6_regex="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"
|
||||
ipv4_regex="^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)$"
|
||||
}
|
||||
|
||||
iplimit_main() {
|
||||
echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit"
|
||||
echo -e "${green}\t2.${plain} Change Ban Duration"
|
||||
echo -e "${green}\t3.${plain} Unban Everyone"
|
||||
echo -e "${green}\t4.${plain} Ban Logs"
|
||||
echo -e "${green}\t5.${plain} Real-Time Logs"
|
||||
echo -e "${green}\t6.${plain} Service Status"
|
||||
echo -e "${green}\t7.${plain} Service Restart"
|
||||
echo -e "${green}\t8.${plain} Uninstall Fail2ban and IP Limit"
|
||||
echo -e "${green}\t5.${plain} Ban an IP Address"
|
||||
echo -e "${green}\t6.${plain} Unban an IP Address"
|
||||
echo -e "${green}\t7.${plain} Real-Time Logs"
|
||||
echo -e "${green}\t8.${plain} Service Status"
|
||||
echo -e "${green}\t9.${plain} Service Restart"
|
||||
echo -e "${green}\t10.${plain} Uninstall Fail2ban and IP Limit"
|
||||
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||||
read -p "Choose an option: " choice
|
||||
case "$choice" in
|
||||
@@ -1391,22 +1473,44 @@ iplimit_main() {
|
||||
iplimit_main
|
||||
;;
|
||||
5)
|
||||
tail -f /var/log/fail2ban.log
|
||||
read -rp "Enter the IP address you want to ban: " ban_ip
|
||||
ip_validation
|
||||
if [[ $ban_ip =~ $ipv4_regex || $ban_ip =~ $ipv6_regex ]]; then
|
||||
fail2ban-client set 3x-ipl banip "$ban_ip"
|
||||
echo -e "${green}IP Address ${ban_ip} has been banned successfully.${plain}"
|
||||
else
|
||||
echo -e "${red}Invalid IP address format! Please try again.${plain}"
|
||||
fi
|
||||
iplimit_main
|
||||
;;
|
||||
6)
|
||||
service fail2ban status
|
||||
read -rp "Enter the IP address you want to unban: " unban_ip
|
||||
ip_validation
|
||||
if [[ $unban_ip =~ $ipv4_regex || $unban_ip =~ $ipv6_regex ]]; then
|
||||
fail2ban-client set 3x-ipl unbanip "$unban_ip"
|
||||
echo -e "${green}IP Address ${unban_ip} has been unbanned successfully.${plain}"
|
||||
else
|
||||
echo -e "${red}Invalid IP address format! Please try again.${plain}"
|
||||
fi
|
||||
iplimit_main
|
||||
;;
|
||||
7)
|
||||
systemctl restart fail2ban
|
||||
tail -f /var/log/fail2ban.log
|
||||
iplimit_main
|
||||
;;
|
||||
8)
|
||||
service fail2ban status
|
||||
iplimit_main
|
||||
;;
|
||||
9)
|
||||
systemctl restart fail2ban
|
||||
iplimit_main
|
||||
;;
|
||||
10)
|
||||
remove_iplimit
|
||||
iplimit_main
|
||||
;;
|
||||
*)
|
||||
*)
|
||||
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
|
||||
iplimit_main
|
||||
;;
|
||||
@@ -1617,61 +1721,63 @@ SSH_port_forwarding() {
|
||||
}
|
||||
|
||||
show_usage() {
|
||||
echo "x-ui control menu usages: "
|
||||
echo "------------------------------------------"
|
||||
echo -e "SUBCOMMANDS:"
|
||||
echo -e "x-ui - Admin Management Script"
|
||||
echo -e "x-ui start - Start"
|
||||
echo -e "x-ui stop - Stop"
|
||||
echo -e "x-ui restart - Restart"
|
||||
echo -e "x-ui status - Current Status"
|
||||
echo -e "x-ui settings - Current Settings"
|
||||
echo -e "x-ui enable - Enable Autostart on OS Startup"
|
||||
echo -e "x-ui disable - Disable Autostart on OS Startup"
|
||||
echo -e "x-ui log - Check logs"
|
||||
echo -e "x-ui banlog - Check Fail2ban ban logs"
|
||||
echo -e "x-ui update - Update"
|
||||
echo -e "x-ui custom - custom version"
|
||||
echo -e "x-ui install - Install"
|
||||
echo -e "x-ui uninstall - Uninstall"
|
||||
echo "------------------------------------------"
|
||||
echo -e "┌───────────────────────────────────────────────────────┐
|
||||
│ ${blue}x-ui control menu usages (subcommands):${plain} │
|
||||
│ │
|
||||
│ ${blue}x-ui${plain} - Admin Management Script │
|
||||
│ ${blue}x-ui start${plain} - Start │
|
||||
│ ${blue}x-ui stop${plain} - Stop │
|
||||
│ ${blue}x-ui restart${plain} - Restart │
|
||||
│ ${blue}x-ui status${plain} - Current Status │
|
||||
│ ${blue}x-ui settings${plain} - Current Settings │
|
||||
│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │
|
||||
│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │
|
||||
│ ${blue}x-ui log${plain} - Check logs │
|
||||
│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
|
||||
│ ${blue}x-ui update${plain} - Update │
|
||||
│ ${blue}x-ui legacy${plain} - legacy version │
|
||||
│ ${blue}x-ui install${plain} - Install │
|
||||
│ ${blue}x-ui uninstall${plain} - Uninstall │
|
||||
└───────────────────────────────────────────────────────┘"
|
||||
}
|
||||
|
||||
show_menu() {
|
||||
echo -e "
|
||||
${green}3X-UI Panel Management Script${plain}
|
||||
${green}0.${plain} Exit Script
|
||||
————————————————
|
||||
${green}1.${plain} Install
|
||||
${green}2.${plain} Update
|
||||
${green}3.${plain} Update Menu
|
||||
${green}4.${plain} Legacy Version
|
||||
${green}5.${plain} Uninstall
|
||||
————————————————
|
||||
${green}6.${plain} Reset Username & Password & Secret Token
|
||||
${green}7.${plain} Reset Web Base Path
|
||||
${green}8.${plain} Reset Settings
|
||||
${green}9.${plain} Change Port
|
||||
${green}10.${plain} View Current Settings
|
||||
————————————————
|
||||
${green}11.${plain} Start
|
||||
${green}12.${plain} Stop
|
||||
${green}13.${plain} Restart
|
||||
${green}14.${plain} Check Status
|
||||
${green}15.${plain} Logs Management
|
||||
————————————————
|
||||
${green}16.${plain} Enable Autostart
|
||||
${green}17.${plain} Disable Autostart
|
||||
————————————————
|
||||
${green}18.${plain} SSL Certificate Management
|
||||
${green}19.${plain} Cloudflare SSL Certificate
|
||||
${green}20.${plain} IP Limit Management
|
||||
${green}21.${plain} Firewall Management
|
||||
${green}22.${plain} SSH Port Forwarding Management
|
||||
————————————————
|
||||
${green}23.${plain} Enable BBR
|
||||
${green}24.${plain} Update Geo Files
|
||||
${green}25.${plain} Speedtest by Ookla
|
||||
╔────────────────────────────────────────────────╗
|
||||
│ ${green}3X-UI Panel Management Script${plain} │
|
||||
│ ${green}0.${plain} Exit Script │
|
||||
│────────────────────────────────────────────────│
|
||||
│ ${green}1.${plain} Install │
|
||||
│ ${green}2.${plain} Update │
|
||||
│ ${green}3.${plain} Update Menu │
|
||||
│ ${green}4.${plain} Legacy Version │
|
||||
│ ${green}5.${plain} Uninstall │
|
||||
│────────────────────────────────────────────────│
|
||||
│ ${green}6.${plain} Reset Username & Password & Secret Token │
|
||||
│ ${green}7.${plain} Reset Web Base Path │
|
||||
│ ${green}8.${plain} Reset Settings │
|
||||
│ ${green}9.${plain} Change Port │
|
||||
│ ${green}10.${plain} View Current Settings │
|
||||
│────────────────────────────────────────────────│
|
||||
│ ${green}11.${plain} Start │
|
||||
│ ${green}12.${plain} Stop │
|
||||
│ ${green}13.${plain} Restart │
|
||||
│ ${green}14.${plain} Check Status │
|
||||
│ ${green}15.${plain} Logs Management │
|
||||
│────────────────────────────────────────────────│
|
||||
│ ${green}16.${plain} Enable Autostart │
|
||||
│ ${green}17.${plain} Disable Autostart │
|
||||
│────────────────────────────────────────────────│
|
||||
│ ${green}18.${plain} SSL Certificate Management │
|
||||
│ ${green}19.${plain} Cloudflare SSL Certificate │
|
||||
│ ${green}20.${plain} IP Limit Management │
|
||||
│ ${green}21.${plain} Firewall Management │
|
||||
│ ${green}22.${plain} SSH Port Forwarding Management │
|
||||
│────────────────────────────────────────────────│
|
||||
│ ${green}23.${plain} Enable BBR │
|
||||
│ ${green}24.${plain} Update Geo Files │
|
||||
│ ${green}25.${plain} Speedtest by Ookla │
|
||||
╚────────────────────────────────────────────────╝
|
||||
"
|
||||
show_status
|
||||
echo && read -p "Please enter your selection [0-25]: " num
|
||||
@@ -1806,4 +1912,4 @@ if [[ $# > 0 ]]; then
|
||||
esac
|
||||
else
|
||||
show_menu
|
||||
fi
|
||||
fi
|
||||