Merge branch 'main' into main
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:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
@ -31,6 +31,8 @@ jobs:
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
install: true
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
|
|
30
.github/workflows/release.yml
vendored
|
@ -72,7 +72,7 @@ jobs:
|
||||||
export GOARCH=s390x
|
export GOARCH=s390x
|
||||||
export CC=s390x-linux-gnu-gcc
|
export CC=s390x-linux-gnu-gcc
|
||||||
fi
|
fi
|
||||||
go build -o xui-release -v main.go
|
go build -ldflags "-w -s" -o xui-release -v main.go
|
||||||
|
|
||||||
mkdir x-ui
|
mkdir x-ui
|
||||||
cp xui-release x-ui/
|
cp xui-release x-ui/
|
||||||
|
@ -83,43 +83,43 @@ jobs:
|
||||||
cd x-ui/bin
|
cd x-ui/bin
|
||||||
|
|
||||||
# Download dependencies
|
# Download dependencies
|
||||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v24.10.16/"
|
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.1.1/"
|
||||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
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
|
unzip Xray-linux-64.zip
|
||||||
rm -f Xray-linux-64.zip
|
rm -f Xray-linux-64.zip
|
||||||
elif [ "${{ matrix.platform }}" == "arm64" ]; then
|
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
|
unzip Xray-linux-arm64-v8a.zip
|
||||||
rm -f Xray-linux-arm64-v8a.zip
|
rm -f Xray-linux-arm64-v8a.zip
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
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
|
unzip Xray-linux-arm32-v7a.zip
|
||||||
rm -f Xray-linux-arm32-v7a.zip
|
rm -f Xray-linux-arm32-v7a.zip
|
||||||
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
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
|
unzip Xray-linux-arm32-v6.zip
|
||||||
rm -f Xray-linux-arm32-v6.zip
|
rm -f Xray-linux-arm32-v6.zip
|
||||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
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
|
unzip Xray-linux-32.zip
|
||||||
rm -f Xray-linux-32.zip
|
rm -f Xray-linux-32.zip
|
||||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
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
|
unzip Xray-linux-arm32-v5.zip
|
||||||
rm -f Xray-linux-arm32-v5.zip
|
rm -f Xray-linux-arm32-v5.zip
|
||||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
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
|
unzip Xray-linux-s390x.zip
|
||||||
rm -f Xray-linux-s390x.zip
|
rm -f Xray-linux-s390x.zip
|
||||||
fi
|
fi
|
||||||
rm -f geoip.dat geosite.dat
|
rm -f geoip.dat geosite.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget -q 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 -q 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 -q -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 -q -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 -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.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 }}
|
mv xray xray-linux-${{ matrix.platform }}
|
||||||
cd ../..
|
cd ../..
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# Start fail2ban
|
# Start fail2ban
|
||||||
fail2ban-client -x start
|
[ $X_UI_ENABLE_FAIL2BAN == "true" ] && fail2ban-client -x start
|
||||||
|
|
||||||
# Run x-ui
|
# Run x-ui
|
||||||
exec /app/x-ui
|
exec /app/x-ui
|
||||||
|
|
|
@ -27,14 +27,14 @@ case $1 in
|
||||||
esac
|
esac
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
cd build/bin
|
cd build/bin
|
||||||
wget "https://github.com/XTLS/Xray-core/releases/download/v24.10.16/Xray-linux-${ARCH}.zip"
|
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.1.1/Xray-linux-${ARCH}.zip"
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget -q 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 -q 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 -q -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 -q -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 -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
|
wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
cd ../../
|
cd ../../
|
|
@ -15,7 +15,7 @@ COPY . .
|
||||||
|
|
||||||
ENV CGO_ENABLED=1
|
ENV CGO_ENABLED=1
|
||||||
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
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"
|
RUN ./DockerInit.sh "$TARGETARCH"
|
||||||
|
|
||||||
# ========================================================
|
# ========================================================
|
||||||
|
@ -48,6 +48,7 @@ RUN chmod +x \
|
||||||
/app/x-ui \
|
/app/x-ui \
|
||||||
/usr/bin/x-ui
|
/usr/bin/x-ui
|
||||||
|
|
||||||
|
ENV X_UI_ENABLE_FAIL2BAN="true"
|
||||||
VOLUME [ "/etc/x-ui" ]
|
VOLUME [ "/etc/x-ui" ]
|
||||||
CMD [ "./x-ui" ]
|
CMD [ "./x-ui" ]
|
||||||
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
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.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**
|
**Un Panel Web Avanzado • Construido sobre Xray Core**
|
||||||
|
|
||||||
|
@ -253,6 +258,7 @@ location /sub {
|
||||||
- Oracle Linux 8+
|
- Oracle Linux 8+
|
||||||
- OpenSUSE Tubleweed
|
- OpenSUSE Tubleweed
|
||||||
- Amazon Linux 2023
|
- Amazon Linux 2023
|
||||||
|
- Windows x64
|
||||||
|
|
||||||
## Arquitecturas y Dispositivos Compatibles
|
## Arquitecturas y Dispositivos Compatibles
|
||||||
|
|
||||||
|
@ -276,14 +282,18 @@ Nuestra plataforma ofrece compatibilidad con una amplia gama de arquitecturas y
|
||||||
|
|
||||||
## Idiomas
|
## Idiomas
|
||||||
|
|
||||||
- Inglés
|
- English (inglés)
|
||||||
- Farsi
|
- Persian (persa)
|
||||||
- Chino
|
- Traditional Chinese (chino tradicional)
|
||||||
- Ruso
|
- Simplified Chinese (chino simplificado)
|
||||||
- Vietnamita
|
- Japanese (japonés)
|
||||||
- Español
|
- Russian (ruso)
|
||||||
- Indonesio
|
- Vietnamese (vietnamita)
|
||||||
- Ucraniano
|
- Spanish (español)
|
||||||
|
- Indonesian (indonesio)
|
||||||
|
- Ukrainian (ucraniano)
|
||||||
|
- Turkish (turco)
|
||||||
|
- Português (Brazil) (portugués (Brasil))
|
||||||
|
|
||||||
|
|
||||||
## Características
|
## Características
|
||||||
|
@ -476,7 +486,7 @@ Ingresa el ID de chat de usuario en el campo de entrada número 4. Las cuentas d
|
||||||
|
|
||||||
#### Uso
|
#### Uso
|
||||||
|
|
||||||
- [Documentación de API](https://documenter.getpostman.com/view/5146551/2sAXxP8Y12)
|
- [Documentación de API](https://www.postman.com/hsanaei/3x-ui/collection/q1l5l0u/3x-ui)
|
||||||
- `/login` con `POST` datos de usuario: `{username: '', password: ''}` para iniciar sesión
|
- `/login` con `POST` datos de usuario: `{username: '', password: ''}` para iniciar sesión
|
||||||
- `/panel/api/inbounds` base para las siguientes acciones:
|
- `/panel/api/inbounds` base para las siguientes acciones:
|
||||||
|
|
||||||
|
@ -506,7 +516,7 @@ Ingresa el ID de chat de usuario en el campo de entrada número 4. Las cuentas d
|
||||||
- `client.password` para TROJAN
|
- `client.password` para TROJAN
|
||||||
- `client.email` para Shadowsocks
|
- `client.email` para Shadowsocks
|
||||||
|
|
||||||
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://god.gw.postman.com/run-collection/5146551-e6aac565-e0e2-46df-acff-2607a51bbd04?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-e6aac565-e0e2-46df-acff-2607a51bbd04%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
|
- [<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>
|
||||||
|
|
||||||
## Variables de Entorno
|
## Variables de Entorno
|
||||||
|
@ -534,13 +544,34 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
|
|
||||||
## Vista previa
|
## 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
|
## Un agradecimiento especial a
|
||||||
|
|
||||||
|
@ -549,8 +580,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
## Reconocimientos
|
## 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._
|
- [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
|
## Estrellas a lo largo del tiempo
|
||||||
|
|
||||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
[](https://starchart.cc/MHSanaei/3x-ui)
|
56
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.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**
|
**An Advanced Web Panel • Built on Xray Core**
|
||||||
|
|
||||||
|
@ -30,7 +35,7 @@
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install Old Version (we don't recommend)
|
## Install legacy Version (we don't recommend)
|
||||||
|
|
||||||
To install your desired version, use following installation command. e.g., ver `v1.7.9`:
|
To install your desired version, use following installation command. e.g., ver `v1.7.9`:
|
||||||
|
|
||||||
|
@ -258,6 +263,7 @@ location /sub {
|
||||||
- Oracle Linux 8+
|
- Oracle Linux 8+
|
||||||
- OpenSUSE Tubleweed
|
- OpenSUSE Tubleweed
|
||||||
- Amazon Linux 2023
|
- Amazon Linux 2023
|
||||||
|
- Windows x64
|
||||||
|
|
||||||
## Supported Architectures and Devices
|
## Supported Architectures and Devices
|
||||||
|
|
||||||
|
@ -284,9 +290,10 @@ Our platform offers compatibility with a diverse range of architectures and devi
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
- English
|
- English
|
||||||
- Farsi
|
- Persian
|
||||||
- Traditional Chinese
|
- Traditional Chinese
|
||||||
- Simplified Chinese
|
- Simplified Chinese
|
||||||
|
- Japanese
|
||||||
- Russian
|
- Russian
|
||||||
- Vietnamese
|
- Vietnamese
|
||||||
- Spanish
|
- Spanish
|
||||||
|
@ -487,7 +494,7 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi
|
||||||
|
|
||||||
#### Usage
|
#### Usage
|
||||||
|
|
||||||
- [API Documentation](https://documenter.getpostman.com/view/5146551/2sAXxP8Y12)
|
- [API Documentation](https://www.postman.com/hsanaei/3x-ui/collection/q1l5l0u/3x-ui)
|
||||||
- `/login` with `POST` user data: `{username: '', password: ''}` for login
|
- `/login` with `POST` user data: `{username: '', password: ''}` for login
|
||||||
- `/panel/api/inbounds` base for following actions:
|
- `/panel/api/inbounds` base for following actions:
|
||||||
|
|
||||||
|
@ -518,7 +525,7 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi
|
||||||
- `client.password` for TROJAN
|
- `client.password` for TROJAN
|
||||||
- `client.email` for Shadowsocks
|
- `client.email` for Shadowsocks
|
||||||
|
|
||||||
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://god.gw.postman.com/run-collection/5146551-e6aac565-e0e2-46df-acff-2607a51bbd04?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-e6aac565-e0e2-46df-acff-2607a51bbd04%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
|
- [<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>
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
@ -546,13 +553,34 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
|
|
||||||
## Preview
|
## 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
|
## A Special Thanks to
|
||||||
|
|
||||||
|
@ -561,8 +589,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
## Acknowledgment
|
## 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._
|
- [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
|
## 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.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**
|
**Продвинутая веб-панель • Построена на основе Xray Core**
|
||||||
|
|
||||||
|
@ -257,6 +262,7 @@ location /sub {
|
||||||
- Oracle Linux 8+
|
- Oracle Linux 8+
|
||||||
- OpenSUSE Tubleweed
|
- OpenSUSE Tubleweed
|
||||||
- Amazon Linux 2023
|
- Amazon Linux 2023
|
||||||
|
- Windows x64
|
||||||
|
|
||||||
## Поддерживаемые архитектуры и устройства
|
## Поддерживаемые архитектуры и устройства
|
||||||
|
|
||||||
|
@ -282,16 +288,18 @@ location /sub {
|
||||||
|
|
||||||
## Языки
|
## Языки
|
||||||
|
|
||||||
- Английский
|
- English (английский)
|
||||||
- Фарси
|
- Persian (персидский)
|
||||||
- Китайский
|
- Traditional Chinese (традиционный китайский)
|
||||||
- Русский
|
- Simplified Chinese (упрощенный китайский)
|
||||||
- Вьетнамский
|
- Japanese (японский)
|
||||||
- Испанский
|
- Russian (русский)
|
||||||
- Индонезийский
|
- Vietnamese (вьетнамский)
|
||||||
- Украинский
|
- Spanish (испанский)
|
||||||
- Турецкий
|
- Indonesian (индонезийский)
|
||||||
- Португальский (Бразилия)
|
- Ukrainian (украинский)
|
||||||
|
- Turkish (турецкий)
|
||||||
|
- Português (Brazil) (португальский (Бразилия))
|
||||||
|
|
||||||
## Возможности
|
## Возможности
|
||||||
|
|
||||||
|
@ -481,7 +489,7 @@ WARP встроен, и дополнительная установка не т
|
||||||
|
|
||||||
#### Использование
|
#### Использование
|
||||||
|
|
||||||
- [API документация](https://documenter.getpostman.com/view/5146551/2sAXxP8Y12)
|
- [API документация](https://www.postman.com/hsanaei/3x-ui/collection/q1l5l0u/3x-ui)
|
||||||
- `/login` с `POST`-данными: `{username: '', password: ''}` для входа
|
- `/login` с `POST`-данными: `{username: '', password: ''}` для входа
|
||||||
- `/panel/api/inbounds` это базовый путь для следующих действий:
|
- `/panel/api/inbounds` это базовый путь для следующих действий:
|
||||||
|
|
||||||
|
@ -515,7 +523,7 @@ WARP встроен, и дополнительная установка не т
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://god.gw.postman.com/run-collection/5146551-e6aac565-e0e2-46df-acff-2607a51bbd04?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-e6aac565-e0e2-46df-acff-2607a51bbd04%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
|
- [<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>
|
||||||
|
|
||||||
## Переменные среды
|
## Переменные среды
|
||||||
|
@ -543,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>
|
||||||
|
|
||||||
## Особая благодарность
|
## Особая благодарность
|
||||||
|
|
||||||
|
@ -558,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._
|
- [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.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构建**
|
**一个更好的面板 • 基于Xray Core构建**
|
||||||
|
|
||||||
|
@ -254,6 +259,7 @@ location /sub {
|
||||||
- Oracle Linux 8+
|
- Oracle Linux 8+
|
||||||
- OpenSUSE Tubleweed
|
- OpenSUSE Tubleweed
|
||||||
- Amazon Linux 2023
|
- Amazon Linux 2023
|
||||||
|
- Windows x64
|
||||||
|
|
||||||
## 支持的架构和设备
|
## 支持的架构和设备
|
||||||
<details>
|
<details>
|
||||||
|
@ -277,13 +283,17 @@ location /sub {
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
- English(英语)
|
- English(英语)
|
||||||
- Farsi(伊朗语)
|
- Persian(波斯语)
|
||||||
- Chinese(中文)
|
- Traditional Chinese(繁体中文)
|
||||||
|
- Simplified Chinese(简体中文)
|
||||||
|
- Japanese(日语)
|
||||||
- Russian(俄语)
|
- Russian(俄语)
|
||||||
- Vietnamese(越南语)
|
- Vietnamese(越南语)
|
||||||
- Spanish(西班牙语)
|
- Spanish(西班牙语)
|
||||||
- Indonesian (印度尼西亚语)
|
- Indonesian(印尼语)
|
||||||
- Ukrainian(乌克兰语)
|
- Ukrainian(乌克兰语)
|
||||||
|
- Turkish(土耳其语)
|
||||||
|
- Português (Brazil)(葡萄牙语(巴西))
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
@ -476,7 +486,7 @@ Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备
|
||||||
|
|
||||||
#### 使用
|
#### 使用
|
||||||
|
|
||||||
- [API 文档](https://documenter.getpostman.com/view/5146551/2sAXxP8Y12)
|
- [API 文档](https://www.postman.com/hsanaei/3x-ui/collection/q1l5l0u/3x-ui)
|
||||||
- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录
|
- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录
|
||||||
- `/panel/api/inbounds` 以下操作的基础:
|
- `/panel/api/inbounds` 以下操作的基础:
|
||||||
|
|
||||||
|
@ -506,7 +516,7 @@ Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备
|
||||||
- `client.password` TROJAN
|
- `client.password` TROJAN
|
||||||
- `client.email` Shadowsocks
|
- `client.email` Shadowsocks
|
||||||
|
|
||||||
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://god.gw.postman.com/run-collection/5146551-e6aac565-e0e2-46df-acff-2607a51bbd04?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-e6aac565-e0e2-46df-acff-2607a51bbd04%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
|
- [<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>
|
||||||
|
|
||||||
## 环境变量
|
## 环境变量
|
||||||
|
@ -534,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>
|
||||||
|
|
||||||
## 特别感谢
|
## 特别感谢
|
||||||
|
|
||||||
|
@ -549,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._
|
- [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趋势
|
## Star趋势
|
||||||
|
|
||||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
[](https://starchart.cc/MHSanaei/3x-ui)
|
|
@ -1 +1 @@
|
||||||
2.4.5
|
2.5.0
|
|
@ -83,8 +83,30 @@ func InitDB(dbPath string) error {
|
||||||
|
|
||||||
c := &gorm.Config{
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -101,6 +123,11 @@ func InitDB(dbPath string) error {
|
||||||
|
|
||||||
func CloseDB() error {
|
func CloseDB() error {
|
||||||
if db != nil {
|
if db != nil {
|
||||||
|
|
||||||
|
if err := Checkpoint(); err != nil {
|
||||||
|
log.Printf("error executing checkpoint: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
sqlDB, err := db.DB()
|
sqlDB, err := db.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -98,5 +98,6 @@ type Client struct {
|
||||||
Enable bool `json:"enable" form:"enable"`
|
Enable bool `json:"enable" form:"enable"`
|
||||||
TgID int64 `json:"tgId" form:"tgId"`
|
TgID int64 `json:"tgId" form:"tgId"`
|
||||||
SubID string `json:"subId" form:"subId"`
|
SubID string `json:"subId" form:"subId"`
|
||||||
|
Comment string `json:"comment" form:"comment"`
|
||||||
Reset int `json:"reset" form:"reset"`
|
Reset int `json:"reset" form:"reset"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ services:
|
||||||
- $PWD/cert/:/root/cert/
|
- $PWD/cert/:/root/cert/
|
||||||
environment:
|
environment:
|
||||||
XRAY_VMESS_AEAD_FORCED: "false"
|
XRAY_VMESS_AEAD_FORCED: "false"
|
||||||
|
X_UI_ENABLE_FAIL2BAN: "true"
|
||||||
tty: true
|
tty: true
|
||||||
network_mode: host
|
network_mode: host
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
79
go.mod
|
@ -1,46 +1,45 @@
|
||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.23.2
|
go 1.23.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/gzip v1.0.1
|
github.com/gin-contrib/gzip v1.2.0
|
||||||
github.com/gin-contrib/sessions v1.0.1
|
github.com/gin-contrib/sessions v1.0.2
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/goccy/go-json v0.10.3
|
github.com/goccy/go-json v0.10.4
|
||||||
github.com/mymmrac/telego v0.31.3
|
github.com/mymmrac/telego v0.32.0
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.1
|
github.com/nicksnyder/go-i18n/v2 v2.5.0
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3
|
github.com/pelletier/go-toml/v2 v2.2.3
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v4 v4.24.9
|
github.com/shirou/gopsutil/v4 v4.24.12
|
||||||
github.com/valyala/fasthttp v1.57.0
|
github.com/valyala/fasthttp v1.58.0
|
||||||
github.com/xtls/xray-core v1.8.25-0.20241005021528-c30f5d47964b
|
github.com/xtls/xray-core v1.8.25-0.20250126155934-7b59379d73c3
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.19.0
|
golang.org/x/text v0.21.0
|
||||||
google.golang.org/grpc v1.67.1
|
google.golang.org/grpc v1.70.0
|
||||||
gorm.io/driver/sqlite v1.5.6
|
gorm.io/driver/sqlite v1.5.7
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
github.com/bytedance/sonic v1.12.3 // indirect
|
github.com/bytedance/sonic v1.12.8 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||||
github.com/cloudflare/circl v1.5.0 // indirect
|
github.com/cloudflare/circl v1.5.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
github.com/ebitengine/purego v0.8.2 // indirect
|
||||||
github.com/ebitengine/purego v0.8.1 // indirect
|
github.com/fasthttp/router v1.5.4 // indirect
|
||||||
github.com/fasthttp/router v1.5.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.3.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/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.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/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/pprof v0.0.0-20241023014458-598669927662 // indirect
|
github.com/google/pprof v0.0.0-20250125003558-7fdb3d7e6fa0 // indirect
|
||||||
github.com/gorilla/context v1.1.2 // indirect
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.4.0 // indirect
|
github.com/gorilla/sessions v1.4.0 // indirect
|
||||||
|
@ -50,7 +49,7 @@ require (
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||||
|
@ -58,15 +57,15 @@ require (
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2 // indirect
|
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
|
||||||
github.com/pires/go-proxyproto v0.8.0 // indirect
|
github.com/pires/go-proxyproto v0.8.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // 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/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/refraction-networking/utls v1.6.7 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
github.com/sagernet/sing v0.4.3 // indirect
|
github.com/sagernet/sing v0.5.1 // indirect
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
||||||
|
@ -78,25 +77,25 @@ require (
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fastjson v1.6.4 // indirect
|
github.com/valyala/fastjson v1.6.4 // indirect
|
||||||
github.com/vishvananda/netlink v1.3.0 // indirect
|
github.com/vishvananda/netlink v1.3.0 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 // indirect
|
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.11.0 // indirect
|
golang.org/x/arch v0.13.0 // indirect
|
||||||
golang.org/x/crypto v0.28.0 // indirect
|
golang.org/x/crypto v0.32.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||||
golang.org/x/mod v0.21.0 // indirect
|
golang.org/x/mod v0.22.0 // indirect
|
||||||
golang.org/x/net v0.30.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/time v0.7.0 // indirect
|
golang.org/x/time v0.9.0 // indirect
|
||||||
golang.org/x/tools v0.26.0 // indirect
|
golang.org/x/tools v0.29.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 // indirect
|
||||||
google.golang.org/protobuf v1.35.1 // indirect
|
google.golang.org/protobuf v1.36.4 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // 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
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
179
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/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 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
|
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
|
||||||
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
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.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
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 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
|
||||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
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.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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-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-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/fasthttp/router v1.5.2 h1:ckJCCdV7hWkkrMeId3WfEhz+4Gyyf6QPwxi/RHIMZ6I=
|
github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8=
|
||||||
github.com/fasthttp/router v1.5.2/go.mod h1:C8EY53ozOwpONyevc/V7Gr8pqnEjwnkFFqPo1alAGs0=
|
github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
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 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
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.2.0 h1:JzN6DT3/xYL5zAdviN1ORNzKeklrwafXCIDKIR+qmUA=
|
||||||
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
|
github.com/gin-contrib/gzip v1.2.0/go.mod h1:C1a5cacjlDsS20cKnHlZRCPUu57D3qH6B2pV0rl+Y/s=
|
||||||
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
|
github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
|
||||||
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
|
github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
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 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
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 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
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.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 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
|
@ -49,14 +50,16 @@ 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/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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
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.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
|
||||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
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 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
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.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.4/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 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||||
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
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=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
@ -64,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.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 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20241023014458-598669927662 h1:SKMkD83p7FwUqKmBsPdLHF5dNyxq3jOWwu9w9UyH5vA=
|
github.com/google/pprof v0.0.0-20250125003558-7fdb3d7e6fa0 h1:my2ucqBZmv+cWHIhZNSIYKzgN8EBGyHdC7zD5sASRAg=
|
||||||
github.com/google/pprof v0.0.0-20241023014458-598669927662/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
github.com/google/pprof v0.0.0-20250125003558-7fdb3d7e6fa0/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 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
@ -85,8 +90,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
@ -107,14 +112,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mymmrac/telego v0.31.3 h1:yZlD+dm+1W6p3OmCG8K+MbS02Y6paUgwPnqfZN3RWQQ=
|
github.com/mymmrac/telego v0.32.0 h1:4X8C1l3k+opkk86r95+eQE8DxiS2LYlR61L/G7yreDY=
|
||||||
github.com/mymmrac/telego v0.31.3/go.mod h1:coOoqXVmjFnwBlzusjfEezbQ7RH9wQnDowJdMm+bnEo=
|
github.com/mymmrac/telego v0.32.0/go.mod h1:qS6NaRhJgcuEEBEMVCV79S2xCAuHq9O+ixwfLuRW31M=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.1 h1:zwzjtX4uYyiaU02K5Ia3zSkpJZrByARkRB4V3YPrr0g=
|
github.com/nicksnyder/go-i18n/v2 v2.5.0 h1:3wH1gpaekcgGuwzWdSu7JwJhH9Tk87k1ezt0i1p2/Is=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.1/go.mod h1:++Pl70FR6Cki7hdzZRnEEqdc2dJt+SAGotyFg/SvZMk=
|
github.com/nicksnyder/go-i18n/v2 v2.5.0/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
|
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
|
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
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 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
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=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
|
@ -129,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/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 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
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.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
|
||||||
github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
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 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
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=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
|
@ -139,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/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 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/sagernet/sing v0.4.3 h1:Ty/NAiNnVd6844k7ujlL5lkzydhcTH5Psc432jXA4Y8=
|
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
||||||
github.com/sagernet/sing v0.4.3/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
|
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 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
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 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
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 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
github.com/shirou/gopsutil/v4 v4.24.9 h1:KIV+/HaHD5ka5f570RZq+2SaeFsb/pq+fp2DGNWYoOI=
|
github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4=
|
||||||
github.com/shirou/gopsutil/v4 v4.24.9/go.mod h1:3fkaHNeYsUFCGZ8+9vZVWtbyM1k2eRnlL+bWO8Bxa/Q=
|
github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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.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.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.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.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.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.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.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.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
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 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||||
|
@ -172,65 +179,75 @@ 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/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 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
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.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
|
||||||
github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE=
|
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 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||||
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
|
||||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
|
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
|
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 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM=
|
||||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg=
|
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg=
|
||||||
github.com/xtls/xray-core v1.8.25-0.20241005021528-c30f5d47964b h1:bWuePNnzV4ptnSYJkY96dAg3WgjYbfVgGnasLe3++9w=
|
github.com/xtls/xray-core v1.8.25-0.20250126155934-7b59379d73c3 h1:JzofxKb13MvNZeSLPS9B07Few0fu11dPEa40EQNV/hE=
|
||||||
github.com/xtls/xray-core v1.8.25-0.20241005021528-c30f5d47964b/go.mod h1:YSvBScSqyzAocGDvzHBbEeoHNrFy8nV6gityRVDvHaM=
|
github.com/xtls/xray-core v1.8.25-0.20250126155934-7b59379d73c3/go.mod h1:KmpYBu4IFdODX7qgG7DP7v9sHJBcHpdKE+qxUZ2N304=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
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 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
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 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
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 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
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 h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
|
||||||
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
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 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
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 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 h1:91mG8dNTpkC0uChJUQ9zCiRqx3GEEFOWaRZ0mI6Oj2I=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
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 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
@ -240,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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
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 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
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-20240320123526-dc6abceb7ff0 h1:P+U/06iIKPQ3DLcg+zBfSCia1luZ2msPZrJ8jYDFPs0=
|
||||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
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 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
|
43
install.sh
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
red='\033[0;31m'
|
red='\033[0;31m'
|
||||||
green='\033[0;32m'
|
green='\033[0;32m'
|
||||||
|
blue='\033[0;34m'
|
||||||
yellow='\033[0;33m'
|
yellow='\033[0;33m'
|
||||||
plain='\033[0m'
|
plain='\033[0m'
|
||||||
|
|
||||||
|
@ -142,6 +143,8 @@ config_after_install() {
|
||||||
local existing_username=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'username: .+' | awk '{print $2}')
|
local existing_username=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'username: .+' | awk '{print $2}')
|
||||||
local existing_password=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'password: .+' | awk '{print $2}')
|
local existing_password=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'password: .+' | awk '{print $2}')
|
||||||
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||||||
|
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
||||||
|
local server_ip=$(curl -s https://api.ipify.org)
|
||||||
|
|
||||||
if [[ ${#existing_webBasePath} -lt 4 ]]; then
|
if [[ ${#existing_webBasePath} -lt 4 ]]; then
|
||||||
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
|
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
|
||||||
|
@ -149,7 +152,7 @@ config_after_install() {
|
||||||
local config_username=$(gen_random_string 10)
|
local config_username=$(gen_random_string 10)
|
||||||
local config_password=$(gen_random_string 10)
|
local config_password=$(gen_random_string 10)
|
||||||
|
|
||||||
read -p "Would you like to customize the Panel Port settings? (If not, random port will be applied) [y/n]: " config_confirm
|
read -p "Would you like to customize the Panel Port settings? (If not, a random port will be applied) [y/n]: " config_confirm
|
||||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
||||||
read -p "Please set up the panel port: " config_port
|
read -p "Please set up the panel port: " config_port
|
||||||
echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
||||||
|
@ -165,6 +168,7 @@ config_after_install() {
|
||||||
echo -e "${green}Password: ${config_password}${plain}"
|
echo -e "${green}Password: ${config_password}${plain}"
|
||||||
echo -e "${green}Port: ${config_port}${plain}"
|
echo -e "${green}Port: ${config_port}${plain}"
|
||||||
echo -e "${green}WebBasePath: ${config_webBasePath}${plain}"
|
echo -e "${green}WebBasePath: ${config_webBasePath}${plain}"
|
||||||
|
echo -e "${green}Access URL: http://${server_ip}:${config_port}/${config_webBasePath}${plain}"
|
||||||
echo -e "###############################################"
|
echo -e "###############################################"
|
||||||
echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check${plain}"
|
echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check${plain}"
|
||||||
else
|
else
|
||||||
|
@ -172,6 +176,7 @@ config_after_install() {
|
||||||
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
|
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
|
||||||
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}"
|
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}"
|
||||||
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
|
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
|
||||||
|
echo -e "${green}Access URL: http://${server_ip}:${existing_port}/${config_webBasePath}${plain}"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
|
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
|
||||||
|
@ -256,24 +261,24 @@ install_x-ui() {
|
||||||
systemctl start x-ui
|
systemctl start x-ui
|
||||||
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
|
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
|
||||||
echo -e ""
|
echo -e ""
|
||||||
echo -e "x-ui control menu usages: "
|
echo -e "┌───────────────────────────────────────────────────────┐
|
||||||
echo -e "----------------------------------------------"
|
│ ${blue}x-ui control menu usages (subcommands):${plain} │
|
||||||
echo -e "SUBCOMMANDS:"
|
│ │
|
||||||
echo -e "x-ui - Admin Management Script"
|
│ ${blue}x-ui${plain} - Admin Management Script │
|
||||||
echo -e "x-ui start - Start"
|
│ ${blue}x-ui start${plain} - Start │
|
||||||
echo -e "x-ui stop - Stop"
|
│ ${blue}x-ui stop${plain} - Stop │
|
||||||
echo -e "x-ui restart - Restart"
|
│ ${blue}x-ui restart${plain} - Restart │
|
||||||
echo -e "x-ui status - Current Status"
|
│ ${blue}x-ui status${plain} - Current Status │
|
||||||
echo -e "x-ui settings - Current Settings"
|
│ ${blue}x-ui settings${plain} - Current Settings │
|
||||||
echo -e "x-ui enable - Enable Autostart on OS Startup"
|
│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │
|
||||||
echo -e "x-ui disable - Disable Autostart on OS Startup"
|
│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │
|
||||||
echo -e "x-ui log - Check logs"
|
│ ${blue}x-ui log${plain} - Check logs │
|
||||||
echo -e "x-ui banlog - Check Fail2ban ban logs"
|
│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
|
||||||
echo -e "x-ui update - Update"
|
│ ${blue}x-ui update${plain} - Update │
|
||||||
echo -e "x-ui custom - custom version"
|
│ ${blue}x-ui legacy${plain} - legacy version │
|
||||||
echo -e "x-ui install - Install"
|
│ ${blue}x-ui install${plain} - Install │
|
||||||
echo -e "x-ui uninstall - Uninstall"
|
│ ${blue}x-ui uninstall${plain} - Uninstall │
|
||||||
echo -e "----------------------------------------------"
|
└───────────────────────────────────────────────────────┘"
|
||||||
}
|
}
|
||||||
|
|
||||||
echo -e "${green}Running...${plain}"
|
echo -e "${green}Running...${plain}"
|
||||||
|
|
75
main.go
|
@ -136,6 +136,15 @@ func showSetting(show bool) {
|
||||||
fmt.Println("get webBasePath failed, error info:", err)
|
fmt.Println("get webBasePath failed, error info:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
certFile, err := settingService.GetCertFile()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get cert file failed, error info:", err)
|
||||||
|
}
|
||||||
|
keyFile, err := settingService.GetKeyFile()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get key file failed, error info:", err)
|
||||||
|
}
|
||||||
|
|
||||||
userService := service.UserService{}
|
userService := service.UserService{}
|
||||||
userModel, err := userService.GetFirstUser()
|
userModel, err := userService.GetFirstUser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -149,14 +158,15 @@ func showSetting(show bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("current panel settings as follows:")
|
fmt.Println("current panel settings as follows:")
|
||||||
|
if certFile == "" || keyFile == "" {
|
||||||
|
fmt.Println("Warning: Panel is not secure with SSL")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Panel is secure with SSL")
|
||||||
|
}
|
||||||
fmt.Println("username:", username)
|
fmt.Println("username:", username)
|
||||||
fmt.Println("password:", userpasswd)
|
fmt.Println("password:", userpasswd)
|
||||||
fmt.Println("port:", port)
|
fmt.Println("port:", port)
|
||||||
if webBasePath != "" {
|
|
||||||
fmt.Println("webBasePath:", webBasePath)
|
fmt.Println("webBasePath:", webBasePath)
|
||||||
} else {
|
|
||||||
fmt.Println("webBasePath is not set")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +226,7 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSetting(port int, username string, password string, webBasePath string) {
|
func updateSetting(port int, username string, password string, webBasePath string, listenIP string) {
|
||||||
err := database.InitDB(config.GetDBPath())
|
err := database.InitDB(config.GetDBPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Database initialization failed:", err)
|
fmt.Println("Database initialization failed:", err)
|
||||||
|
@ -252,6 +262,15 @@ func updateSetting(port int, username string, password string, webBasePath strin
|
||||||
fmt.Println("Base URI path set successfully")
|
fmt.Println("Base URI path set successfully")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if listenIP != "" {
|
||||||
|
err := settingService.SetListen(listenIP)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to set listen IP:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("listen %v set successfully", listenIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateCert(publicKey string, privateKey string) {
|
func updateCert(publicKey string, privateKey string) {
|
||||||
|
@ -281,6 +300,37 @@ func updateCert(publicKey string, privateKey string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCertificate(getCert bool) {
|
||||||
|
if getCert {
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
certFile, err := settingService.GetCertFile()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get cert file failed, error info:", err)
|
||||||
|
}
|
||||||
|
keyFile, err := settingService.GetKeyFile()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get key file failed, error info:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("cert:", certFile)
|
||||||
|
fmt.Println("key:", keyFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetListenIP(getListen bool) {
|
||||||
|
if getListen {
|
||||||
|
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
ListenIP, err := settingService.GetListen()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to retrieve listen IP: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("listenIP:", ListenIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func migrateDb() {
|
func migrateDb() {
|
||||||
inboundService := service.InboundService{}
|
inboundService := service.InboundService{}
|
||||||
|
|
||||||
|
@ -339,6 +389,8 @@ func main() {
|
||||||
var username string
|
var username string
|
||||||
var password string
|
var password string
|
||||||
var webBasePath string
|
var webBasePath string
|
||||||
|
var listenIP string
|
||||||
|
var getListen bool
|
||||||
var webCertFile string
|
var webCertFile string
|
||||||
var webKeyFile string
|
var webKeyFile string
|
||||||
var tgbottoken string
|
var tgbottoken string
|
||||||
|
@ -347,6 +399,7 @@ func main() {
|
||||||
var tgbotRuntime string
|
var tgbotRuntime string
|
||||||
var reset bool
|
var reset bool
|
||||||
var show bool
|
var show bool
|
||||||
|
var getCert bool
|
||||||
var remove_secret bool
|
var remove_secret bool
|
||||||
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
|
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
|
||||||
settingCmd.BoolVar(&show, "show", false, "Display current settings")
|
settingCmd.BoolVar(&show, "show", false, "Display current settings")
|
||||||
|
@ -355,6 +408,9 @@ func main() {
|
||||||
settingCmd.StringVar(&username, "username", "", "Set login username")
|
settingCmd.StringVar(&username, "username", "", "Set login username")
|
||||||
settingCmd.StringVar(&password, "password", "", "Set login password")
|
settingCmd.StringVar(&password, "password", "", "Set login password")
|
||||||
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
|
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
|
||||||
|
settingCmd.StringVar(&listenIP, "listenIP", "", "set panel listenIP IP")
|
||||||
|
settingCmd.BoolVar(&getListen, "getListen", false, "Display current panel listenIP IP")
|
||||||
|
settingCmd.BoolVar(&getCert, "getCert", false, "Display current certificate settings")
|
||||||
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
|
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
|
||||||
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
|
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
|
||||||
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
|
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
|
||||||
|
@ -397,11 +453,17 @@ func main() {
|
||||||
if reset {
|
if reset {
|
||||||
resetSetting()
|
resetSetting()
|
||||||
} else {
|
} else {
|
||||||
updateSetting(port, username, password, webBasePath)
|
updateSetting(port, username, password, webBasePath, listenIP)
|
||||||
}
|
}
|
||||||
if show {
|
if show {
|
||||||
showSetting(show)
|
showSetting(show)
|
||||||
}
|
}
|
||||||
|
if getListen {
|
||||||
|
GetListenIP(getListen)
|
||||||
|
}
|
||||||
|
if getCert {
|
||||||
|
GetCertificate(getCert)
|
||||||
|
}
|
||||||
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
|
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
|
||||||
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
||||||
}
|
}
|
||||||
|
@ -422,7 +484,6 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
updateCert(webCertFile, webKeyFile)
|
updateCert(webCertFile, webKeyFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fmt.Println("Invalid subcommands")
|
fmt.Println("Invalid subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
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",
|
"tag": "direct",
|
||||||
"protocol": "freedom",
|
"protocol": "freedom",
|
||||||
"settings": {
|
"settings": {
|
||||||
"domainStrategy": "UseIP",
|
"domainStrategy": "AsIs",
|
||||||
"redirect": "",
|
"redirect": "",
|
||||||
"noises": []
|
"noises": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,7 +217,7 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
||||||
delete(streamSettings, "sockopt")
|
delete(streamSettings, "sockopt")
|
||||||
|
|
||||||
if s.fragment != "" {
|
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
|
// remove proxy protocol
|
||||||
|
|
|
@ -208,11 +208,6 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
obj["host"] = searchHost(headers)
|
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":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
obj["path"] = grpc["serviceName"].(string)
|
obj["path"] = grpc["serviceName"].(string)
|
||||||
|
@ -229,15 +224,16 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||||
obj["host"] = searchHost(headers)
|
obj["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "splithttp":
|
case "xhttp":
|
||||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||||
obj["path"] = splithttp["path"].(string)
|
obj["path"] = xhttp["path"].(string)
|
||||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
obj["host"] = host
|
obj["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||||
obj["host"] = searchHost(headers)
|
obj["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
|
obj["mode"] = xhttp["mode"].(string)
|
||||||
}
|
}
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
obj["tls"] = security
|
obj["tls"] = security
|
||||||
|
@ -360,10 +356,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "http":
|
|
||||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
|
||||||
params["path"] = http["path"].(string)
|
|
||||||
params["host"] = searchHost(http)
|
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
@ -380,15 +372,16 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "splithttp":
|
case "xhttp":
|
||||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||||
params["path"] = splithttp["path"].(string)
|
params["path"] = xhttp["path"].(string)
|
||||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
|
params["mode"] = xhttp["mode"].(string)
|
||||||
}
|
}
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
|
@ -452,38 +445,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if security == "xtls" {
|
if security != "tls" && security != "reality" {
|
||||||
params["security"] = "xtls"
|
|
||||||
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
|
||||||
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
|
||||||
var alpn []string
|
|
||||||
for _, a := range alpns {
|
|
||||||
alpn = append(alpn, a.(string))
|
|
||||||
}
|
|
||||||
if len(alpn) > 0 {
|
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
|
||||||
}
|
|
||||||
if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok {
|
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
|
||||||
if xtlsSetting != nil {
|
|
||||||
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
|
||||||
params["fp"], _ = fpValue.(string)
|
|
||||||
}
|
|
||||||
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
|
||||||
if insecure.(bool) {
|
|
||||||
params["allowInsecure"] = "1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
|
||||||
params["flow"] = clients[clientIndex].Flow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if security != "tls" && security != "reality" && security != "xtls" {
|
|
||||||
params["security"] = "none"
|
params["security"] = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -588,10 +550,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "http":
|
|
||||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
|
||||||
params["path"] = http["path"].(string)
|
|
||||||
params["host"] = searchHost(http)
|
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
@ -608,15 +566,16 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "splithttp":
|
case "xhttp":
|
||||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||||
params["path"] = splithttp["path"].(string)
|
params["path"] = xhttp["path"].(string)
|
||||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
|
params["mode"] = xhttp["mode"].(string)
|
||||||
}
|
}
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
|
@ -676,39 +635,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if security == "xtls" {
|
if security != "tls" && security != "reality" {
|
||||||
params["security"] = "xtls"
|
|
||||||
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
|
||||||
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
|
||||||
var alpn []string
|
|
||||||
for _, a := range alpns {
|
|
||||||
alpn = append(alpn, a.(string))
|
|
||||||
}
|
|
||||||
if len(alpn) > 0 {
|
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
|
||||||
}
|
|
||||||
if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok {
|
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
|
||||||
if xtlsSetting != nil {
|
|
||||||
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
|
||||||
params["fp"], _ = fpValue.(string)
|
|
||||||
}
|
|
||||||
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
|
||||||
if insecure.(bool) {
|
|
||||||
params["allowInsecure"] = "1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
|
||||||
params["flow"] = clients[clientIndex].Flow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if security != "tls" && security != "reality" && security != "xtls" {
|
|
||||||
params["security"] = "none"
|
params["security"] = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -817,10 +744,6 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "http":
|
|
||||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
|
||||||
params["path"] = http["path"].(string)
|
|
||||||
params["host"] = searchHost(http)
|
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
@ -837,15 +760,16 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "splithttp":
|
case "xhttp":
|
||||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||||
params["path"] = splithttp["path"].(string)
|
params["path"] = xhttp["path"].(string)
|
||||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
|
params["mode"] = xhttp["mode"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
|
2
web/assets/axios/axios.min.js
vendored
|
@ -63,7 +63,7 @@
|
||||||
return scriptHint(editor, javascriptKeywords,
|
return scriptHint(editor, javascriptKeywords,
|
||||||
function (e, cur) {return e.getTokenAt(cur);},
|
function (e, cur) {return e.getTokenAt(cur);},
|
||||||
options);
|
options);
|
||||||
};
|
}
|
||||||
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
|
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
|
||||||
|
|
||||||
function getCoffeeScriptToken(editor, cur) {
|
function getCoffeeScriptToken(editor, cur) {
|
||||||
|
|
|
@ -362,7 +362,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||||
if (type == wanted) return cont();
|
if (type == wanted) return cont();
|
||||||
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
|
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
|
||||||
else return cont(exp);
|
else return cont(exp);
|
||||||
};
|
}
|
||||||
return exp;
|
return exp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
.CodeMirror-lint-tooltip {
|
.CodeMirror-lint-tooltip {
|
||||||
background-color: #ffd;
|
background-color: #ffd;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-radius: 4px 4px 4px 4px;
|
border-radius: 4px;
|
||||||
color: black;
|
color: black;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
|
|
2
web/assets/css/custom.min.css
vendored
|
@ -19,6 +19,11 @@ const supportLangs = [
|
||||||
value: "zh-TW",
|
value: "zh-TW",
|
||||||
icon: "🇹🇼",
|
icon: "🇹🇼",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "日本語",
|
||||||
|
value: "ja-JP",
|
||||||
|
icon: "🇯🇵",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Русский",
|
name: "Русский",
|
||||||
value: "ru-RU",
|
value: "ru-RU",
|
||||||
|
|
|
@ -21,11 +21,6 @@ const SSMethods = {
|
||||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||||
};
|
};
|
||||||
|
|
||||||
const XTLS_FLOW_CONTROL = {
|
|
||||||
ORIGIN: "xtls-rprx-origin",
|
|
||||||
DIRECT: "xtls-rprx-direct",
|
|
||||||
};
|
|
||||||
|
|
||||||
const TLS_FLOW_CONTROL = {
|
const TLS_FLOW_CONTROL = {
|
||||||
VISION: "xtls-rprx-vision",
|
VISION: "xtls-rprx-vision",
|
||||||
VISION_UDP443: "xtls-rprx-vision-udp443",
|
VISION_UDP443: "xtls-rprx-vision-udp443",
|
||||||
|
@ -39,10 +34,6 @@ const TLS_VERSION_OPTION = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const TLS_CIPHER_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_128_GCM: "TLS_AES_128_GCM_SHA256",
|
||||||
AES_256_GCM: "TLS_AES_256_GCM_SHA384",
|
AES_256_GCM: "TLS_AES_256_GCM_SHA384",
|
||||||
CHACHA20_POLY1305: "TLS_CHACHA20_POLY1305_SHA256",
|
CHACHA20_POLY1305: "TLS_CHACHA20_POLY1305_SHA256",
|
||||||
|
@ -69,6 +60,7 @@ const UTLS_FINGERPRINT = {
|
||||||
UTLS_QQ: "qq",
|
UTLS_QQ: "qq",
|
||||||
UTLS_RANDOM: "random",
|
UTLS_RANDOM: "random",
|
||||||
UTLS_RANDOMIZED: "randomized",
|
UTLS_RANDOMIZED: "randomized",
|
||||||
|
UTLS_UNSAFE: "unsafe",
|
||||||
};
|
};
|
||||||
|
|
||||||
const ALPN_OPTION = {
|
const ALPN_OPTION = {
|
||||||
|
@ -118,9 +110,15 @@ const USERS_SECURITY = {
|
||||||
ZERO: "zero",
|
ZERO: "zero",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MODE_OPTION = {
|
||||||
|
AUTO: "auto",
|
||||||
|
PACKET_UP: "packet-up",
|
||||||
|
STREAM_UP: "stream-up",
|
||||||
|
STREAM_ONE: "stream-one",
|
||||||
|
};
|
||||||
|
|
||||||
Object.freeze(Protocols);
|
Object.freeze(Protocols);
|
||||||
Object.freeze(SSMethods);
|
Object.freeze(SSMethods);
|
||||||
Object.freeze(XTLS_FLOW_CONTROL);
|
|
||||||
Object.freeze(TLS_FLOW_CONTROL);
|
Object.freeze(TLS_FLOW_CONTROL);
|
||||||
Object.freeze(TLS_VERSION_OPTION);
|
Object.freeze(TLS_VERSION_OPTION);
|
||||||
Object.freeze(TLS_CIPHER_OPTION);
|
Object.freeze(TLS_CIPHER_OPTION);
|
||||||
|
@ -131,6 +129,7 @@ Object.freeze(USAGE_OPTION);
|
||||||
Object.freeze(DOMAIN_STRATEGY_OPTION);
|
Object.freeze(DOMAIN_STRATEGY_OPTION);
|
||||||
Object.freeze(TCP_CONGESTION_OPTION);
|
Object.freeze(TCP_CONGESTION_OPTION);
|
||||||
Object.freeze(USERS_SECURITY);
|
Object.freeze(USERS_SECURITY);
|
||||||
|
Object.freeze(MODE_OPTION);
|
||||||
|
|
||||||
class XrayCommonClass {
|
class XrayCommonClass {
|
||||||
|
|
||||||
|
@ -377,13 +376,15 @@ class WsStreamSettings extends XrayCommonClass {
|
||||||
acceptProxyProtocol = false,
|
acceptProxyProtocol = false,
|
||||||
path = '/',
|
path = '/',
|
||||||
host = '',
|
host = '',
|
||||||
headers = []
|
headers = [],
|
||||||
|
heartbeatPeriod = 0,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.acceptProxyProtocol = acceptProxyProtocol;
|
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
|
this.heartbeatPeriod = heartbeatPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
addHeader(name, value) {
|
addHeader(name, value) {
|
||||||
|
@ -400,6 +401,7 @@ class WsStreamSettings extends XrayCommonClass {
|
||||||
json.path,
|
json.path,
|
||||||
json.host,
|
json.host,
|
||||||
XrayCommonClass.toHeaders(json.headers),
|
XrayCommonClass.toHeaders(json.headers),
|
||||||
|
json.heartbeatPeriod,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,46 +411,11 @@ class WsStreamSettings extends XrayCommonClass {
|
||||||
path: this.path,
|
path: this.path,
|
||||||
host: this.host,
|
host: this.host,
|
||||||
headers: XrayCommonClass.toV2Headers(this.headers, false),
|
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 {
|
class GrpcStreamSettings extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
serviceName = "",
|
serviceName = "",
|
||||||
|
@ -519,33 +486,28 @@ class HTTPUpgradeStreamSettings extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SplitHTTPStreamSettings extends XrayCommonClass {
|
class xHTTPStreamSettings extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
path = '/',
|
path = '/',
|
||||||
host = '',
|
host = '',
|
||||||
headers = [],
|
headers = [],
|
||||||
scMaxConcurrentPosts = "100-200",
|
scMaxBufferedPosts = 30,
|
||||||
scMaxEachPostBytes = "1000000-2000000",
|
scMaxEachPostBytes = "1000000",
|
||||||
scMinPostsIntervalMs = "10-50",
|
scStreamUpServerSecs = "20-80",
|
||||||
noSSEHeader = false,
|
noSSEHeader = false,
|
||||||
xPaddingBytes = "100-1000",
|
xPaddingBytes = "100-1000",
|
||||||
xmux = {
|
mode = MODE_OPTION.AUTO,
|
||||||
maxConcurrency: "16-32",
|
|
||||||
maxConnections: 0,
|
|
||||||
cMaxReuseTimes: "64-128",
|
|
||||||
cMaxLifetimeMs: 0
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
this.scMaxConcurrentPosts = scMaxConcurrentPosts;
|
this.scMaxBufferedPosts = scMaxBufferedPosts;
|
||||||
this.scMaxEachPostBytes = scMaxEachPostBytes;
|
this.scMaxEachPostBytes = scMaxEachPostBytes;
|
||||||
this.scMinPostsIntervalMs = scMinPostsIntervalMs;
|
this.scStreamUpServerSecs = scStreamUpServerSecs;
|
||||||
this.noSSEHeader = noSSEHeader;
|
this.noSSEHeader = noSSEHeader;
|
||||||
this.xPaddingBytes = xPaddingBytes;
|
this.xPaddingBytes = xPaddingBytes;
|
||||||
this.xmux = xmux;
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
addHeader(name, value) {
|
addHeader(name, value) {
|
||||||
|
@ -557,16 +519,16 @@ class SplitHTTPStreamSettings extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new SplitHTTPStreamSettings(
|
return new xHTTPStreamSettings(
|
||||||
json.path,
|
json.path,
|
||||||
json.host,
|
json.host,
|
||||||
XrayCommonClass.toHeaders(json.headers),
|
XrayCommonClass.toHeaders(json.headers),
|
||||||
json.scMaxConcurrentPosts,
|
json.scMaxBufferedPosts,
|
||||||
json.scMaxEachPostBytes,
|
json.scMaxEachPostBytes,
|
||||||
json.scMinPostsIntervalMs,
|
json.scStreamUpServerSecs,
|
||||||
json.noSSEHeader,
|
json.noSSEHeader,
|
||||||
json.xPaddingBytes,
|
json.xPaddingBytes,
|
||||||
json.xmux,
|
json.mode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,17 +537,12 @@ class SplitHTTPStreamSettings extends XrayCommonClass {
|
||||||
path: this.path,
|
path: this.path,
|
||||||
host: this.host,
|
host: this.host,
|
||||||
headers: XrayCommonClass.toV2Headers(this.headers, false),
|
headers: XrayCommonClass.toV2Headers(this.headers, false),
|
||||||
scMaxConcurrentPosts: this.scMaxConcurrentPosts,
|
scMaxBufferedPosts: this.scMaxBufferedPosts,
|
||||||
scMaxEachPostBytes: this.scMaxEachPostBytes,
|
scMaxEachPostBytes: this.scMaxEachPostBytes,
|
||||||
scMinPostsIntervalMs: this.scMinPostsIntervalMs,
|
scStreamUpServerSecs: this.scStreamUpServerSecs,
|
||||||
noSSEHeader: this.noSSEHeader,
|
noSSEHeader: this.noSSEHeader,
|
||||||
xPaddingBytes: this.xPaddingBytes,
|
xPaddingBytes: this.xPaddingBytes,
|
||||||
xmux: {
|
mode: this.mode,
|
||||||
maxConcurrency: this.xmux.maxConcurrency,
|
|
||||||
maxConnections: this.xmux.maxConnections,
|
|
||||||
cMaxReuseTimes: this.xmux.cMaxReuseTimes,
|
|
||||||
cMaxLifetimeMs: this.xmux.cMaxLifetimeMs
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -597,6 +554,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
maxVersion = TLS_VERSION_OPTION.TLS13,
|
maxVersion = TLS_VERSION_OPTION.TLS13,
|
||||||
cipherSuites = '',
|
cipherSuites = '',
|
||||||
rejectUnknownSni = false,
|
rejectUnknownSni = false,
|
||||||
|
serverNameToVerify = 'dns.google',
|
||||||
disableSystemRoot = false,
|
disableSystemRoot = false,
|
||||||
enableSessionResumption = false,
|
enableSessionResumption = false,
|
||||||
certificates = [new TlsStreamSettings.Cert()],
|
certificates = [new TlsStreamSettings.Cert()],
|
||||||
|
@ -609,6 +567,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
this.maxVersion = maxVersion;
|
this.maxVersion = maxVersion;
|
||||||
this.cipherSuites = cipherSuites;
|
this.cipherSuites = cipherSuites;
|
||||||
this.rejectUnknownSni = rejectUnknownSni;
|
this.rejectUnknownSni = rejectUnknownSni;
|
||||||
|
this.serverNameToVerify = serverNameToVerify;
|
||||||
this.disableSystemRoot = disableSystemRoot;
|
this.disableSystemRoot = disableSystemRoot;
|
||||||
this.enableSessionResumption = enableSessionResumption;
|
this.enableSessionResumption = enableSessionResumption;
|
||||||
this.certs = certificates;
|
this.certs = certificates;
|
||||||
|
@ -640,6 +599,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
json.maxVersion,
|
json.maxVersion,
|
||||||
json.cipherSuites,
|
json.cipherSuites,
|
||||||
json.rejectUnknownSni,
|
json.rejectUnknownSni,
|
||||||
|
json.serverNameToVerify,
|
||||||
json.disableSystemRoot,
|
json.disableSystemRoot,
|
||||||
json.enableSessionResumption,
|
json.enableSessionResumption,
|
||||||
certs,
|
certs,
|
||||||
|
@ -655,6 +615,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
maxVersion: this.maxVersion,
|
maxVersion: this.maxVersion,
|
||||||
cipherSuites: this.cipherSuites,
|
cipherSuites: this.cipherSuites,
|
||||||
rejectUnknownSni: this.rejectUnknownSni,
|
rejectUnknownSni: this.rejectUnknownSni,
|
||||||
|
serverNameToVerify: this.serverNameToVerify,
|
||||||
disableSystemRoot: this.disableSystemRoot,
|
disableSystemRoot: this.disableSystemRoot,
|
||||||
enableSessionResumption: this.enableSessionResumption,
|
enableSessionResumption: this.enableSessionResumption,
|
||||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||||
|
@ -736,7 +697,10 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
};
|
};
|
||||||
|
|
||||||
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(allowInsecure = false, fingerprint = '') {
|
constructor(
|
||||||
|
allowInsecure = false,
|
||||||
|
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.allowInsecure = allowInsecure;
|
this.allowInsecure = allowInsecure;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
|
@ -755,137 +719,6 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class XtlsStreamSettings extends XrayCommonClass {
|
|
||||||
constructor(
|
|
||||||
serverName = '',
|
|
||||||
certificates = [new XtlsStreamSettings.Cert()],
|
|
||||||
alpn = [ALPN_OPTION.H3, ALPN_OPTION.H2, ALPN_OPTION.HTTP1],
|
|
||||||
settings = new XtlsStreamSettings.Settings()
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.sni = serverName;
|
|
||||||
this.certs = certificates;
|
|
||||||
this.alpn = alpn;
|
|
||||||
this.settings = settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
addCert() {
|
|
||||||
this.certs.push(new XtlsStreamSettings.Cert());
|
|
||||||
}
|
|
||||||
|
|
||||||
removeCert(index) {
|
|
||||||
this.certs.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
|
||||||
let certs;
|
|
||||||
let settings;
|
|
||||||
if (!ObjectUtil.isEmpty(json.certificates)) {
|
|
||||||
certs = json.certificates.map(cert => XtlsStreamSettings.Cert.fromJson(cert));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
|
||||||
settings = new XtlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.serverName);
|
|
||||||
}
|
|
||||||
return new XtlsStreamSettings(
|
|
||||||
json.serverName,
|
|
||||||
certs,
|
|
||||||
json.alpn,
|
|
||||||
settings,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJson() {
|
|
||||||
return {
|
|
||||||
serverName: this.sni,
|
|
||||||
certificates: XtlsStreamSettings.toJsonArray(this.certs),
|
|
||||||
alpn: this.alpn,
|
|
||||||
settings: this.settings,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
XtlsStreamSettings.Cert = class extends XrayCommonClass {
|
|
||||||
constructor(
|
|
||||||
useFile = true,
|
|
||||||
certificateFile = '',
|
|
||||||
keyFile = '',
|
|
||||||
certificate = '',
|
|
||||||
key = '',
|
|
||||||
ocspStapling = 3600,
|
|
||||||
oneTimeLoading = false,
|
|
||||||
usage = USAGE_OPTION.ENCIPHERMENT
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.useFile = useFile;
|
|
||||||
this.certFile = certificateFile;
|
|
||||||
this.keyFile = keyFile;
|
|
||||||
this.cert = Array.isArray(certificate) ? certificate.join('\n') : certificate;
|
|
||||||
this.key = Array.isArray(key) ? key.join('\n') : key;
|
|
||||||
this.ocspStapling = ocspStapling;
|
|
||||||
this.oneTimeLoading = oneTimeLoading;
|
|
||||||
this.usage = usage;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
|
||||||
if ('certificateFile' in json && 'keyFile' in json) {
|
|
||||||
return new XtlsStreamSettings.Cert(
|
|
||||||
true,
|
|
||||||
json.certificateFile,
|
|
||||||
json.keyFile, '', '',
|
|
||||||
json.ocspStapling,
|
|
||||||
json.oneTimeLoading,
|
|
||||||
json.usage,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return new XtlsStreamSettings.Cert(
|
|
||||||
false, '', '',
|
|
||||||
json.certificate.join('\n'),
|
|
||||||
json.key.join('\n'),
|
|
||||||
json.ocspStapling,
|
|
||||||
json.oneTimeLoading,
|
|
||||||
json.usage,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJson() {
|
|
||||||
if (this.useFile) {
|
|
||||||
return {
|
|
||||||
certificateFile: this.certFile,
|
|
||||||
keyFile: this.keyFile,
|
|
||||||
ocspStapling: this.ocspStapling,
|
|
||||||
oneTimeLoading: this.oneTimeLoading,
|
|
||||||
usage: this.usage,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
certificate: this.cert.split('\n'),
|
|
||||||
key: this.key.split('\n'),
|
|
||||||
ocspStapling: this.ocspStapling,
|
|
||||||
oneTimeLoading: this.oneTimeLoading,
|
|
||||||
usage: this.usage,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
XtlsStreamSettings.Settings = class extends XrayCommonClass {
|
|
||||||
constructor(allowInsecure = false) {
|
|
||||||
super();
|
|
||||||
this.allowInsecure = allowInsecure;
|
|
||||||
}
|
|
||||||
static fromJson(json = {}) {
|
|
||||||
return new XtlsStreamSettings.Settings(
|
|
||||||
json.allowInsecure,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
toJson() {
|
|
||||||
return {
|
|
||||||
allowInsecure: this.allowInsecure,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class RealityStreamSettings extends XrayCommonClass {
|
class RealityStreamSettings extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -933,7 +766,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
||||||
json.maxClient,
|
json.maxClient,
|
||||||
json.maxTimediff,
|
json.maxTimediff,
|
||||||
json.shortIds,
|
json.shortIds,
|
||||||
json.settings,
|
settings,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -956,7 +789,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
||||||
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
publicKey = '',
|
publicKey = '',
|
||||||
fingerprint = UTLS_FINGERPRINT.UTLS_RANDOM,
|
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
|
||||||
serverName = '',
|
serverName = '',
|
||||||
spiderX = '/'
|
spiderX = '/'
|
||||||
) {
|
) {
|
||||||
|
@ -991,7 +824,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
||||||
mark = 0,
|
mark = 0,
|
||||||
tproxy = "off",
|
tproxy = "off",
|
||||||
tcpMptcp = false,
|
tcpMptcp = false,
|
||||||
tcpNoDelay = false,
|
penetrate = false,
|
||||||
domainStrategy = DOMAIN_STRATEGY_OPTION.USE_IP,
|
domainStrategy = DOMAIN_STRATEGY_OPTION.USE_IP,
|
||||||
tcpMaxSeg = 1440,
|
tcpMaxSeg = 1440,
|
||||||
dialerProxy = "",
|
dialerProxy = "",
|
||||||
|
@ -1009,7 +842,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
||||||
this.mark = mark;
|
this.mark = mark;
|
||||||
this.tproxy = tproxy;
|
this.tproxy = tproxy;
|
||||||
this.tcpMptcp = tcpMptcp;
|
this.tcpMptcp = tcpMptcp;
|
||||||
this.tcpNoDelay = tcpNoDelay;
|
this.penetrate = penetrate;
|
||||||
this.domainStrategy = domainStrategy;
|
this.domainStrategy = domainStrategy;
|
||||||
this.tcpMaxSeg = tcpMaxSeg;
|
this.tcpMaxSeg = tcpMaxSeg;
|
||||||
this.dialerProxy = dialerProxy;
|
this.dialerProxy = dialerProxy;
|
||||||
|
@ -1030,7 +863,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
||||||
json.mark,
|
json.mark,
|
||||||
json.tproxy,
|
json.tproxy,
|
||||||
json.tcpMptcp,
|
json.tcpMptcp,
|
||||||
json.tcpNoDelay,
|
json.penetrate,
|
||||||
json.domainStrategy,
|
json.domainStrategy,
|
||||||
json.tcpMaxSeg,
|
json.tcpMaxSeg,
|
||||||
json.dialerProxy,
|
json.dialerProxy,
|
||||||
|
@ -1051,7 +884,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
||||||
mark: this.mark,
|
mark: this.mark,
|
||||||
tproxy: this.tproxy,
|
tproxy: this.tproxy,
|
||||||
tcpMptcp: this.tcpMptcp,
|
tcpMptcp: this.tcpMptcp,
|
||||||
tcpNoDelay: this.tcpNoDelay,
|
penetrate: this.penetrate,
|
||||||
domainStrategy: this.domainStrategy,
|
domainStrategy: this.domainStrategy,
|
||||||
tcpMaxSeg: this.tcpMaxSeg,
|
tcpMaxSeg: this.tcpMaxSeg,
|
||||||
dialerProxy: this.dialerProxy,
|
dialerProxy: this.dialerProxy,
|
||||||
|
@ -1071,15 +904,13 @@ class StreamSettings extends XrayCommonClass {
|
||||||
security = 'none',
|
security = 'none',
|
||||||
externalProxy = [],
|
externalProxy = [],
|
||||||
tlsSettings = new TlsStreamSettings(),
|
tlsSettings = new TlsStreamSettings(),
|
||||||
xtlsSettings = new XtlsStreamSettings(),
|
|
||||||
realitySettings = new RealityStreamSettings(),
|
realitySettings = new RealityStreamSettings(),
|
||||||
tcpSettings = new TcpStreamSettings(),
|
tcpSettings = new TcpStreamSettings(),
|
||||||
kcpSettings = new KcpStreamSettings(),
|
kcpSettings = new KcpStreamSettings(),
|
||||||
wsSettings = new WsStreamSettings(),
|
wsSettings = new WsStreamSettings(),
|
||||||
httpSettings = new HttpStreamSettings(),
|
|
||||||
grpcSettings = new GrpcStreamSettings(),
|
grpcSettings = new GrpcStreamSettings(),
|
||||||
httpupgradeSettings = new HTTPUpgradeStreamSettings(),
|
httpupgradeSettings = new HTTPUpgradeStreamSettings(),
|
||||||
splithttpSettings = new SplitHTTPStreamSettings(),
|
xhttpSettings = new xHTTPStreamSettings(),
|
||||||
sockopt = undefined,
|
sockopt = undefined,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -1087,15 +918,13 @@ class StreamSettings extends XrayCommonClass {
|
||||||
this.security = security;
|
this.security = security;
|
||||||
this.externalProxy = externalProxy;
|
this.externalProxy = externalProxy;
|
||||||
this.tls = tlsSettings;
|
this.tls = tlsSettings;
|
||||||
this.xtls = xtlsSettings;
|
|
||||||
this.reality = realitySettings;
|
this.reality = realitySettings;
|
||||||
this.tcp = tcpSettings;
|
this.tcp = tcpSettings;
|
||||||
this.kcp = kcpSettings;
|
this.kcp = kcpSettings;
|
||||||
this.ws = wsSettings;
|
this.ws = wsSettings;
|
||||||
this.http = httpSettings;
|
|
||||||
this.grpc = grpcSettings;
|
this.grpc = grpcSettings;
|
||||||
this.httpupgrade = httpupgradeSettings;
|
this.httpupgrade = httpupgradeSettings;
|
||||||
this.splithttp = splithttpSettings;
|
this.xhttp = xhttpSettings;
|
||||||
this.sockopt = sockopt;
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1111,18 +940,6 @@ class StreamSettings extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isXtls() {
|
|
||||||
return this.security === "xtls";
|
|
||||||
}
|
|
||||||
|
|
||||||
set isXtls(isXtls) {
|
|
||||||
if (isXtls) {
|
|
||||||
this.security = 'xtls';
|
|
||||||
} else {
|
|
||||||
this.security = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//for Reality
|
//for Reality
|
||||||
get isReality() {
|
get isReality() {
|
||||||
return this.security === "reality";
|
return this.security === "reality";
|
||||||
|
@ -1150,15 +967,13 @@ class StreamSettings extends XrayCommonClass {
|
||||||
json.security,
|
json.security,
|
||||||
json.externalProxy,
|
json.externalProxy,
|
||||||
TlsStreamSettings.fromJson(json.tlsSettings),
|
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||||
XtlsStreamSettings.fromJson(json.xtlsSettings),
|
|
||||||
RealityStreamSettings.fromJson(json.realitySettings),
|
RealityStreamSettings.fromJson(json.realitySettings),
|
||||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||||
WsStreamSettings.fromJson(json.wsSettings),
|
WsStreamSettings.fromJson(json.wsSettings),
|
||||||
HttpStreamSettings.fromJson(json.httpSettings),
|
|
||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||||
SplitHTTPStreamSettings.fromJson(json.splithttpSettings),
|
xHTTPStreamSettings.fromJson(json.xhttpSettings),
|
||||||
SockoptStreamSettings.fromJson(json.sockopt),
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1170,15 +985,13 @@ class StreamSettings extends XrayCommonClass {
|
||||||
security: this.security,
|
security: this.security,
|
||||||
externalProxy: this.externalProxy,
|
externalProxy: this.externalProxy,
|
||||||
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
||||||
xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined,
|
|
||||||
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
||||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||||
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
|
||||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.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,
|
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1283,18 +1096,6 @@ class Inbound extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get xtls() {
|
|
||||||
return this.stream.security === 'xtls';
|
|
||||||
}
|
|
||||||
|
|
||||||
set xtls(isXtls) {
|
|
||||||
if (isXtls) {
|
|
||||||
this.stream.security = 'xtls';
|
|
||||||
} else {
|
|
||||||
this.stream.security = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get network() {
|
get network() {
|
||||||
return this.stream.network;
|
return this.stream.network;
|
||||||
}
|
}
|
||||||
|
@ -1319,16 +1120,12 @@ class Inbound extends XrayCommonClass {
|
||||||
return this.network === "grpc";
|
return this.network === "grpc";
|
||||||
}
|
}
|
||||||
|
|
||||||
get isH2() {
|
|
||||||
return this.network === "http";
|
|
||||||
}
|
|
||||||
|
|
||||||
get isHttpupgrade() {
|
get isHttpupgrade() {
|
||||||
return this.network === "httpupgrade";
|
return this.network === "httpupgrade";
|
||||||
}
|
}
|
||||||
|
|
||||||
get isSplithttp() {
|
get isXHTTP() {
|
||||||
return this.network === "splithttp";
|
return this.network === "xhttp";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shadowsocks
|
// Shadowsocks
|
||||||
|
@ -1349,7 +1146,6 @@ class Inbound extends XrayCommonClass {
|
||||||
|
|
||||||
get serverName() {
|
get serverName() {
|
||||||
if (this.stream.isTls) return this.stream.tls.sni;
|
if (this.stream.isTls) return this.stream.tls.sni;
|
||||||
if (this.stream.isXtls) return this.stream.xtls.sni;
|
|
||||||
if (this.stream.isReality) return this.stream.reality.serverNames;
|
if (this.stream.isReality) return this.stream.reality.serverNames;
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -1368,12 +1164,10 @@ class Inbound extends XrayCommonClass {
|
||||||
return this.getHeader(this.stream.tcp.request, 'host');
|
return this.getHeader(this.stream.tcp.request, 'host');
|
||||||
} else if (this.isWs) {
|
} else if (this.isWs) {
|
||||||
return this.stream.ws.host?.length > 0 ? this.stream.ws.host : this.getHeader(this.stream.ws, 'host');
|
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) {
|
} else if (this.isHttpupgrade) {
|
||||||
return this.stream.httpupgrade.host?.length > 0 ? this.stream.httpupgrade.host : this.getHeader(this.stream.httpupgrade, 'host');
|
return this.stream.httpupgrade.host?.length > 0 ? this.stream.httpupgrade.host : this.getHeader(this.stream.httpupgrade, 'host');
|
||||||
} else if (this.isSplithttp) {
|
} else if (this.isXHTTP) {
|
||||||
return this.stream.splithttp.host?.length > 0 ? this.stream.splithttp.host : this.getHeader(this.stream.splithttp, 'host');
|
return this.stream.xhttp.host?.length > 0 ? this.stream.xhttp.host : this.getHeader(this.stream.xhttp, 'host');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1383,12 +1177,10 @@ class Inbound extends XrayCommonClass {
|
||||||
return this.stream.tcp.request.path[0];
|
return this.stream.tcp.request.path[0];
|
||||||
} else if (this.isWs) {
|
} else if (this.isWs) {
|
||||||
return this.stream.ws.path;
|
return this.stream.ws.path;
|
||||||
} else if (this.isH2) {
|
|
||||||
return this.stream.http.path;
|
|
||||||
} else if (this.isHttpupgrade) {
|
} else if (this.isHttpupgrade) {
|
||||||
return this.stream.httpupgrade.path;
|
return this.stream.httpupgrade.path;
|
||||||
} else if (this.isSplithttp) {
|
} else if (this.isXHTTP) {
|
||||||
return this.stream.splithttp.path;
|
return this.stream.xhttp.path;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1412,7 +1204,7 @@ class Inbound extends XrayCommonClass {
|
||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
if (![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
|
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
|
//this is used for xtls-rprx-vision
|
||||||
|
@ -1425,12 +1217,7 @@ class Inbound extends XrayCommonClass {
|
||||||
|
|
||||||
canEnableReality() {
|
canEnableReality() {
|
||||||
if (![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
|
if (![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
|
||||||
return ["tcp", "http", "grpc"].includes(this.network);
|
return ["tcp", "http", "grpc", "xhttp"].includes(this.network);
|
||||||
}
|
|
||||||
|
|
||||||
canEnableXtls() {
|
|
||||||
if (![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
|
|
||||||
return this.network === "tcp";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableStream() {
|
canEnableStream() {
|
||||||
|
@ -1482,10 +1269,6 @@ class Inbound extends XrayCommonClass {
|
||||||
const ws = this.stream.ws;
|
const ws = this.stream.ws;
|
||||||
obj.path = ws.path;
|
obj.path = ws.path;
|
||||||
obj.host = ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host');
|
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') {
|
} else if (network === 'grpc') {
|
||||||
obj.path = this.stream.grpc.serviceName;
|
obj.path = this.stream.grpc.serviceName;
|
||||||
obj.authority = this.stream.grpc.authority;
|
obj.authority = this.stream.grpc.authority;
|
||||||
|
@ -1496,13 +1279,14 @@ class Inbound extends XrayCommonClass {
|
||||||
const httpupgrade = this.stream.httpupgrade;
|
const httpupgrade = this.stream.httpupgrade;
|
||||||
obj.path = httpupgrade.path;
|
obj.path = httpupgrade.path;
|
||||||
obj.host = httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host');
|
obj.host = httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host');
|
||||||
} else if (network === 'splithttp') {
|
} else if (network === 'xhttp') {
|
||||||
const splithttp = this.stream.splithttp;
|
const xhttp = this.stream.xhttp;
|
||||||
obj.path = splithttp.path;
|
obj.path = xhttp.path;
|
||||||
obj.host = splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host');
|
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)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||||
obj.sni = this.stream.tls.sni;
|
obj.sni = this.stream.tls.sni;
|
||||||
}
|
}
|
||||||
|
@ -1550,11 +1334,6 @@ class Inbound extends XrayCommonClass {
|
||||||
params.set("path", ws.path);
|
params.set("path", ws.path);
|
||||||
params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host'));
|
params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host'));
|
||||||
break;
|
break;
|
||||||
case "http":
|
|
||||||
const http = this.stream.http;
|
|
||||||
params.set("path", http.path);
|
|
||||||
params.set("host", http.host);
|
|
||||||
break;
|
|
||||||
case "grpc":
|
case "grpc":
|
||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
@ -1568,10 +1347,11 @@ class Inbound extends XrayCommonClass {
|
||||||
params.set("path", httpupgrade.path);
|
params.set("path", httpupgrade.path);
|
||||||
params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
|
params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
|
||||||
break;
|
break;
|
||||||
case "splithttp":
|
case "xhttp":
|
||||||
const splithttp = this.stream.splithttp;
|
const xhttp = this.stream.xhttp;
|
||||||
params.set("path", splithttp.path);
|
params.set("path", xhttp.path);
|
||||||
params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host'));
|
params.set("host", xhttp.host?.length > 0 ? xhttp.host : this.getHeader(xhttp, 'host'));
|
||||||
|
params.set("mode", xhttp.mode);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1592,18 +1372,6 @@ class Inbound extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (security === 'xtls') {
|
|
||||||
params.set("security", "xtls");
|
|
||||||
params.set("alpn", this.stream.xtls.alpn);
|
|
||||||
if (this.stream.xtls.settings.allowInsecure) {
|
|
||||||
params.set("allowInsecure", "1");
|
|
||||||
}
|
|
||||||
if (!ObjectUtil.isEmpty(this.stream.xtls.sni)) {
|
|
||||||
params.set("sni", this.stream.xtls.sni);
|
|
||||||
}
|
|
||||||
params.set("flow", flow);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (security === 'reality') {
|
else if (security === 'reality') {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
|
@ -1665,11 +1433,6 @@ class Inbound extends XrayCommonClass {
|
||||||
params.set("path", ws.path);
|
params.set("path", ws.path);
|
||||||
params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host'));
|
params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host'));
|
||||||
break;
|
break;
|
||||||
case "http":
|
|
||||||
const http = this.stream.http;
|
|
||||||
params.set("path", http.path);
|
|
||||||
params.set("host", http.host);
|
|
||||||
break;
|
|
||||||
case "grpc":
|
case "grpc":
|
||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
@ -1683,10 +1446,11 @@ class Inbound extends XrayCommonClass {
|
||||||
params.set("path", httpupgrade.path);
|
params.set("path", httpupgrade.path);
|
||||||
params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
|
params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
|
||||||
break;
|
break;
|
||||||
case "splithttp":
|
case "xhttp":
|
||||||
const splithttp = this.stream.splithttp;
|
const xhttp = this.stream.xhttp;
|
||||||
params.set("path", splithttp.path);
|
params.set("path", xhttp.path);
|
||||||
params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host'));
|
params.set("host", xhttp.host?.length > 0 ? xhttp.host : this.getHeader(xhttp, 'host'));
|
||||||
|
params.set("mode", xhttp.mode);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1747,11 +1511,6 @@ class Inbound extends XrayCommonClass {
|
||||||
params.set("path", ws.path);
|
params.set("path", ws.path);
|
||||||
params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host'));
|
params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host'));
|
||||||
break;
|
break;
|
||||||
case "http":
|
|
||||||
const http = this.stream.http;
|
|
||||||
params.set("path", http.path);
|
|
||||||
params.set("host", http.host);
|
|
||||||
break;
|
|
||||||
case "grpc":
|
case "grpc":
|
||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
@ -1765,10 +1524,11 @@ class Inbound extends XrayCommonClass {
|
||||||
params.set("path", httpupgrade.path);
|
params.set("path", httpupgrade.path);
|
||||||
params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
|
params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
|
||||||
break;
|
break;
|
||||||
case "splithttp":
|
case "xhttp":
|
||||||
const splithttp = this.stream.splithttp;
|
const xhttp = this.stream.xhttp;
|
||||||
params.set("path", splithttp.path);
|
params.set("path", xhttp.path);
|
||||||
params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host'));
|
params.set("host", xhttp.host?.length > 0 ? xhttp.host : this.getHeader(xhttp, 'host'));
|
||||||
|
params.set("mode", xhttp.mode);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1801,18 +1561,6 @@ class Inbound extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (security === 'xtls') {
|
|
||||||
params.set("security", "xtls");
|
|
||||||
params.set("alpn", this.stream.xtls.alpn);
|
|
||||||
if (this.stream.xtls.settings.allowInsecure) {
|
|
||||||
params.set("allowInsecure", "1");
|
|
||||||
}
|
|
||||||
if (!ObjectUtil.isEmpty(this.stream.xtls.sni)) {
|
|
||||||
params.set("sni", this.stream.xtls.sni);
|
|
||||||
}
|
|
||||||
params.set("flow", flow);
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
else {
|
||||||
params.set("security", "none");
|
params.set("security", "none");
|
||||||
}
|
}
|
||||||
|
@ -2038,6 +1786,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
||||||
enable = true,
|
enable = true,
|
||||||
tgId = '',
|
tgId = '',
|
||||||
subId = RandomUtil.randomLowerAndNum(16),
|
subId = RandomUtil.randomLowerAndNum(16),
|
||||||
|
comment = '',
|
||||||
reset = 0
|
reset = 0
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -2050,6 +1799,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.comment = comment;
|
||||||
this.reset = reset;
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2064,6 +1814,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.comment,
|
||||||
json.reset,
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2144,6 +1895,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
enable = true,
|
enable = true,
|
||||||
tgId = '',
|
tgId = '',
|
||||||
subId = RandomUtil.randomLowerAndNum(16),
|
subId = RandomUtil.randomLowerAndNum(16),
|
||||||
|
comment = '',
|
||||||
reset = 0
|
reset = 0
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -2156,6 +1908,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.comment = comment;
|
||||||
this.reset = reset;
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2170,6 +1923,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.comment,
|
||||||
json.reset,
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2273,7 +2027,6 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
||||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
password = RandomUtil.randomSeq(10),
|
password = RandomUtil.randomSeq(10),
|
||||||
flow = '',
|
|
||||||
email = RandomUtil.randomLowerAndNum(8),
|
email = RandomUtil.randomLowerAndNum(8),
|
||||||
limitIp = 0,
|
limitIp = 0,
|
||||||
totalGB = 0,
|
totalGB = 0,
|
||||||
|
@ -2281,11 +2034,11 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
enable = true,
|
enable = true,
|
||||||
tgId = '',
|
tgId = '',
|
||||||
subId = RandomUtil.randomLowerAndNum(16),
|
subId = RandomUtil.randomLowerAndNum(16),
|
||||||
|
comment = '',
|
||||||
reset = 0
|
reset = 0
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.flow = flow;
|
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.limitIp = limitIp;
|
this.limitIp = limitIp;
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
|
@ -2293,13 +2046,13 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.comment = comment;
|
||||||
this.reset = reset;
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
password: this.password,
|
password: this.password,
|
||||||
flow: this.flow,
|
|
||||||
email: this.email,
|
email: this.email,
|
||||||
limitIp: this.limitIp,
|
limitIp: this.limitIp,
|
||||||
totalGB: this.totalGB,
|
totalGB: this.totalGB,
|
||||||
|
@ -2307,6 +2060,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
enable: this.enable,
|
enable: this.enable,
|
||||||
tgId: this.tgId,
|
tgId: this.tgId,
|
||||||
subId: this.subId,
|
subId: this.subId,
|
||||||
|
comment: this.comment,
|
||||||
reset: this.reset,
|
reset: this.reset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2314,7 +2068,6 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new Inbound.TrojanSettings.Trojan(
|
return new Inbound.TrojanSettings.Trojan(
|
||||||
json.password,
|
json.password,
|
||||||
json.flow,
|
|
||||||
json.email,
|
json.email,
|
||||||
json.limitIp,
|
json.limitIp,
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
|
@ -2322,6 +2075,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.comment,
|
||||||
json.reset,
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2397,13 +2151,15 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||||
method = SSMethods.BLAKE3_AES_256_GCM,
|
method = SSMethods.BLAKE3_AES_256_GCM,
|
||||||
password = RandomUtil.randomShadowsocksPassword(),
|
password = RandomUtil.randomShadowsocksPassword(),
|
||||||
network = 'tcp,udp',
|
network = 'tcp,udp',
|
||||||
shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()]
|
shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()],
|
||||||
|
ivCheck = false,
|
||||||
) {
|
) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.network = network;
|
this.network = network;
|
||||||
this.shadowsockses = shadowsockses;
|
this.shadowsockses = shadowsockses;
|
||||||
|
this.ivCheck = ivCheck;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
@ -2413,6 +2169,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||||
json.password,
|
json.password,
|
||||||
json.network,
|
json.network,
|
||||||
json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
|
json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
|
||||||
|
json.ivCheck,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2421,7 +2178,8 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||||
method: this.method,
|
method: this.method,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
network: this.network,
|
network: this.network,
|
||||||
clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses)
|
clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses),
|
||||||
|
ivCheck: this.ivCheck,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2437,6 +2195,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
enable = true,
|
enable = true,
|
||||||
tgId = '',
|
tgId = '',
|
||||||
subId = RandomUtil.randomLowerAndNum(16),
|
subId = RandomUtil.randomLowerAndNum(16),
|
||||||
|
comment = '',
|
||||||
reset = 0
|
reset = 0
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -2449,6 +2208,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.comment = comment;
|
||||||
this.reset = reset;
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2463,6 +2223,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
enable: this.enable,
|
enable: this.enable,
|
||||||
tgId: this.tgId,
|
tgId: this.tgId,
|
||||||
subId: this.subId,
|
subId: this.subId,
|
||||||
|
comment: this.comment,
|
||||||
reset: this.reset,
|
reset: this.reset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2478,6 +2239,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.comment,
|
||||||
json.reset,
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -39,6 +39,7 @@ const UTLS_FINGERPRINT = {
|
||||||
UTLS_QQ: "qq",
|
UTLS_QQ: "qq",
|
||||||
UTLS_RANDOM: "random",
|
UTLS_RANDOM: "random",
|
||||||
UTLS_RANDOMIZED: "randomized",
|
UTLS_RANDOMIZED: "randomized",
|
||||||
|
UTLS_UNSAFE: "unsafe",
|
||||||
};
|
};
|
||||||
|
|
||||||
const ALPN_OPTION = {
|
const ALPN_OPTION = {
|
||||||
|
@ -77,6 +78,13 @@ const USERS_SECURITY = {
|
||||||
ZERO: "zero",
|
ZERO: "zero",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MODE_OPTION = {
|
||||||
|
AUTO: "auto",
|
||||||
|
PACKET_UP: "packet-up",
|
||||||
|
STREAM_UP: "stream-up",
|
||||||
|
STREAM_ONE: "stream-one",
|
||||||
|
};
|
||||||
|
|
||||||
Object.freeze(Protocols);
|
Object.freeze(Protocols);
|
||||||
Object.freeze(SSMethods);
|
Object.freeze(SSMethods);
|
||||||
Object.freeze(TLS_FLOW_CONTROL);
|
Object.freeze(TLS_FLOW_CONTROL);
|
||||||
|
@ -85,6 +93,7 @@ Object.freeze(ALPN_OPTION);
|
||||||
Object.freeze(OutboundDomainStrategies);
|
Object.freeze(OutboundDomainStrategies);
|
||||||
Object.freeze(WireguardDomainStrategy);
|
Object.freeze(WireguardDomainStrategy);
|
||||||
Object.freeze(USERS_SECURITY);
|
Object.freeze(USERS_SECURITY);
|
||||||
|
Object.freeze(MODE_OPTION);
|
||||||
|
|
||||||
|
|
||||||
class CommonClass {
|
class CommonClass {
|
||||||
|
@ -198,16 +207,23 @@ class KcpStreamSettings extends CommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
class WsStreamSettings extends CommonClass {
|
class WsStreamSettings extends CommonClass {
|
||||||
constructor(path = '/', host = '') {
|
constructor(
|
||||||
|
path = '/',
|
||||||
|
host = '',
|
||||||
|
heartbeatPeriod = 0,
|
||||||
|
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
|
this.heartbeatPeriod = heartbeatPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new WsStreamSettings(
|
return new WsStreamSettings(
|
||||||
json.path,
|
json.path,
|
||||||
json.host,
|
json.host,
|
||||||
|
json.heartbeatPeriod,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,63 +231,11 @@ class WsStreamSettings extends CommonClass {
|
||||||
return {
|
return {
|
||||||
path: this.path,
|
path: this.path,
|
||||||
host: this.host,
|
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 {
|
class GrpcStreamSettings extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
serviceName = "",
|
serviceName = "",
|
||||||
|
@ -319,17 +283,39 @@ class HttpUpgradeStreamSettings extends CommonClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SplitHTTPStreamSettings extends CommonClass {
|
class xHTTPStreamSettings extends CommonClass {
|
||||||
constructor(path = '/', host = '') {
|
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();
|
super();
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
|
this.mode = mode;
|
||||||
|
this.noGRPCHeader = noGRPCHeader;
|
||||||
|
this.scMinPostsIntervalMs = scMinPostsIntervalMs;
|
||||||
|
this.xmux = xmux;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new SplitHTTPStreamSettings(
|
return new xHTTPStreamSettings(
|
||||||
json.path,
|
json.path,
|
||||||
json.host,
|
json.host,
|
||||||
|
json.mode,
|
||||||
|
json.noGRPCHeader,
|
||||||
|
json.scMinPostsIntervalMs,
|
||||||
|
json.xmux
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,6 +323,17 @@ class SplitHTTPStreamSettings extends CommonClass {
|
||||||
return {
|
return {
|
||||||
path: this.path,
|
path: this.path,
|
||||||
host: this.host,
|
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,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -414,14 +411,14 @@ class SockoptStreamSettings extends CommonClass {
|
||||||
tcpFastOpen = false,
|
tcpFastOpen = false,
|
||||||
tcpKeepAliveInterval = 0,
|
tcpKeepAliveInterval = 0,
|
||||||
tcpMptcp = false,
|
tcpMptcp = false,
|
||||||
tcpNoDelay = false
|
penetrate = false
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.dialerProxy = dialerProxy;
|
this.dialerProxy = dialerProxy;
|
||||||
this.tcpFastOpen = tcpFastOpen;
|
this.tcpFastOpen = tcpFastOpen;
|
||||||
this.tcpKeepAliveInterval = tcpKeepAliveInterval;
|
this.tcpKeepAliveInterval = tcpKeepAliveInterval;
|
||||||
this.tcpMptcp = tcpMptcp;
|
this.tcpMptcp = tcpMptcp;
|
||||||
this.tcpNoDelay = tcpNoDelay;
|
this.penetrate = penetrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
@ -431,7 +428,7 @@ class SockoptStreamSettings extends CommonClass {
|
||||||
json.tcpFastOpen,
|
json.tcpFastOpen,
|
||||||
json.tcpKeepAliveInterval,
|
json.tcpKeepAliveInterval,
|
||||||
json.tcpMptcp,
|
json.tcpMptcp,
|
||||||
json.tcpNoDelay,
|
json.penetrate,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,7 +438,7 @@ class SockoptStreamSettings extends CommonClass {
|
||||||
tcpFastOpen: this.tcpFastOpen,
|
tcpFastOpen: this.tcpFastOpen,
|
||||||
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
|
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
|
||||||
tcpMptcp: this.tcpMptcp,
|
tcpMptcp: this.tcpMptcp,
|
||||||
tcpNoDelay: this.tcpNoDelay,
|
penetrate: this.penetrate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -455,11 +452,9 @@ class StreamSettings extends CommonClass {
|
||||||
tcpSettings = new TcpStreamSettings(),
|
tcpSettings = new TcpStreamSettings(),
|
||||||
kcpSettings = new KcpStreamSettings(),
|
kcpSettings = new KcpStreamSettings(),
|
||||||
wsSettings = new WsStreamSettings(),
|
wsSettings = new WsStreamSettings(),
|
||||||
httpSettings = new HttpStreamSettings(),
|
|
||||||
quicSettings = new QuicStreamSettings(),
|
|
||||||
grpcSettings = new GrpcStreamSettings(),
|
grpcSettings = new GrpcStreamSettings(),
|
||||||
httpupgradeSettings = new HttpUpgradeStreamSettings(),
|
httpupgradeSettings = new HttpUpgradeStreamSettings(),
|
||||||
splithttpSettings = new SplitHTTPStreamSettings(),
|
xhttpSettings = new xHTTPStreamSettings(),
|
||||||
sockopt = undefined,
|
sockopt = undefined,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -470,10 +465,9 @@ class StreamSettings extends CommonClass {
|
||||||
this.tcp = tcpSettings;
|
this.tcp = tcpSettings;
|
||||||
this.kcp = kcpSettings;
|
this.kcp = kcpSettings;
|
||||||
this.ws = wsSettings;
|
this.ws = wsSettings;
|
||||||
this.http = httpSettings;
|
|
||||||
this.grpc = grpcSettings;
|
this.grpc = grpcSettings;
|
||||||
this.httpupgrade = httpupgradeSettings;
|
this.httpupgrade = httpupgradeSettings;
|
||||||
this.splithttp = splithttpSettings;
|
this.xhttp = xhttpSettings;
|
||||||
this.sockopt = sockopt;
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -502,11 +496,9 @@ class StreamSettings extends CommonClass {
|
||||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||||
WsStreamSettings.fromJson(json.wsSettings),
|
WsStreamSettings.fromJson(json.wsSettings),
|
||||||
HttpStreamSettings.fromJson(json.httpSettings),
|
|
||||||
QuicStreamSettings.fromJson(json.quicSettings),
|
|
||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||||
SplitHTTPStreamSettings.fromJson(json.splithttpSettings),
|
xHTTPStreamSettings.fromJson(json.xhttpSettings),
|
||||||
SockoptStreamSettings.fromJson(json.sockopt),
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -521,10 +513,9 @@ class StreamSettings extends CommonClass {
|
||||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||||
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
|
||||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.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,
|
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -562,7 +553,7 @@ class Mux extends CommonClass {
|
||||||
class Outbound extends CommonClass {
|
class Outbound extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
tag = '',
|
tag = '',
|
||||||
protocol = Protocols.VMess,
|
protocol = Protocols.VLESS,
|
||||||
settings = null,
|
settings = null,
|
||||||
streamSettings = new StreamSettings(),
|
streamSettings = new StreamSettings(),
|
||||||
sendThrough,
|
sendThrough,
|
||||||
|
@ -589,7 +580,7 @@ class Outbound extends CommonClass {
|
||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
|
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
|
//this is used for xtls-rprx-vision
|
||||||
|
@ -602,7 +593,7 @@ class Outbound extends CommonClass {
|
||||||
|
|
||||||
canEnableReality() {
|
canEnableReality() {
|
||||||
if (![Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
if (![Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
||||||
return ["tcp", "http", "grpc"].includes(this.stream.network);
|
return ["tcp", "http", "grpc", "xhttp"].includes(this.stream.network);
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableStream() {
|
canEnableStream() {
|
||||||
|
@ -700,17 +691,12 @@ class Outbound extends CommonClass {
|
||||||
stream.seed = json.path;
|
stream.seed = json.path;
|
||||||
} else if (network === 'ws') {
|
} else if (network === 'ws') {
|
||||||
stream.ws = new WsStreamSettings(json.path, json.host);
|
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') {
|
} else if (network === 'grpc') {
|
||||||
stream.grpc = new GrpcStreamSettings(json.path, json.authority, json.type == 'multi');
|
stream.grpc = new GrpcStreamSettings(json.path, json.authority, json.type == 'multi');
|
||||||
} else if (network === 'httpupgrade') {
|
} else if (network === 'httpupgrade') {
|
||||||
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path, json.host);
|
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path, json.host);
|
||||||
} else if (network === 'splithttp') {
|
} else if (network === 'xhttp') {
|
||||||
stream.splithttp = new SplitHTTPStreamSettings(json.path, json.host);
|
stream.xhttp = new xHTTPStreamSettings(json.path, json.host, json.mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json.tls && json.tls == 'tls') {
|
if (json.tls && json.tls == 'tls') {
|
||||||
|
@ -735,6 +721,7 @@ class Outbound extends CommonClass {
|
||||||
let headerType = url.searchParams.get('headerType') ?? undefined;
|
let headerType = url.searchParams.get('headerType') ?? undefined;
|
||||||
let host = url.searchParams.get('host') ?? undefined;
|
let host = url.searchParams.get('host') ?? undefined;
|
||||||
let path = url.searchParams.get('path') ?? undefined;
|
let path = url.searchParams.get('path') ?? undefined;
|
||||||
|
let mode = url.searchParams.get('mode') ?? undefined;
|
||||||
|
|
||||||
if (type === 'tcp' || type === 'none') {
|
if (type === 'tcp' || type === 'none') {
|
||||||
stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path);
|
stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path);
|
||||||
|
@ -744,8 +731,6 @@ class Outbound extends CommonClass {
|
||||||
stream.kcp.seed = path;
|
stream.kcp.seed = path;
|
||||||
} else if (type === 'ws') {
|
} else if (type === 'ws') {
|
||||||
stream.ws = new WsStreamSettings(path, host);
|
stream.ws = new WsStreamSettings(path, host);
|
||||||
} else if (type === 'http' || type == 'h2') {
|
|
||||||
stream.http = new HttpStreamSettings(path, host);
|
|
||||||
} else if (type === 'grpc') {
|
} else if (type === 'grpc') {
|
||||||
stream.grpc = new GrpcStreamSettings(
|
stream.grpc = new GrpcStreamSettings(
|
||||||
url.searchParams.get('serviceName') ?? '',
|
url.searchParams.get('serviceName') ?? '',
|
||||||
|
@ -753,8 +738,8 @@ class Outbound extends CommonClass {
|
||||||
url.searchParams.get('mode') == 'multi');
|
url.searchParams.get('mode') == 'multi');
|
||||||
} else if (type === 'httpupgrade') {
|
} else if (type === 'httpupgrade') {
|
||||||
stream.httpupgrade = new HttpUpgradeStreamSettings(path, host);
|
stream.httpupgrade = new HttpUpgradeStreamSettings(path, host);
|
||||||
} else if (type === 'splithttp') {
|
} else if (type === 'xhttp') {
|
||||||
stream.splithttp = new SplitHTTPStreamSettings(path, host);
|
stream.xhttp = new xHTTPStreamSettings(path, host, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security == 'tls') {
|
if (security == 'tls') {
|
||||||
|
@ -960,7 +945,7 @@ Outbound.BlackholeSettings = class extends CommonClass {
|
||||||
Outbound.DNSSettings = class extends CommonClass {
|
Outbound.DNSSettings = class extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
network = 'udp',
|
network = 'udp',
|
||||||
address = '1.1.1.1',
|
address = '',
|
||||||
port = 53,
|
port = 53,
|
||||||
nonIPQuery = 'drop',
|
nonIPQuery = 'drop',
|
||||||
blockTypes = []
|
blockTypes = []
|
||||||
|
|
|
@ -26,6 +26,7 @@ class AllSetting {
|
||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
this.secretEnable = false;
|
this.secretEnable = false;
|
||||||
this.subEnable = false;
|
this.subEnable = false;
|
||||||
|
this.subSyncEnable = true;
|
||||||
this.subListen = "";
|
this.subListen = "";
|
||||||
this.subPort = 2096;
|
this.subPort = 2096;
|
||||||
this.subPath = "/sub/";
|
this.subPath = "/sub/";
|
||||||
|
@ -43,7 +44,7 @@ class AllSetting {
|
||||||
this.subJsonMux = "";
|
this.subJsonMux = "";
|
||||||
this.subJsonRules = "";
|
this.subJsonRules = "";
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
this.timeLocation = "Local";
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -70,6 +70,41 @@ class HttpUtil {
|
||||||
}
|
}
|
||||||
return msg;
|
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 {
|
class PromiseUtil {
|
||||||
|
|
3
web/assets/moment/moment.min.js
vendored
|
@ -1,10 +1,10 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
@ -33,9 +33,13 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/clientIps/:email", a.getClientIps)
|
g.POST("/clientIps/:email", a.getClientIps)
|
||||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
g.POST("/clearClientIps/:email", a.clearClientIps)
|
||||||
g.POST("/addClient", a.addInboundClient)
|
g.POST("/addClient", a.addInboundClient)
|
||||||
|
g.POST("/addGroupClient", a.addGroupInboundClient)
|
||||||
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
||||||
|
g.POST("/delGroupClients", a.delGroupClients)
|
||||||
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
||||||
|
g.POST("/updateClients", a.updateGroupInboundClient)
|
||||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||||
|
g.POST("/resetGroupClientTraffic", a.resetGroupClientTraffic)
|
||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
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) {
|
func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
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) {
|
func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||||
clientId := c.Param("clientId")
|
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) {
|
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
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) {
|
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
||||||
err := a.inboundService.ResetAllTraffics()
|
err := a.inboundService.ResetAllTraffics()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,8 +50,8 @@ func (a *IndexController) index(c *gin.Context) {
|
||||||
|
|
||||||
func (a *IndexController) login(c *gin.Context) {
|
func (a *IndexController) login(c *gin.Context) {
|
||||||
var form LoginForm
|
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"))
|
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -68,29 +69,31 @@ func (a *IndexController) login(c *gin.Context) {
|
||||||
safeUser := template.HTMLEscapeString(form.Username)
|
safeUser := template.HTMLEscapeString(form.Username)
|
||||||
safePass := template.HTMLEscapeString(form.Password)
|
safePass := template.HTMLEscapeString(form.Password)
|
||||||
safeSecret := template.HTMLEscapeString(form.LoginSecret)
|
safeSecret := template.HTMLEscapeString(form.LoginSecret)
|
||||||
|
|
||||||
if user == nil {
|
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)
|
a.tgbot.UserLoginNotify(safeUser, safePass, getRemoteIp(c), timeStr, 0)
|
||||||
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
|
|
||||||
logger.Infof("%s logged in successfully, Ip Address: %s\n", safeUser, getRemoteIp(c))
|
logger.Infof("%s logged in successfully, Ip Address: %s\n", safeUser, getRemoteIp(c))
|
||||||
a.tgbot.UserLoginNotify(safeUser, ``, getRemoteIp(c), timeStr, 1)
|
a.tgbot.UserLoginNotify(safeUser, ``, getRemoteIp(c), timeStr, 1)
|
||||||
}
|
|
||||||
|
|
||||||
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Unable to get session's max age from DB")
|
logger.Warning("Unable to get session's max age from DB")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = session.SetMaxAge(c, sessionMaxAge*60)
|
session.SetMaxAge(c, sessionMaxAge*60)
|
||||||
if err != nil {
|
session.SetLoginUser(c, user)
|
||||||
logger.Warning("Unable to set session's max age")
|
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", safeUser)
|
||||||
logger.Infof("%s logged in successfully", user.Username)
|
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), nil)
|
||||||
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *IndexController) logout(c *gin.Context) {
|
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)
|
logger.Infof("%s logged out successfully", user.Username)
|
||||||
}
|
}
|
||||||
session.ClearSession(c)
|
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"))
|
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ type AllSetting struct {
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
||||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||||
|
SubSyncEnable bool `json:"subSyncEnable" form:"subSyncEnable"`
|
||||||
SubListen string `json:"subListen" form:"subListen"`
|
SubListen string `json:"subListen" form:"subListen"`
|
||||||
SubPort int `json:"subPort" form:"subPort"`
|
SubPort int `json:"subPort" form:"subPort"`
|
||||||
SubPath string `json:"subPath" form:"subPath"`
|
SubPath string `json:"subPath" form:"subPath"`
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
</tr-qr-bg>
|
</tr-qr-bg>
|
||||||
</tr-qr-box>
|
</tr-qr-box>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="!isJustSub">
|
||||||
<template v-for="(row, index) in qrModal.qrcodes">
|
<template v-for="(row, index) in qrModal.qrcodes">
|
||||||
<tr-qr-box class="qr-box">
|
<tr-qr-box class="qr-box">
|
||||||
<a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
|
<a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
</tr-qr-bg>
|
</tr-qr-bg>
|
||||||
</tr-qr-box>
|
</tr-qr-box>
|
||||||
</template>
|
</template>
|
||||||
|
</template>
|
||||||
</tr-qr-modal>
|
</tr-qr-modal>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
|
@ -43,12 +45,14 @@
|
||||||
qrcodes: [],
|
qrcodes: [],
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
visible: false,
|
visible: false,
|
||||||
|
isJustSub: false,
|
||||||
subId: '',
|
subId: '',
|
||||||
show: function(title = '', dbInbound, client) {
|
show: function(title = '', dbInbound, client, isJustSub = false) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.dbInbound = dbInbound;
|
this.dbInbound = dbInbound;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
this.isJustSub = isJustSub;
|
||||||
this.subId = '';
|
this.subId = '';
|
||||||
this.qrcodes = [];
|
this.qrcodes = [];
|
||||||
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
||||||
|
@ -76,7 +80,9 @@
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#qrcode-modal',
|
el: '#qrcode-modal',
|
||||||
data: {
|
data: {
|
||||||
qrModal: qrModal,
|
qrModal: qrModal,get isJustSub(){
|
||||||
|
return qrModal.isJustSub
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyToClipboard(elementId, content) {
|
copyToClipboard(elementId, content) {
|
||||||
|
|
|
@ -14,10 +14,10 @@
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
|
<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>
|
||||||
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
|
<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>
|
||||||
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
|
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
|
||||||
<a-input v-model.trim="clientsBulkModal.emailPrefix"></a-input>
|
<a-input v-model.trim="clientsBulkModal.emailPrefix"></a-input>
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
<a-input v-model.trim="clientsBulkModal.emailPostfix"></a-input>
|
<a-input v-model.trim="clientsBulkModal.emailPostfix"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
|
<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>
|
||||||
<a-form-item label='{{ i18n "security" }}' v-if="inbound.protocol === Protocols.VMESS">
|
<a-form-item label='{{ i18n "security" }}' v-if="inbound.protocol === Protocols.VMESS">
|
||||||
<a-select v-model="clientsBulkModal.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="clientsBulkModal.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
@ -39,12 +39,6 @@
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.xtls">
|
|
||||||
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="app.subSettings.enable">
|
<a-form-item v-if="app.subSettings.enable">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
@ -67,7 +61,7 @@
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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>
|
||||||
<a-form-item v-if="app.ipLimitEnable">
|
<a-form-item v-if="app.ipLimitEnable">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
|
@ -79,7 +73,7 @@
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
|
@ -91,7 +85,7 @@
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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>
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||||
|
@ -181,9 +175,6 @@
|
||||||
if (clientsBulkModal.inbound.canEnableTlsFlow()) {
|
if (clientsBulkModal.inbound.canEnableTlsFlow()) {
|
||||||
newClient.flow = clientsBulkModal.flow;
|
newClient.flow = clientsBulkModal.flow;
|
||||||
}
|
}
|
||||||
if (clientsBulkModal.inbound.xtls) {
|
|
||||||
newClient.flow = clientsBulkModal.flow;
|
|
||||||
}
|
|
||||||
newClient.reset = clientsBulkModal.reset;
|
newClient.reset = clientsBulkModal.reset;
|
||||||
clients.push(newClient);
|
clients.push(newClient);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,16 @@
|
||||||
title: '',
|
title: '',
|
||||||
okText: '',
|
okText: '',
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
|
group: {
|
||||||
|
canGroup: true,
|
||||||
|
isGroup: false,
|
||||||
|
currentClient: null,
|
||||||
|
inbounds: [],
|
||||||
|
clients: [],
|
||||||
|
editIds: []
|
||||||
|
},
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
|
dbInbounds: null,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
clients: [],
|
clients: [],
|
||||||
clientStats: [],
|
clientStats: [],
|
||||||
|
@ -25,33 +34,126 @@
|
||||||
clientIps: null,
|
clientIps: null,
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
|
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 {
|
||||||
if (clientModal.isEdit) {
|
if (clientModal.isEdit) {
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
||||||
} else {
|
} else {
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, 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.visible = true;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
if (app.subSettings.enable && dbInbounds !== null && Array.isArray(dbInbounds)) {
|
||||||
this.inbound = dbInbound.toInbound();
|
|
||||||
this.clients = this.inbound.clients;
|
|
||||||
this.index = index === null ? this.clients.length : index;
|
|
||||||
this.delayedStart = false;
|
|
||||||
if (isEdit) {
|
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) {
|
if (this.clients[index].expiryTime < 0) {
|
||||||
this.delayedStart = true;
|
this.delayedStart = true;
|
||||||
}
|
}
|
||||||
this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]);
|
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]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.showProcess(dbInbound, index);
|
||||||
|
if (isEdit) {
|
||||||
|
this.singleEditClientProcess(index)
|
||||||
} else {
|
} else {
|
||||||
this.addClient(this.inbound.protocol, this.clients);
|
this.addClient(this.inbound.protocol, this.clients);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
|
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
|
||||||
this.confirm = confirm;
|
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) {
|
getClientId(protocol, client) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case Protocols.TROJAN: return client.password;
|
case Protocols.TROJAN: return client.password;
|
||||||
|
@ -94,6 +196,18 @@
|
||||||
get isEdit() {
|
get isEdit() {
|
||||||
return this.clientModal.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() {
|
get datepicker() {
|
||||||
return app.datepicker;
|
return app.datepicker;
|
||||||
},
|
},
|
||||||
|
@ -120,6 +234,36 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
async getDBClientIps(email) {
|
||||||
const msg = await HttpUtil.post(`/panel/inbound/clientIps/${email}`);
|
const msg = await HttpUtil.post(`/panel/inbound/clientIps/${email}`);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
|
@ -147,7 +291,22 @@
|
||||||
} catch (error) {
|
} 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({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
|
@ -156,8 +315,8 @@
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
iconElement.disabled = true;
|
iconElement.disabled = true;
|
||||||
const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
|
const msg = await this.resetClientTrafficHandler(client, dbInboundId, clients);
|
||||||
if (msg.success) {
|
if (msg && msg.success) {
|
||||||
this.clientModal.clientStats.up = 0;
|
this.clientModal.clientStats.up = 0;
|
||||||
this.clientModal.clientStats.down = 0;
|
this.clientModal.clientStats.down = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
|
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.fakedns.poolSize" }}'>
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
|
@ -3,6 +3,18 @@
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
||||||
<a-switch v-model="client.enable"></a-switch>
|
<a-switch v-model="client.enable"></a-switch>
|
||||||
</a-form-item>
|
</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>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
@ -66,7 +78,10 @@
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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>
|
||||||
<a-form-item v-if="app.ipLimitEnable">
|
<a-form-item v-if="app.ipLimitEnable">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
|
@ -78,7 +93,7 @@
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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>
|
||||||
<a-form-item v-if="app.ipLimitEnable && client.limitIp > 0 && client.email && isEdit">
|
<a-form-item v-if="app.ipLimitEnable && client.limitIp > 0 && client.email && isEdit">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
|
@ -104,12 +119,6 @@
|
||||||
</a-textarea>
|
</a-textarea>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.stream.isXtls" label='Flow'>
|
|
||||||
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="inbound.canEnableTlsFlow()" label='Flow'>
|
<a-form-item v-if="inbound.canEnableTlsFlow()" label='Flow'>
|
||||||
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
|
@ -126,7 +135,7 @@
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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>
|
||||||
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
|
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
|
||||||
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
|
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
|
||||||
|
@ -137,7 +146,7 @@
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
<a-icon type="retweet"
|
<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>
|
v-if="client.email.length > 0"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
|
|
|
@ -267,13 +267,12 @@
|
||||||
<template v-if="outbound.canEnableStream()">
|
<template v-if="outbound.canEnableStream()">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="outbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="outbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP (RAW)</a-select-option>
|
||||||
<a-select-option value="kcp">mKCP</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="ws">WebSocket</a-select-option>
|
||||||
<a-select-option value="http">H2</a-select-option>
|
|
||||||
<a-select-option value="grpc">gRPC</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="httpupgrade">HTTPUpgrade</a-select-option>
|
||||||
<a-select-option value="splithttp">SplitHTTP</a-select-option>
|
<a-select-option value="xhttp">XHTTP</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="outbound.stream.network === 'tcp'">
|
<template v-if="outbound.stream.network === 'tcp'">
|
||||||
|
@ -337,15 +336,8 @@
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
<a-form-item label='Heartbeat Period'>
|
||||||
|
<a-input-number v-model.number="outbound.stream.ws.heartbeatPeriod" :min="0"></a-input-number>
|
||||||
<!-- 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>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -372,13 +364,42 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- splithttp -->
|
<!-- xhttp -->
|
||||||
<template v-if="outbound.stream.network === 'splithttp'">
|
<template v-if="outbound.stream.network === 'xhttp'">
|
||||||
<a-form-item label='{{ i18n "host" }}'>
|
<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>
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<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.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>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
@ -448,13 +469,13 @@
|
||||||
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
|
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Keep Alive Interval">
|
<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>
|
||||||
<a-form-item label="Multipath TCP">
|
<a-form-item label="Multipath TCP">
|
||||||
<a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
|
<a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TCP No-Delay" v-if="outbound.stream.sockopt.tcpMptcp">
|
<a-form-item label="Penetrate">
|
||||||
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch>
|
<a-switch v-model="outbound.stream.sockopt.penetrate"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -465,10 +486,10 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="outbound.mux.enabled">
|
<template v-if="outbound.mux.enabled">
|
||||||
<a-form-item label="Concurrency">
|
<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>
|
||||||
<a-form-item label="xudp Concurrency">
|
<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>
|
||||||
<a-form-item label="xudp UDP 443">
|
<a-form-item label="xudp UDP 443">
|
||||||
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
|
|
@ -43,5 +43,8 @@
|
||||||
<a-select-option value="udp">UDP</a-select-option>
|
<a-select-option value="udp">UDP</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='ivCheck'>
|
||||||
|
<a-switch v-model="inbound.settings.ivCheck"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</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 :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
|
<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-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='xVer'>
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider style="margin:5px 0;"></a-divider>
|
<a-divider style="margin:5px 0;"></a-divider>
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</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 :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
|
<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-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='xVer'>
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider style="margin:5px 0;"></a-divider>
|
<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-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<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>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers">
|
<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="tcp">TCP (RAW)</a-select-option>
|
||||||
<a-select-option value="kcp">mKCP</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="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="grpc">gRPC</a-select-option>
|
||||||
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
||||||
<a-select-option value="splithttp">SplitHTTP</a-select-option>
|
<a-select-option value="xhttp">XHTTP</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
@ -30,11 +29,6 @@
|
||||||
{{template "form/streamWS"}}
|
{{template "form/streamWS"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- http -->
|
|
||||||
<template v-if="inbound.stream.network === 'http'">
|
|
||||||
{{template "form/streamHTTP"}}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- grpc -->
|
<!-- grpc -->
|
||||||
<template v-if="inbound.stream.network === 'grpc'">
|
<template v-if="inbound.stream.network === 'grpc'">
|
||||||
{{template "form/streamGRPC"}}
|
{{template "form/streamGRPC"}}
|
||||||
|
@ -45,9 +39,9 @@
|
||||||
{{template "form/streamHTTPUpgrade"}}
|
{{template "form/streamHTTPUpgrade"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- splithttp -->
|
<!-- xhttp -->
|
||||||
<template v-if="inbound.stream.network === 'splithttp'">
|
<template v-if="inbound.stream.network === 'xhttp'">
|
||||||
{{template "form/streamSplitHTTP"}}
|
{{template "form/streamXHTTP"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- sockopt -->
|
<!-- sockopt -->
|
||||||
|
|
|
@ -6,22 +6,22 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.stream.sockoptSwitch">
|
<template v-if="inbound.stream.sockoptSwitch">
|
||||||
<a-form-item label="Route Mark">
|
<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>
|
||||||
<a-form-item label="TCP Keep Alive Interval">
|
<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>
|
||||||
<a-form-item label="TCP Keep Alive Idle">
|
<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>
|
||||||
<a-form-item label="TCP Max Seg">
|
<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>
|
||||||
<a-form-item label="TCP User Timeout">
|
<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>
|
||||||
<a-form-item label="TCP Window Clamp">
|
<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>
|
||||||
<a-form-item label="Proxy Protocol">
|
<a-form-item label="Proxy Protocol">
|
||||||
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||||
|
@ -32,8 +32,8 @@
|
||||||
<a-form-item label="Multipath TCP">
|
<a-form-item label="Multipath TCP">
|
||||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
|
<a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TCP No-Delay" v-if="inbound.stream.sockopt.tcpMptcp">
|
<a-form-item label="Penetrate">
|
||||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpNoDelay"></a-switch>
|
<a-switch v-model.trim="inbound.stream.sockopt.penetrate"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="V6 Only">
|
<a-form-item label="V6 Only">
|
||||||
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
|
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
|
||||||
|
|
|
@ -1,53 +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="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-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</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-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>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
<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}}
|
|
@ -5,18 +5,7 @@
|
||||||
<a-form-item label='{{ i18n "security" }}'>
|
<a-form-item label='{{ i18n "security" }}'>
|
||||||
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
||||||
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||||
<a-tooltip>
|
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-radio-button v-if="inbound.canEnableXtls()" value="xtls">XTLS</a-radio-button>
|
|
||||||
</a-tooltip>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.realityDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-radio-button v-if="inbound.canEnableReality()" value="reality">REALITY</a-radio-button>
|
|
||||||
</a-tooltip>
|
|
||||||
<a-radio-button value="tls">TLS</a-radio-button>
|
<a-radio-button value="tls">TLS</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -68,6 +57,9 @@
|
||||||
<a-form-item label="Session Resumption">
|
<a-form-item label="Session Resumption">
|
||||||
<a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
|
<a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
|
||||||
</a-form-item>
|
</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">
|
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||||
|
@ -116,11 +108,6 @@
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- xtls settings -->
|
|
||||||
<template v-else-if="inbound.stream.isXtls">
|
|
||||||
{{template "form/xtlsSettings"}}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- reality settings -->
|
<!-- reality settings -->
|
||||||
<template v-if="inbound.stream.isReality">
|
<template v-if="inbound.stream.isReality">
|
||||||
{{template "form/realitySettings"}}
|
{{template "form/realitySettings"}}
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
{{define "form/xtlsSettings"}}
|
|
||||||
<template>
|
|
||||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
|
||||||
<a-input v-model.trim="inbound.stream.xtls.sni"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="ALPN">
|
|
||||||
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" v-model="inbound.stream.xtls.alpn">
|
|
||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Allow Insecure">
|
|
||||||
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<template v-for="cert,index in inbound.stream.xtls.certs">
|
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
|
||||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
<a-button icon="plus" v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()"
|
|
||||||
style="margin-left: 10px"></a-button>
|
|
||||||
<a-button icon="minus" v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small"
|
|
||||||
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px"></a-button>
|
|
||||||
</a-form-item>
|
|
||||||
<template v-if="cert.useFile">
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
|
||||||
<a-input v-model.trim="cert.certFile"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
|
||||||
<a-input v-model.trim="cert.keyFile"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label=" ">
|
|
||||||
<a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">
|
|
||||||
{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
<a-form-item label='OCSP stapling'>
|
|
||||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="One Time Loading">
|
|
||||||
<a-switch v-model="cert.oneTimeLoading"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='Usage Option'>
|
|
||||||
<a-select v-model="cert.usage" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option v-for="key in USAGE_OPTION" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
{{end}}
|
|
|
@ -12,7 +12,7 @@
|
||||||
<template slot="title">{{ i18n "info" }}</template>
|
<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-icon style="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip v-if="hasClientStats(record, client.email)">
|
||||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
<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-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>
|
<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>
|
<a-tag color="green">[[ inbound.network ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td>{{ i18n "host" }}</td>
|
<td>{{ i18n "host" }}</td>
|
||||||
<td v-if="inbound.host">
|
<td v-if="inbound.host">
|
||||||
|
@ -58,6 +58,14 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="inbound.isXHTTP">
|
||||||
|
<tr>
|
||||||
|
<td>Mode</td>
|
||||||
|
<td>
|
||||||
|
<a-tag>[[ inbound.stream.xhttp.mode ]]</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
<template v-if="inbound.isKcp">
|
<template v-if="inbound.isKcp">
|
||||||
<tr>
|
<tr>
|
||||||
<td>kcp {{ i18n "encryption" }}</td>
|
<td>kcp {{ i18n "encryption" }}</td>
|
||||||
|
@ -154,15 +162,6 @@
|
||||||
<a-tag color="orange">{{ i18n "none" }}</a-tag>
|
<a-tag color="orange">{{ i18n "none" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="infoModal.inbound.xtls">
|
|
||||||
<td>Flow</td>
|
|
||||||
<td v-if="infoModal.clientSettings.flow">
|
|
||||||
<a-tag>[[ infoModal.clientSettings.flow ]]</a-tag>
|
|
||||||
</td>
|
|
||||||
<td v-else>
|
|
||||||
<a-tag color="orange">{{ i18n "none" }}</a-tag>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="infoModal.clientSettings.password">
|
<tr v-if="infoModal.clientSettings.password">
|
||||||
<td>{{ i18n "password" }}</td>
|
<td>{{ i18n "password" }}</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -186,6 +185,33 @@
|
||||||
<a-tag>↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
<a-tag>↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
||||||
<table style="display: inline-table; margin-block: 10px; width: 100%; text-align: center;">
|
<table style="display: inline-table; margin-block: 10px; width: 100%; text-align: center;">
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -401,7 +427,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<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">
|
<tr-info-title class="tr-info-title">
|
||||||
<a-tag color="blue">Config</a-tag>
|
<a-tag color="blue">Config</a-tag>
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
@ -418,6 +444,18 @@
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<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 = {
|
const infoModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
|
@ -432,6 +470,7 @@
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
subLink: '',
|
subLink: '',
|
||||||
subJsonLink: '',
|
subJsonLink: '',
|
||||||
|
clientIps: '',
|
||||||
show(dbInbound, index) {
|
show(dbInbound, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
|
@ -439,6 +478,12 @@
|
||||||
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
||||||
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index) : this.dbInbound.isExpiry;
|
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) : [];
|
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) {
|
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
||||||
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
|
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
|
||||||
} else {
|
} else {
|
||||||
|
@ -467,6 +512,7 @@
|
||||||
el: '#inbound-info-modal',
|
el: '#inbound-info-modal',
|
||||||
data: {
|
data: {
|
||||||
infoModal,
|
infoModal,
|
||||||
|
refreshing: false,
|
||||||
get dbInbound() {
|
get dbInbound() {
|
||||||
return this.infoModal.dbInbound;
|
return this.infoModal.dbInbound;
|
||||||
},
|
},
|
||||||
|
@ -503,6 +549,26 @@
|
||||||
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down;
|
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down;
|
||||||
return remained > 0 ? sizeFormat(remained) : '-';
|
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>
|
</script>
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
okText: '{{ i18n "sure" }}',
|
okText: '{{ i18n "sure" }}',
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
|
isGroup: false,
|
||||||
confirm: null,
|
confirm: null,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
|
@ -61,6 +62,9 @@
|
||||||
get isEdit() {
|
get isEdit() {
|
||||||
return inModal.isEdit;
|
return inModal.isEdit;
|
||||||
},
|
},
|
||||||
|
get isGroup() {
|
||||||
|
return inModal.isGroup;
|
||||||
|
},
|
||||||
get client() {
|
get client() {
|
||||||
return inModal.inbound.clients[0];
|
return inModal.inbound.clients[0];
|
||||||
},
|
},
|
||||||
|
@ -102,11 +106,6 @@
|
||||||
client.flow = "";
|
client.flow = "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if ((this.inModal.inbound.protocol == Protocols.VLESS || this.inModal.inbound.protocol == Protocols.TROJAN) && !inModal.inbound.xtls) {
|
|
||||||
this.inModal.inbound.settings.vlesses.forEach(client => {
|
|
||||||
client.flow = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
SSMethodChange() {
|
SSMethodChange() {
|
||||||
if (this.inModal.inbound.isSSMultiUser) {
|
if (this.inModal.inbound.isSSMultiUser) {
|
||||||
|
@ -132,10 +131,6 @@
|
||||||
inModal.inbound.stream.tls.certs[index].certFile = app.defaultCert;
|
inModal.inbound.stream.tls.certs[index].certFile = app.defaultCert;
|
||||||
inModal.inbound.stream.tls.certs[index].keyFile = app.defaultKey;
|
inModal.inbound.stream.tls.certs[index].keyFile = app.defaultKey;
|
||||||
},
|
},
|
||||||
setDefaultCertXtls(index) {
|
|
||||||
inModal.inbound.stream.xtls.certs[index].certFile = app.defaultCert;
|
|
||||||
inModal.inbound.stream.xtls.certs[index].keyFile = app.defaultKey;
|
|
||||||
},
|
|
||||||
async getNewX25519Cert() {
|
async getNewX25519Cert() {
|
||||||
inModal.loading(true);
|
inModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper {
|
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper {
|
||||||
margin:-10px 22px -10px !important;
|
margin:-10px 22px !important;
|
||||||
}
|
}
|
||||||
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper .ant-table {
|
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper .ant-table {
|
||||||
border-bottom-left-radius: 1rem;
|
border-bottom-left-radius: 1rem;
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
}
|
}
|
||||||
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper {
|
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper {
|
||||||
margin:-10px 2px -10px !important;
|
margin:-10px 2px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
|
@ -224,6 +224,10 @@
|
||||||
<a-icon type="rest"></a-icon>
|
<a-icon type="rest"></a-icon>
|
||||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
</a-menu-item>
|
</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-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
@ -338,7 +342,6 @@
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="blue">XTLS</a-tag>
|
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
@ -549,7 +552,7 @@
|
||||||
<script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
{{template "component/persianDatepicker" .}}
|
{{template "component/persianDatepicker" .}}
|
||||||
|
@ -860,6 +863,9 @@
|
||||||
case "delDepletedClients":
|
case "delDepletedClients":
|
||||||
this.delDepletedClients(-1)
|
this.delDepletedClients(-1)
|
||||||
break;
|
break;
|
||||||
|
case "addGroupClient":
|
||||||
|
this.openGroupAddClient()
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clickAction(action, dbInbound) {
|
clickAction(action, dbInbound) {
|
||||||
|
@ -1005,6 +1011,21 @@
|
||||||
|
|
||||||
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
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) {
|
openAddClient(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
clientModal.show({
|
clientModal.show({
|
||||||
|
@ -1012,7 +1033,11 @@
|
||||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (clients, dbInboundId) => {
|
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
|
isEdit: false
|
||||||
});
|
});
|
||||||
|
@ -1035,6 +1060,7 @@
|
||||||
clientModal.show({
|
clientModal.show({
|
||||||
title: '{{ i18n "pages.client.edit"}}',
|
title: '{{ i18n "pages.client.edit"}}',
|
||||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||||
|
dbInbounds: this.dbInbounds,
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
index: index,
|
index: index,
|
||||||
confirm: async (client, dbInboundId, clientId) => {
|
confirm: async (client, dbInboundId, clientId) => {
|
||||||
|
@ -1060,12 +1086,36 @@
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/addClient`, data, modal);
|
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) {
|
async updateClient(client, dbInboundId, clientId) {
|
||||||
|
if (Array.isArray(client) && Array.isArray(dbInboundId) && Array.isArray(clientId)){
|
||||||
|
const data = []
|
||||||
|
client.forEach((client, index) => {
|
||||||
|
data.push({
|
||||||
|
clientId: clientId[index],
|
||||||
|
inbound: {
|
||||||
|
id: dbInboundId[index],
|
||||||
|
settings: '{"clients": [' + client.toString() + ']}',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await this.submit(`/panel/inbound/updateClients`, data, clientModal, true);
|
||||||
|
}else{
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + client.toString() + ']}',
|
settings: '{"clients": [' + client.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
|
await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resetTraffic(dbInboundId) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
@ -1093,21 +1143,99 @@
|
||||||
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delClient(dbInboundId, client,confirmation = true) {
|
async delClientHandler(dbInboundId, currentClient, clients = []) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
if (clients.length > 0) {
|
||||||
clientId = this.getClientId(dbInbound.protocol, client);
|
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){
|
if (confirmation){
|
||||||
|
const clientEmails = clients.length > 0 ? clients.map(item => item.email) : currentClient.email
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
|
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + clientEmails,
|
||||||
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
|
onOk: () => this.delClientHandler(dbInboundId, currentClient, clients),
|
||||||
});
|
});
|
||||||
} else {
|
} 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) {
|
getClientId(protocol, client) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
|
@ -1136,10 +1264,10 @@
|
||||||
}
|
}
|
||||||
return newDbInbound;
|
return newDbInbound;
|
||||||
},
|
},
|
||||||
showQrcode(dbInboundId, client) {
|
showQrcode(dbInboundId, client, isJustSub = false) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client);
|
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client, isJustSub);
|
||||||
},
|
},
|
||||||
showInfo(dbInboundId, client) {
|
showInfo(dbInboundId, client) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
@ -1159,6 +1287,13 @@
|
||||||
},
|
},
|
||||||
async switchEnableClient(dbInboundId, client) {
|
async switchEnableClient(dbInboundId, client) {
|
||||||
this.loading()
|
this.loading()
|
||||||
|
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);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
inbound = dbInbound.toInbound();
|
inbound = dbInbound.toInbound();
|
||||||
clients = inbound.clients;
|
clients = inbound.clients;
|
||||||
|
@ -1166,29 +1301,47 @@
|
||||||
clients[index].enable = !clients[index].enable;
|
clients[index].enable = !clients[index].enable;
|
||||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||||
|
}
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
async submit(url, data, modal) {
|
async submit(url, data, model, isJson = false) {
|
||||||
const msg = await HttpUtil.postWithModal(url, data, modal);
|
const msg = isJson ? await HttpUtil.postWithModalJson(url, data, model) : await HttpUtil.postWithModal(url, data, model);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
await this.getDBInbounds();
|
await this.getDBInbounds();
|
||||||
}
|
}
|
||||||
|
return msg
|
||||||
},
|
},
|
||||||
getInboundClients(dbInbound) {
|
getInboundClients(dbInbound) {
|
||||||
return dbInbound.toInbound().clients;
|
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) {
|
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){
|
if (confirmation){
|
||||||
|
const clientEmails = clients.length > 0 ? clients.map(item => item.email) : client.email
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + client.email,
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + clientEmails,
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
onOk: () => this.resetClientTrafficHandler(client, dbInboundId, clients),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
|
this.resetClientTrafficHandler(client, dbInboundId, clients);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetAllTraffic() {
|
resetAllTraffic() {
|
||||||
|
@ -1224,6 +1377,10 @@
|
||||||
isExpiry(dbInbound, index) {
|
isExpiry(dbInbound, index) {
|
||||||
return dbInbound.toInbound().isExpiry(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) {
|
getUpStats(dbInbound, email) {
|
||||||
if (email.length == 0) return 0;
|
if (email.length == 0) return 0;
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
@ -1311,7 +1468,7 @@
|
||||||
if (clients != null){
|
if (clients != null){
|
||||||
clients.forEach(c => {
|
clients.forEach(c => {
|
||||||
if (c.subId && c.subId.length>0){
|
if (c.subId && c.subId.length>0){
|
||||||
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
|
subLinks.push(this.subSettings.subURI + c.subId)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1338,7 +1495,7 @@
|
||||||
if (clients != null){
|
if (clients != null){
|
||||||
clients.forEach(c => {
|
clients.forEach(c => {
|
||||||
if (c.subId && c.subId.length>0){
|
if (c.subId && c.subId.length>0){
|
||||||
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
|
subLinks.push(this.subSettings.subURI + c.subId)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: rgb(255 145 0 / 15%);
|
background: rgb(255 145 0 / 15%);
|
||||||
margin: 1.5rem 2.5rem 0rem 2.5rem;
|
margin: 1.5rem 2.5rem 0rem;
|
||||||
border-radius: .5rem;
|
border-radius: .5rem;
|
||||||
transition: all 0.5s;
|
transition: all 0.5s;
|
||||||
animation: signal 3s cubic-bezier(0.18, 0.89, 0.32, 1.28) infinite;
|
animation: signal 3s cubic-bezier(0.18, 0.89, 0.32, 1.28) infinite;
|
||||||
|
@ -268,6 +268,7 @@
|
||||||
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}'>
|
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}'>
|
||||||
<a-list item-layout="horizontal">
|
<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.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.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="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>
|
||||||
|
@ -381,14 +382,40 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-collapse v-if="enableDirect" style="margin-top: 14px;">
|
<a-collapse v-if="enableDirect" style="margin-top: 14px;">
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.directips"}}'>
|
<a-collapse-panel header='{{ i18n "pages.settings.direct"}}'>
|
||||||
<a-list-item style="padding: 10px 20px">
|
<a-list-item>
|
||||||
<a-checkbox-group v-model="directIPs" :options="IPsOptions"></a-checkbox-group>
|
<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-list-item>
|
||||||
</a-collapse-panel>
|
<a-list-item>
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.directdomains"}}'>
|
<a-row style="padding: 0 20px">
|
||||||
<a-list-item style="padding: 10px 20px">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-checkbox-group v-model="directDomains" :options="DomainsOptions"></a-checkbox-group>
|
<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-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
|
@ -439,7 +466,7 @@
|
||||||
sockopt: {
|
sockopt: {
|
||||||
tcpKeepAliveIdle: 100,
|
tcpKeepAliveIdle: 100,
|
||||||
tcpMptcp: true,
|
tcpMptcp: true,
|
||||||
tcpNoDelay: true
|
penetrate: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -476,25 +503,26 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
IPsOptions: [
|
directIPsOptions: [
|
||||||
{ label: 'Private IP', value: 'private' },
|
{ label: 'Private IP', value: 'geoip:private' },
|
||||||
{ label: '🇮🇷 Iran', value: 'ir' },
|
{ label: '🇮🇷 Iran', value: 'geoip:ir' },
|
||||||
{ label: '🇨🇳 China', value: 'cn' },
|
{ label: '🇨🇳 China', value: 'geoip:cn' },
|
||||||
{ label: '🇷🇺 Russia', value: 'ru' },
|
{ label: '🇷🇺 Russia', value: 'geoip:ru' },
|
||||||
{ label: '🇻🇳 Vietnam', value: 'vn' },
|
{ label: '🇻🇳 Vietnam', value: 'geoip:vn' },
|
||||||
{ label: '🇪🇸 Spain', value: 'es' },
|
{ label: '🇪🇸 Spain', value: 'geoip:es' },
|
||||||
{ label: '🇮🇩 Indonesia', value: 'id' },
|
{ label: '🇮🇩 Indonesia', value: 'geoip:id' },
|
||||||
{ label: '🇺🇦 Ukraine', value: 'ua' },
|
{ label: '🇺🇦 Ukraine', value: 'geoip:ua' },
|
||||||
{ label: '🇹🇷 Türkiye', value: 'tr' },
|
{ label: '🇹🇷 Türkiye', value: 'geoip:tr' },
|
||||||
{ label: '🇧🇷 Brazil', value: 'br' },
|
{ label: '🇧🇷 Brazil', value: 'geoip:br' },
|
||||||
],
|
],
|
||||||
DomainsOptions: [
|
diretDomainsOptions: [
|
||||||
{ label: '🇮🇷 Iran', value: 'ir' },
|
{ label: 'Private DNS', value: 'geosite:private' },
|
||||||
{ label: '🇨🇳 China', value: 'cn' },
|
{ label: '🇮🇷 Iran', value: 'geosite:category-ir' },
|
||||||
{ label: '🇷🇺 Russia', value: 'ru' },
|
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
||||||
{ label: 'Apple', value: 'apple' },
|
{ label: '🇷🇺 Russia', value: 'geosite:category-ru' },
|
||||||
{ label: 'Meta', value: 'meta' },
|
{ label: 'Apple', value: 'geosite:apple' },
|
||||||
{ label: 'Google', value: 'google' },
|
{ label: 'Meta', value: 'geosite:meta' },
|
||||||
|
{ label: 'Google', value: 'geosite:google' },
|
||||||
],
|
],
|
||||||
get remarkModel() {
|
get remarkModel() {
|
||||||
rm = this.allSetting.remarkModel;
|
rm = this.allSetting.remarkModel;
|
||||||
|
@ -752,7 +780,7 @@
|
||||||
const rules = JSON.parse(this.allSetting.subJsonRules);
|
const rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
if (!Array.isArray(rules)) return [];
|
if (!Array.isArray(rules)) return [];
|
||||||
const ipRule = rules.find(r => r.ip);
|
const ipRule = rules.find(r => r.ip);
|
||||||
return ipRule?.ip.map(d => d.replace("geoip:", "")) ?? [];
|
return ipRule?.ip ?? [];
|
||||||
},
|
},
|
||||||
set: function (v) {
|
set: function (v) {
|
||||||
let rules = JSON.parse(this.allSetting.subJsonRules);
|
let rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
|
@ -766,7 +794,7 @@
|
||||||
|
|
||||||
rules[ruleIndex].ip = [];
|
rules[ruleIndex].ip = [];
|
||||||
v.forEach(d => {
|
v.forEach(d => {
|
||||||
rules[ruleIndex].ip.push("geoip:" + d);
|
rules[ruleIndex].ip.push(d);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.allSetting.subJsonRules = JSON.stringify(rules);
|
this.allSetting.subJsonRules = JSON.stringify(rules);
|
||||||
|
@ -778,34 +806,18 @@
|
||||||
const rules = JSON.parse(this.allSetting.subJsonRules);
|
const rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
if (!Array.isArray(rules)) return [];
|
if (!Array.isArray(rules)) return [];
|
||||||
const domainRule = rules.find(r => r.domain);
|
const domainRule = rules.find(r => r.domain);
|
||||||
return domainRule?.domain.map(d => {
|
return domainRule?.domain ?? [];
|
||||||
if (d.startsWith("geosite:category-")) {
|
|
||||||
return d.replace("geosite:category-", "");
|
|
||||||
}
|
|
||||||
return d.replace("geosite:", "");
|
|
||||||
})
|
|
||||||
?? [];
|
|
||||||
},
|
},
|
||||||
set: function (v) {
|
set: function (v) {
|
||||||
let rules = JSON.parse(this.allSetting.subJsonRules);
|
let rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
if (!Array.isArray(rules)) return;
|
if (!Array.isArray(rules)) return;
|
||||||
|
|
||||||
if (v.length == 0) {
|
if (v.length == 0) {
|
||||||
rules = rules.filter(r => !r.domain);
|
rules = rules.filter(r => !r.domain);
|
||||||
} else {
|
} else {
|
||||||
let ruleIndex = rules.findIndex(r => r.domain);
|
let ruleIndex = rules.findIndex(r => r.domain);
|
||||||
if (ruleIndex == -1) ruleIndex = rules.push(this.defaultRules[0]) - 1;
|
if (ruleIndex == -1) ruleIndex = rules.push(this.defaultRules[0]) - 1;
|
||||||
|
|
||||||
rules[ruleIndex].domain = [];
|
rules[ruleIndex].domain = v;
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
this.allSetting.subJsonRules = JSON.stringify(rules);
|
this.allSetting.subJsonRules = JSON.stringify(rules);
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
.ant-collapse-content-box>li {
|
.ant-collapse-content-box>li {
|
||||||
padding: 12px 0 0 0 !important;
|
padding: 12px 0 0 !important;
|
||||||
}
|
}
|
||||||
.ant-list-item>li {
|
.ant-list-item>li {
|
||||||
padding: 10px 20px !important;
|
padding: 10px 20px !important;
|
||||||
|
@ -143,6 +143,7 @@
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</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-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.logConfigs" }}'>
|
<a-collapse-panel header='{{ i18n "pages.xray.logConfigs" }}'>
|
||||||
|
@ -901,8 +902,8 @@
|
||||||
{ label: 'Private IPs', value: 'geoip:private' },
|
{ label: 'Private IPs', value: 'geoip:private' },
|
||||||
{ label: '🇮🇷 Iran', value: 'ext:geoip_IR.dat:ir' },
|
{ label: '🇮🇷 Iran', value: 'ext:geoip_IR.dat:ir' },
|
||||||
{ label: '🇨🇳 China', value: 'geoip:cn' },
|
{ label: '🇨🇳 China', value: 'geoip:cn' },
|
||||||
{ label: '🇷🇺 Russia', value: 'geoip:ru' },
|
{ label: '🇷🇺 Russia', value: 'ext:geoip_RU.dat:ru' },
|
||||||
{ label: '🇻🇳 Vietnam', value: 'ext:geoip_VN.dat:vn' },
|
{ label: '🇻🇳 Vietnam', value: 'geoip:vn' },
|
||||||
{ label: '🇪🇸 Spain', value: 'geoip:es' },
|
{ label: '🇪🇸 Spain', value: 'geoip:es' },
|
||||||
{ label: '🇮🇩 Indonesia', value: 'geoip:id' },
|
{ label: '🇮🇩 Indonesia', value: 'geoip:id' },
|
||||||
{ label: '🇺🇦 Ukraine', value: 'geoip:ua' },
|
{ label: '🇺🇦 Ukraine', value: 'geoip:ua' },
|
||||||
|
@ -915,15 +916,16 @@
|
||||||
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
|
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
|
||||||
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
||||||
{ label: '🇨🇳 .cn', value: 'regexp:.*\\.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: '🇷🇺 .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$' },
|
{ label: '🇻🇳 .vn', value: 'regexp:.*\\.vn$' },
|
||||||
],
|
],
|
||||||
BlockDomainsOptions: [
|
BlockDomainsOptions: [
|
||||||
{ label: 'Ads All', value: 'geosite:category-ads-all' },
|
{ label: 'Ads All', value: 'geosite:category-ads-all' },
|
||||||
{ label: 'Ads IR 🇮🇷', value: 'ext:geosite_IR.dat: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: 'Malware 🇮🇷', value: 'ext:geosite_IR.dat:malware' },
|
||||||
{ label: 'Phishing 🇮🇷', value: 'ext:geosite_IR.dat:phishing' },
|
{ label: 'Phishing 🇮🇷', value: 'ext:geosite_IR.dat:phishing' },
|
||||||
{ label: 'Cryptominers 🇮🇷', value: 'ext:geosite_IR.dat:cryptominers' },
|
{ label: 'Cryptominers 🇮🇷', value: 'ext:geosite_IR.dat:cryptominers' },
|
||||||
|
@ -932,9 +934,10 @@
|
||||||
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
|
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
|
||||||
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
||||||
{ label: '🇨🇳 .cn', value: 'regexp:.*\\.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: '🇷🇺 .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$' },
|
{ label: '🇻🇳 .vn', value: 'regexp:.*\\.vn$' },
|
||||||
],
|
],
|
||||||
ServicesOptions: [
|
ServicesOptions: [
|
||||||
|
@ -1821,6 +1824,18 @@
|
||||||
this.templateSettings = newTemplateSettings;
|
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: {
|
maskAddressLog: {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.maskAddress) return "";
|
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 {
|
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: (.+)$`)
|
emailRegex := regexp.MustCompile(`email: (.+)$`)
|
||||||
|
|
||||||
accessLogPath, _ := xray.GetAccessLogPath()
|
accessLogPath, _ := xray.GetAccessLogPath()
|
||||||
|
@ -151,13 +151,13 @@ func (j *CheckClientIpJob) processLogFile() bool {
|
||||||
}
|
}
|
||||||
sort.Strings(ips)
|
sort.Strings(ips)
|
||||||
|
|
||||||
inboundClientIps, err := j.getInboundClientIps(email)
|
clientIpsRecord, err := j.getInboundClientIps(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
j.addInboundClientIps(email, ips)
|
j.addInboundClientIps(email, ips)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, email, ips) || shouldCleanLog
|
shouldCleanLog = j.updateInboundClientIps(clientIpsRecord, email, ips) || shouldCleanLog
|
||||||
}
|
}
|
||||||
|
|
||||||
return shouldCleanLog
|
return shouldCleanLog
|
||||||
|
@ -309,12 +309,12 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
||||||
|
|
||||||
func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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",
|
"tag": "direct",
|
||||||
"protocol": "freedom",
|
"protocol": "freedom",
|
||||||
"settings": {
|
"settings": {
|
||||||
"domainStrategy": "UseIP",
|
"domainStrategy": "AsIs",
|
||||||
"redirect": "",
|
"redirect": "",
|
||||||
"noises": []
|
"noises": []
|
||||||
}
|
}
|
||||||
|
@ -51,8 +51,8 @@
|
||||||
"system": {
|
"system": {
|
||||||
"statsInboundDownlink": true,
|
"statsInboundDownlink": true,
|
||||||
"statsInboundUplink": true,
|
"statsInboundUplink": true,
|
||||||
"statsOutboundDownlink": true,
|
"statsOutboundDownlink": false,
|
||||||
"statsOutboundUplink": true
|
"statsOutboundUplink": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"routing": {
|
"routing": {
|
||||||
|
|
|
@ -588,9 +588,13 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||||
logger.Debug("Client deleted by api:", email)
|
logger.Debug("Client deleted by api:", email)
|
||||||
needRestart = false
|
needRestart = false
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("Unable to del client by api:", err1)
|
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
|
needRestart = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
s.xrayApi.Close()
|
s.xrayApi.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -713,12 +717,16 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||||
if oldClients[clientIndex].Enable {
|
if oldClients[clientIndex].Enable {
|
||||||
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
|
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
|
||||||
if err1 == nil {
|
if err1 == nil {
|
||||||
logger.Debug("Old client deleted by api:", clients[0].Email)
|
logger.Debug("Old client deleted by api:", oldEmail)
|
||||||
|
} else {
|
||||||
|
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", oldEmail)) {
|
||||||
|
logger.Debug("User is already deleted. Nothing to do more...")
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("Error in deleting client by api:", err1)
|
logger.Debug("Error in deleting client by api:", err1)
|
||||||
needRestart = true
|
needRestart = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if clients[0].Enable {
|
if clients[0].Enable {
|
||||||
cipher := ""
|
cipher := ""
|
||||||
if oldInbound.Protocol == "shadowsocks" {
|
if oldInbound.Protocol == "shadowsocks" {
|
||||||
|
@ -1037,14 +1045,10 @@ func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error
|
||||||
if err1 == nil {
|
if err1 == nil {
|
||||||
logger.Debug("Inbound disabled by api:", tag)
|
logger.Debug("Inbound disabled by api:", tag)
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", tag)) {
|
logger.Debug("Error in disabling inbound by api:", err1)
|
||||||
logger.Debug("User is already disabled. Nothing to do more...")
|
|
||||||
} else {
|
|
||||||
logger.Debug("Error in disabling client by api:", err1)
|
|
||||||
needRestart = true
|
needRestart = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
s.xrayApi.Close()
|
s.xrayApi.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -248,28 +248,45 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||||
url := "https://api.github.com/repos/XTLS/Xray-core/releases"
|
const (
|
||||||
resp, err := http.Get(url)
|
XrayURL = "https://api.github.com/repos/XTLS/Xray-core/releases"
|
||||||
|
bufferSize = 8192
|
||||||
|
)
|
||||||
|
|
||||||
|
resp, err := http.Get(XrayURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
|
||||||
|
buffer := bytes.NewBuffer(make([]byte, bufferSize))
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
_, err = buffer.ReadFrom(resp.Body)
|
if _, err := buffer.ReadFrom(resp.Body); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
releases := make([]Release, 0)
|
var releases []Release
|
||||||
err = json.Unmarshal(buffer.Bytes(), &releases)
|
if err := json.Unmarshal(buffer.Bytes(), &releases); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var versions []string
|
var versions []string
|
||||||
for _, release := range releases {
|
for _, release := range releases {
|
||||||
if release.TagName >= "v1.7.5" {
|
tagVersion := strings.TrimPrefix(release.TagName, "v")
|
||||||
|
tagParts := strings.Split(tagVersion, ".")
|
||||||
|
if len(tagParts) != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
major, err1 := strconv.Atoi(tagParts[0])
|
||||||
|
minor, err2 := strconv.Atoi(tagParts[1])
|
||||||
|
patch, err3 := strconv.Atoi(tagParts[2])
|
||||||
|
if err1 != nil || err2 != nil || err3 != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (major == 1 && minor == 8 && patch == 24) ||
|
||||||
|
(major >= 25) {
|
||||||
versions = append(versions, release.TagName)
|
versions = append(versions, release.TagName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ var defaultValueMap = map[string]string{
|
||||||
"expireDiff": "0",
|
"expireDiff": "0",
|
||||||
"trafficDiff": "0",
|
"trafficDiff": "0",
|
||||||
"remarkModel": "-ieo",
|
"remarkModel": "-ieo",
|
||||||
"timeLocation": "Asia/Tehran",
|
"timeLocation": "Local",
|
||||||
"tgBotEnable": "false",
|
"tgBotEnable": "false",
|
||||||
"tgBotToken": "",
|
"tgBotToken": "",
|
||||||
"tgBotProxy": "",
|
"tgBotProxy": "",
|
||||||
|
@ -50,6 +50,7 @@ var defaultValueMap = map[string]string{
|
||||||
"tgLang": "en-US",
|
"tgLang": "en-US",
|
||||||
"secretEnable": "false",
|
"secretEnable": "false",
|
||||||
"subEnable": "false",
|
"subEnable": "false",
|
||||||
|
"subSyncEnable": "true",
|
||||||
"subListen": "",
|
"subListen": "",
|
||||||
"subPort": "2096",
|
"subPort": "2096",
|
||||||
"subPath": "/sub/",
|
"subPath": "/sub/",
|
||||||
|
@ -243,6 +244,10 @@ func (s *SettingService) GetListen() (string, error) {
|
||||||
return s.getString("webListen")
|
return s.getString("webListen")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetListen(ip string) error {
|
||||||
|
return s.setString("webListen", ip)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetWebDomain() (string, error) {
|
func (s *SettingService) GetWebDomain() (string, error) {
|
||||||
return s.getString("webDomain")
|
return s.getString("webDomain")
|
||||||
}
|
}
|
||||||
|
@ -412,6 +417,14 @@ func (s *SettingService) GetSubEnable() (bool, error) {
|
||||||
return s.getBool("subEnable")
|
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) {
|
func (s *SettingService) GetSubListen() (string, error) {
|
||||||
return s.getString("subListen")
|
return s.getString("subListen")
|
||||||
}
|
}
|
||||||
|
@ -540,6 +553,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||||
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
|
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
|
||||||
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() },
|
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() },
|
||||||
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
||||||
|
"subSyncEnable": func() (interface{}, error) { return s.GetSubSyncEnable() },
|
||||||
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
||||||
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
|
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
|
||||||
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WarpService struct {
|
type WarpService struct {
|
||||||
|
@ -150,13 +151,23 @@ func (s *WarpService) SetWarpLicense(license string) (string, error) {
|
||||||
return "", err
|
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
|
warpData["license_key"] = license
|
||||||
newWarpData, err := json.MarshalIndent(warpData, "", " ")
|
newWarpData, err := json.MarshalIndent(warpData, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
s.SettingService.SetWarp(string(newWarpData))
|
s.SettingService.SetWarp(string(newWarpData))
|
||||||
println(string(newWarpData))
|
|
||||||
|
|
||||||
return string(newWarpData), nil
|
return string(newWarpData), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
loginUser = "LOGIN_USER"
|
loginUserKey = "LOGIN_USER"
|
||||||
defaultPath = "/"
|
defaultPath = "/"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,30 +18,33 @@ func init() {
|
||||||
gob.Register(model.User{})
|
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 := sessions.Default(c)
|
||||||
s.Set(loginUser, user)
|
s.Set(loginUserKey, *user)
|
||||||
return s.Save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetMaxAge(c *gin.Context, maxAge int) error {
|
func SetMaxAge(c *gin.Context, maxAge int) {
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
s.Options(sessions.Options{
|
s.Options(sessions.Options{
|
||||||
Path: defaultPath,
|
Path: defaultPath,
|
||||||
MaxAge: maxAge,
|
MaxAge: maxAge,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
})
|
})
|
||||||
return s.Save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLoginUser(c *gin.Context) *model.User {
|
func GetLoginUser(c *gin.Context) *model.User {
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
obj := s.Get(loginUser)
|
obj := s.Get(loginUserKey)
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
user, ok := obj.(model.User)
|
user, ok := obj.(model.User)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
||||||
|
s.Delete(loginUserKey)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &user
|
return &user
|
||||||
|
@ -51,7 +54,7 @@ func IsLogin(c *gin.Context) bool {
|
||||||
return GetLoginUser(c) != nil
|
return GetLoginUser(c) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClearSession(c *gin.Context) error {
|
func ClearSession(c *gin.Context) {
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
s.Clear()
|
s.Clear()
|
||||||
s.Options(sessions.Options{
|
s.Options(sessions.Options{
|
||||||
|
@ -59,5 +62,4 @@ func ClearSession(c *gin.Context) error {
|
||||||
MaxAge: -1,
|
MaxAge: -1,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
})
|
})
|
||||||
return s.Save()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
"monitor" = "Listen IP"
|
"monitor" = "Listen IP"
|
||||||
"certificate" = "Digital Certificate"
|
"certificate" = "Digital Certificate"
|
||||||
"fail" = "Failed"
|
"fail" = "Failed"
|
||||||
|
"comment" = "Comment"
|
||||||
"success" = "Successfully"
|
"success" = "Successfully"
|
||||||
"getVersion" = "Get Version"
|
"getVersion" = "Get Version"
|
||||||
"install" = "Install"
|
"install" = "Install"
|
||||||
|
@ -178,8 +179,6 @@
|
||||||
"IPLimitlogDesc" = "The IPs history log. (to enable inbound after disabling, clear the log)"
|
"IPLimitlogDesc" = "The IPs history log. (to enable inbound after disabling, clear the log)"
|
||||||
"IPLimitlogclear" = "Clear The Log"
|
"IPLimitlogclear" = "Clear The Log"
|
||||||
"setDefaultCert" = "Set Cert from Panel"
|
"setDefaultCert" = "Set Cert from Panel"
|
||||||
"xtlsDesc" = "Xray must be v1.7.5"
|
|
||||||
"realityDesc" = "Xray must be v1.8.0+"
|
|
||||||
"telegramDesc" = "Please provide Telegram Chat ID. (use '/id' command in the bot) or (@userinfobot)"
|
"telegramDesc" = "Please provide Telegram Chat ID. (use '/id' command in the bot) or (@userinfobot)"
|
||||||
"subscriptionDesc" = "To find your subscription URL, navigate to the 'Details'. Additionally, you can use the same name for several clients."
|
"subscriptionDesc" = "To find your subscription URL, navigate to the 'Details'. Additionally, you can use the same name for several clients."
|
||||||
"info" = "Info"
|
"info" = "Info"
|
||||||
|
@ -191,6 +190,9 @@
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Add Client"
|
"add" = "Add Client"
|
||||||
|
"groupAdd" = "Add subscription user"
|
||||||
|
"isGroupEdit" = "Group editing"
|
||||||
|
"isGroupEditDesc" = "All clients with the same subscription are edited"
|
||||||
"edit" = "Edit Client"
|
"edit" = "Edit Client"
|
||||||
"submitAdd" = "Add Client"
|
"submitAdd" = "Add Client"
|
||||||
"submitEdit" = "Save Changes"
|
"submitEdit" = "Save Changes"
|
||||||
|
@ -288,6 +290,8 @@
|
||||||
"subSettings" = "Subscription"
|
"subSettings" = "Subscription"
|
||||||
"subEnable" = "Enable Subscription Service"
|
"subEnable" = "Enable Subscription Service"
|
||||||
"subEnableDesc" = "Enables the 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"
|
"subListen" = "Listen IP"
|
||||||
"subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)"
|
"subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)"
|
||||||
"subPort" = "Listen Port"
|
"subPort" = "Listen Port"
|
||||||
|
@ -369,6 +373,8 @@
|
||||||
"errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs"
|
"errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs"
|
||||||
"dnsLog" = "DNS Log"
|
"dnsLog" = "DNS Log"
|
||||||
"dnsLogDesc" = "Whether to enable DNS query logs"
|
"dnsLogDesc" = "Whether to enable DNS query logs"
|
||||||
|
"outboundTraffic" = "Outbounds Traffic"
|
||||||
|
"outboundTrafficDesc" = "Whether to enable outbound traffic"
|
||||||
"maskAddress" = "Mask Address"
|
"maskAddress" = "Mask Address"
|
||||||
"maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log."
|
"maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log."
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
"monitor" = "Listening IP"
|
"monitor" = "Listening IP"
|
||||||
"certificate" = "Certificado Digital"
|
"certificate" = "Certificado Digital"
|
||||||
"fail" = "Falló"
|
"fail" = "Falló"
|
||||||
|
"comment" = "Comentario"
|
||||||
"success" = "Éxito"
|
"success" = "Éxito"
|
||||||
"getVersion" = "Obtener versión"
|
"getVersion" = "Obtener versión"
|
||||||
"install" = "Instalar"
|
"install" = "Instalar"
|
||||||
|
@ -178,8 +179,6 @@
|
||||||
"IPLimitlogDesc" = "Registro de historial de IPs (antes de habilitar la entrada después de que haya sido desactivada por el límite de IP, debes borrar el registro)."
|
"IPLimitlogDesc" = "Registro de historial de IPs (antes de habilitar la entrada después de que haya sido desactivada por el límite de IP, debes borrar el registro)."
|
||||||
"IPLimitlogclear" = "Limpiar el Registro"
|
"IPLimitlogclear" = "Limpiar el Registro"
|
||||||
"setDefaultCert" = "Establecer certificado desde el panel"
|
"setDefaultCert" = "Establecer certificado desde el panel"
|
||||||
"xtlsDesc" = "La versión del núcleo de Xray debe ser 1.7.5"
|
|
||||||
"realityDesc" = "La versión del núcleo de Xray debe ser 1.8.0 o superior."
|
|
||||||
"telegramDesc" = "Por favor, proporciona el ID de Chat de Telegram. (usa el comando '/id' en el bot) o (@userinfobot)"
|
"telegramDesc" = "Por favor, proporciona el ID de Chat de Telegram. (usa el comando '/id' en el bot) o (@userinfobot)"
|
||||||
"subscriptionDesc" = "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones."
|
"subscriptionDesc" = "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones."
|
||||||
"info" = "Info"
|
"info" = "Info"
|
||||||
|
@ -191,6 +190,7 @@
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Agregar Cliente"
|
"add" = "Agregar Cliente"
|
||||||
|
"groupAdd" = "Agregar usuario de suscripción"
|
||||||
"edit" = "Editar Cliente"
|
"edit" = "Editar Cliente"
|
||||||
"submitAdd" = "Agregar Cliente"
|
"submitAdd" = "Agregar Cliente"
|
||||||
"submitEdit" = "Guardar Cambios"
|
"submitEdit" = "Guardar Cambios"
|
||||||
|
@ -288,6 +288,8 @@
|
||||||
"subSettings" = "Suscripción"
|
"subSettings" = "Suscripción"
|
||||||
"subEnable" = "Habilitar Servicio"
|
"subEnable" = "Habilitar Servicio"
|
||||||
"subEnableDesc" = "Función de suscripción con configuración separada."
|
"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"
|
"subListen" = "Listening IP"
|
||||||
"subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs."
|
"subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs."
|
||||||
"subPort" = "Puerto de Suscripción"
|
"subPort" = "Puerto de Suscripción"
|
||||||
|
@ -369,6 +371,8 @@
|
||||||
"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'none' desactiva los registros de errores."
|
"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'none' desactiva los registros de errores."
|
||||||
"dnsLog" = "Registro DNS"
|
"dnsLog" = "Registro DNS"
|
||||||
"dnsLogDesc" = "Si habilitar los registros de consulta 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"
|
"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."
|
"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" = "آیپی اتصال"
|
"monitor" = "آیپی اتصال"
|
||||||
"certificate" = "گواهی دیجیتال"
|
"certificate" = "گواهی دیجیتال"
|
||||||
"fail" = "ناموفق"
|
"fail" = "ناموفق"
|
||||||
|
"comment" = "توضیحات"
|
||||||
"success" = "موفق"
|
"success" = "موفق"
|
||||||
"getVersion" = "دریافت نسخه"
|
"getVersion" = "دریافت نسخه"
|
||||||
"install" = "نصب"
|
"install" = "نصب"
|
||||||
|
@ -178,8 +179,6 @@
|
||||||
"IPLimitlogDesc" = "گزارش تاریخچه آیپی. برای فعال کردن ورودی پس از غیرفعال شدن، گزارش را پاک کنید"
|
"IPLimitlogDesc" = "گزارش تاریخچه آیپی. برای فعال کردن ورودی پس از غیرفعال شدن، گزارش را پاک کنید"
|
||||||
"IPLimitlogclear" = "پاک کردن گزارشها"
|
"IPLimitlogclear" = "پاک کردن گزارشها"
|
||||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||||
"xtlsDesc" = "ایکسری باید 1.7.5 باشد"
|
|
||||||
"realityDesc" = "ایکسری باید +1.8.0 باشد"
|
|
||||||
"telegramDesc" = "لطفا شناسه گفتگوی تلگرام را وارد کنید. (از دستور '/id' در ربات استفاده کنید) یا (@userinfobot)"
|
"telegramDesc" = "لطفا شناسه گفتگوی تلگرام را وارد کنید. (از دستور '/id' در ربات استفاده کنید) یا (@userinfobot)"
|
||||||
"subscriptionDesc" = "شما میتوانید لینک سابسکربپشن خودرا در 'جزئیات' پیدا کنید، همچنین میتوانید از همین نام برای چندین کاربر استفادهکنید"
|
"subscriptionDesc" = "شما میتوانید لینک سابسکربپشن خودرا در 'جزئیات' پیدا کنید، همچنین میتوانید از همین نام برای چندین کاربر استفادهکنید"
|
||||||
"info" = "اطلاعات"
|
"info" = "اطلاعات"
|
||||||
|
@ -191,6 +190,9 @@
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "کاربر جدید"
|
"add" = "کاربر جدید"
|
||||||
|
"groupAdd" = "افزودن کاربر سابسکریپشن"
|
||||||
|
"isGroupEdit" = "ویرایش گروهی"
|
||||||
|
"isGroupEditDesc" = "همه کاربران با سابسکریپشن یکسان ویرایش می شوند"
|
||||||
"edit" = "ویرایش کاربر"
|
"edit" = "ویرایش کاربر"
|
||||||
"submitAdd" = "اضافه کردن"
|
"submitAdd" = "اضافه کردن"
|
||||||
"submitEdit" = "ذخیره تغییرات"
|
"submitEdit" = "ذخیره تغییرات"
|
||||||
|
@ -288,6 +290,8 @@
|
||||||
"subSettings" = "سابسکریپشن"
|
"subSettings" = "سابسکریپشن"
|
||||||
"subEnable" = "فعالسازی سرویس سابسکریپشن"
|
"subEnable" = "فعالسازی سرویس سابسکریپشن"
|
||||||
"subEnableDesc" = "سرویس سابسکریپشن را فعالمیکند"
|
"subEnableDesc" = "سرویس سابسکریپشن را فعالمیکند"
|
||||||
|
"subSyncEnable" = "فعالسازی همگام سازی سابسکریپشن"
|
||||||
|
"subSyncEnableDesc" = "ترافیک کلاینت هایی که سابسکریپشن یکسان دارند هر ۱۰ ثانیه همگام میشوند."
|
||||||
"subListen" = "آدرس آیپی"
|
"subListen" = "آدرس آیپی"
|
||||||
"subListenDesc" = "آدرس آیپی برای سرویس سابسکریپشن. برای گوش دادن بهتمام آیپیها خالیبگذارید"
|
"subListenDesc" = "آدرس آیپی برای سرویس سابسکریپشن. برای گوش دادن بهتمام آیپیها خالیبگذارید"
|
||||||
"subPort" = "پورت"
|
"subPort" = "پورت"
|
||||||
|
@ -369,6 +373,8 @@
|
||||||
"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند"
|
"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند"
|
||||||
"dnsLog" = "گزارش DNS"
|
"dnsLog" = "گزارش DNS"
|
||||||
"dnsLogDesc" = "آیا ثبتهای درخواست DNS را فعال کنید"
|
"dnsLogDesc" = "آیا ثبتهای درخواست DNS را فعال کنید"
|
||||||
|
"outboundTraffic" = "ترافیک خروجی"
|
||||||
|
"outboundTrafficDesc" = "فعال کردن ترافیک خروجی"
|
||||||
"maskAddress" = "پنهان کردن آدرس"
|
"maskAddress" = "پنهان کردن آدرس"
|
||||||
"maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال میشود، به طور خودکار آدرس IP که در لاگ ظاهر میشود را جایگزین میکند."
|
"maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال میشود، به طور خودکار آدرس IP که در لاگ ظاهر میشود را جایگزین میکند."
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
"monitor" = "IP Pemantauan"
|
"monitor" = "IP Pemantauan"
|
||||||
"certificate" = "Sertifikat Digital"
|
"certificate" = "Sertifikat Digital"
|
||||||
"fail" = "Gagal"
|
"fail" = "Gagal"
|
||||||
|
"comment" = "Komentar"
|
||||||
"success" = "Berhasil"
|
"success" = "Berhasil"
|
||||||
"getVersion" = "Dapatkan Versi"
|
"getVersion" = "Dapatkan Versi"
|
||||||
"install" = "Instal"
|
"install" = "Instal"
|
||||||
|
@ -178,8 +179,6 @@
|
||||||
"IPLimitlogDesc" = "Log histori IP. (untuk mengaktifkan masuk setelah menonaktifkan, hapus log)"
|
"IPLimitlogDesc" = "Log histori IP. (untuk mengaktifkan masuk setelah menonaktifkan, hapus log)"
|
||||||
"IPLimitlogclear" = "Hapus Log"
|
"IPLimitlogclear" = "Hapus Log"
|
||||||
"setDefaultCert" = "Atur Sertifikat dari Panel"
|
"setDefaultCert" = "Atur Sertifikat dari Panel"
|
||||||
"xtlsDesc" = "Xray harus versi 1.7.5"
|
|
||||||
"realityDesc" = "Xray harus versi 1.8.0+"
|
|
||||||
"telegramDesc" = "Harap berikan ID Obrolan Telegram. (gunakan perintah '/id' di bot) atau (@userinfobot)"
|
"telegramDesc" = "Harap berikan ID Obrolan Telegram. (gunakan perintah '/id' di bot) atau (@userinfobot)"
|
||||||
"subscriptionDesc" = "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien."
|
"subscriptionDesc" = "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien."
|
||||||
"info" = "Info"
|
"info" = "Info"
|
||||||
|
@ -191,6 +190,9 @@
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Tambah Klien"
|
"add" = "Tambah Klien"
|
||||||
|
"groupAdd" = "Tambahkan pengguna langganan"
|
||||||
|
"isGroupEdit" = "Pengeditan grup"
|
||||||
|
"isGroupEditDesc" = "Semua klien dengan langganan yang sama akan diedit"
|
||||||
"edit" = "Edit Klien"
|
"edit" = "Edit Klien"
|
||||||
"submitAdd" = "Tambah Klien"
|
"submitAdd" = "Tambah Klien"
|
||||||
"submitEdit" = "Simpan Perubahan"
|
"submitEdit" = "Simpan Perubahan"
|
||||||
|
@ -288,6 +290,8 @@
|
||||||
"subSettings" = "Langganan"
|
"subSettings" = "Langganan"
|
||||||
"subEnable" = "Aktifkan Layanan Langganan"
|
"subEnable" = "Aktifkan Layanan Langganan"
|
||||||
"subEnableDesc" = "Mengaktifkan 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"
|
"subListen" = "IP Pendengar"
|
||||||
"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
|
"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
|
||||||
"subPort" = "Port Pendengar"
|
"subPort" = "Port Pendengar"
|
||||||
|
@ -369,6 +373,8 @@
|
||||||
"errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan"
|
"errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan"
|
||||||
"dnsLog" = "Log DNS"
|
"dnsLog" = "Log DNS"
|
||||||
"dnsLogDesc" = "Apakah akan mengaktifkan log kueri DNS"
|
"dnsLogDesc" = "Apakah akan mengaktifkan log kueri DNS"
|
||||||
|
"outboundTraffic" = "Lalu Lintas Keluar"
|
||||||
|
"outboundTrafficDesc" = "Apakah mengaktifkan lalu lintas keluar"
|
||||||
"maskAddress" = "Alamat Masker"
|
"maskAddress" = "Alamat Masker"
|
||||||
"maskAddressDesc" = "Masker alamat IP, ketika diaktifkan, akan secara otomatis mengganti alamat IP yang muncul di log."
|
"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"
|
"monitor" = "IP de Escuta"
|
||||||
"certificate" = "Certificado Digital"
|
"certificate" = "Certificado Digital"
|
||||||
"fail" = "Falhou"
|
"fail" = "Falhou"
|
||||||
|
"comment" = "Comentário"
|
||||||
"success" = "Com Sucesso"
|
"success" = "Com Sucesso"
|
||||||
"getVersion" = "Obter Versão"
|
"getVersion" = "Obter Versão"
|
||||||
"install" = "Instalar"
|
"install" = "Instalar"
|
||||||
|
@ -178,8 +179,6 @@
|
||||||
"IPLimitlogDesc" = "O histórico de IPs. (para ativar o inbound após a desativação, limpe o log)"
|
"IPLimitlogDesc" = "O histórico de IPs. (para ativar o inbound após a desativação, limpe o log)"
|
||||||
"IPLimitlogclear" = "Limpar o Log"
|
"IPLimitlogclear" = "Limpar o Log"
|
||||||
"setDefaultCert" = "Definir Certificado pelo Painel"
|
"setDefaultCert" = "Definir Certificado pelo Painel"
|
||||||
"xtlsDesc" = "O Xray deve ser v1.7.5"
|
|
||||||
"realityDesc" = "O Xray deve ser v1.8.0+"
|
|
||||||
"telegramDesc" = "Por favor, forneça o ID do Chat do Telegram. (use o comando '/id' no bot) ou (@userinfobot)"
|
"telegramDesc" = "Por favor, forneça o ID do Chat do Telegram. (use o comando '/id' no bot) ou (@userinfobot)"
|
||||||
"subscriptionDesc" = "Para encontrar seu URL de assinatura, navegue até 'Detalhes'. Além disso, você pode usar o mesmo nome para vários clientes."
|
"subscriptionDesc" = "Para encontrar seu URL de assinatura, navegue até 'Detalhes'. Além disso, você pode usar o mesmo nome para vários clientes."
|
||||||
"info" = "Informações"
|
"info" = "Informações"
|
||||||
|
@ -191,6 +190,9 @@
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Adicionar Cliente"
|
"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"
|
"edit" = "Editar Cliente"
|
||||||
"submitAdd" = "Adicionar Cliente"
|
"submitAdd" = "Adicionar Cliente"
|
||||||
"submitEdit" = "Salvar Alterações"
|
"submitEdit" = "Salvar Alterações"
|
||||||
|
@ -288,6 +290,8 @@
|
||||||
"subSettings" = "Assinatura"
|
"subSettings" = "Assinatura"
|
||||||
"subEnable" = "Ativar Serviço de Assinatura"
|
"subEnable" = "Ativar Serviço de Assinatura"
|
||||||
"subEnableDesc" = "Ativa o 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"
|
"subListen" = "IP de Escuta"
|
||||||
"subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)"
|
"subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)"
|
||||||
"subPort" = "Porta de Escuta"
|
"subPort" = "Porta de Escuta"
|
||||||
|
@ -369,6 +373,8 @@
|
||||||
"errorLogDesc" = "O caminho do arquivo para o log de erros. O valor especial 'none' desativa os logs de erro."
|
"errorLogDesc" = "O caminho do arquivo para o log de erros. O valor especial 'none' desativa os logs de erro."
|
||||||
"dnsLog" = "Log DNS"
|
"dnsLog" = "Log DNS"
|
||||||
"dnsLogDesc" = "Se ativar logs de consulta 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"
|
"maskAddress" = "Mascarar Endereço"
|
||||||
"maskAddressDesc" = "Máscara de endereço IP, quando ativado, substitui automaticamente o endereço IP que aparece no log."
|
"maskAddressDesc" = "Máscara de endereço IP, quando ativado, substitui automaticamente o endereço IP que aparece no log."
|
||||||
|
|
||||||
|
|
|
@ -41,16 +41,17 @@
|
||||||
"offline" = "Офлайн"
|
"offline" = "Офлайн"
|
||||||
"online" = "Онлайн"
|
"online" = "Онлайн"
|
||||||
"domainName" = "Домен"
|
"domainName" = "Домен"
|
||||||
"monitor" = "Порт IP"
|
"monitor" = "Слушать IP"
|
||||||
"certificate" = "Цифровой сертификат"
|
"certificate" = "Цифровой сертификат"
|
||||||
"fail" = "Неудачно"
|
"fail" = "Неудачно"
|
||||||
|
"comment" = "Комментарий"
|
||||||
"success" = "Успешно"
|
"success" = "Успешно"
|
||||||
"getVersion" = "Узнать версию"
|
"getVersion" = "Узнать версию"
|
||||||
"install" = "Установка"
|
"install" = "Установка"
|
||||||
"clients" = "Клиенты"
|
"clients" = "Клиенты"
|
||||||
"usage" = "Использование"
|
"usage" = "Использование"
|
||||||
"secretToken" = "Секретный токен"
|
"secretToken" = "Секретный токен"
|
||||||
"remained" = "остались"
|
"remained" = "Остаток"
|
||||||
"security" = "Безопасность"
|
"security" = "Безопасность"
|
||||||
"secAlertTitle" = "Предупреждение системы безопасности"
|
"secAlertTitle" = "Предупреждение системы безопасности"
|
||||||
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
|
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
|
||||||
|
@ -78,7 +79,7 @@
|
||||||
"invalidFormData" = "Недопустимый формат данных"
|
"invalidFormData" = "Недопустимый формат данных"
|
||||||
"emptyUsername" = "Введите имя пользователя"
|
"emptyUsername" = "Введите имя пользователя"
|
||||||
"emptyPassword" = "Введите пароль"
|
"emptyPassword" = "Введите пароль"
|
||||||
"wrongUsernameOrPassword" = "Неверное имя пользователя или пароль"
|
"wrongUsernameOrPassword" = "Неверное имя пользователя, пароль или секретный токен."
|
||||||
"successLogin" = "Успешный вход"
|
"successLogin" = "Успешный вход"
|
||||||
|
|
||||||
[pages.index]
|
[pages.index]
|
||||||
|
@ -108,7 +109,7 @@
|
||||||
"config" = "Конфигурация"
|
"config" = "Конфигурация"
|
||||||
"backup" = "Бэкап и восстановление"
|
"backup" = "Бэкап и восстановление"
|
||||||
"backupTitle" = "База данных бэкапа и восстановления"
|
"backupTitle" = "База данных бэкапа и восстановления"
|
||||||
"backupDescription" = "Не забудьте сделать резервную копию перед импортом новой базы данных"
|
"backupDescription" = "Рекомендуется сделать резервную копию перед восстановлением базы данных."
|
||||||
"exportDatabase" = "Экспорт базы данных"
|
"exportDatabase" = "Экспорт базы данных"
|
||||||
"importDatabase" = "Импорт базы данных"
|
"importDatabase" = "Импорт базы данных"
|
||||||
|
|
||||||
|
@ -178,8 +179,6 @@
|
||||||
"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)"
|
"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)"
|
||||||
"IPLimitlogclear" = "Очистить лог"
|
"IPLimitlogclear" = "Очистить лог"
|
||||||
"setDefaultCert" = "Установить сертификат с панели"
|
"setDefaultCert" = "Установить сертификат с панели"
|
||||||
"xtlsDesc" = "Версия Xray должна быть не ниже 1.7.5"
|
|
||||||
"realityDesc" = "Версия Xray должна быть не ниже 1.8.0"
|
|
||||||
"telegramDesc" = "Пожалуйста, укажите ID чата Telegram. (используйте команду '/id' в боте) или (@userinfobot)"
|
"telegramDesc" = "Пожалуйста, укажите ID чата Telegram. (используйте команду '/id' в боте) или (@userinfobot)"
|
||||||
"subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее', также вы можете использовать одно и то же имя для нескольких конфигураций"
|
"subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее', также вы можете использовать одно и то же имя для нескольких конфигураций"
|
||||||
"info" = "Информация"
|
"info" = "Информация"
|
||||||
|
@ -187,10 +186,13 @@
|
||||||
"inboundData" = "Входящие данные"
|
"inboundData" = "Входящие данные"
|
||||||
"exportInbound" = "Экспорт входящих"
|
"exportInbound" = "Экспорт входящих"
|
||||||
"import" = "Импортировать"
|
"import" = "Импортировать"
|
||||||
"importInbound" = "Импортировать входящее сообщение"
|
"importInbound" = "Импортировать подключение"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Добавить пользователя"
|
"add" = "Добавить пользователя"
|
||||||
|
"groupAdd" = "Добавить пользователя подписки"
|
||||||
|
"isGroupEdit" = "Групповое редактирование"
|
||||||
|
"isGroupEditDesc" = "Все клиенты с одинаковой подпиской редактируются"
|
||||||
"edit" = "Редактировать пользователя"
|
"edit" = "Редактировать пользователя"
|
||||||
"submitAdd" = "Добавить пользователя"
|
"submitAdd" = "Добавить пользователя"
|
||||||
"submitEdit" = "Сохранить изменения"
|
"submitEdit" = "Сохранить изменения"
|
||||||
|
@ -214,7 +216,7 @@
|
||||||
"request" = "Запрос"
|
"request" = "Запрос"
|
||||||
"response" = "Ответ"
|
"response" = "Ответ"
|
||||||
"name" = "Имя"
|
"name" = "Имя"
|
||||||
"value" = "Ценить"
|
"value" = "Значение"
|
||||||
|
|
||||||
[pages.inbounds.stream.tcp]
|
[pages.inbounds.stream.tcp]
|
||||||
"version" = "Версия"
|
"version" = "Версия"
|
||||||
|
@ -288,6 +290,8 @@
|
||||||
"subSettings" = "Подписка"
|
"subSettings" = "Подписка"
|
||||||
"subEnable" = "Включить службу"
|
"subEnable" = "Включить службу"
|
||||||
"subEnableDesc" = "Функция подписки с отдельной конфигурацией"
|
"subEnableDesc" = "Функция подписки с отдельной конфигурацией"
|
||||||
|
"subSyncEnable" = "Включить синхронизацию подписки"
|
||||||
|
"subSyncEnableDesc" = "Трафик от клиентов с одинаковой подпиской будет синхронизироваться каждые 10 секунд."
|
||||||
"subListen" = "Прослушивание IP"
|
"subListen" = "Прослушивание IP"
|
||||||
"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса"
|
"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса"
|
||||||
"subPort" = "Порт подписки"
|
"subPort" = "Порт подписки"
|
||||||
|
@ -369,6 +373,8 @@
|
||||||
"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок."
|
"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок."
|
||||||
"dnsLog" = "DNS Журнал"
|
"dnsLog" = "DNS Журнал"
|
||||||
"dnsLogDesc" = "Включить логи запросов DNS"
|
"dnsLogDesc" = "Включить логи запросов DNS"
|
||||||
|
"outboundTraffic" = "Исходящий трафик"
|
||||||
|
"outboundTrafficDesc" = "Включить исходящий трафик"
|
||||||
"maskAddress" = "Маскировать Адрес"
|
"maskAddress" = "Маскировать Адрес"
|
||||||
"maskAddressDesc" = "Маска IP-адреса, при активации автоматически заменяет IP-адрес, который появляется в логе."
|
"maskAddressDesc" = "Маска IP-адреса, при активации автоматически заменяет IP-адрес, который появляется в логе."
|
||||||
|
|
||||||
|
@ -402,7 +408,7 @@
|
||||||
"portal" = "Портал"
|
"portal" = "Портал"
|
||||||
"intercon" = "Соединение"
|
"intercon" = "Соединение"
|
||||||
"settings" = "Настройки"
|
"settings" = "Настройки"
|
||||||
"accountInfo" = "Информация Об учетной записи"
|
"accountInfo" = "Информация об учетной записи"
|
||||||
"outboundStatus" = "Исходящий статус"
|
"outboundStatus" = "Исходящий статус"
|
||||||
"sendThrough" = "Отправить через"
|
"sendThrough" = "Отправить через"
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
"monitor" = "Dinleme IP"
|
"monitor" = "Dinleme IP"
|
||||||
"certificate" = "Dijital Sertifika"
|
"certificate" = "Dijital Sertifika"
|
||||||
"fail" = "Başarısız"
|
"fail" = "Başarısız"
|
||||||
|
"comment" = "Yorum"
|
||||||
"success" = "Başarılı"
|
"success" = "Başarılı"
|
||||||
"getVersion" = "Sürümü Al"
|
"getVersion" = "Sürümü Al"
|
||||||
"install" = "Yükle"
|
"install" = "Yükle"
|
||||||
|
@ -178,8 +179,6 @@
|
||||||
"IPLimitlogDesc" = "IP geçmiş günlüğü. (devre dışı bırakıldıktan sonra gelini etkinleştirmek için günlüğü temizleyin)"
|
"IPLimitlogDesc" = "IP geçmiş günlüğü. (devre dışı bırakıldıktan sonra gelini etkinleştirmek için günlüğü temizleyin)"
|
||||||
"IPLimitlogclear" = "Günlüğü Temizle"
|
"IPLimitlogclear" = "Günlüğü Temizle"
|
||||||
"setDefaultCert" = "Panelden Sertifikayı Ayarla"
|
"setDefaultCert" = "Panelden Sertifikayı Ayarla"
|
||||||
"xtlsDesc" = "Xray v1.7.5 olmalıdır"
|
|
||||||
"realityDesc" = "Xray v1.8.0+ olmalıdır"
|
|
||||||
"telegramDesc" = "Lütfen Telegram Sohbet Kimliği sağlayın. (botta '/id' komutunu kullanın) veya (@userinfobot)"
|
"telegramDesc" = "Lütfen Telegram Sohbet Kimliği sağlayın. (botta '/id' komutunu kullanın) veya (@userinfobot)"
|
||||||
"subscriptionDesc" = "Abonelik URL'inizi bulmak için 'Detaylar'a gidin. Ayrıca, aynı adı birden fazla müşteri için kullanabilirsiniz."
|
"subscriptionDesc" = "Abonelik URL'inizi bulmak için 'Detaylar'a gidin. Ayrıca, aynı adı birden fazla müşteri için kullanabilirsiniz."
|
||||||
"info" = "Bilgi"
|
"info" = "Bilgi"
|
||||||
|
@ -191,6 +190,9 @@
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Müşteri Ekle"
|
"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"
|
"edit" = "Müşteriyi Düzenle"
|
||||||
"submitAdd" = "Müşteri Ekle"
|
"submitAdd" = "Müşteri Ekle"
|
||||||
"submitEdit" = "Değişiklikleri Kaydet"
|
"submitEdit" = "Değişiklikleri Kaydet"
|
||||||
|
@ -288,6 +290,8 @@
|
||||||
"subSettings" = "Abonelik"
|
"subSettings" = "Abonelik"
|
||||||
"subEnable" = "Abonelik Hizmetini Etkinleştir"
|
"subEnable" = "Abonelik Hizmetini Etkinleştir"
|
||||||
"subEnableDesc" = "Abonelik hizmetini etkinleştirir."
|
"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"
|
"subListen" = "Dinleme IP"
|
||||||
"subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)"
|
"subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)"
|
||||||
"subPort" = "Dinleme Portu"
|
"subPort" = "Dinleme Portu"
|
||||||
|
@ -369,6 +373,8 @@
|
||||||
"errorLogDesc" = "Hata günlüğü için dosya yolu. 'none' özel değeri hata günlüklerini devre dışı bırakır"
|
"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üğü"
|
"dnsLog" = "DNS Günlüğü"
|
||||||
"dnsLogDesc" = "DNS sorgu günlüklerini etkinleştirin"
|
"dnsLogDesc" = "DNS sorgu günlüklerini etkinleştirin"
|
||||||
|
"outboundTraffic" = "Outbounds Traffic"
|
||||||
|
"outboundTrafficDesc" = "Çıkış trafiğini etkinleştirip etkinleştirmeyeceğiniz"
|
||||||
"maskAddress" = "Adres Maskesi"
|
"maskAddress" = "Adres Maskesi"
|
||||||
"maskAddressDesc" = "IP adresi maskesi, etkinleştirildiğinde, günlükte görünen IP adresini otomatik olarak değiştirecektir."
|
"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"
|
"monitor" = "Слухати IP"
|
||||||
"certificate" = "Цифровий сертифікат"
|
"certificate" = "Цифровий сертифікат"
|
||||||
"fail" = "Помилка"
|
"fail" = "Помилка"
|
||||||
|
"comment" = "Коментар"
|
||||||
"success" = "Успішно"
|
"success" = "Успішно"
|
||||||
"getVersion" = "Отримати версію"
|
"getVersion" = "Отримати версію"
|
||||||
"install" = "Встановити"
|
"install" = "Встановити"
|
||||||
|
@ -178,8 +179,6 @@
|
||||||
"IPLimitlogDesc" = "Журнал історії IP-адрес. (щоб увімкнути вхідну після вимкнення, очистіть журнал)"
|
"IPLimitlogDesc" = "Журнал історії IP-адрес. (щоб увімкнути вхідну після вимкнення, очистіть журнал)"
|
||||||
"IPLimitlogclear" = "Очистити журнал"
|
"IPLimitlogclear" = "Очистити журнал"
|
||||||
"setDefaultCert" = "Установити сертифікат з панелі"
|
"setDefaultCert" = "Установити сертифікат з панелі"
|
||||||
"xtlsDesc" = "Xray має бути v1.7.5"
|
|
||||||
"realityDesc" = "Xray має бути v1.8.0+"
|
|
||||||
"telegramDesc" = "Будь ласка, вкажіть ID чату Telegram. (використовуйте команду '/id' у боті) або (@userinfobot)"
|
"telegramDesc" = "Будь ласка, вкажіть ID чату Telegram. (використовуйте команду '/id' у боті) або (@userinfobot)"
|
||||||
"subscriptionDesc" = "Щоб знайти URL-адресу вашої підписки, перейдіть до «Деталі». Крім того, ви можете використовувати одне ім'я для кількох клієнтів."
|
"subscriptionDesc" = "Щоб знайти URL-адресу вашої підписки, перейдіть до «Деталі». Крім того, ви можете використовувати одне ім'я для кількох клієнтів."
|
||||||
"info" = "Інформація"
|
"info" = "Інформація"
|
||||||
|
@ -191,6 +190,9 @@
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Додати клієнта"
|
"add" = "Додати клієнта"
|
||||||
|
"groupAdd" = "Додати підписаного користувача"
|
||||||
|
"isGroupEdit" = "Редагування групи"
|
||||||
|
"isGroupEditDesc" = "Всі клієнти з однаковою підпискою редагуються"
|
||||||
"edit" = "Редагувати клієнта"
|
"edit" = "Редагувати клієнта"
|
||||||
"submitAdd" = "Додати клієнта"
|
"submitAdd" = "Додати клієнта"
|
||||||
"submitEdit" = "Зберегти зміни"
|
"submitEdit" = "Зберегти зміни"
|
||||||
|
@ -288,6 +290,8 @@
|
||||||
"subSettings" = "Підписка"
|
"subSettings" = "Підписка"
|
||||||
"subEnable" = "Увімкнути службу підписки"
|
"subEnable" = "Увімкнути службу підписки"
|
||||||
"subEnableDesc" = "Вмикає службу підписки."
|
"subEnableDesc" = "Вмикає службу підписки."
|
||||||
|
"subSyncEnable" = "Увімкнути синхронізацію підписки"
|
||||||
|
"subSyncEnableDesc" = "Трафік від клієнтів з однаковою підпискою буде синхронізовано кожні 10 секунд."
|
||||||
"subListen" = "Слухати IP"
|
"subListen" = "Слухати IP"
|
||||||
"subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)"
|
"subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)"
|
||||||
"subPort" = "Слухати порт"
|
"subPort" = "Слухати порт"
|
||||||
|
@ -369,6 +373,8 @@
|
||||||
"errorLogDesc" = "Шлях до файлу журналу помилок. Спеціальне значення 'none' вимикає журнали помилок"
|
"errorLogDesc" = "Шлях до файлу журналу помилок. Спеціальне значення 'none' вимикає журнали помилок"
|
||||||
"dnsLog" = "Журнал DNS"
|
"dnsLog" = "Журнал DNS"
|
||||||
"dnsLogDesc" = "Чи включити журнали запитів DNS"
|
"dnsLogDesc" = "Чи включити журнали запитів DNS"
|
||||||
|
"outboundTraffic" = "Вихідний трафік"
|
||||||
|
"outboundTrafficDesc" = "Чи потрібно увімкнути вихідний трафік"
|
||||||
"maskAddress" = "Маскувати Адресу"
|
"maskAddress" = "Маскувати Адресу"
|
||||||
"maskAddressDesc" = "Маска IP-адреси, при активації автоматично замінює IP-адресу, яка з'являється у журналі."
|
"maskAddressDesc" = "Маска IP-адреси, при активації автоматично замінює IP-адресу, яка з'являється у журналі."
|
||||||
|
|
||||||
|
|