mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-21 10:05:49 +00:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f726474a5d | ||
|
|
cd7a790637 | ||
|
|
4f74f5154b | ||
|
|
6e22aa59e7 | ||
|
|
85df1301dc | ||
|
|
e92aba0179 | ||
|
|
85fb2fda5e | ||
|
|
f57e693023 | ||
|
|
83f6f13b50 | ||
|
|
b833ed7992 | ||
|
|
5188d516e3 | ||
|
|
97925eeebe | ||
|
|
1328bb5aba | ||
|
|
4cc755c883 | ||
|
|
4e89c71095 | ||
|
|
d40e61fc45 | ||
|
|
c0f1a926e5 | ||
|
|
970dd0915e | ||
|
|
526e2578b0 | ||
|
|
ad9134bc1a | ||
|
|
b5657ab87d | ||
|
|
2c7ec3dc8a | ||
|
|
0fe35fde49 | ||
|
|
c7658973c3 | ||
|
|
dea61b839c | ||
|
|
9063336778 | ||
|
|
7fc3d37851 | ||
|
|
81aa3ed10e | ||
|
|
ca10623e40 | ||
|
|
547e38079f | ||
|
|
29e40a0bce | ||
|
|
4c1fa59453 | ||
|
|
84b0471ca0 | ||
|
|
1c785b930f | ||
|
|
c8ea9e43ec | ||
|
|
594d682e20 | ||
|
|
70f250dfe1 | ||
|
|
1030bcf321 | ||
|
|
fdc1124ea4 | ||
|
|
33a598366b | ||
|
|
459c19eee2 | ||
|
|
d73a995257 | ||
|
|
e4e0deeed4 | ||
|
|
48dce38e14 | ||
|
|
3eebd7c3e5 | ||
|
|
2fa151d52e | ||
|
|
c182f48079 | ||
|
|
94fad02737 | ||
|
|
d694e6eafc | ||
|
|
2a9cb6d29e | ||
|
|
c565a429af | ||
|
|
572d912858 | ||
|
|
6ae80fc992 | ||
|
|
ef7b979d53 | ||
|
|
b203067dfd | ||
|
|
1c9fc9422e | ||
|
|
6c26e40aea | ||
|
|
fe963d91ef | ||
|
|
c9461f1647 | ||
|
|
ea7fe09c27 | ||
|
|
8170b65db4 | ||
|
|
a2d8c98b0d | ||
|
|
8442836512 | ||
|
|
331f2adb1c | ||
|
|
45a8d6c95e | ||
|
|
1f2eb2ca1a | ||
|
|
de571a2da4 | ||
|
|
2c233dffa5 |
10
.github/workflows/docker.yml
vendored
10
.github/workflows/docker.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
|||||||
- name: Check out the code
|
- name: Check out the code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2.1.0
|
uses: docker/setup-qemu-action@v2.2.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2.5.0
|
uses: docker/setup-buildx-action@v2.7.0
|
||||||
|
|
||||||
- name: Log in to GitHub Container Registry
|
- name: Log in to GitHub Container Registry
|
||||||
uses: docker/login-action@v2.1.0
|
uses: docker/login-action@v2.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -27,12 +27,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4.4.0
|
uses: docker/metadata-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v4.0.0
|
uses: docker/build-push-action@v4.1.1
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
|||||||
120
.github/workflows/release.yml
vendored
120
.github/workflows/release.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Release X-ui
|
name: Release 3X-ui
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -6,85 +7,70 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linuxamd64build:
|
build:
|
||||||
name: build x-ui amd64 version
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [amd64, arm64]
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.5.2
|
- name: Checkout repository
|
||||||
- name: Set up Go
|
uses: actions/checkout@v3.5.2
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v4.0.1
|
uses: actions/setup-go@v4.0.1
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: 'stable'
|
||||||
- name: build linux amd64 version
|
|
||||||
run: |
|
- name: Install dependencies for arm64
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
if: matrix.platform == 'arm64'
|
||||||
mkdir x-ui
|
|
||||||
cp xui-release x-ui/xui-release
|
|
||||||
cp x-ui.service x-ui/x-ui.service
|
|
||||||
cp x-ui.sh x-ui/x-ui.sh
|
|
||||||
cd x-ui
|
|
||||||
mv xui-release x-ui
|
|
||||||
mkdir bin
|
|
||||||
cd bin
|
|
||||||
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip
|
|
||||||
unzip Xray-linux-64.zip
|
|
||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
|
||||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
|
||||||
mv xray xray-linux-amd64
|
|
||||||
cd ..
|
|
||||||
cd ..
|
|
||||||
- name: package
|
|
||||||
run: tar -zcvf x-ui-linux-amd64.tar.gz x-ui
|
|
||||||
- name: upload
|
|
||||||
uses: svenstaro/upload-release-action@2.6.0
|
|
||||||
with:
|
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
tag: ${{ github.ref }}
|
|
||||||
file: x-ui-linux-amd64.tar.gz
|
|
||||||
asset_name: x-ui-linux-amd64.tar.gz
|
|
||||||
prerelease: true
|
|
||||||
overwrite: true
|
|
||||||
linuxarm64build:
|
|
||||||
name: build x-ui arm64 version
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3.5.2
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v4.0.1
|
|
||||||
with:
|
|
||||||
go-version: "stable"
|
|
||||||
- name: build linux arm64 version
|
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt install gcc-aarch64-linux-gnu
|
sudo apt install gcc-aarch64-linux-gnu
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc go build -o xui-release -v main.go
|
|
||||||
|
- name: Build x-ui
|
||||||
|
run: |
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
export GOOS=linux
|
||||||
|
export GOARCH=${{ matrix.platform }}
|
||||||
|
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||||
|
export CC=aarch64-linux-gnu-gcc
|
||||||
|
fi
|
||||||
|
go build -o xui-release -v main.go
|
||||||
|
|
||||||
mkdir x-ui
|
mkdir x-ui
|
||||||
cp xui-release x-ui/xui-release
|
cp xui-release x-ui/
|
||||||
cp x-ui.service x-ui/x-ui.service
|
cp x-ui.service x-ui/
|
||||||
cp x-ui.sh x-ui/x-ui.sh
|
cp x-ui.sh x-ui/
|
||||||
cd x-ui
|
mv x-ui/xui-release x-ui/x-ui
|
||||||
mv xui-release x-ui
|
mkdir x-ui/bin
|
||||||
mkdir bin
|
cd x-ui/bin
|
||||||
cd bin
|
|
||||||
wget https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
|
# Download dependencies
|
||||||
unzip Xray-linux-arm64-v8a.zip
|
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||||
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat iran.dat
|
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip
|
||||||
|
unzip Xray-linux-64.zip
|
||||||
|
rm -f Xray-linux-64.zip
|
||||||
|
else
|
||||||
|
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
|
||||||
|
unzip Xray-linux-arm64-v8a.zip
|
||||||
|
rm -f Xray-linux-arm64-v8a.zip
|
||||||
|
fi
|
||||||
|
rm -f geoip.dat geosite.dat iran.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||||
mv xray xray-linux-arm64
|
mv xray xray-linux-${{ matrix.platform }}
|
||||||
cd ..
|
cd ../..
|
||||||
cd ..
|
|
||||||
- name: package
|
- name: Package
|
||||||
run: tar -zcvf x-ui-linux-arm64.tar.gz x-ui
|
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||||
- name: upload
|
|
||||||
uses: svenstaro/upload-release-action@2.6.0
|
- name: Upload
|
||||||
|
uses: svenstaro/upload-release-action@2.6.1
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file: x-ui-linux-arm64.tar.gz
|
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
asset_name: x-ui-linux-arm64.tar.gz
|
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
prerelease: true
|
prerelease: true
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
269
README.md
269
README.md
@@ -9,7 +9,6 @@
|
|||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian)**
|
3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian)**
|
||||||
|
|
||||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||||
|
|
||||||
**Buy Me a Coffee :**
|
**Buy Me a Coffee :**
|
||||||
@@ -22,12 +21,12 @@
|
|||||||
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 custom version
|
# Install custom version
|
||||||
|
|
||||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.6.0`:
|
To install your desired version you can add the version to the end of install command. Example for ver `v1.6.1`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.6.0
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.6.1
|
||||||
```
|
```
|
||||||
|
|
||||||
# SSL
|
# SSL
|
||||||
@@ -38,97 +37,7 @@ certbot certonly --standalone --agree-tos --register-unsafely-without-email -d y
|
|||||||
certbot renew --dry-run
|
certbot renew --dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
|
You also can use `x-ui` menu then select `16. SSL Certificate Management`
|
||||||
|
|
||||||
# Default settings
|
|
||||||
|
|
||||||
- Port: 2053
|
|
||||||
- username and password will be generated randomly if you skip to modify your own security(x-ui "7")
|
|
||||||
- database path: /etc/x-ui/x-ui.db
|
|
||||||
- xray config path: /usr/local/x-ui/bin/config.json
|
|
||||||
|
|
||||||
Before you set ssl on settings
|
|
||||||
|
|
||||||
- http://ip:2053/panel
|
|
||||||
- http://domain:2053/panel
|
|
||||||
|
|
||||||
After you set ssl on settings
|
|
||||||
|
|
||||||
- https://yourdomain:2053/panel
|
|
||||||
|
|
||||||
# Environment Variables
|
|
||||||
|
|
||||||
| Variable | Type | Default |
|
|
||||||
| -------------- | :--------------------------------------------: | :------------ |
|
|
||||||
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
|
||||||
| XUI_DEBUG | `boolean` | `false` |
|
|
||||||
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
|
||||||
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
|
||||||
```
|
|
||||||
|
|
||||||
# Install with Docker
|
|
||||||
|
|
||||||
1. Install Docker:
|
|
||||||
```sh
|
|
||||||
bash <(curl -sSL https://get.docker.com)
|
|
||||||
```
|
|
||||||
2. Run 3x-ui:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
OR
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker run -itd \
|
|
||||||
-e XRAY_VMESS_AEAD_FORCED=false \
|
|
||||||
-v $PWD/db/:/etc/x-ui/ \
|
|
||||||
-v $PWD/cert/:/root/cert/ \
|
|
||||||
--network=host \
|
|
||||||
--restart=unless-stopped \
|
|
||||||
--name 3x-ui \
|
|
||||||
ghcr.io/mhsanaei/3x-ui:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
# Xray Configurations:
|
|
||||||
|
|
||||||
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
|
||||||
|
|
||||||
- [traffic](./media/configs/traffic.json)
|
|
||||||
- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
|
|
||||||
- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json)
|
|
||||||
- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json)
|
|
||||||
- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json)
|
|
||||||
|
|
||||||
# [WARP Configuration](https://github.com/fscarmen/warp) (Optional)
|
|
||||||
|
|
||||||
If you want to use routing to WARP follow steps as below:
|
|
||||||
|
|
||||||
1. If you already installed warp, you can uninstall using below command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
warp u
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install WARP on **socks proxy mode**:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json)
|
|
||||||
|
|
||||||
Config Features:
|
|
||||||
|
|
||||||
- Block Ads
|
|
||||||
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
|
|
||||||
- Fix Google 403 error
|
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
@@ -147,7 +56,145 @@ If you want to use routing to WARP follow steps as below:
|
|||||||
- Support to change configs by different items provided in panel
|
- Support to change configs by different items provided in panel
|
||||||
- Support export/import database from panel
|
- Support export/import database from panel
|
||||||
|
|
||||||
# Tg robot use
|
# Manual Install & Upgrade
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Manual Install details</summary>
|
||||||
|
|
||||||
|
1. To download the latest version of the compressed package directly to your server, run the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
|
||||||
|
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Once the compressed package is downloaded, execute the following commands to install or upgrade x-ui:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
|
||||||
|
cd /root/
|
||||||
|
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
||||||
|
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
|
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
|
||||||
|
cp x-ui/x-ui.sh /usr/bin/x-ui
|
||||||
|
cp -f x-ui/x-ui.service /etc/systemd/system/
|
||||||
|
mv x-ui/ /usr/local/
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable x-ui
|
||||||
|
systemctl restart x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Install with Docker
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Docker details</summary>
|
||||||
|
|
||||||
|
1. Install Docker:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash <(curl -sSL https://get.docker.com)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Clone the Project Repository:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/MHSanaei/3x-ui.git
|
||||||
|
cd 3x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start the Service
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run -itd \
|
||||||
|
-e XRAY_VMESS_AEAD_FORCED=false \
|
||||||
|
-v $PWD/db/:/etc/x-ui/ \
|
||||||
|
-v $PWD/cert/:/root/cert/ \
|
||||||
|
--network=host \
|
||||||
|
--restart=unless-stopped \
|
||||||
|
--name 3x-ui \
|
||||||
|
ghcr.io/mhsanaei/3x-ui:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Default settings
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Default settings details</summary>
|
||||||
|
|
||||||
|
- Port: 2053
|
||||||
|
- username and password will be generated randomly if you skip to modify your own security(x-ui "7")
|
||||||
|
- database path: /etc/x-ui/x-ui.db
|
||||||
|
- xray config path: /usr/local/x-ui/bin/config.json
|
||||||
|
|
||||||
|
Before you set ssl on settings
|
||||||
|
|
||||||
|
- http://ip:2053/panel
|
||||||
|
- http://domain:2053/panel
|
||||||
|
|
||||||
|
After you set ssl on settings
|
||||||
|
|
||||||
|
- https://yourdomain:2053/panel
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Xray Configurations:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Xray Configurations details</summary>
|
||||||
|
|
||||||
|
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
||||||
|
|
||||||
|
- [traffic](./media/configs/traffic.json)
|
||||||
|
- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
|
||||||
|
- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json)
|
||||||
|
- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json)
|
||||||
|
- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# [WARP Configuration](https://github.com/fscarmen/warp) (Optional)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for WARP Configuration details</summary>
|
||||||
|
|
||||||
|
If you want to use routing to WARP follow steps as below:
|
||||||
|
|
||||||
|
1. If you already installed warp, you can uninstall using below command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
warp u
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install WARP on **socks proxy mode**:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash <(curl -sSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json)
|
||||||
|
|
||||||
|
Config Features:
|
||||||
|
|
||||||
|
- Block Ads
|
||||||
|
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
|
||||||
|
- Fix Google 403 error
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Telegram Bot
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Telegram Bot details</summary>
|
||||||
|
|
||||||
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
||||||
Set the robot-related parameters in the panel background, including:
|
Set the robot-related parameters in the panel background, including:
|
||||||
@@ -176,15 +223,20 @@ Reference syntax:
|
|||||||
- CPU threshold notification
|
- CPU threshold notification
|
||||||
- Threshold for Expiration time and Traffic to report in advance
|
- Threshold for Expiration time and Traffic to report in advance
|
||||||
- Support client report menu if client's telegram username added to the user's configurations
|
- Support client report menu if client's telegram username added to the user's configurations
|
||||||
- Support telegram traffic report searched with UID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
- Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
||||||
- Menu based bot
|
- Menu based bot
|
||||||
- Search client by email ( only admin )
|
- Search client by email ( only admin )
|
||||||
- Check all inbounds
|
- Check all inbounds
|
||||||
- Check server status
|
- Check server status
|
||||||
- Check depleted users
|
- Check depleted users
|
||||||
- Receive backup by request and in periodic reports
|
- Receive backup by request and in periodic reports
|
||||||
|
- Multi language bot
|
||||||
|
</details>
|
||||||
|
|
||||||
## API routes
|
# API routes
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for API routes details</summary>
|
||||||
|
|
||||||
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
||||||
- `/panel/api/inbounds` base for following actions:
|
- `/panel/api/inbounds` base for following actions:
|
||||||
@@ -215,6 +267,27 @@ Reference syntax:
|
|||||||
- `client.email` for Shadowsocks
|
- `client.email` for Shadowsocks
|
||||||
|
|
||||||
- [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
|
- [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Environment Variables
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Environment Variables details</summary>
|
||||||
|
|
||||||
|
| Variable | Type | Default |
|
||||||
|
| -------------- | :--------------------------------------------: | :------------ |
|
||||||
|
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
||||||
|
| XUI_DEBUG | `boolean` | `false` |
|
||||||
|
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
||||||
|
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
# A Special Thanks To
|
# A Special Thanks To
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ var name string
|
|||||||
type LogLevel string
|
type LogLevel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Debug LogLevel = "debug"
|
Debug LogLevel = "debug"
|
||||||
Info LogLevel = "info"
|
Info LogLevel = "info"
|
||||||
Warn LogLevel = "warn"
|
Notice LogLevel = "notice"
|
||||||
Error LogLevel = "error"
|
Warn LogLevel = "warn"
|
||||||
|
Error LogLevel = "error"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetVersion() string {
|
func GetVersion() string {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.6.1
|
1.7.0
|
||||||
@@ -76,7 +76,6 @@ type Client struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
AlterIds uint16 `json:"alterId"`
|
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
LimitIP int `json:"limitIp"`
|
LimitIP int `json:"limitIp"`
|
||||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||||
|
|||||||
51
go.mod
51
go.mod
@@ -3,37 +3,38 @@ module x-ui
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Calidity/gin-sessions v1.3.1
|
||||||
github.com/Workiva/go-datastructures v1.1.0
|
github.com/Workiva/go-datastructures v1.1.0
|
||||||
github.com/gin-contrib/sessions v0.0.4
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/gin-gonic/gin v1.9.0
|
|
||||||
github.com/go-cmd/cmd v1.4.1
|
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/mymmrac/telego v0.24.0
|
github.com/mymmrac/telego v0.25.1
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||||
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.0.8
|
github.com/pelletier/go-toml/v2 v2.0.8
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.23.4
|
github.com/shirou/gopsutil/v3 v3.23.5
|
||||||
github.com/xtls/xray-core v1.8.1
|
github.com/xtls/xray-core v1.8.3
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.9.0
|
golang.org/x/text v0.10.0
|
||||||
google.golang.org/grpc v1.55.0
|
google.golang.org/grpc v1.56.1
|
||||||
gorm.io/driver/sqlite v1.5.1
|
gorm.io/driver/sqlite v1.5.2
|
||||||
gorm.io/gorm v1.25.1
|
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
github.com/bytedance/sonic v1.8.10 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||||
github.com/fasthttp/router v1.4.19 // indirect
|
github.com/fasthttp/router v1.4.19 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
|
github.com/gaukas/godicttls v0.0.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // 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.14.0 // indirect
|
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
@@ -41,30 +42,36 @@ require (
|
|||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
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.16.5 // indirect
|
github.com/klauspost/compress v1.16.6 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/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/pires/go-proxyproto v0.7.0 // indirect
|
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||||
|
github.com/refraction-networking/utls v1.3.2 // indirect
|
||||||
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
|
github.com/sagernet/sing v0.2.6 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasthttp v1.47.0 // indirect
|
github.com/valyala/fasthttp v1.48.0 // indirect
|
||||||
|
github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.9.0 // indirect
|
golang.org/x/crypto v0.10.0 // indirect
|
||||||
golang.org/x/net v0.10.0 // indirect
|
golang.org/x/net v0.11.0 // indirect
|
||||||
golang.org/x/sys v0.8.0 // indirect
|
golang.org/x/sys v0.9.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230524185152-1884fd1fac28 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
159
go.sum
159
go.sum
@@ -1,77 +1,63 @@
|
|||||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
||||||
|
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
||||||
github.com/Workiva/go-datastructures v1.1.0 h1:hu20UpgZneBhQ3ZvwiOGlqJSKIosin2Rd5wAKUHEO/k=
|
github.com/Workiva/go-datastructures v1.1.0 h1:hu20UpgZneBhQ3ZvwiOGlqJSKIosin2Rd5wAKUHEO/k=
|
||||||
github.com/Workiva/go-datastructures v1.1.0/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
github.com/Workiva/go-datastructures v1.1.0/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
|
||||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
|
||||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.8.10 h1:XFSQg4/rwpQnNWSybNDr8oz6QtQY9uRGfRKDVWVsvP8=
|
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
github.com/bytedance/sonic v1.8.10/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
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-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||||
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
github.com/fasthttp/router v1.4.19 h1:RLE539IU/S4kfb4MP56zgP0TIBU9kEg0ID9GpWO0vqk=
|
github.com/fasthttp/router v1.4.19 h1:RLE539IU/S4kfb4MP56zgP0TIBU9kEg0ID9GpWO0vqk=
|
||||||
github.com/fasthttp/router v1.4.19/go.mod h1:+Fh3YOd8x1+he6ZS+d2iUDBH9MGGZ1xQFUor0DE9rKE=
|
github.com/fasthttp/router v1.4.19/go.mod h1:+Fh3YOd8x1+he6ZS+d2iUDBH9MGGZ1xQFUor0DE9rKE=
|
||||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
|
github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
|
||||||
|
github.com/gaukas/godicttls v0.0.3/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||||
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/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
|
||||||
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
|
||||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
|
||||||
github.com/go-cmd/cmd v1.4.1 h1:JUcEIE84v8DSy02XTZpUDeGKExk2oW3DA10hTjbQwmc=
|
|
||||||
github.com/go-cmd/cmd v1.4.1/go.mod h1:tbBenttXtZU4c5djS1o7PWL5pd2xAr5sIqH1kGdNiRc=
|
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
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-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
|
||||||
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.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
||||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
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/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
|
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
||||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
|
||||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
|
||||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
@@ -79,40 +65,32 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
|
||||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
|
||||||
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.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
|
||||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
|
||||||
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
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 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
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.24.0 h1:0fd+v2/dToL6/DtsnWr+2saK7ZxIgLY+LI9kqJQbPEo=
|
github.com/mymmrac/telego v0.25.1 h1:tMNmrRm0YGyLS56CBi0NDHwO1ZI6V7QMgX4KWSWuT1U=
|
||||||
github.com/mymmrac/telego v0.24.0/go.mod h1:y557P/iMHSaOVDi5Nmy1gNelqrw+jaBMvP9guPaNJsQ=
|
github.com/mymmrac/telego v0.25.1/go.mod h1:nBO4SUqRV8j60JOS7trIr6bHPofwYCGJxYeqtQWgu2c=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||||
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=
|
||||||
@@ -121,71 +99,71 @@ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNc
|
|||||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||||
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
|
github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo=
|
||||||
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
|
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
|
||||||
|
github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E=
|
||||||
github.com/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=
|
||||||
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
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/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
|
github.com/sagernet/sing v0.2.6 h1:Fvqv7/Bwc72ERT6dE8yQLLY6SMc/syO3VMCtxVO4DNw=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw=
|
github.com/sagernet/sing v0.2.6/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||||
|
github.com/sagernet/sing-shadowsocks v0.2.2 h1:ezSdVhrmIcwDXmCZF3bOJVMuVtTQWpda+1Op+Ie2TA4=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8=
|
github.com/shirou/gopsutil/v3 v3.23.5 h1:5SgDCeQ0KW0S4N0znjeM/eFHXXOKyv2dVNgRq/c9P6Y=
|
||||||
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
github.com/shirou/gopsutil/v3 v3.23.5/go.mod h1:Ng3Maa27Q2KARVJ0SPZF5NdrQSC3XHKP8IIWrHgMeLY=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
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/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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
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.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
||||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
|
||||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
|
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.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c=
|
github.com/valyala/fasthttp v1.48.0 h1:oJWvHb9BIZToTQS3MuQ2R3bJZiNSa2KiNdeI8A+79Tc=
|
||||||
github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
github.com/valyala/fasthttp v1.48.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||||
github.com/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE=
|
github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983 h1:AMyzgjkh54WocjQSlCnT1LhDc/BKiUqtNOv40AkpURs=
|
||||||
github.com/xtls/xray-core v1.8.1 h1:iSTTqXj82ZdwC1ah+eV331X4JTcnrDz+WuKuB/EB3P4=
|
github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983/go.mod h1:rkuAY1S9F8eI8gDiPDYvACE8e2uwkyg8qoOTuwWov7Y=
|
||||||
github.com/xtls/xray-core v1.8.1/go.mod h1:AXxSso0MZwUE4NhRocCfHCg73BtJ+T2dSpQVo1Cg9VM=
|
github.com/xtls/xray-core v1.8.3 h1:lxaVklPjLKqUU4ua4qH8SBaRcAaNHlH+LmXOx0U/Ejg=
|
||||||
|
github.com/xtls/xray-core v1.8.3/go.mod h1:i7t4JFnq828P2+XK0XjGQ8W9x78iu+EJ7jI4l3sonIw=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg=
|
|
||||||
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=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
@@ -195,79 +173,76 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||||
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto v0.0.0-20230524185152-1884fd1fac28 h1:+55/MuGJORMxCrkAgo2595fMAnN/4rweCuwibbqrvpc=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||||
google.golang.org/genproto v0.0.0-20230524185152-1884fd1fac28/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ=
|
||||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 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.1 h1:hYyrLkAWE71bcarJDPdZNTLWtr8XrSjOWyjUYI6xdL4=
|
gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc=
|
||||||
gorm.io/driver/sqlite v1.5.1/go.mod h1:7MZZ2Z8bqyfSQA1gYEV6MagQWj3cpUkJj9Z+d1HEMEQ=
|
gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||||
gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64=
|
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55 h1:sC1Xj4TYrLqg1n3AN10w871An7wJM0gzgcm8jkIkECQ=
|
||||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
@@ -84,3 +84,15 @@ func Errorf(format string, args ...interface{}) {
|
|||||||
logger.Errorf(format, args...)
|
logger.Errorf(format, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Notice(args ...interface{}) {
|
||||||
|
if logger != nil {
|
||||||
|
logger.Notice(args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Noticef(format string, args ...interface{}) {
|
||||||
|
if logger != nil {
|
||||||
|
logger.Noticef(format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
22
main.go
22
main.go
@@ -12,7 +12,6 @@ import (
|
|||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/sub"
|
"x-ui/sub"
|
||||||
"x-ui/v2ui"
|
|
||||||
"x-ui/web"
|
"x-ui/web"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
@@ -28,6 +27,8 @@ func runWebServer() {
|
|||||||
logger.InitLogger(logging.DEBUG)
|
logger.InitLogger(logging.DEBUG)
|
||||||
case config.Info:
|
case config.Info:
|
||||||
logger.InitLogger(logging.INFO)
|
logger.InitLogger(logging.INFO)
|
||||||
|
case config.Notice:
|
||||||
|
logger.InitLogger(logging.NOTICE)
|
||||||
case config.Warn:
|
case config.Warn:
|
||||||
logger.InitLogger(logging.WARNING)
|
logger.InitLogger(logging.WARNING)
|
||||||
case config.Error:
|
case config.Error:
|
||||||
@@ -270,10 +271,6 @@ func main() {
|
|||||||
|
|
||||||
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
||||||
|
|
||||||
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
|
||||||
var dbPath string
|
|
||||||
v2uiCmd.StringVar(&dbPath, "db", fmt.Sprintf("%s/v2-ui.db", config.GetDBFolderPath()), "set v2-ui db file path")
|
|
||||||
|
|
||||||
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
||||||
var port int
|
var port int
|
||||||
var username string
|
var username string
|
||||||
@@ -301,7 +298,6 @@ func main() {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
fmt.Println(" run run web panel")
|
fmt.Println(" run run web panel")
|
||||||
fmt.Println(" v2-ui migrate form v2-ui")
|
|
||||||
fmt.Println(" migrate migrate form other/old x-ui")
|
fmt.Println(" migrate migrate form other/old x-ui")
|
||||||
fmt.Println(" setting set settings")
|
fmt.Println(" setting set settings")
|
||||||
}
|
}
|
||||||
@@ -322,16 +318,6 @@ func main() {
|
|||||||
runWebServer()
|
runWebServer()
|
||||||
case "migrate":
|
case "migrate":
|
||||||
migrateDb()
|
migrateDb()
|
||||||
case "v2-ui":
|
|
||||||
err := v2uiCmd.Parse(os.Args[2:])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = v2ui.MigrateFromV2UI(dbPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("migrate from v2-ui failed:", err)
|
|
||||||
}
|
|
||||||
case "setting":
|
case "setting":
|
||||||
err := settingCmd.Parse(os.Args[2:])
|
err := settingCmd.Parse(os.Args[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -356,12 +342,10 @@ func main() {
|
|||||||
updateTgbotEnableSts(enabletgbot)
|
updateTgbotEnableSts(enabletgbot)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
fmt.Println("except 'run' or 'setting' subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
runCmd.Usage()
|
runCmd.Usage()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
v2uiCmd.Usage()
|
|
||||||
fmt.Println()
|
|
||||||
settingCmd.Usage()
|
settingCmd.Usage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
sub/sub.go
17
sub/sub.go
@@ -7,10 +7,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
|
"x-ui/web/middleware"
|
||||||
"x-ui/web/network"
|
"x-ui/web/network"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
@@ -58,18 +58,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if subDomain != "" {
|
if subDomain != "" {
|
||||||
validateDomain := func(c *gin.Context) {
|
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
|
||||||
|
|
||||||
if host != subDomain {
|
|
||||||
c.AbortWithStatus(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
engine.Use(validateDomain)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g := engine.Group(subPath)
|
g := engine.Group(subPath)
|
||||||
@@ -116,11 +105,13 @@ func (s *Server) Start() (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
|
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
|
||||||
listener, err := net.Listen("tcp", listenAddr)
|
listener, err := net.Listen("tcp", listenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if certFile != "" || keyFile != "" {
|
if certFile != "" || keyFile != "" {
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -236,7 +236,6 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
obj["id"] = clients[clientIndex].ID
|
obj["id"] = clients[clientIndex].ID
|
||||||
obj["aid"] = clients[clientIndex].AlterIds
|
|
||||||
|
|
||||||
if len(domains) > 0 {
|
if len(domains) > 0 {
|
||||||
links := ""
|
links := ""
|
||||||
@@ -435,6 +434,10 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if security != "tls" && security != "reality" && security != "xtls" {
|
||||||
|
params["security"] = "none"
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
q := url.Query()
|
q := url.Query()
|
||||||
@@ -576,7 +579,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||||
params["pbk"], _ = pbkValue.(string)
|
params["pbk"], _ = pbkValue.(string)
|
||||||
}
|
}
|
||||||
if sidValue, ok := searchKey(realitySettings, "shortIds"); ok {
|
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||||
shortIds, _ := sidValue.([]interface{})
|
shortIds, _ := sidValue.([]interface{})
|
||||||
params["sid"], _ = shortIds[0].(string)
|
params["sid"], _ = shortIds[0].(string)
|
||||||
}
|
}
|
||||||
@@ -639,6 +642,10 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if security != "tls" && security != "reality" && security != "xtls" {
|
||||||
|
params["security"] = "none"
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
||||||
|
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
|
|||||||
28
v2ui/db.go
28
v2ui/db.go
@@ -1,28 +0,0 @@
|
|||||||
package v2ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
var v2db *gorm.DB
|
|
||||||
|
|
||||||
func initDB(dbPath string) error {
|
|
||||||
c := &gorm.Config{
|
|
||||||
Logger: logger.Discard,
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
v2db, err = gorm.Open(sqlite.Open(dbPath), c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getV2Inbounds() ([]*V2Inbound, error) {
|
|
||||||
inbounds := make([]*V2Inbound, 0)
|
|
||||||
err := v2db.Model(V2Inbound{}).Find(&inbounds).Error
|
|
||||||
return inbounds, err
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package v2ui
|
|
||||||
|
|
||||||
import "x-ui/database/model"
|
|
||||||
|
|
||||||
type V2Inbound struct {
|
|
||||||
Id int `gorm:"primaryKey;autoIncrement"`
|
|
||||||
Port int `gorm:"unique"`
|
|
||||||
Listen string
|
|
||||||
Protocol string
|
|
||||||
Settings string
|
|
||||||
StreamSettings string
|
|
||||||
Tag string `gorm:"unique"`
|
|
||||||
Sniffing string
|
|
||||||
Remark string
|
|
||||||
Up int64
|
|
||||||
Down int64
|
|
||||||
Enable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *V2Inbound) TableName() string {
|
|
||||||
return "inbound"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *V2Inbound) ToInbound(userId int) *model.Inbound {
|
|
||||||
return &model.Inbound{
|
|
||||||
UserId: userId,
|
|
||||||
Up: i.Up,
|
|
||||||
Down: i.Down,
|
|
||||||
Total: 0,
|
|
||||||
Remark: i.Remark,
|
|
||||||
Enable: i.Enable,
|
|
||||||
ExpiryTime: 0,
|
|
||||||
Listen: i.Listen,
|
|
||||||
Port: i.Port,
|
|
||||||
Protocol: model.Protocol(i.Protocol),
|
|
||||||
Settings: i.Settings,
|
|
||||||
StreamSettings: i.StreamSettings,
|
|
||||||
Tag: i.Tag,
|
|
||||||
Sniffing: i.Sniffing,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
51
v2ui/v2ui.go
51
v2ui/v2ui.go
@@ -1,51 +0,0 @@
|
|||||||
package v2ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"x-ui/config"
|
|
||||||
"x-ui/database"
|
|
||||||
"x-ui/database/model"
|
|
||||||
"x-ui/util/common"
|
|
||||||
"x-ui/web/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
func MigrateFromV2UI(dbPath string) error {
|
|
||||||
err := initDB(dbPath)
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("init v2-ui database failed:", err)
|
|
||||||
}
|
|
||||||
err = database.InitDB(config.GetDBPath())
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("init x-ui database failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
v2Inbounds, err := getV2Inbounds()
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("get v2-ui inbounds failed:", err)
|
|
||||||
}
|
|
||||||
if len(v2Inbounds) == 0 {
|
|
||||||
fmt.Println("migrate v2-ui inbounds success: 0")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
userService := service.UserService{}
|
|
||||||
user, err := userService.GetFirstUser()
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("get x-ui user failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
inbounds := make([]*model.Inbound, 0)
|
|
||||||
for _, v2inbound := range v2Inbounds {
|
|
||||||
inbounds = append(inbounds, v2inbound.ToInbound(user.Id))
|
|
||||||
}
|
|
||||||
|
|
||||||
inboundService := service.InboundService{}
|
|
||||||
err = inboundService.AddInbounds(inbounds)
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("add x-ui inbounds failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("migrate v2-ui inbounds success:", len(inbounds))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -202,9 +202,25 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark:hover {
|
.ant-card-dark:hover {
|
||||||
border-color: #e8e8e8;
|
/*border-color: #e8e8e8;
|
||||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 70%);
|
animation:light-shadow ease-in 3s infinite;*/
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 80%);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
@keyframes light-shadow {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 60%);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 60%);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
box-shadow: 0 1px 11px 2px rgb(154 175 238 / 70%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 60%);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
.ant-setting-textarea {
|
.ant-setting-textarea {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const supportLangs = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'فارسی',
|
name: 'فارسی',
|
||||||
value: 'fa_IR',
|
value: 'fa-IR',
|
||||||
icon: '🇮🇷',
|
icon: '🇮🇷',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,7 @@ const supportLangs = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Русский',
|
name: 'Русский',
|
||||||
value: 'ru_RU',
|
value: 'ru-RU',
|
||||||
icon: '🇷🇺',
|
icon: '🇷🇺',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ class AllSetting {
|
|||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.webListen = "";
|
this.webListen = "";
|
||||||
|
this.webDomain = "";
|
||||||
this.webPort = 2053;
|
this.webPort = 2053;
|
||||||
this.webCertFile = "";
|
this.webCertFile = "";
|
||||||
this.webKeyFile = "";
|
this.webKeyFile = "";
|
||||||
@@ -180,6 +181,7 @@ class AllSetting {
|
|||||||
this.tgBotChatId = "";
|
this.tgBotChatId = "";
|
||||||
this.tgRunTime = "@daily";
|
this.tgRunTime = "@daily";
|
||||||
this.tgBotBackup = false;
|
this.tgBotBackup = false;
|
||||||
|
this.tgBotLoginNotify = false;
|
||||||
this.tgCpu = "";
|
this.tgCpu = "";
|
||||||
this.tgLang = "en-US";
|
this.tgLang = "en-US";
|
||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
@@ -187,7 +189,7 @@ class AllSetting {
|
|||||||
this.subEnable = false;
|
this.subEnable = false;
|
||||||
this.subListen = "";
|
this.subListen = "";
|
||||||
this.subPort = "2096";
|
this.subPort = "2096";
|
||||||
this.subPath = "sub/";
|
this.subPath = "/sub/";
|
||||||
this.subDomain = "";
|
this.subDomain = "";
|
||||||
this.subCertFile = "";
|
this.subCertFile = "";
|
||||||
this.subKeyFile = "";
|
this.subKeyFile = "";
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ const Protocols = {
|
|||||||
TROJAN: 'trojan',
|
TROJAN: 'trojan',
|
||||||
SHADOWSOCKS: 'shadowsocks',
|
SHADOWSOCKS: 'shadowsocks',
|
||||||
DOKODEMO: 'dokodemo-door',
|
DOKODEMO: 'dokodemo-door',
|
||||||
MTPROTO: 'mtproto',
|
|
||||||
SOCKS: 'socks',
|
SOCKS: 'socks',
|
||||||
HTTP: 'http',
|
HTTP: 'http',
|
||||||
};
|
};
|
||||||
@@ -379,10 +378,15 @@ class WsStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HttpStreamSettings extends XrayCommonClass {
|
class HttpStreamSettings extends XrayCommonClass {
|
||||||
constructor(path='/', host=['']) {
|
constructor(
|
||||||
|
path='/',
|
||||||
|
host=[''],
|
||||||
|
sockopt={acceptProxyProtocol: false}
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.host = host.length === 0 ? [''] : host;
|
this.host = host.length === 0 ? [''] : host;
|
||||||
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
addHost(host) {
|
addHost(host) {
|
||||||
@@ -394,7 +398,7 @@ class HttpStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new HttpStreamSettings(json.path, json.host);
|
return new HttpStreamSettings(json.path, json.host, json.sockopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -407,10 +411,12 @@ class HttpStreamSettings extends XrayCommonClass {
|
|||||||
return {
|
return {
|
||||||
path: this.path,
|
path: this.path,
|
||||||
host: host,
|
host: host,
|
||||||
|
sockopt: this.sockopt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class QuicStreamSettings extends XrayCommonClass {
|
class QuicStreamSettings extends XrayCommonClass {
|
||||||
constructor(security=VmessMethods.NONE,
|
constructor(security=VmessMethods.NONE,
|
||||||
key='', type='none') {
|
key='', type='none') {
|
||||||
@@ -442,33 +448,39 @@ class QuicStreamSettings extends XrayCommonClass {
|
|||||||
class GrpcStreamSettings extends XrayCommonClass {
|
class GrpcStreamSettings extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
serviceName="",
|
serviceName="",
|
||||||
multiMode=false
|
multiMode=false,
|
||||||
|
sockopt={acceptProxyProtocol: false}
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.serviceName = serviceName;
|
this.serviceName = serviceName;
|
||||||
this.multiMode = multiMode;
|
this.multiMode = multiMode;
|
||||||
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new GrpcStreamSettings(
|
return new GrpcStreamSettings(
|
||||||
json.serviceName,
|
json.serviceName,
|
||||||
json.multiMode
|
json.multiMode,
|
||||||
|
json.sockopt
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
serviceName: this.serviceName,
|
serviceName: this.serviceName,
|
||||||
multiMode: this.multiMode
|
multiMode: this.multiMode,
|
||||||
|
sockopt: this.sockopt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TlsStreamSettings extends XrayCommonClass {
|
class TlsStreamSettings extends XrayCommonClass {
|
||||||
constructor(serverName='',
|
constructor(serverName='',
|
||||||
minVersion = TLS_VERSION_OPTION.TLS12,
|
minVersion = TLS_VERSION_OPTION.TLS12,
|
||||||
maxVersion = TLS_VERSION_OPTION.TLS13,
|
maxVersion = TLS_VERSION_OPTION.TLS13,
|
||||||
cipherSuites = '',
|
cipherSuites = '',
|
||||||
|
rejectUnknownSni = false,
|
||||||
certificates=[new TlsStreamSettings.Cert()],
|
certificates=[new TlsStreamSettings.Cert()],
|
||||||
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||||
settings=new TlsStreamSettings.Settings()) {
|
settings=new TlsStreamSettings.Settings()) {
|
||||||
@@ -477,6 +489,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
this.minVersion = minVersion;
|
this.minVersion = minVersion;
|
||||||
this.maxVersion = maxVersion;
|
this.maxVersion = maxVersion;
|
||||||
this.cipherSuites = cipherSuites;
|
this.cipherSuites = cipherSuites;
|
||||||
|
this.rejectUnknownSni = rejectUnknownSni;
|
||||||
this.certs = certificates;
|
this.certs = certificates;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
@@ -504,6 +517,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
json.minVersion,
|
json.minVersion,
|
||||||
json.maxVersion,
|
json.maxVersion,
|
||||||
json.cipherSuites,
|
json.cipherSuites,
|
||||||
|
json.rejectUnknownSni,
|
||||||
certs,
|
certs,
|
||||||
json.alpn,
|
json.alpn,
|
||||||
settings,
|
settings,
|
||||||
@@ -516,6 +530,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
minVersion: this.minVersion,
|
minVersion: this.minVersion,
|
||||||
maxVersion: this.maxVersion,
|
maxVersion: this.maxVersion,
|
||||||
cipherSuites: this.cipherSuites,
|
cipherSuites: this.cipherSuites,
|
||||||
|
rejectUnknownSni: this.rejectUnknownSni,
|
||||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
settings: this.settings,
|
settings: this.settings,
|
||||||
@@ -1207,7 +1222,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
add: address,
|
add: address,
|
||||||
port: this.port,
|
port: this.port,
|
||||||
id: this.settings.vmesses[clientIndex].id,
|
id: this.settings.vmesses[clientIndex].id,
|
||||||
aid: this.settings.vmesses[clientIndex].alterId,
|
|
||||||
net: this.stream.network,
|
net: this.stream.network,
|
||||||
type: 'none',
|
type: 'none',
|
||||||
tls: this.stream.security,
|
tls: this.stream.security,
|
||||||
@@ -1344,7 +1358,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.xtls) {
|
else if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.xtls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.xtls.settings.allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
@@ -1359,7 +1373,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
else if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
@@ -1380,6 +1394,10 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
params.set("security", "none");
|
||||||
|
}
|
||||||
|
|
||||||
const link = `vless://${uuid}@${address}:${port}`;
|
const link = `vless://${uuid}@${address}:${port}`;
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
for (const [key, value] of params) {
|
for (const [key, value] of params) {
|
||||||
@@ -1465,7 +1483,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
else if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
@@ -1483,7 +1501,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.xtls) {
|
else if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.xtls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.xtls.settings.allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
@@ -1498,6 +1516,10 @@ class Inbound extends XrayCommonClass {
|
|||||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
params.set("flow", this.settings.trojans[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
params.set("security", "none");
|
||||||
|
}
|
||||||
|
|
||||||
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}`;
|
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}`;
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
for (const [key, value] of params) {
|
for (const [key, value] of params) {
|
||||||
@@ -1586,7 +1608,6 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||||||
case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol);
|
case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol);
|
||||||
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol);
|
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol);
|
||||||
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol);
|
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol);
|
||||||
case Protocols.MTPROTO: return new Inbound.MtprotoSettings(protocol);
|
|
||||||
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
||||||
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
||||||
default: return null;
|
default: return null;
|
||||||
@@ -1600,7 +1621,6 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||||||
case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json);
|
case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json);
|
||||||
case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json);
|
case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json);
|
||||||
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
|
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
|
||||||
case Protocols.MTPROTO: return Inbound.MtprotoSettings.fromJson(json);
|
|
||||||
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
||||||
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
||||||
default: return null;
|
default: return null;
|
||||||
@@ -1655,10 +1675,9 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.alterId = alterId;
|
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.limitIp = limitIp;
|
this.limitIp = limitIp;
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
@@ -1671,7 +1690,6 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
|||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new Inbound.VmessSettings.Vmess(
|
return new Inbound.VmessSettings.Vmess(
|
||||||
json.id,
|
json.id,
|
||||||
json.alterId,
|
|
||||||
json.email,
|
json.email,
|
||||||
json.limitIp,
|
json.limitIp,
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
@@ -1747,7 +1765,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||||||
|
|
||||||
};
|
};
|
||||||
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
@@ -1870,7 +1888,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
super();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
@@ -2012,7 +2030,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
constructor(password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
constructor(password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
super();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
@@ -2106,36 +2124,6 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Inbound.MtprotoSettings = class extends Inbound.Settings {
|
|
||||||
constructor(protocol, users=[new Inbound.MtprotoSettings.MtUser()]) {
|
|
||||||
super(protocol);
|
|
||||||
this.users = users;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(json={}) {
|
|
||||||
return new Inbound.MtprotoSettings(
|
|
||||||
Protocols.MTPROTO,
|
|
||||||
json.users.map(user => Inbound.MtprotoSettings.MtUser.fromJson(user)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJson() {
|
|
||||||
return {
|
|
||||||
users: XrayCommonClass.toJsonArray(this.users),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Inbound.MtprotoSettings.MtUser = class extends XrayCommonClass {
|
|
||||||
constructor(secret=RandomUtil.randomMTSecret()) {
|
|
||||||
super();
|
|
||||||
this.secret = secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(json={}) {
|
|
||||||
return new Inbound.MtprotoSettings.MtUser(json.secret);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Inbound.SocksSettings = class extends Inbound.Settings {
|
Inbound.SocksSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol, auth='password', accounts=[new Inbound.SocksSettings.SocksAccount()], udp=false, ip='127.0.0.1') {
|
constructor(protocol, auth='password', accounts=[new Inbound.SocksSettings.SocksAccount()], udp=false, ip='127.0.0.1') {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
|
|||||||
@@ -135,3 +135,21 @@ function doAllItemsExist(array1, array2) {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildURL({ host, port, isTLS, base, path }) {
|
||||||
|
if (!host || host.length === 0) host = window.location.hostname;
|
||||||
|
if (!port || port.length === 0) port = window.location.port;
|
||||||
|
|
||||||
|
if (isTLS === undefined) isTLS = window.location.protocol === "https:";
|
||||||
|
|
||||||
|
const protocol = isTLS ? "https:" : "http:";
|
||||||
|
|
||||||
|
port = String(port);
|
||||||
|
if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
|
||||||
|
port = "";
|
||||||
|
} else {
|
||||||
|
port = `:${port}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${protocol}//${host}${port}${base}${path}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,9 +75,7 @@ class PromiseUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const seq = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
const seq = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||||
|
|
||||||
const shortIdSeq = 'abcdef0123456789'.split('');
|
|
||||||
|
|
||||||
class RandomUtil {
|
class RandomUtil {
|
||||||
static randomIntRange(min, max) {
|
static randomIntRange(min, max) {
|
||||||
@@ -96,57 +94,33 @@ class RandomUtil {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomShortIdSeq(count) {
|
static randomShortId() {
|
||||||
let str = '';
|
let str = '';
|
||||||
for (let i = 0; i < count; ++i) {
|
for (let i = 0; i < 8; ++i) {
|
||||||
str += shortIdSeq[this.randomInt(16)];
|
str += seq[this.randomInt(16)];
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomShortId() {
|
static randomLowerAndNum(len) {
|
||||||
return this.randomShortIdSeq(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
static randomLowerAndNum(count) {
|
|
||||||
let str = '';
|
let str = '';
|
||||||
for (let i = 0; i < count; ++i) {
|
for (let i = 0; i < len; ++i) {
|
||||||
str += seq[this.randomInt(36)];
|
str += seq[this.randomInt(36)];
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomMTSecret() {
|
|
||||||
let str = '';
|
|
||||||
for (let i = 0; i < 32; ++i) {
|
|
||||||
let index = this.randomInt(16);
|
|
||||||
if (index <= 9) {
|
|
||||||
str += index;
|
|
||||||
} else {
|
|
||||||
str += seq[index - 10];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
static randomUUID() {
|
static randomUUID() {
|
||||||
let d = new Date().getTime();
|
const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
|
||||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
return template.replace(/[xy]/g, function (c) {
|
||||||
let r = (d + Math.random() * 16) % 16 | 0;
|
const randomValues = new Uint8Array(1);
|
||||||
d = Math.floor(d / 16);
|
crypto.getRandomValues(randomValues);
|
||||||
return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16);
|
let randomValue = randomValues[0] % 16;
|
||||||
|
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
|
||||||
|
return calculatedValue.toString(16);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomText() {
|
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
|
||||||
var string = '';
|
|
||||||
for (var ii = 0; ii < 8; ii++) {
|
|
||||||
string += chars[Math.floor(Math.random() * chars.length)];
|
|
||||||
}
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
static randomShadowsocksPassword() {
|
static randomShadowsocksPassword() {
|
||||||
let array = new Uint8Array(32);
|
let array = new Uint8Array(32);
|
||||||
window.crypto.getRandomValues(array);
|
window.crypto.getRandomValues(array);
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
jsonObj(c, inbounds, nil)
|
jsonObj(c, inbounds, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) getInbound(c *gin.Context) {
|
func (a *InboundController) getInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -78,7 +79,6 @@ func (a *InboundController) getInbound(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
jsonObj(c, inbound, nil)
|
jsonObj(c, inbound, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) getClientTraffics(c *gin.Context) {
|
func (a *InboundController) getClientTraffics(c *gin.Context) {
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email)
|
clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email)
|
||||||
@@ -145,12 +145,7 @@ func (a *InboundController) getClientIps(c *gin.Context) {
|
|||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
ips, err := a.inboundService.GetInboundClientIps(email)
|
ips, err := a.inboundService.GetInboundClientIps(email)
|
||||||
if err != nil {
|
if err != nil || ips == "" {
|
||||||
jsonObj(c, "Failed to get client IPs", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ips == "" {
|
|
||||||
jsonObj(c, "No IP Record", nil)
|
jsonObj(c, "No IP Record", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -168,6 +163,7 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
jsonMsg(c, "Log Cleared", nil)
|
jsonMsg(c, "Log Cleared", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) addInboundClient(c *gin.Context) {
|
func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||||
data := &model.Inbound{}
|
data := &model.Inbound{}
|
||||||
err := c.ShouldBind(data)
|
err := c.ShouldBind(data)
|
||||||
@@ -176,13 +172,15 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.inboundService.AddInboundClient(data)
|
needRestart := false
|
||||||
|
|
||||||
|
needRestart, err = a.inboundService.AddInboundClient(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client(s) added", nil)
|
jsonMsg(c, "Client(s) added", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,13 +193,15 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
clientId := c.Param("clientId")
|
clientId := c.Param("clientId")
|
||||||
|
|
||||||
err = a.inboundService.DelInboundClient(id, clientId)
|
needRestart := false
|
||||||
|
|
||||||
|
needRestart, err = a.inboundService.DelInboundClient(id, clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client deleted", nil)
|
jsonMsg(c, "Client deleted", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,13 +216,15 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
needRestart := false
|
||||||
|
|
||||||
|
needRestart, err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client updated", nil)
|
jsonMsg(c, "Client updated", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,13 +237,15 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
err = a.inboundService.ResetClientTraffic(id, email)
|
needRestart := false
|
||||||
|
|
||||||
|
needRestart, err = a.inboundService.ResetClientTraffic(id, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "traffic reseted", nil)
|
jsonMsg(c, "traffic reseted", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,6 +255,8 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics reseted", nil)
|
jsonMsg(c, "All traffics reseted", nil)
|
||||||
}
|
}
|
||||||
@@ -266,6 +272,8 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics of client reseted", nil)
|
jsonMsg(c, "All traffics of client reseted", nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
|
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
|
||||||
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||||
if user == nil {
|
if user == nil {
|
||||||
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||||
return
|
return
|
||||||
@@ -75,13 +75,13 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
|
|
||||||
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("Unable to get session's max age from DB")
|
logger.Warningf("Unable to get session's max age from DB")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sessionMaxAge > 0 {
|
if sessionMaxAge > 0 {
|
||||||
err = session.SetMaxAge(c, sessionMaxAge*60)
|
err = session.SetMaxAge(c, sessionMaxAge*60)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("Unable to set session's max age")
|
logger.Warningf("Unable to set session's max age")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,8 @@ func (a *ServerController) restartXrayService(c *gin.Context) {
|
|||||||
|
|
||||||
func (a *ServerController) getLogs(c *gin.Context) {
|
func (a *ServerController) getLogs(c *gin.Context) {
|
||||||
count := c.Param("count")
|
count := c.Param("count")
|
||||||
logs, err := a.serverService.GetLogs(count)
|
logLevel := c.PostForm("logLevel")
|
||||||
|
logs, err := a.serverService.GetLogs(count, logLevel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "getLogs", err)
|
jsonMsg(c, "getLogs", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -65,77 +65,42 @@ func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||||
expireDiff, err := a.settingService.GetExpireDiff()
|
type settingFunc func() (interface{}, error)
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
settings := map[string]settingFunc{
|
||||||
return
|
"expireDiff": func() (interface{}, error) { return a.settingService.GetExpireDiff() },
|
||||||
|
"trafficDiff": func() (interface{}, error) { return a.settingService.GetTrafficDiff() },
|
||||||
|
"defaultCert": func() (interface{}, error) { return a.settingService.GetCertFile() },
|
||||||
|
"defaultKey": func() (interface{}, error) { return a.settingService.GetKeyFile() },
|
||||||
|
"tgBotEnable": func() (interface{}, error) { return a.settingService.GetTgbotenabled() },
|
||||||
|
"subEnable": func() (interface{}, error) { return a.settingService.GetSubEnable() },
|
||||||
|
"subPort": func() (interface{}, error) { return a.settingService.GetSubPort() },
|
||||||
|
"subPath": func() (interface{}, error) { return a.settingService.GetSubPath() },
|
||||||
|
"subDomain": func() (interface{}, error) { return a.settingService.GetSubDomain() },
|
||||||
|
"subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
|
||||||
|
"subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
|
||||||
}
|
}
|
||||||
trafficDiff, err := a.settingService.GetTrafficDiff()
|
|
||||||
if err != nil {
|
result := make(map[string]interface{})
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
for key, fn := range settings {
|
||||||
}
|
value, err := fn()
|
||||||
defaultCert, err := a.settingService.GetCertFile()
|
if err != nil {
|
||||||
if err != nil {
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
return
|
||||||
return
|
}
|
||||||
}
|
result[key] = value
|
||||||
defaultKey, err := a.settingService.GetKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tgBotEnable, err := a.settingService.GetTgbotenabled()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subEnable, err := a.settingService.GetSubEnable()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subPort, err := a.settingService.GetSubPort()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subPath, err := a.settingService.GetSubPath()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subDomain, err := a.settingService.GetSubDomain()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subKeyFile, err := a.settingService.GetSubKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subCertFile, err := a.settingService.GetSubCertFile()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subTLS := false
|
subTLS := false
|
||||||
if subKeyFile != "" || subCertFile != "" {
|
if result["subKeyFile"] != "" || result["subCertFile"] != "" {
|
||||||
subTLS = true
|
subTLS = true
|
||||||
}
|
}
|
||||||
result := map[string]interface{}{
|
result["subTLS"] = subTLS
|
||||||
"expireDiff": expireDiff,
|
|
||||||
"trafficDiff": trafficDiff,
|
delete(result, "subKeyFile")
|
||||||
"defaultCert": defaultCert,
|
delete(result, "subCertFile")
|
||||||
"defaultKey": defaultKey,
|
|
||||||
"tgBotEnable": tgBotEnable,
|
|
||||||
"subEnable": subEnable,
|
|
||||||
"subPort": subPort,
|
|
||||||
"subPath": subPath,
|
|
||||||
"subDomain": subDomain,
|
|
||||||
"subTLS": subTLS,
|
|
||||||
}
|
|
||||||
jsonObj(c, result, nil)
|
jsonObj(c, result, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type Pager struct {
|
|||||||
|
|
||||||
type AllSetting struct {
|
type AllSetting struct {
|
||||||
WebListen string `json:"webListen" form:"webListen"`
|
WebListen string `json:"webListen" form:"webListen"`
|
||||||
|
WebDomain string `json:"webDomain" form:"webDomain"`
|
||||||
WebPort int `json:"webPort" form:"webPort"`
|
WebPort int `json:"webPort" form:"webPort"`
|
||||||
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
||||||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||||
@@ -40,6 +41,7 @@ type AllSetting struct {
|
|||||||
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
||||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||||
|
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
TgLang string `json:"tgLang" form:"tgLang"`
|
TgLang string `json:"tgLang" form:"tgLang"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type HashStorage struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
Data map[string]HashEntry
|
Data map[string]HashEntry
|
||||||
Expiration time.Duration
|
Expiration time.Duration
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHashStorage(expiration time.Duration) *HashStorage {
|
func NewHashStorage(expiration time.Duration) *HashStorage {
|
||||||
@@ -46,7 +45,6 @@ func (h *HashStorage) SaveHash(query string) string {
|
|||||||
return md5HashString
|
return md5HashString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (h *HashStorage) GetValue(hash string) (string, bool) {
|
func (h *HashStorage) GetValue(hash string) (string, bool) {
|
||||||
h.RLock()
|
h.RLock()
|
||||||
defer h.RUnlock()
|
defer h.RUnlock()
|
||||||
|
|||||||
@@ -68,8 +68,8 @@
|
|||||||
qrModal: qrModal,
|
qrModal: qrModal,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyToClipboard(elmentId,content) {
|
copyToClipboard(elmentId, content) {
|
||||||
this.qrModal.clipboard = new ClipboardJS('#'+elmentId, {
|
this.qrModal.clipboard = new ClipboardJS('#' + elmentId, {
|
||||||
text: () => content,
|
text: () => content,
|
||||||
});
|
});
|
||||||
this.qrModal.clipboard.on('success', () => {
|
this.qrModal.clipboard.on('success', () => {
|
||||||
@@ -77,29 +77,25 @@
|
|||||||
this.qrModal.clipboard.destroy();
|
this.qrModal.clipboard.destroy();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setQrCode(elmentId,content) {
|
setQrCode(elmentId, content) {
|
||||||
new QRious({
|
new QRious({
|
||||||
element: document.querySelector('#'+elmentId),
|
element: document.querySelector('#' + elmentId),
|
||||||
size: 260,
|
size: 260,
|
||||||
value: content,
|
value: content,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
protocol = app.subSettings.tls ? "https://" : "http://";
|
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||||
hostName = app.subSettings.domain === "" ? window.location.hostname : app.subSettings.domain;
|
return buildURL({ host, port, isTLS, base, path: subID });
|
||||||
subPort = app.subSettings.port;
|
|
||||||
port = (subPort === 443 && app.subSettings.tls) || (subPort === 80 && !app.subSettings.tls) ? "" : ":" + String(subPort);
|
|
||||||
subPath = app.subSettings.path;
|
|
||||||
return protocol + hostName + port + subPath + subID;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
if (qrModal.client.subId){
|
if (qrModal.client && qrModal.client.subId) {
|
||||||
qrModal.subId = qrModal.client.subId;
|
qrModal.subId = qrModal.client.subId;
|
||||||
this.setQrCode("qrCode-sub",this.genSubLink(qrModal.subId));
|
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
|
||||||
}
|
}
|
||||||
qrModal.qrcodes.forEach((element,index) => {
|
qrModal.qrcodes.forEach((element, index) => {
|
||||||
this.setQrCode("qrCode-"+index, element.link);
|
this.setQrCode("qrCode-" + index, element.link);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -86,8 +86,8 @@
|
|||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
|
|||||||
@@ -120,26 +120,32 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async getDBClientIps(email, event) {
|
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) {
|
||||||
|
document.getElementById("clientIPs").value = msg.obj;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
let ips = msg.obj;
|
||||||
ips = JSON.parse(msg.obj)
|
if (typeof ips === 'string' && ips.startsWith('[') && ips.endsWith(']')) {
|
||||||
ips = ips.join(",")
|
try {
|
||||||
event.target.value = ips
|
ips = JSON.parse(ips);
|
||||||
} catch (error) {
|
ips = Array.isArray(ips) ? ips.join("\n") : ips;
|
||||||
// text
|
} catch (e) {
|
||||||
event.target.value = msg.obj
|
console.error('Error parsing JSON:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
document.getElementById("clientIPs").value = ips;
|
||||||
},
|
},
|
||||||
async clearDBClientIps(email) {
|
async clearDBClientIps(email) {
|
||||||
const msg = await HttpUtil.post('/panel/inbound/clearClientIps/' + email);
|
try {
|
||||||
if (!msg.success) {
|
const msg = await HttpUtil.post(`/panel/inbound/clearClientIps/${email}`);
|
||||||
return;
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById("clientIPs").value = "";
|
||||||
|
} catch (error) {
|
||||||
}
|
}
|
||||||
document.getElementById("clientIPs").value = ""
|
|
||||||
},
|
},
|
||||||
resetClientTraffic(email, dbInboundId, iconElement) {
|
resetClientTraffic(email, dbInboundId, iconElement) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
<a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
||||||
@@ -27,9 +27,6 @@
|
|||||||
<a-input v-model.trim="client.password" style="width: 300px;"></a-input>
|
<a-input v-model.trim="client.password" style="width: 300px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
|
|
||||||
<a-input-number v-model="client.alterId"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="ID" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
<a-form-item label="ID" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||||
@@ -44,7 +41,7 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable" >
|
<a-form-item v-if="client.email && app.tgBotEnable" >
|
||||||
@@ -72,31 +69,32 @@
|
|||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
||||||
</span>
|
</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-form layout="block">
|
<a-form layout="block">
|
||||||
<a-textarea id="clientIPs" readonly
|
<a-textarea id="clientIPs" readonly
|
||||||
@click="getDBClientIps(client.email,$event)"
|
@click="getDBClientIps(client.email)"
|
||||||
placeholder="Click To Get IPs"
|
placeholder="Click To Get IPs"
|
||||||
:auto-size="{ minRows: 2, maxRows: 10 }">
|
:auto-size="{ minRows: 5, maxRows: 10 }"
|
||||||
</a-textarea>
|
>
|
||||||
</a-form>
|
</a-textarea>
|
||||||
</a-form-item>
|
</a-form>
|
||||||
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
{{define "form/inbound"}}
|
{{define "form/inbound"}}
|
||||||
<!-- base -->
|
<!-- base -->
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "remark" }}'>
|
|
||||||
<a-input v-model.trim="dbInbound.remark"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "enable" }}'>
|
<a-form-item label='{{ i18n "enable" }}'>
|
||||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
|
<a-form-item label='{{ i18n "remark" }}'>
|
||||||
|
<a-input v-model.trim="dbInbound.remark"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "protocol" }}'>
|
<a-form-item label='{{ i18n "protocol" }}'>
|
||||||
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Password">
|
<a-form-item label="Password">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
@@ -96,10 +96,12 @@
|
|||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.shadowsockses.length">
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.shadowsockses.length">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr class="client-table-header">
|
<tr class="client-table-header">
|
||||||
<th v-for="col in Object.keys(inbound.settings.shadowsockses[0]).slice(0, 3)">[[ col ]]</th>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>Password</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.shadowsockses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
<tr v-for="(client, index) in inbound.settings.shadowsockses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.password ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
@@ -112,6 +114,7 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
|
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
|
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Password">
|
<a-form-item label="Password">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
@@ -102,10 +102,12 @@
|
|||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr class="client-table-header">
|
<tr class="client-table-header">
|
||||||
<th v-for="col in Object.keys(inbound.settings.trojans[0]).slice(0, 3)">[[ col ]]</th>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>Password</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.password ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="ID">
|
<a-form-item label="ID">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
@@ -108,10 +108,14 @@
|
|||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr class="client-table-header">
|
<tr class="client-table-header">
|
||||||
<th v-for="col in Object.keys(inbound.settings.vlesses[0]).slice(0, 3)">[[ col ]]</th>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>Flow</th>
|
||||||
|
<th>ID</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.flow ]]</td>
|
||||||
|
<td>[[ client.id ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
|
|||||||
@@ -11,14 +11,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item label='{{ i18n "additional" }} ID'>
|
|
||||||
<a-input-number v-model="client.alterId"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label="ID">
|
<a-form-item label="ID">
|
||||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||||
@@ -33,7 +29,7 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
@@ -101,10 +97,12 @@
|
|||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount" }}: ' + inbound.settings.vmesses.length">
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount" }}: ' + inbound.settings.vmesses.length">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr class="client-table-header">
|
<tr class="client-table-header">
|
||||||
<th v-for="col in Object.keys(inbound.settings.vmesses[0]).slice(0, 3)">[[ col ]]</th>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>ID</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.id ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{{define "form/streamGRPC"}}
|
{{define "form/streamGRPC"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
|
<a-form-item label="AcceptProxyProtocol">
|
||||||
|
<a-switch v-model="inbound.stream.grpc.sockopt.acceptProxyProtocol"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="ServiceName">
|
<a-form-item label="ServiceName">
|
||||||
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{{define "form/streamHTTP"}}
|
{{define "form/streamHTTP"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
|
<a-form-item label="AcceptProxyProtocol">
|
||||||
|
<a-switch v-model="inbound.stream.http.sockopt.acceptProxyProtocol"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
<a-form-item label="AcceptProxyProtocol">
|
<a-form-item label="AcceptProxyProtocol">
|
||||||
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
<br>
|
||||||
<a-form layout="inline">
|
|
||||||
<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>
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
<a-form v-if="inbound.tls" layout="inline">
|
<a-form v-if="inbound.tls" layout="inline">
|
||||||
<a-form-item label='Multi Domain'>
|
<a-form-item label='Multi Domain'>
|
||||||
<a-switch v-model="multiDomain"></a-switch>
|
<a-switch v-model="multiDomain"></a-switch>
|
||||||
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="multiDomain">
|
<a-form-item v-if="multiDomain">
|
||||||
<a-row>
|
<a-row>
|
||||||
@@ -85,9 +84,14 @@
|
|||||||
<a-checkbox v-for="key,value in ALPN_OPTION" :value="key">[[ value ]]</a-checkbox>
|
<a-checkbox v-for="key,value in ALPN_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||||
</a-checkbox-group>
|
</a-checkbox-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="Allow insecure">
|
<a-form-item label="Allow insecure">
|
||||||
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
|
<a-form-item label="Reject Unknown SNI">
|
||||||
|
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
||||||
|
</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">
|
||||||
|
|||||||
@@ -29,11 +29,25 @@
|
|||||||
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, client">
|
<template slot="traffic" slot-scope="text, client">
|
||||||
<a-tag :color="statsColor(record, client.email)">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag>
|
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
||||||
<template v-if="client._totalGB > 0">
|
<template slot="content" v-if="client.email">
|
||||||
<a-tag :color="statsColor(record, client.email)">[[client._totalGB]]GB</a-tag>
|
<table cellpadding="2" width="100%">
|
||||||
</template>
|
<tr>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
|
||||||
|
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="client.totalGB > 0">
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(client.totalGB - getUpStats(record, client.email) - getDownStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-tag :color="statsColor(record, client.email)">
|
||||||
|
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] /
|
||||||
|
<template v-if="client.totalGB > 0">[[client._totalGB]]GB</template>
|
||||||
|
<template v-else>♾</template>
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, client, index">
|
<template slot="expiryTime" slot-scope="text, client, index">
|
||||||
<template v-if="client.expiryTime > 0">
|
<template v-if="client.expiryTime > 0">
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
width="600px"
|
width="600px"
|
||||||
>
|
>
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
<table style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr>
|
<tr><td>
|
||||||
<td>
|
|
||||||
<table>
|
<table>
|
||||||
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
||||||
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
|
||||||
@@ -21,7 +20,6 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
||||||
<tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr>
|
<tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr>
|
||||||
<tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
<tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||||
@@ -46,8 +44,7 @@
|
|||||||
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td></tr>
|
||||||
</tr>
|
|
||||||
<tr colspan="2" v-if="dbInbound.hasLink()">
|
<tr colspan="2" v-if="dbInbound.hasLink()">
|
||||||
<td v-if="inbound.tls">
|
<td v-if="inbound.tls">
|
||||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
@@ -66,36 +63,56 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<template v-if="infoModal.clientSettings">
|
<template v-if="infoModal.clientSettings">
|
||||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
<table style="margin-bottom: 10px;">
|
<table style="margin-bottom: 10px;">
|
||||||
<tr v-for="col,index in Object.keys(infoModal.clientSettings).slice(0, 3)">
|
|
||||||
<td>[[ col ]]</td>
|
|
||||||
<td><a-tag color="green">[[ infoModal.clientSettings[col] ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "status" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
|
||||||
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
|
||||||
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ i18n "usage" }}</th>
|
<td>{{ i18n "pages.inbounds.email" }}</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.email ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.clientSettings.id">
|
||||||
|
<td>ID</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.id ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.inbound.canEnableTlsFlow()">
|
||||||
|
<td>Flow</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.flow ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.clientSettings.password">
|
||||||
|
<td>Password</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.password ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "status" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
||||||
|
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
|
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.clientStats">
|
||||||
|
<td>{{ i18n "usage" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
|
||||||
|
<a-tag color="blue">↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<table style="margin-bottom: 10px; width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<th>{{ i18n "remained" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
|
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="infoModal.clientStats" color="green">
|
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
||||||
[[ sizeFormat(infoModal.clientStats['up']) ]] /
|
[[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
|
||||||
[[ sizeFormat(infoModal.clientStats['down']) ]]
|
|
||||||
([[ sizeFormat(infoModal.clientStats['up'] + infoModal.clientStats['down']) ]])
|
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">[[ sizeFormat(infoModal.clientSettings.totalGB) ]]</a-tag>
|
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
||||||
|
[[ sizeFormat(infoModal.clientSettings.totalGB) ]]
|
||||||
|
</a-tag>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -111,15 +128,44 @@
|
|||||||
</table>
|
</table>
|
||||||
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
||||||
<a-divider>Subscription link</a-divider>
|
<a-divider>Subscription link</a-divider>
|
||||||
<a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
|
<a-row>
|
||||||
<a-icon id="copy-sub-link" type="snippets" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"></a-icon>
|
<a-col :span="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
||||||
|
<a-col :span="2">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||||
<a-divider>Telegram Username</a-divider>
|
<a-divider>Telegram Username</a-divider>
|
||||||
<a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a>
|
<a-row>
|
||||||
<a-icon id="copy-tg-link" type="snippets" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)"></a-icon>
|
<a-col :span="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
|
||||||
|
<a-col :span="2">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="dbInbound.hasLink()">
|
||||||
|
<a-divider>URL</a-divider>
|
||||||
|
<a-row v-for="(link,index) in infoModal.links">
|
||||||
|
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||||
|
<a-col :span="2" style="text-align: right;">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-divider></a-divider>
|
<a-divider></a-divider>
|
||||||
<table v-if="inbound.protocol == Protocols.SHADOWSOCKS" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="inbound.protocol == Protocols.SHADOWSOCKS" style="margin-bottom: 10px; width: 100%;">
|
||||||
@@ -127,8 +173,7 @@
|
|||||||
<th>{{ i18n "encryption" }}</th>
|
<th>{{ i18n "encryption" }}</th>
|
||||||
<th>{{ i18n "password" }}</th>
|
<th>{{ i18n "password" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.network" }}</th>
|
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||||
</tr>
|
</tr><tr>
|
||||||
<tr>
|
|
||||||
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
@@ -140,8 +185,7 @@
|
|||||||
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
|
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.network" }}</th>
|
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||||
<th>FollowRedirect</th>
|
<th>FollowRedirect</th>
|
||||||
</tr>
|
</tr><tr>
|
||||||
<tr>
|
|
||||||
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
@@ -154,18 +198,15 @@
|
|||||||
<th>{{ i18n "password" }} Auth</th>
|
<th>{{ i18n "password" }} Auth</th>
|
||||||
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
||||||
<th>IP</th>
|
<th>IP</th>
|
||||||
</tr>
|
</tr><tr>
|
||||||
<tr>
|
|
||||||
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
||||||
</tr>
|
</tr><tr v-if="inbound.settings.auth == 'password'">
|
||||||
<tr v-if="inbound.settings.auth == 'password'">
|
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td>{{ i18n "username" }}</td>
|
<td>{{ i18n "username" }}</td>
|
||||||
<td>{{ i18n "password" }}</td>
|
<td>{{ i18n "password" }}</td>
|
||||||
</tr>
|
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||||
<tr v-for="account,index in inbound.settings.accounts">
|
|
||||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||||
@@ -177,8 +218,7 @@
|
|||||||
<th> </th>
|
<th> </th>
|
||||||
<th>{{ i18n "username" }}</th>
|
<th>{{ i18n "username" }}</th>
|
||||||
<th>{{ i18n "password" }}</th>
|
<th>{{ i18n "password" }}</th>
|
||||||
</tr>
|
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||||
<tr v-for="account,index in inbound.settings.accounts">
|
|
||||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||||
@@ -186,20 +226,8 @@
|
|||||||
</table>
|
</table>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="dbInbound.hasLink()">
|
|
||||||
<a-divider>URL</a-divider>
|
|
||||||
<a-row v-for="(link,index) in infoModal.links">
|
|
||||||
<a-col :span="21"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
|
||||||
<a-col :span="3" style="text-align: right;">
|
|
||||||
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
|
||||||
<a-icon type="snippets"></a-icon>{{ i18n "copy" }}
|
|
||||||
</button>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</div>
|
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const infoModal = {
|
const infoModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
@@ -253,12 +281,8 @@
|
|||||||
infoModal.visible = false;
|
infoModal.visible = false;
|
||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
protocol = app.subSettings.tls ? "https://" : "http://";
|
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||||
hostName = app.subSettings.domain === "" ? window.location.hostname : app.subSettings.domain;
|
return buildURL({ host, port, isTLS, base, path: subID });
|
||||||
subPort = app.subSettings.port;
|
|
||||||
port = (subPort === 443 && app.subSettings.tls) || (subPort === 80 && !app.subSettings.tls) ? "" : ":" + String(subPort);
|
|
||||||
subPath = app.subSettings.path;
|
|
||||||
return protocol + hostName + port + subPath + subID;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
set multiDomain(value) {
|
set multiDomain(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
inModal.inbound.stream.tls.server = "";
|
inModal.inbound.stream.tls.server = "";
|
||||||
inModal.inbound.stream.tls.settings.domains = [{remark: "", domain: window.location.host.split(":")[0]}];
|
inModal.inbound.stream.tls.settings.domains = [{ remark: "", domain: window.location.hostname }];
|
||||||
} else {
|
} else {
|
||||||
inModal.inbound.stream.tls.server = "";
|
inModal.inbound.stream.tls.server = "";
|
||||||
inModal.inbound.stream.tls.settings.domains = [];
|
inModal.inbound.stream.tls.settings.domains = [];
|
||||||
|
|||||||
@@ -206,12 +206,27 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, dbInbound">
|
<template slot="traffic" slot-scope="text, dbInbound">
|
||||||
<a-tag color="blue">[[ sizeFormat(dbInbound.up) ]] / [[ sizeFormat(dbInbound.down) ]]</a-tag>
|
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
||||||
<template v-if="dbInbound.total > 0">
|
<template slot="content">
|
||||||
<a-tag v-if="dbInbound.up + dbInbound.down < dbInbound.total" color="cyan">[[ sizeFormat(dbInbound.total) ]]</a-tag>
|
<table cellpadding="2" width="100%">
|
||||||
<a-tag v-else color="red">[[ sizeFormat(dbInbound.total) ]]</a-tag>
|
<tr>
|
||||||
</template>
|
<td>↑[[ sizeFormat(dbInbound.up) ]]</td>
|
||||||
<a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag>
|
<td>↓[[ sizeFormat(dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-tag :color="dbInbound.total == 0 ? 'green' : dbInbound.up + dbInbound.down < dbInbound.total ? 'cyan' : 'red'">
|
||||||
|
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
|
<template v-if="dbInbound.total > 0">
|
||||||
|
[[ sizeFormat(dbInbound.total) ]]
|
||||||
|
</template>
|
||||||
|
<template v-else>♾</template>
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="enable" slot-scope="text, dbInbound">
|
<template slot="enable" slot-scope="text, dbInbound">
|
||||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||||
@@ -291,12 +306,12 @@
|
|||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "clients" }}',
|
title: '{{ i18n "clients" }}',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 50,
|
width: 40,
|
||||||
scopedSlots: { customRender: 'clients' },
|
scopedSlots: { customRender: 'clients' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
|
title: '{{ i18n "pages.inbounds.traffic" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 120,
|
width: 60,
|
||||||
scopedSlots: { customRender: 'traffic' },
|
scopedSlots: { customRender: 'traffic' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
||||||
@@ -309,17 +324,17 @@
|
|||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'UID', width: 120, dataIndex: "id" },
|
{ title: 'UUID', width: 120, dataIndex: "id" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const innerTrojanColumns = [
|
const innerTrojanColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'Password', width: 170, dataIndex: "password" },
|
{ title: 'Password', width: 170, dataIndex: "password" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -78,15 +78,15 @@
|
|||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
3x-ui: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
3X: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||||
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
||||||
Telegram: <a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
{{ i18n "menu.link" }}:
|
{{ i18n "menu.link" }}:
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">{{ i18n "pages.index.logs" }}</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(logModal.rows, logModal.logLevel)">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
<a-form-item label="Count">
|
<a-form-item label="Count">
|
||||||
<a-select v-model="logModal.rows"
|
<a-select v-model="logModal.rows"
|
||||||
style="width: 80px"
|
style="width: 80px"
|
||||||
@change="openLogs(logModal.rows)"
|
@change="openLogs(logModal.rows, logModal.logLevel)"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
@@ -261,8 +261,20 @@
|
|||||||
<a-select-option value="100">100</a-select-option>
|
<a-select-option value="100">100</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Log Level">
|
||||||
|
<a-select v-model="logModal.logLevel"
|
||||||
|
style="width: 120px"
|
||||||
|
@change="openLogs(logModal.rows, logModal.logLevel)"
|
||||||
|
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
|
<a-select-option value="info">Info</a-select-option>
|
||||||
|
<a-select-option value="notice">Notice</a-select-option>
|
||||||
|
<a-select-option value="warning">Warning</a-select-option>
|
||||||
|
<a-select-option value="err">Error</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<button class="ant-btn ant-btn-primary" @click="openLogs(logModal.rows)"><a-icon type="sync"></a-icon> Reload</button>
|
<button class="ant-btn ant-btn-primary" @click="openLogs(logModal.rows, logModal.logLevel)"><a-icon type="sync"></a-icon> Reload</button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button type="primary" style="margin-bottom: 10px;"
|
<a-button type="primary" style="margin-bottom: 10px;"
|
||||||
@@ -397,6 +409,7 @@
|
|||||||
visible: false,
|
visible: false,
|
||||||
logs: '',
|
logs: '',
|
||||||
rows: 20,
|
rows: 20,
|
||||||
|
logLevel: 'info',
|
||||||
show(logs, rows) {
|
show(logs, rows) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.rows = rows;
|
this.rows = rows;
|
||||||
@@ -449,9 +462,13 @@
|
|||||||
this.loadingTip = tip;
|
this.loadingTip = tip;
|
||||||
},
|
},
|
||||||
async getStatus() {
|
async getStatus() {
|
||||||
const msg = await HttpUtil.post('/server/status');
|
try {
|
||||||
if (msg.success) {
|
const msg = await HttpUtil.post('/server/status');
|
||||||
this.setStatus(msg.obj);
|
if (msg.success) {
|
||||||
|
this.setStatus(msg.obj);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to get status:", e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setStatus(data) {
|
setStatus(data) {
|
||||||
@@ -497,9 +514,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async openLogs(rows) {
|
async openLogs(rows, logLevel) {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/logs/' + rows);
|
const msg = await HttpUtil.post('server/logs/' + rows, { logLevel: `${logLevel}` });
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
@@ -560,11 +577,14 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
while (true) {
|
let retries = 0;
|
||||||
|
while (retries < 5) {
|
||||||
try {
|
try {
|
||||||
await this.getStatus();
|
await this.getStatus();
|
||||||
|
retries = 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error("Error occurred while fetching status:", e);
|
||||||
|
retries++;
|
||||||
}
|
}
|
||||||
await PromiseUtil.sleep(2000);
|
await PromiseUtil.sleep(2000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
</a-row>
|
</a-row>
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
||||||
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="0"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="0"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
||||||
@@ -115,7 +116,7 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -306,23 +307,37 @@
|
|||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualLists"}}'>
|
</a-collapse>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
</a-tab-pane>
|
||||||
<h2 class="collapse-title">
|
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.manualLists"}}' style="padding-top: 20px;">
|
||||||
<a-icon type="warning"></a-icon>
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.settings.templates.manualListsDesc" }}
|
<h2 class="collapse-title">
|
||||||
</h2>
|
<a-icon type="warning"></a-icon>
|
||||||
</a-row>
|
{{ i18n "pages.settings.templates.manualListsDesc" }}
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualBlockedIPs"}}' v-model="manualBlockedIPs"></setting-list-item>
|
</h2>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualBlockedDomains"}}' v-model="manualBlockedDomains"></setting-list-item>
|
</a-row>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectIPs"}}' v-model="manualDirectIPs"></setting-list-item>
|
<a-collapse>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectDomains"}}' v-model="manualDirectDomains"></setting-list-item>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedIPs"}}'>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualIPv4Domains"}}' v-model="manualIPv4Domains"></setting-list-item>
|
<setting-list-item type="textarea" v-model="manualBlockedIPs"></setting-list-item>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualWARPDomains"}}' v-model="manualWARPDomains"></setting-list-item>
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedDomains"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualBlockedDomains"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectIPs"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualDirectIPs"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectDomains"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualDirectDomains"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualIPv4Domains"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualIPv4Domains"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualWARPDomains"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualWARPDomains"></setting-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
|
||||||
<a-collapse>
|
<a-collapse>
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}'>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}'>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
|
||||||
@@ -335,7 +350,7 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
|
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigTemplate"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigTemplate"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
@@ -355,6 +370,7 @@
|
|||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramNotifyTime"}}' desc='{{ i18n "pages.settings.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramNotifyTime"}}' desc='{{ i18n "pages.settings.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyBackup" }}' desc='{{ i18n "pages.settings.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyBackup" }}' desc='{{ i18n "pages.settings.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyLogin" }}' desc='{{ i18n "pages.settings.tgNotifyLoginDesc" }}' v-model="allSetting.tgBotLoginNotify"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.tgNotifyCpu" }}' desc='{{ i18n "pages.settings.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.tgNotifyCpu" }}' desc='{{ i18n "pages.settings.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
|
||||||
<a-list-item>
|
<a-list-item>
|
||||||
<a-row style="padding: 20px">
|
<a-row style="padding: 20px">
|
||||||
@@ -371,7 +387,7 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -391,9 +407,9 @@
|
|||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></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>
|
||||||
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
|
||||||
@@ -522,7 +538,7 @@
|
|||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.user = {};
|
this.user = {};
|
||||||
window.location.replace(basePath + "logout")
|
window.location.replace(basePath + "logout");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async restartPanel() {
|
async restartPanel() {
|
||||||
@@ -541,12 +557,10 @@
|
|||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
await PromiseUtil.sleep(5000);
|
await PromiseUtil.sleep(5000);
|
||||||
let protocol = "http://";
|
const { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
|
||||||
if (this.allSetting.webCertFile !== "") {
|
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
||||||
protocol = "https://";
|
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
|
||||||
}
|
window.location.replace(url);
|
||||||
const { host } = window.location;
|
|
||||||
window.location.replace(protocol + host + this.allSetting.webBasePath + "panel/settings");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async fetchUserSecret() {
|
async fetchUserSecret() {
|
||||||
|
|||||||
@@ -2,57 +2,91 @@ package job
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sync"
|
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
"net"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-cmd/cmd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CheckClientIpJob struct {
|
type CheckClientIpJob struct {
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
AllowedIps []string
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var job *CheckClientIpJob
|
var job *CheckClientIpJob
|
||||||
var AllowedIps []string
|
var disAllowedIps []string
|
||||||
var ipRegx *regexp.Regexp = regexp.MustCompile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
|
||||||
var emailRegx *regexp.Regexp = regexp.MustCompile(`email:.+`)
|
|
||||||
|
|
||||||
func NewCheckClientIpJob() *CheckClientIpJob {
|
func NewCheckClientIpJob() *CheckClientIpJob {
|
||||||
job := &CheckClientIpJob{}
|
job = new(CheckClientIpJob)
|
||||||
return job
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) Run() {
|
func (j *CheckClientIpJob) Run() {
|
||||||
logger.Debug("Check Client IP Job...")
|
logger.Debug("Check Client IP Job...")
|
||||||
j.processLogFile()
|
|
||||||
|
|
||||||
// AllowedIps = []string{"192.168.1.183","192.168.1.197"}
|
if hasLimitIp() {
|
||||||
allowedIps := []byte(strings.Join(j.getAllowedIps(), ","))
|
//create log file for Fail2ban IP Limit
|
||||||
|
logIpFile, err := os.OpenFile("/var/log/3xipl.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
|
checkError(err)
|
||||||
|
defer logIpFile.Close()
|
||||||
|
log.SetOutput(logIpFile)
|
||||||
|
log.SetFlags(log.LstdFlags)
|
||||||
|
|
||||||
|
//create file to collect access.log to another file accessp.log (p=persistent)
|
||||||
|
logAccessP, err := os.OpenFile("/usr/local/x-ui/accessp.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
|
checkError(err)
|
||||||
|
defer logAccessP.Close()
|
||||||
|
|
||||||
|
processLogFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
blockedIps := []byte(strings.Join(disAllowedIps, ","))
|
||||||
|
|
||||||
// check if file exists, if not create one
|
// check if file exists, if not create one
|
||||||
_, err := os.Stat(xray.GetAllowedIPsPath())
|
_, err := os.Stat(xray.GetBlockedIPsPath())
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
_, err = os.OpenFile(xray.GetAllowedIPsPath(), os.O_RDWR|os.O_CREATE, 0755)
|
_, err = os.OpenFile(xray.GetBlockedIPsPath(), os.O_RDWR|os.O_CREATE, 0755)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
}
|
}
|
||||||
err = os.WriteFile(xray.GetAllowedIPsPath(), allowedIps, 0755)
|
err = os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0755)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) processLogFile() {
|
func hasLimitIp() bool {
|
||||||
|
db := database.GetDB()
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err := db.Model(model.Inbound{}).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
if inbound.Settings == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
settings := map[string][]model.Client{}
|
||||||
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
clients := settings["clients"]
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
limitIp := client.LimitIP
|
||||||
|
if limitIp > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func processLogFile() {
|
||||||
accessLogPath := GetAccessLogPath()
|
accessLogPath := GetAccessLogPath()
|
||||||
if accessLogPath == "" {
|
if accessLogPath == "" {
|
||||||
logger.Warning("access.log doesn't exist in your config.json")
|
logger.Warning("access.log doesn't exist in your config.json")
|
||||||
@@ -63,13 +97,10 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
InboundClientIps := make(map[string][]string)
|
InboundClientIps := make(map[string][]string)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
// clean log
|
|
||||||
if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
|
|
||||||
checkError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(string(data), "\n")
|
lines := strings.Split(string(data), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
|
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
||||||
|
emailRegx, _ := regexp.Compile(`email:.+`)
|
||||||
|
|
||||||
matchesIp := ipRegx.FindString(line)
|
matchesIp := ipRegx.FindString(line)
|
||||||
if len(matchesIp) > 0 {
|
if len(matchesIp) > 0 {
|
||||||
@@ -84,13 +115,20 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
}
|
}
|
||||||
matchesEmail = strings.TrimSpace(strings.Split(matchesEmail, "email: ")[1])
|
matchesEmail = strings.TrimSpace(strings.Split(matchesEmail, "email: ")[1])
|
||||||
|
|
||||||
if !contains(InboundClientIps[matchesEmail], ip) {
|
if InboundClientIps[matchesEmail] != nil {
|
||||||
|
if contains(InboundClientIps[matchesEmail], ip) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
|
|
||||||
|
} else {
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
j.setAllowedIps([]string{})
|
disAllowedIps = []string{}
|
||||||
|
shouldCleanLog := false
|
||||||
|
|
||||||
for clientEmail, ips := range InboundClientIps {
|
for clientEmail, ips := range InboundClientIps {
|
||||||
inboundClientIps, err := GetInboundClientIps(clientEmail)
|
inboundClientIps, err := GetInboundClientIps(clientEmail)
|
||||||
@@ -99,20 +137,30 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
addInboundClientIps(clientEmail, ips)
|
addInboundClientIps(clientEmail, ips)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
shouldCleanLog = updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if inbound connection is more than limited ip and drop connection
|
time.Sleep(time.Second * 3)
|
||||||
LimitDevice := func() { LimitDevice() }
|
//added 3 seconds delay before cleaning logs to reduce chance of logging IP that already has been banned
|
||||||
|
if shouldCleanLog {
|
||||||
stop := schedule(LimitDevice, 1000*time.Millisecond)
|
//copy log
|
||||||
time.Sleep(10 * time.Second)
|
logAccessP, err := os.OpenFile("/usr/local/x-ui/accessp.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
stop <- true
|
checkError(err)
|
||||||
|
input, err := os.ReadFile(accessLogPath)
|
||||||
|
checkError(err)
|
||||||
|
if _, err := logAccessP.Write(input); err != nil {
|
||||||
|
checkError(err)
|
||||||
|
}
|
||||||
|
defer logAccessP.Close()
|
||||||
|
// clean log
|
||||||
|
if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
|
||||||
|
checkError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAccessLogPath() string {
|
func GetAccessLogPath() string {
|
||||||
|
|
||||||
config, err := os.ReadFile(xray.GetConfigPath())
|
config, err := os.ReadFile(xray.GetConfigPath())
|
||||||
@@ -133,22 +181,20 @@ func GetAccessLogPath() string {
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkError(e error) {
|
func checkError(e error) {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
logger.Warning("client ip job err:", e)
|
logger.Warning("client ip job err:", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(s []string, str string) bool {
|
func contains(s []string, str string) bool {
|
||||||
for _, v := range s {
|
for _, v := range s {
|
||||||
if v == str {
|
if v == str {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
InboundClientIps := &model.InboundClientIps{}
|
InboundClientIps := &model.InboundClientIps{}
|
||||||
@@ -158,7 +204,6 @@ func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
|||||||
}
|
}
|
||||||
return InboundClientIps, nil
|
return InboundClientIps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addInboundClientIps(clientEmail string, ips []string) error {
|
func addInboundClientIps(clientEmail string, ips []string) error {
|
||||||
inboundClientIps := &model.InboundClientIps{}
|
inboundClientIps := &model.InboundClientIps{}
|
||||||
jsonIps, err := json.Marshal(ips)
|
jsonIps, err := json.Marshal(ips)
|
||||||
@@ -171,10 +216,10 @@ func addInboundClientIps(clientEmail string, ips []string) error {
|
|||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if err == nil {
|
||||||
tx.Rollback()
|
|
||||||
} else {
|
|
||||||
tx.Commit()
|
tx.Commit()
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -184,7 +229,13 @@ func addInboundClientIps(clientEmail string, ips []string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) error {
|
func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool {
|
||||||
|
|
||||||
|
jsonIps, err := json.Marshal(ips)
|
||||||
|
checkError(err)
|
||||||
|
|
||||||
|
inboundClientIps.ClientEmail = clientEmail
|
||||||
|
inboundClientIps.Ips = string(jsonIps)
|
||||||
|
|
||||||
// check inbound limitation
|
// check inbound limitation
|
||||||
inbound, err := GetInboundByEmail(clientEmail)
|
inbound, err := GetInboundByEmail(clientEmail)
|
||||||
@@ -192,56 +243,41 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
|||||||
|
|
||||||
if inbound.Settings == "" {
|
if inbound.Settings == "" {
|
||||||
logger.Debug("wrong data ", inbound)
|
logger.Debug("wrong data ", inbound)
|
||||||
return nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
settings := map[string][]model.Client{}
|
settings := map[string][]model.Client{}
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
clients := settings["clients"]
|
clients := settings["clients"]
|
||||||
|
shouldCleanLog := false
|
||||||
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Email == clientEmail {
|
if client.Email == clientEmail {
|
||||||
|
|
||||||
limitIp := client.LimitIP
|
limitIp := client.LimitIP
|
||||||
if limitIp < len(ips) && limitIp != 0 && inbound.Enable {
|
|
||||||
for _, ip := range ips[:limitIp] {
|
if limitIp != 0 {
|
||||||
j.addAllowedIp(ip)
|
|
||||||
|
shouldCleanLog = true
|
||||||
|
|
||||||
|
if limitIp < len(ips) && inbound.Enable {
|
||||||
|
disAllowedIps = append(disAllowedIps, ips[limitIp:]...)
|
||||||
|
for i := limitIp; i < len(ips); i++ {
|
||||||
|
log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
logger.Debug("disAllowedIps ", disAllowedIps)
|
||||||
jsonIps, err := json.Marshal(ips) // marshal the possibly truncated list of IPs
|
sort.Strings(disAllowedIps)
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
inboundClientIps.ClientEmail = clientEmail
|
|
||||||
inboundClientIps.Ips = string(jsonIps)
|
|
||||||
|
|
||||||
logger.Debug("Allowed IPs: ", ips)
|
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
err = db.Save(inboundClientIps).Error
|
err = db.Save(inboundClientIps).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return shouldCleanLog
|
||||||
}
|
}
|
||||||
return nil
|
return shouldCleanLog
|
||||||
}
|
|
||||||
|
|
||||||
func (j *CheckClientIpJob) setAllowedIps(ips []string) {
|
|
||||||
j.mutex.Lock()
|
|
||||||
defer j.mutex.Unlock()
|
|
||||||
j.AllowedIps = ips
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *CheckClientIpJob) addAllowedIp(ip string) {
|
|
||||||
j.mutex.Lock()
|
|
||||||
defer j.mutex.Unlock()
|
|
||||||
j.AllowedIps = append(j.AllowedIps, ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *CheckClientIpJob) getAllowedIps() []string {
|
|
||||||
j.mutex.Lock()
|
|
||||||
defer j.mutex.Unlock()
|
|
||||||
return j.AllowedIps
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisableInbound(id int) error {
|
func DisableInbound(id int) error {
|
||||||
@@ -268,103 +304,3 @@ func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
|||||||
}
|
}
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LimitDevice() {
|
|
||||||
|
|
||||||
localIp, err := LocalIP()
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
c := cmd.NewCmd("bash", "-c", "ss --tcp | grep -E '"+IPsToRegex(localIp)+"'| awk '{if($1==\"ESTAB\") print $4,$5;}'", "| sort | uniq -c | sort -nr | head")
|
|
||||||
|
|
||||||
<-c.Start()
|
|
||||||
if len(c.Status().Stdout) > 0 {
|
|
||||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
|
||||||
portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
|
|
||||||
|
|
||||||
for _, row := range c.Status().Stdout {
|
|
||||||
|
|
||||||
data := strings.Split(row, " ")
|
|
||||||
|
|
||||||
destIp, destPort, srcIp, srcPort := "", "", "", ""
|
|
||||||
|
|
||||||
destIp = string(ipRegx.FindString(data[0]))
|
|
||||||
|
|
||||||
destPort = portRegx.FindString(data[0])
|
|
||||||
destPort = strings.Replace(destPort, ":", "", -1)
|
|
||||||
|
|
||||||
srcIp = string(ipRegx.FindString(data[1]))
|
|
||||||
|
|
||||||
srcPort = portRegx.FindString(data[1])
|
|
||||||
srcPort = strings.Replace(srcPort, ":", "", -1)
|
|
||||||
|
|
||||||
if contains(AllowedIps, srcIp) {
|
|
||||||
dropCmd := cmd.NewCmd("bash", "-c", "ss -K dport = "+srcPort)
|
|
||||||
dropCmd.Start()
|
|
||||||
|
|
||||||
logger.Debug("request droped : ", srcIp, srcPort, "to", destIp, destPort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func LocalIP() ([]string, error) {
|
|
||||||
// get machine ips
|
|
||||||
|
|
||||||
ifaces, err := net.Interfaces()
|
|
||||||
ips := []string{}
|
|
||||||
if err != nil {
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
for _, i := range ifaces {
|
|
||||||
addrs, err := i.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range addrs {
|
|
||||||
var ip net.IP
|
|
||||||
switch v := addr.(type) {
|
|
||||||
case *net.IPNet:
|
|
||||||
ip = v.IP
|
|
||||||
case *net.IPAddr:
|
|
||||||
ip = v.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
ips = append(ips, ip.String())
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Debug("System IPs : ", ips)
|
|
||||||
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func IPsToRegex(ips []string) string {
|
|
||||||
|
|
||||||
regx := ""
|
|
||||||
for _, ip := range ips {
|
|
||||||
regx += "(" + strings.Replace(ip, ".", "\\.", -1) + ")"
|
|
||||||
|
|
||||||
}
|
|
||||||
regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")"
|
|
||||||
|
|
||||||
return regx
|
|
||||||
}
|
|
||||||
|
|
||||||
func schedule(LimitDevice func(), delay time.Duration) chan bool {
|
|
||||||
stop := make(chan bool)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
LimitDevice()
|
|
||||||
select {
|
|
||||||
case <-time.After(delay):
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return stop
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,19 +15,21 @@ func NewCheckInboundJob() *CheckInboundJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckInboundJob) Run() {
|
func (j *CheckInboundJob) Run() {
|
||||||
count, err := j.inboundService.DisableInvalidClients()
|
needRestart, count, err := j.inboundService.DisableInvalidClients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("disable invalid Client err:", err)
|
logger.Warning("Error in disabling invalid clients:", err)
|
||||||
} else if count > 0 {
|
} else if count > 0 {
|
||||||
logger.Debugf("disabled %v Client", count)
|
logger.Debugf("%v clients disabled", count)
|
||||||
j.xrayService.SetToNeedRestart()
|
if needRestart {
|
||||||
|
j.xrayService.SetToNeedRestart()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err = j.inboundService.DisableInvalidInbounds()
|
count, err = j.inboundService.DisableInvalidInbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("disable invalid inbounds err:", err)
|
logger.Warning("Error in disabling invalid inbounds:", err)
|
||||||
} else if count > 0 {
|
} else if count > 0 {
|
||||||
logger.Debugf("disabled %v inbounds", count)
|
logger.Debugf("%v inbounds disabled", count)
|
||||||
j.xrayService.SetToNeedRestart()
|
j.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ type SettingService interface {
|
|||||||
|
|
||||||
func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
|
func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
|
||||||
// set default bundle to english
|
// set default bundle to english
|
||||||
i18nBundle = i18n.NewBundle(language.English)
|
i18nBundle = i18n.NewBundle(language.MustParse("en-US"))
|
||||||
i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||||
|
|
||||||
// parse files
|
// parse files
|
||||||
|
|||||||
21
web/middleware/domainValidator.go
Normal file
21
web/middleware/domainValidator.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DomainValidatorMiddleware(domain string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
|
|
||||||
|
if host != domain {
|
||||||
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
34
web/middleware/redirect.go
Normal file
34
web/middleware/redirect.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RedirectMiddleware(basePath string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Redirect from old '/xui' path to '/panel'
|
||||||
|
redirects := map[string]string{
|
||||||
|
"panel/API": "panel/api",
|
||||||
|
"xui/API": "panel/api",
|
||||||
|
"xui": "panel",
|
||||||
|
}
|
||||||
|
|
||||||
|
path := c.Request.URL.Path
|
||||||
|
for from, to := range redirects {
|
||||||
|
from, to = basePath+from, basePath+to
|
||||||
|
|
||||||
|
if strings.HasPrefix(path, from) {
|
||||||
|
newPath := to + path[len(from):]
|
||||||
|
|
||||||
|
c.Redirect(http.StatusMovedPermanently, newPath)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type InboundService struct {
|
type InboundService struct {
|
||||||
|
xrayApi xray.XrayAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
|
func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
|
||||||
@@ -156,11 +157,19 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
tx.Commit()
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
err = db.Save(inbound).Error
|
err = tx.Save(inbound).Error
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
s.AddClientStat(inbound.Id, &client)
|
s.AddClientStat(tx, inbound.Id, &client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return inbound, err
|
return inbound, err
|
||||||
@@ -244,6 +253,12 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = s.updateClientTraffics(oldInbound, inbound)
|
||||||
|
if err != nil {
|
||||||
|
return inbound, err
|
||||||
|
}
|
||||||
|
|
||||||
oldInbound.Up = inbound.Up
|
oldInbound.Up = inbound.Up
|
||||||
oldInbound.Down = inbound.Down
|
oldInbound.Down = inbound.Down
|
||||||
oldInbound.Total = inbound.Total
|
oldInbound.Total = inbound.Total
|
||||||
@@ -262,36 +277,92 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
return inbound, db.Save(oldInbound).Error
|
return inbound, db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbound *model.Inbound) error {
|
||||||
clients, err := s.GetClients(data)
|
oldClients, err := s.GetClients(oldInbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
newClients, err := s.GetClients(newInbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
} else {
|
||||||
|
tx.Commit()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var emailExists bool
|
||||||
|
|
||||||
|
for _, oldClient := range oldClients {
|
||||||
|
emailExists = false
|
||||||
|
for _, newClient := range newClients {
|
||||||
|
if oldClient.Email == newClient.Email {
|
||||||
|
emailExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !emailExists {
|
||||||
|
err = s.DelClientStat(tx, oldClient.Email)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, newClient := range newClients {
|
||||||
|
emailExists = false
|
||||||
|
for _, oldClient := range oldClients {
|
||||||
|
if newClient.Email == oldClient.Email {
|
||||||
|
emailExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !emailExists {
|
||||||
|
err = s.AddClientStat(tx, oldInbound.Id, &newClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
||||||
|
clients, err := s.GetClients(data)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]interface{}
|
||||||
err = json.Unmarshal([]byte(data.Settings), &settings)
|
err = json.Unmarshal([]byte(data.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaceClients := settings["clients"].([]interface{})
|
interfaceClients := settings["clients"].([]interface{})
|
||||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
if existEmail != "" {
|
if existEmail != "" {
|
||||||
return common.NewError("Duplicate email:", existEmail)
|
return false, common.NewError("Duplicate email:", existEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(data.Id)
|
oldInbound, err := s.GetInbound(data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldSettings map[string]interface{}
|
var oldSettings map[string]interface{}
|
||||||
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldClients := oldSettings["clients"].([]interface{})
|
oldClients := oldSettings["clients"].([]interface{})
|
||||||
@@ -301,30 +372,57 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
|||||||
|
|
||||||
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound.Settings = string(newSettings)
|
oldInbound.Settings = string(newSettings)
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
} else {
|
||||||
|
tx.Commit()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
needRestart := false
|
||||||
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if len(client.Email) > 0 {
|
if len(client.Email) > 0 {
|
||||||
s.AddClientStat(data.Id, &client)
|
s.AddClientStat(tx, data.Id, &client)
|
||||||
|
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
|
||||||
|
"email": client.Email,
|
||||||
|
"id": client.ID,
|
||||||
|
"flow": client.Flow,
|
||||||
|
"password": client.Password,
|
||||||
|
})
|
||||||
|
if err1 == nil {
|
||||||
|
logger.Debug("Client added by api:", client.Email)
|
||||||
|
} else {
|
||||||
|
needRestart = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
needRestart = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db := database.GetDB()
|
s.xrayApi.Close()
|
||||||
return db.Save(oldInbound).Error
|
|
||||||
|
return needRestart, tx.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DelInboundClient(inboundId int, clientId string) error {
|
func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool, error) {
|
||||||
oldInbound, err := s.GetInbound(inboundId)
|
oldInbound, err := s.GetInbound(inboundId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Load Old Data Error")
|
logger.Error("Load Old Data Error")
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
var settings map[string]interface{}
|
var settings map[string]interface{}
|
||||||
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
email := ""
|
email := ""
|
||||||
@@ -351,7 +449,7 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error
|
|||||||
settings["clients"] = newClients
|
settings["clients"] = newClients
|
||||||
newSettings, err := json.MarshalIndent(settings, "", " ")
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound.Settings = string(newSettings)
|
oldInbound.Settings = string(newSettings)
|
||||||
@@ -360,39 +458,49 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error
|
|||||||
err = s.DelClientStat(db, email)
|
err = s.DelClientStat(db, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Delete stats Data Error")
|
logger.Error("Delete stats Data Error")
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.DelClientIPs(db, email)
|
err = s.DelClientIPs(db, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error in delete client IPs")
|
logger.Error("Error in delete client IPs")
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
return db.Save(oldInbound).Error
|
needRestart := true
|
||||||
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
|
if len(email) > 0 {
|
||||||
|
err = s.xrayApi.RemoveUser(oldInbound.Tag, email)
|
||||||
|
if err == nil {
|
||||||
|
logger.Debug("Client deleted by api:", email)
|
||||||
|
needRestart = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.xrayApi.Close()
|
||||||
|
return needRestart, db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) error {
|
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) (bool, error) {
|
||||||
clients, err := s.GetClients(data)
|
clients, err := s.GetClients(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]interface{}
|
||||||
err = json.Unmarshal([]byte(data.Settings), &settings)
|
err = json.Unmarshal([]byte(data.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
inerfaceClients := settings["clients"].([]interface{})
|
inerfaceClients := settings["clients"].([]interface{})
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(data.Id)
|
oldInbound, err := s.GetInbound(data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldClients, err := s.GetClients(oldInbound)
|
oldClients, err := s.GetClients(oldInbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldEmail := ""
|
oldEmail := ""
|
||||||
@@ -416,17 +524,17 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
if len(clients[0].Email) > 0 && clients[0].Email != oldEmail {
|
if len(clients[0].Email) > 0 && clients[0].Email != oldEmail {
|
||||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
if existEmail != "" {
|
if existEmail != "" {
|
||||||
return common.NewError("Duplicate email:", existEmail)
|
return false, common.NewError("Duplicate email:", existEmail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldSettings map[string]interface{}
|
var oldSettings map[string]interface{}
|
||||||
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
settingsClients := oldSettings["clients"].([]interface{})
|
settingsClients := oldSettings["clients"].([]interface{})
|
||||||
settingsClients[clientIndex] = inerfaceClients[0]
|
settingsClients[clientIndex] = inerfaceClients[0]
|
||||||
@@ -434,36 +542,66 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
|
|
||||||
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound.Settings = string(newSettings)
|
oldInbound.Settings = string(newSettings)
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
} else {
|
||||||
|
tx.Commit()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if len(clients[0].Email) > 0 {
|
if len(clients[0].Email) > 0 {
|
||||||
if len(oldEmail) > 0 {
|
if len(oldEmail) > 0 {
|
||||||
err = s.UpdateClientStat(oldEmail, &clients[0])
|
err = s.UpdateClientStat(oldEmail, &clients[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
err = s.UpdateClientIPs(db, oldEmail, clients[0].Email)
|
err = s.UpdateClientIPs(tx, oldEmail, clients[0].Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.AddClientStat(data.Id, &clients[0])
|
s.AddClientStat(tx, data.Id, &clients[0])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = s.DelClientStat(db, oldEmail)
|
err = s.DelClientStat(tx, oldEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
err = s.DelClientIPs(db, oldEmail)
|
err = s.DelClientIPs(tx, oldEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return db.Save(oldInbound).Error
|
needRestart := true
|
||||||
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
|
if len(oldEmail) > 0 {
|
||||||
|
s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
|
||||||
|
if clients[0].Enable {
|
||||||
|
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
|
||||||
|
"email": clients[0].Email,
|
||||||
|
"id": clients[0].ID,
|
||||||
|
"flow": clients[0].Flow,
|
||||||
|
"password": clients[0].Password,
|
||||||
|
})
|
||||||
|
if err1 == nil {
|
||||||
|
logger.Debug("Client edited by api:", clients[0].Email)
|
||||||
|
needRestart = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Debug("Client disabled by api:", clients[0].Email)
|
||||||
|
needRestart = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.xrayApi.Close()
|
||||||
|
return needRestart, tx.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddTraffic(traffics []*xray.Traffic) error {
|
func (s *InboundService) AddTraffic(traffics []*xray.Traffic) error {
|
||||||
@@ -489,6 +627,7 @@ func (s *InboundService) AddTraffic(traffics []*xray.Traffic) error {
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err error) {
|
func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err error) {
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -601,15 +740,42 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
|||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DisableInvalidClients() (int64, error) {
|
func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
now := time.Now().Unix() * 1000
|
now := time.Now().Unix() * 1000
|
||||||
|
needRestart := false
|
||||||
|
|
||||||
|
if p != nil {
|
||||||
|
var results []struct {
|
||||||
|
Tag string
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.Table("inbounds").
|
||||||
|
Select("inbounds.tag, client_traffics.email").
|
||||||
|
Joins("JOIN client_traffics ON inbounds.id = client_traffics.inbound_id").
|
||||||
|
Where("((client_traffics.total > 0 AND client_traffics.up + client_traffics.down >= client_traffics.total) OR (client_traffics.expiry_time > 0 AND client_traffics.expiry_time <= ?)) AND client_traffics.enable = ?", now, true).
|
||||||
|
Scan(&results).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
|
for _, result := range results {
|
||||||
|
err = s.xrayApi.RemoveUser(result.Tag, result.Email)
|
||||||
|
if err == nil {
|
||||||
|
logger.Debug("Client deleted by api:", result.Email)
|
||||||
|
} else {
|
||||||
|
needRestart = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.xrayApi.Close()
|
||||||
|
}
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||||
Update("enable", false)
|
Update("enable", false)
|
||||||
err := result.Error
|
err := result.Error
|
||||||
count := result.RowsAffected
|
count := result.RowsAffected
|
||||||
return count, err
|
return needRestart, count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) MigrationRemoveOrphanedTraffics() {
|
func (s *InboundService) MigrationRemoveOrphanedTraffics() {
|
||||||
@@ -624,9 +790,7 @@ func (s *InboundService) MigrationRemoveOrphanedTraffics() {
|
|||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
|
func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model.Client) error {
|
||||||
db := database.GetDB()
|
|
||||||
|
|
||||||
clientTraffic := xray.ClientTraffic{}
|
clientTraffic := xray.ClientTraffic{}
|
||||||
clientTraffic.InboundId = inboundId
|
clientTraffic.InboundId = inboundId
|
||||||
clientTraffic.Email = client.Email
|
clientTraffic.Email = client.Email
|
||||||
@@ -635,7 +799,7 @@ func (s *InboundService) AddClientStat(inboundId int, client *model.Client) erro
|
|||||||
clientTraffic.Enable = true
|
clientTraffic.Enable = true
|
||||||
clientTraffic.Up = 0
|
clientTraffic.Up = 0
|
||||||
clientTraffic.Down = 0
|
clientTraffic.Down = 0
|
||||||
result := db.Create(&clientTraffic)
|
result := tx.Create(&clientTraffic)
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -779,7 +943,11 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId string) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
inbound.Settings = string(modifiedSettings)
|
inbound.Settings = string(modifiedSettings)
|
||||||
return s.UpdateInboundClient(inbound, clientId)
|
_, err = s.UpdateInboundClient(inbound, clientId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, error) {
|
func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, error) {
|
||||||
@@ -835,7 +1003,13 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, er
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
inbound.Settings = string(modifiedSettings)
|
inbound.Settings = string(modifiedSettings)
|
||||||
return !clientOldEnabled, s.UpdateInboundClient(inbound, clientId)
|
|
||||||
|
_, err = s.UpdateInboundClient(inbound, clientId)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return !clientOldEnabled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) error {
|
func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) error {
|
||||||
@@ -889,9 +1063,13 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
inbound.Settings = string(modifiedSettings)
|
inbound.Settings = string(modifiedSettings)
|
||||||
return s.UpdateInboundClient(inbound, clientId)
|
_, err = s.UpdateInboundClient(inbound, clientId)
|
||||||
}
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) error {
|
func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) error {
|
||||||
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -943,7 +1121,12 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
inbound.Settings = string(modifiedSettings)
|
inbound.Settings = string(modifiedSettings)
|
||||||
return s.UpdateInboundClient(inbound, clientId)
|
_, err = s.UpdateInboundClient(inbound, clientId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
||||||
@@ -961,19 +1144,54 @@ func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
|
func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, error) {
|
||||||
db := database.GetDB()
|
needRestart := false
|
||||||
|
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
|
||||||
Where("inbound_id = ? and email = ?", id, clientEmail).
|
|
||||||
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
|
||||||
|
|
||||||
err := result.Error
|
|
||||||
|
|
||||||
|
traffic, err := s.GetClientTrafficByEmail(clientEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
if !traffic.Enable {
|
||||||
|
inbound, err := s.GetInbound(id)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
clients, err := s.GetClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, client := range clients {
|
||||||
|
if client.Email == clientEmail {
|
||||||
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
|
err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]interface{}{
|
||||||
|
"email": client.Email,
|
||||||
|
"id": client.ID,
|
||||||
|
"flow": client.Flow,
|
||||||
|
"password": client.Password,
|
||||||
|
})
|
||||||
|
if err1 == nil {
|
||||||
|
logger.Debug("Client enabled due to reset traffic:", clientEmail)
|
||||||
|
} else {
|
||||||
|
needRestart = true
|
||||||
|
}
|
||||||
|
s.xrayApi.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traffic.Up = 0
|
||||||
|
traffic.Down = 0
|
||||||
|
traffic.Enable = true
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
|
err = db.Save(traffic).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return needRestart, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) ResetAllClientTraffics(id int) error {
|
func (s *InboundService) ResetAllClientTraffics(id int) error {
|
||||||
@@ -1185,6 +1403,7 @@ func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error)
|
|||||||
}
|
}
|
||||||
return InboundClientIps.Ips, nil
|
return InboundClientIps.Ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) ClearClientIps(clientEmail string) error {
|
func (s *InboundService) ClearClientIps(clientEmail string) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
@@ -1211,10 +1430,19 @@ func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error)
|
|||||||
|
|
||||||
func (s *InboundService) MigrationRequirements() {
|
func (s *InboundService) MigrationRequirements() {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
tx.Commit()
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Fix inbounds based problems
|
// Fix inbounds based problems
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
|
err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
|
||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1249,6 +1477,7 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
|
|
||||||
inbounds[inbound_index].Settings = string(modifiedSettings)
|
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add client traffic row for all clients which has email
|
// Add client traffic row for all clients which has email
|
||||||
modelClients, err := s.GetClients(inbounds[inbound_index])
|
modelClients, err := s.GetClients(inbounds[inbound_index])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1257,17 +1486,17 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
for _, modelClient := range modelClients {
|
for _, modelClient := range modelClients {
|
||||||
if len(modelClient.Email) > 0 {
|
if len(modelClient.Email) > 0 {
|
||||||
var count int64
|
var count int64
|
||||||
db.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count)
|
tx.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count)
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
s.AddClientStat(inbounds[inbound_index].Id, &modelClient)
|
s.AddClientStat(tx, inbounds[inbound_index].Id, &modelClient)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.Save(inbounds)
|
tx.Save(inbounds)
|
||||||
|
|
||||||
// Remove orphaned traffics
|
// Remove orphaned traffics
|
||||||
db.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) MigrateDB() {
|
func (s *InboundService) MigrateDB() {
|
||||||
|
|||||||
@@ -86,31 +86,24 @@ type ServerService struct {
|
|||||||
inboundService InboundService
|
inboundService InboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
const DebugMode = false // Set to true during development
|
|
||||||
|
|
||||||
func getPublicIP(url string) string {
|
func getPublicIP(url string) string {
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if DebugMode {
|
|
||||||
logger.Warning("get public IP failed:", err)
|
|
||||||
}
|
|
||||||
return "N/A"
|
return "N/A"
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
ip, err := io.ReadAll(resp.Body)
|
ip, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if DebugMode {
|
|
||||||
logger.Warning("read public IP failed:", err)
|
|
||||||
}
|
|
||||||
return "N/A"
|
return "N/A"
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(ip) == "" {
|
ipString := string(ip)
|
||||||
return "N/A" // default value
|
if ipString == "" {
|
||||||
|
return "N/A"
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(ip)
|
return ipString
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||||
@@ -387,16 +380,17 @@ func (s *ServerService) UpdateXray(version string) error {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetLogs(count string) ([]string, error) {
|
func (s *ServerService) GetLogs(count string, logLevel string) ([]string, error) {
|
||||||
// Define the journalctl command and its arguments
|
|
||||||
var cmdArgs []string
|
var cmdArgs []string
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
cmdArgs = []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count}
|
cmdArgs = []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count}
|
||||||
|
if logLevel != "" {
|
||||||
|
cmdArgs = append(cmdArgs, "-p", logLevel)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return []string{"Unsupported operating system"}, nil
|
return []string{"Unsupported operating system"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the command
|
|
||||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ var xrayTemplateConfig string
|
|||||||
var defaultValueMap = map[string]string{
|
var defaultValueMap = map[string]string{
|
||||||
"xrayTemplateConfig": xrayTemplateConfig,
|
"xrayTemplateConfig": xrayTemplateConfig,
|
||||||
"webListen": "",
|
"webListen": "",
|
||||||
|
"webDomain": "",
|
||||||
"webPort": "2053",
|
"webPort": "2053",
|
||||||
"webCertFile": "",
|
"webCertFile": "",
|
||||||
"webKeyFile": "",
|
"webKeyFile": "",
|
||||||
@@ -38,13 +39,14 @@ var defaultValueMap = map[string]string{
|
|||||||
"tgBotChatId": "",
|
"tgBotChatId": "",
|
||||||
"tgRunTime": "@daily",
|
"tgRunTime": "@daily",
|
||||||
"tgBotBackup": "false",
|
"tgBotBackup": "false",
|
||||||
|
"tgBotLoginNotify": "false",
|
||||||
"tgCpu": "0",
|
"tgCpu": "0",
|
||||||
"tgLang": "en-US",
|
"tgLang": "en-US",
|
||||||
"secretEnable": "false",
|
"secretEnable": "false",
|
||||||
"subEnable": "false",
|
"subEnable": "false",
|
||||||
"subListen": "",
|
"subListen": "",
|
||||||
"subPort": "2096",
|
"subPort": "2096",
|
||||||
"subPath": "sub/",
|
"subPath": "/sub/",
|
||||||
"subDomain": "",
|
"subDomain": "",
|
||||||
"subCertFile": "",
|
"subCertFile": "",
|
||||||
"subKeyFile": "",
|
"subKeyFile": "",
|
||||||
@@ -225,6 +227,10 @@ func (s *SettingService) GetListen() (string, error) {
|
|||||||
return s.getString("webListen")
|
return s.getString("webListen")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetWebDomain() (string, error) {
|
||||||
|
return s.getString("webDomain")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetTgBotToken() (string, error) {
|
func (s *SettingService) GetTgBotToken() (string, error) {
|
||||||
return s.getString("tgBotToken")
|
return s.getString("tgBotToken")
|
||||||
}
|
}
|
||||||
@@ -261,6 +267,10 @@ func (s *SettingService) GetTgBotBackup() (bool, error) {
|
|||||||
return s.getBool("tgBotBackup")
|
return s.getBool("tgBotBackup")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTgBotLoginNotify() (bool, error) {
|
||||||
|
return s.getBool("tgBotLoginNotify")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetTgCpu() (int, error) {
|
func (s *SettingService) GetTgCpu() (int, error) {
|
||||||
return s.getInt("tgCpu")
|
return s.getInt("tgCpu")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,13 +77,15 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, adminId := range strings.Split(tgBotid, ",") {
|
if tgBotid != "" {
|
||||||
id, err := strconv.Atoi(adminId)
|
for _, adminId := range strings.Split(tgBotid, ",") {
|
||||||
if err != nil {
|
id, err := strconv.Atoi(adminId)
|
||||||
logger.Warning("Failed to get IDs from GetTgBotChatId:", err)
|
if err != nil {
|
||||||
return err
|
logger.Warning("Failed to get IDs from GetTgBotChatId:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
adminIds = append(adminIds, int64(id))
|
||||||
}
|
}
|
||||||
adminIds = append(adminIds, int64(id))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bot, err = telego.NewBot(tgBottoken)
|
bot, err = telego.NewBot(tgBottoken)
|
||||||
@@ -188,7 +190,7 @@ func (t *Tgbot) OnReceive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin bool) {
|
func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin bool) {
|
||||||
msg := ""
|
msg, onlyMessage := "", false
|
||||||
|
|
||||||
command, commandArgs := tu.ParseCommand(message.Text)
|
command, commandArgs := tu.ParseCommand(message.Text)
|
||||||
|
|
||||||
@@ -204,8 +206,13 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
|||||||
}
|
}
|
||||||
msg += "\n\n" + t.I18nBot("tgbot.commands.pleaseChoose")
|
msg += "\n\n" + t.I18nBot("tgbot.commands.pleaseChoose")
|
||||||
case "status":
|
case "status":
|
||||||
|
onlyMessage = true
|
||||||
msg += t.I18nBot("tgbot.commands.status")
|
msg += t.I18nBot("tgbot.commands.status")
|
||||||
|
case "id":
|
||||||
|
onlyMessage = true
|
||||||
|
msg += t.I18nBot("tgbot.commands.getID", "ID=="+strconv.FormatInt(message.From.ID, 10))
|
||||||
case "usage":
|
case "usage":
|
||||||
|
onlyMessage = true
|
||||||
if len(commandArgs) > 0 {
|
if len(commandArgs) > 0 {
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
t.searchClient(chatId, commandArgs[0])
|
t.searchClient(chatId, commandArgs[0])
|
||||||
@@ -216,6 +223,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
|||||||
msg += t.I18nBot("tgbot.commands.usage")
|
msg += t.I18nBot("tgbot.commands.usage")
|
||||||
}
|
}
|
||||||
case "inbound":
|
case "inbound":
|
||||||
|
onlyMessage = true
|
||||||
if isAdmin && len(commandArgs) > 0 {
|
if isAdmin && len(commandArgs) > 0 {
|
||||||
t.searchInbound(chatId, commandArgs[0])
|
t.searchInbound(chatId, commandArgs[0])
|
||||||
} else {
|
} else {
|
||||||
@@ -224,6 +232,11 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
|||||||
default:
|
default:
|
||||||
msg += t.I18nBot("tgbot.commands.unknown")
|
msg += t.I18nBot("tgbot.commands.unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if onlyMessage {
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
t.SendAnswer(chatId, msg, isAdmin)
|
t.SendAnswer(chatId, msg, isAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,6 +511,7 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R
|
|||||||
if !isRunning {
|
if !isRunning {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg == "" {
|
if msg == "" {
|
||||||
logger.Info("[tgbot] message is empty!")
|
logger.Info("[tgbot] message is empty!")
|
||||||
return
|
return
|
||||||
@@ -629,6 +643,11 @@ func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loginNotifyEnabled, err := t.settingService.GetTgBotLoginNotify()
|
||||||
|
if err != nil || !loginNotifyEnabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
msg := ""
|
msg := ""
|
||||||
if status == LoginSuccess {
|
if status == LoginSuccess {
|
||||||
msg += t.I18nBot("tgbot.messages.loginSuccess")
|
msg += t.I18nBot("tgbot.messages.loginSuccess")
|
||||||
@@ -659,9 +678,9 @@ func (t *Tgbot) getInboundUsages() string {
|
|||||||
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
||||||
|
|
||||||
if inbound.ExpiryTime == 0 {
|
if inbound.ExpiryTime == 0 {
|
||||||
info += t.I18nBot("tgbot.messages.expire", "DateTime=="+t.I18nBot("tgbot.unlimited"))
|
info += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited"))
|
||||||
} else {
|
} else {
|
||||||
info += t.I18nBot("tgbot.messages.expire", "DateTime=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
info += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -697,14 +716,21 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string, tgUserID string)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
|
flag := false
|
||||||
|
diff := traffic.ExpiryTime/1000 - now
|
||||||
if traffic.ExpiryTime == 0 {
|
if traffic.ExpiryTime == 0 {
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||||
|
} else if diff > 172800 || !traffic.Enable {
|
||||||
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
} else if traffic.ExpiryTime < 0 {
|
} else if traffic.ExpiryTime < 0 {
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||||
|
flag = true
|
||||||
} else {
|
} else {
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours"))
|
||||||
|
flag = true
|
||||||
}
|
}
|
||||||
|
|
||||||
total := ""
|
total := ""
|
||||||
@@ -715,13 +741,22 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string, tgUserID string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
output := ""
|
output := ""
|
||||||
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||||
|
if traffic.Enable {
|
||||||
|
output += t.I18nBot("tgbot.messages.active")
|
||||||
|
if flag {
|
||||||
|
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||||
|
} else {
|
||||||
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output += t.I18nBot("tgbot.messages.inactive")
|
||||||
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
|
||||||
|
}
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
|
||||||
t.SendMsgToTgbot(chatId, output)
|
t.SendMsgToTgbot(chatId, output)
|
||||||
}
|
}
|
||||||
@@ -775,6 +810,7 @@ func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...
|
|||||||
output := ""
|
output := ""
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+email)
|
output += t.I18nBot("tgbot.messages.email", "Email=="+email)
|
||||||
output += t.I18nBot("tgbot.messages.TGUser", "TelegramID=="+tgId)
|
output += t.I18nBot("tgbot.messages.TGUser", "TelegramID=="+tgId)
|
||||||
|
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
|
||||||
inlineKeyboard := tu.InlineKeyboard(
|
inlineKeyboard := tu.InlineKeyboard(
|
||||||
tu.InlineKeyboardRow(
|
tu.InlineKeyboardRow(
|
||||||
@@ -819,13 +855,20 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
|
flag := false
|
||||||
|
diff := traffic.ExpiryTime/1000 - now
|
||||||
if traffic.ExpiryTime == 0 {
|
if traffic.ExpiryTime == 0 {
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||||
|
} else if diff > 172800 || !traffic.Enable {
|
||||||
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
} else if traffic.ExpiryTime < 0 {
|
} else if traffic.ExpiryTime < 0 {
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||||
|
flag = true
|
||||||
} else {
|
} else {
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours"))
|
||||||
|
flag = true
|
||||||
}
|
}
|
||||||
|
|
||||||
total := ""
|
total := ""
|
||||||
@@ -836,13 +879,22 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
output := ""
|
output := ""
|
||||||
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||||
|
if traffic.Enable {
|
||||||
|
output += t.I18nBot("tgbot.messages.active")
|
||||||
|
if flag {
|
||||||
|
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||||
|
} else {
|
||||||
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output += t.I18nBot("tgbot.messages.inactive")
|
||||||
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
|
||||||
|
}
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
|
||||||
inlineKeyboard := tu.InlineKeyboard(
|
inlineKeyboard := tu.InlineKeyboard(
|
||||||
tu.InlineKeyboardRow(
|
tu.InlineKeyboardRow(
|
||||||
@@ -887,6 +939,7 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
for _, inbound := range inbouds {
|
for _, inbound := range inbouds {
|
||||||
info := ""
|
info := ""
|
||||||
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
||||||
@@ -894,20 +947,26 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
|||||||
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
||||||
|
|
||||||
if inbound.ExpiryTime == 0 {
|
if inbound.ExpiryTime == 0 {
|
||||||
info += t.I18nBot("tgbot.messages.expire", "DateTime=="+t.I18nBot("tgbot.unlimited"))
|
info += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited"))
|
||||||
} else {
|
} else {
|
||||||
info += t.I18nBot("tgbot.messages.expire", "DateTime=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
info += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
||||||
}
|
}
|
||||||
t.SendMsgToTgbot(chatId, info)
|
t.SendMsgToTgbot(chatId, info)
|
||||||
|
|
||||||
for _, traffic := range inbound.ClientStats {
|
for _, traffic := range inbound.ClientStats {
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
|
flag := false
|
||||||
|
diff := traffic.ExpiryTime/1000 - now
|
||||||
if traffic.ExpiryTime == 0 {
|
if traffic.ExpiryTime == 0 {
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||||
|
} else if diff > 172800 || !traffic.Enable {
|
||||||
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
} else if traffic.ExpiryTime < 0 {
|
} else if traffic.ExpiryTime < 0 {
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||||
|
flag = true
|
||||||
} else {
|
} else {
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours"))
|
||||||
|
flag = true
|
||||||
}
|
}
|
||||||
|
|
||||||
total := ""
|
total := ""
|
||||||
@@ -918,13 +977,22 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
output := ""
|
output := ""
|
||||||
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||||
|
if traffic.Enable {
|
||||||
|
output += t.I18nBot("tgbot.messages.active")
|
||||||
|
if flag {
|
||||||
|
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||||
|
} else {
|
||||||
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output += t.I18nBot("tgbot.messages.inactive")
|
||||||
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
|
||||||
|
}
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
|
||||||
t.SendMsgToTgbot(chatId, output)
|
t.SendMsgToTgbot(chatId, output)
|
||||||
}
|
}
|
||||||
@@ -945,13 +1013,20 @@ func (t *Tgbot) searchForClient(chatId int64, query string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
|
flag := false
|
||||||
|
diff := traffic.ExpiryTime/1000 - now
|
||||||
if traffic.ExpiryTime == 0 {
|
if traffic.ExpiryTime == 0 {
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||||
|
} else if diff > 172800 || !traffic.Enable {
|
||||||
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
} else if traffic.ExpiryTime < 0 {
|
} else if traffic.ExpiryTime < 0 {
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||||
|
flag = true
|
||||||
} else {
|
} else {
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours"))
|
||||||
|
flag = true
|
||||||
}
|
}
|
||||||
|
|
||||||
total := ""
|
total := ""
|
||||||
@@ -962,13 +1037,22 @@ func (t *Tgbot) searchForClient(chatId int64, query string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
output := ""
|
output := ""
|
||||||
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||||
|
if traffic.Enable {
|
||||||
|
output += t.I18nBot("tgbot.messages.active")
|
||||||
|
if flag {
|
||||||
|
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||||
|
} else {
|
||||||
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output += t.I18nBot("tgbot.messages.inactive")
|
||||||
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
|
||||||
|
}
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
|
||||||
t.SendMsgToTgbot(chatId, output)
|
t.SendMsgToTgbot(chatId, output)
|
||||||
}
|
}
|
||||||
@@ -1033,9 +1117,9 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
output += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
output += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
||||||
output += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
output += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
||||||
if inbound.ExpiryTime == 0 {
|
if inbound.ExpiryTime == 0 {
|
||||||
output += t.I18nBot("tgbot.messages.expire", "DateTime=="+t.I18nBot("tgbot.unlimited"))
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited"))
|
||||||
} else {
|
} else {
|
||||||
output += t.I18nBot("tgbot.messages.expire", "DateTime=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
||||||
}
|
}
|
||||||
output += "\r\n \r\n"
|
output += "\r\n \r\n"
|
||||||
}
|
}
|
||||||
@@ -1052,12 +1136,18 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
|
|
||||||
for _, traffic := range exhaustedClients {
|
for _, traffic := range exhaustedClients {
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
|
flag := false
|
||||||
|
diff := (traffic.ExpiryTime - now) / 1000
|
||||||
if traffic.ExpiryTime == 0 {
|
if traffic.ExpiryTime == 0 {
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||||
} else if traffic.ExpiryTime < 0 {
|
} else if diff > 172800 || !traffic.Enable {
|
||||||
expiryTime += fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
|
||||||
} else {
|
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
|
} else if traffic.ExpiryTime < 0 {
|
||||||
|
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||||
|
flag = true
|
||||||
|
} else {
|
||||||
|
expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours"))
|
||||||
|
flag = true
|
||||||
}
|
}
|
||||||
|
|
||||||
total := ""
|
total := ""
|
||||||
@@ -1067,13 +1157,22 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
total = common.FormatTraffic((traffic.Total))
|
total = common.FormatTraffic((traffic.Total))
|
||||||
}
|
}
|
||||||
|
|
||||||
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+strconv.FormatBool(traffic.Enable))
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||||
|
if traffic.Enable {
|
||||||
|
output += t.I18nBot("tgbot.messages.active")
|
||||||
|
if flag {
|
||||||
|
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||||
|
} else {
|
||||||
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output += t.I18nBot("tgbot.messages.inactive")
|
||||||
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime)
|
||||||
|
}
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
||||||
output += "\r\n \r\n"
|
output += "\r\n \r\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ var result string
|
|||||||
type XrayService struct {
|
type XrayService struct {
|
||||||
inboundService InboundService
|
inboundService InboundService
|
||||||
settingService SettingService
|
settingService SettingService
|
||||||
|
xrayAPI xray.XrayAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) IsXrayRunning() bool {
|
func (s *XrayService) IsXrayRunning() bool {
|
||||||
@@ -115,7 +116,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for key := range c {
|
for key := range c {
|
||||||
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "alterId" {
|
if key != "email" && key != "id" && key != "password" && key != "flow" {
|
||||||
delete(c, key)
|
delete(c, key)
|
||||||
}
|
}
|
||||||
if c["flow"] == "xtls-rprx-vision-udp443" {
|
if c["flow"] == "xtls-rprx-vision-udp443" {
|
||||||
@@ -143,7 +144,9 @@ func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic,
|
|||||||
if !s.IsXrayRunning() {
|
if !s.IsXrayRunning() {
|
||||||
return nil, nil, errors.New("xray is not running")
|
return nil, nil, errors.New("xray is not running")
|
||||||
}
|
}
|
||||||
return p.GetTraffic(true)
|
s.xrayAPI.Init(p.GetAPIPort())
|
||||||
|
defer s.xrayAPI.Close()
|
||||||
|
return s.xrayAPI.GetTraffic(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) RestartXray(isForce bool) error {
|
func (s *XrayService) RestartXray(isForce bool) error {
|
||||||
@@ -158,7 +161,7 @@ func (s *XrayService) RestartXray(isForce bool) error {
|
|||||||
|
|
||||||
if p != nil && p.IsRunning() {
|
if p != nil && p.IsRunning() {
|
||||||
if !isForce && p.GetConfig().Equals(xrayConfig) {
|
if !isForce && p.GetConfig().Equals(xrayConfig) {
|
||||||
logger.Debug("not need to restart xray")
|
logger.Debug("It does not need to restart xray")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
p.Stop()
|
p.Stop()
|
||||||
@@ -166,7 +169,11 @@ func (s *XrayService) RestartXray(isForce bool) error {
|
|||||||
|
|
||||||
p = xray.NewProcess(xrayConfig)
|
p = xray.NewProcess(xrayConfig)
|
||||||
result = ""
|
result = ""
|
||||||
return p.Start()
|
err = p.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) StopXray() error {
|
func (s *XrayService) StopXray() error {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
sessions "github.com/Calidity/gin-sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
"depleted" = "Depleted"
|
"depleted" = "Depleted"
|
||||||
"depletingSoon" = "Depleting soon"
|
"depletingSoon" = "Depleting soon"
|
||||||
"domainName" = "Domain name"
|
"domainName" = "Domain name"
|
||||||
"additional" = "Alter"
|
|
||||||
"monitor" = "Listening IP"
|
"monitor" = "Listening IP"
|
||||||
"certificate" = "Certificate"
|
"certificate" = "Certificate"
|
||||||
"fail" = "Fail"
|
"fail" = "Fail"
|
||||||
@@ -49,6 +48,7 @@
|
|||||||
"clients" = "Clients"
|
"clients" = "Clients"
|
||||||
"usage" = "Usage"
|
"usage" = "Usage"
|
||||||
"secretToken" = "Secret Token"
|
"secretToken" = "Secret Token"
|
||||||
|
"remained" = "Remained"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "System Status"
|
"dashboard" = "System Status"
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"title" = "System Status"
|
"title" = "System Status"
|
||||||
"memory" = "Memory"
|
"memory" = "Memory"
|
||||||
"hard" = "Hard Disk"
|
"hard" = "Hard Disk"
|
||||||
"xrayStatus" = "Xray Status"
|
"xrayStatus" = "Status"
|
||||||
"stopXray" = "Stop"
|
"stopXray" = "Stop"
|
||||||
"restartXray" = "Restart"
|
"restartXray" = "Restart"
|
||||||
"xraySwitch" = "SwitchV"
|
"xraySwitch" = "SwitchV"
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
"setDefaultCert" = "Set cert from panel"
|
"setDefaultCert" = "Set cert from panel"
|
||||||
"xtlsDesc" = "Xray core needs to be 1.7.5"
|
"xtlsDesc" = "Xray core needs to be 1.7.5"
|
||||||
"realityDesc" = "Xray core needs to be 1.8.0 or higher."
|
"realityDesc" = "Xray core needs to be 1.8.0 or higher."
|
||||||
"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot )"
|
"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot or use '/id' command in bot )"
|
||||||
"subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations"
|
"subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
@@ -221,6 +221,8 @@
|
|||||||
"TGBotSettings" = "Telegram Bot Settings"
|
"TGBotSettings" = "Telegram Bot Settings"
|
||||||
"panelListeningIP" = "Panel Listening IP"
|
"panelListeningIP" = "Panel Listening IP"
|
||||||
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs."
|
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs."
|
||||||
|
"panelListeningDomain" = "Panel Listening Domain"
|
||||||
|
"panelListeningDomainDesc" = "Leave blank by default to monitor all domains and IPs"
|
||||||
"panelPort" = "Panel Port"
|
"panelPort" = "Panel Port"
|
||||||
"panelPortDesc" = "The port used to display this panel"
|
"panelPortDesc" = "The port used to display this panel"
|
||||||
"publicKeyPath" = "Panel Certificate Public Key File Path"
|
"publicKeyPath" = "Panel Certificate Public Key File Path"
|
||||||
@@ -238,11 +240,13 @@
|
|||||||
"telegramToken" = "Telegram Token"
|
"telegramToken" = "Telegram Token"
|
||||||
"telegramTokenDesc" = "You must get the token from the manager of Telegram bots @botfather"
|
"telegramTokenDesc" = "You must get the token from the manager of Telegram bots @botfather"
|
||||||
"telegramChatId" = "Telegram Admin Chat IDs"
|
"telegramChatId" = "Telegram Admin Chat IDs"
|
||||||
"telegramChatIdDesc" = "Multiple Chat IDs separated by comma. use @userinfobot to get your Chat IDs."
|
"telegramChatIdDesc" = "Multiple Chat IDs separated by comma. use @userinfobot or use '/id' command in bot to get your Chat IDs."
|
||||||
"telegramNotifyTime" = "Telegram bot notification time"
|
"telegramNotifyTime" = "Telegram bot notification time"
|
||||||
"telegramNotifyTimeDesc" = "Use Crontab timing format."
|
"telegramNotifyTimeDesc" = "Use Crontab timing format."
|
||||||
"tgNotifyBackup" = "Database Backup"
|
"tgNotifyBackup" = "Database Backup"
|
||||||
"tgNotifyBackupDesc" = "Include database backup file with report notification."
|
"tgNotifyBackupDesc" = "Include database backup file with report notification."
|
||||||
|
"tgNotifyLogin" = "Login Notification"
|
||||||
|
"tgNotifyLoginDesc" = "Displays the username, IP address, and time when someone tries to log into your panel."
|
||||||
"sessionMaxAge" = "Session maximum age"
|
"sessionMaxAge" = "Session maximum age"
|
||||||
"sessionMaxAgeDesc" = "The duration of a login session (unit: minute)"
|
"sessionMaxAgeDesc" = "The duration of a login session (unit: minute)"
|
||||||
"expireTimeDiff" = "Expiration threshold for notification"
|
"expireTimeDiff" = "Expiration threshold for notification"
|
||||||
@@ -384,6 +388,7 @@
|
|||||||
"months" = "Months"
|
"months" = "Months"
|
||||||
"day" = "Day"
|
"day" = "Day"
|
||||||
"days" = "Days"
|
"days" = "Days"
|
||||||
|
"hours" = "Hours"
|
||||||
"unknown" = "Unknown"
|
"unknown" = "Unknown"
|
||||||
"inbounds" = "Inbounds"
|
"inbounds" = "Inbounds"
|
||||||
"clients" = "Clients"
|
"clients" = "Clients"
|
||||||
@@ -396,6 +401,7 @@
|
|||||||
"welcome" = "🤖 Welcome to <b>{{ .Hostname }}</b> management bot.\r\n"
|
"welcome" = "🤖 Welcome to <b>{{ .Hostname }}</b> management bot.\r\n"
|
||||||
"status" = "✅ Bot is ok!"
|
"status" = "✅ Bot is ok!"
|
||||||
"usage" = "❗ Please provide a text to search!"
|
"usage" = "❗ Please provide a text to search!"
|
||||||
|
"getID" = "🆔 Your ID: <code>{{ .ID }}</code>"
|
||||||
"helpAdminCommands" = "Search for a client email:\r\n<code>/usage [Email]</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>"
|
"helpAdminCommands" = "Search for a client email:\r\n<code>/usage [Email]</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>"
|
||||||
"helpClientCommands" = "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\nUse UUID for vmess/vless and Password for Trojan."
|
"helpClientCommands" = "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\nUse UUID for vmess/vless and Password for Trojan."
|
||||||
|
|
||||||
@@ -424,20 +430,21 @@
|
|||||||
"time" = "⏰ Time: {{ .Time }}\r\n"
|
"time" = "⏰ Time: {{ .Time }}\r\n"
|
||||||
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
||||||
"port" = "🔌 Port: {{ .Port }}\r\n"
|
"port" = "🔌 Port: {{ .Port }}\r\n"
|
||||||
"expire" = "📅 Expire Date: {{ .DateTime }}\r\n \r\n"
|
"expire" = "📅 Expire Date: {{ .Time }}\r\n"
|
||||||
"expireIn" = "📅 Expire In: {{ .Time }}\r\n \r\n"
|
"expireIn" = "📅 Expire In: {{ .Time }}\r\n"
|
||||||
"active" = "💡 Active: {{ .Enable }}\r\n"
|
"active" = "💡 Active: ✅ Yes\r\n"
|
||||||
|
"inactive" = "💡 Active: ❌ No\r\n"
|
||||||
"email" = "📧 Email: {{ .Email }}\r\n"
|
"email" = "📧 Email: {{ .Email }}\r\n"
|
||||||
"upload" = "🔼 Upload↑: {{ .Upload }}\r\n"
|
"upload" = "🔼 Upload: ↑{{ .Upload }}\r\n"
|
||||||
"download" = "🔽 Download↓: {{ .Download }}\r\n"
|
"download" = "🔽 Download: ↓{{ .Download }}\r\n"
|
||||||
"total" = "🔄 Total: {{ .UpDown }} / {{ .Total }}\r\n"
|
"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
"TGUser" = "👤 Telegram User: {{ .TelegramID }}\r\n"
|
"TGUser" = "👤 Telegram User: {{ .TelegramID }}\r\n"
|
||||||
"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n"
|
"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n"
|
||||||
"exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n"
|
"exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n"
|
||||||
"disabled" = "🛑 Disabled: {{ .Disabled }}\r\n"
|
"disabled" = "🛑 Disabled: {{ .Disabled }}\r\n"
|
||||||
"depleteSoon" = "🔜 Deplete soon: {{ .Deplete }}\r\n \r\n"
|
"depleteSoon" = "🔜 Deplete soon: {{ .Deplete }}\r\n \r\n"
|
||||||
"backupTime" = "🗄 Backup Time: {{ .Time }}\r\n"
|
"backupTime" = "🗄 Backup Time: {{ .Time }}\r\n"
|
||||||
"refreshedOn" = "🔄🕒 Refreshed On: {{ .Time }}\r\n"
|
"refreshedOn" = "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n \r\n"
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ Close Keyboard"
|
"closeKeyboard" = "❌ Close Keyboard"
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
"depleted" = "منقضی"
|
"depleted" = "منقضی"
|
||||||
"depletingSoon" = "در حال انقضا"
|
"depletingSoon" = "در حال انقضا"
|
||||||
"domainName" = "آدرس دامنه"
|
"domainName" = "آدرس دامنه"
|
||||||
"additional" = "آی دی جایگزین"
|
|
||||||
"monitor" = "آی پی اتصال"
|
"monitor" = "آی پی اتصال"
|
||||||
"certificate" = "گواهی دیجیتال"
|
"certificate" = "گواهی دیجیتال"
|
||||||
"fail" = "خطا"
|
"fail" = "خطا"
|
||||||
@@ -49,6 +48,7 @@
|
|||||||
"clients" = "کاربران"
|
"clients" = "کاربران"
|
||||||
"usage" = "استفاده"
|
"usage" = "استفاده"
|
||||||
"secretToken" = "توکن امنیتی"
|
"secretToken" = "توکن امنیتی"
|
||||||
|
"remained" = "باقیمانده"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "وضعیت سیستم"
|
"dashboard" = "وضعیت سیستم"
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"title" = "وضعیت سیستم"
|
"title" = "وضعیت سیستم"
|
||||||
"memory" = "حافظه رم"
|
"memory" = "حافظه رم"
|
||||||
"hard" = "حافظه دیسک"
|
"hard" = "حافظه دیسک"
|
||||||
"xrayStatus" = "وضعیت Xray"
|
"xrayStatus" = "وضعیت"
|
||||||
"stopXray" = "توقف"
|
"stopXray" = "توقف"
|
||||||
"restartXray" = "شروع مجدد"
|
"restartXray" = "شروع مجدد"
|
||||||
"xraySwitch" = "تغییر ورژن"
|
"xraySwitch" = "تغییر ورژن"
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||||
"xtlsDesc" = "هسته Xray باید 1.7.5 باشد"
|
"xtlsDesc" = "هسته Xray باید 1.7.5 باشد"
|
||||||
"realityDesc" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
"realityDesc" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
||||||
"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot)"
|
"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot یا در ربات دستور '/id' را وارد کنید)"
|
||||||
"subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید"
|
"subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
@@ -221,6 +221,8 @@
|
|||||||
"TGBotSettings" = "تنظیمات ربات تلگرام"
|
"TGBotSettings" = "تنظیمات ربات تلگرام"
|
||||||
"panelListeningIP" = "محدودیت آی پی پنل"
|
"panelListeningIP" = "محدودیت آی پی پنل"
|
||||||
"panelListeningIPDesc" = "برای استفاده از تمام آیپیها به طور پیش فرض خالی بگذارید"
|
"panelListeningIPDesc" = "برای استفاده از تمام آیپیها به طور پیش فرض خالی بگذارید"
|
||||||
|
"panelListeningDomain" = "محدودیت دامین پنل"
|
||||||
|
"panelListeningDomainDesc" = "برای استفاده از تمام دامنهها و آیپیها به طور پیش فرض خالی بگذارید"
|
||||||
"panelPort" = "پورت پنل"
|
"panelPort" = "پورت پنل"
|
||||||
"panelPortDesc" = "پورت مورد استفاده برای نمایش این پنل"
|
"panelPortDesc" = "پورت مورد استفاده برای نمایش این پنل"
|
||||||
"publicKeyPath" = "مسیر فایل گواهی کلید عمومی پنل"
|
"publicKeyPath" = "مسیر فایل گواهی کلید عمومی پنل"
|
||||||
@@ -238,11 +240,13 @@
|
|||||||
"telegramToken" = "توکن تلگرام"
|
"telegramToken" = "توکن تلگرام"
|
||||||
"telegramTokenDesc" = "توکن را باید از مدیر بات های تلگرام دریافت کنید @botfather"
|
"telegramTokenDesc" = "توکن را باید از مدیر بات های تلگرام دریافت کنید @botfather"
|
||||||
"telegramChatId" = "آی دی تلگرام مدیریت"
|
"telegramChatId" = "آی دی تلگرام مدیریت"
|
||||||
"telegramChatIdDesc" = "از @userinfobot برای دریافت شناسه های چت خود استفاده کنید. با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. "
|
"telegramChatIdDesc" = "از @userinfobot یا دستور '/id' در ربات برای دریافت شناسه های چت خود استفاده کنید. با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. "
|
||||||
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
||||||
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید "
|
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید "
|
||||||
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
||||||
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
||||||
|
"tgNotifyLogin" = "اعلان ورود"
|
||||||
|
"tgNotifyLoginDesc" = "نام کاربری، آدرس ای پی، و زمان وقتی که فردی سعی میکند به پنل شما وارد شود نمایش میدهد"
|
||||||
"sessionMaxAge" = "بیشینه زمان جلسه وب"
|
"sessionMaxAge" = "بیشینه زمان جلسه وب"
|
||||||
"sessionMaxAgeDesc" = "بیشینه زمانی که میتوانید لاگین بمانید (واحد: دقیقه)"
|
"sessionMaxAgeDesc" = "بیشینه زمانی که میتوانید لاگین بمانید (واحد: دقیقه)"
|
||||||
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
||||||
@@ -384,6 +388,7 @@
|
|||||||
"months" = "ماهها"
|
"months" = "ماهها"
|
||||||
"day" = "روز"
|
"day" = "روز"
|
||||||
"days" = "روزها"
|
"days" = "روزها"
|
||||||
|
"hours" = "ساعت ها"
|
||||||
"unknown" = "نامشخص"
|
"unknown" = "نامشخص"
|
||||||
"inbounds" = "ورودیها"
|
"inbounds" = "ورودیها"
|
||||||
"clients" = "کلاینتها"
|
"clients" = "کلاینتها"
|
||||||
@@ -396,6 +401,7 @@
|
|||||||
"welcome" = "🤖 به ربات مدیریت <b>{{ .Hostname }}</b> خوش آمدید.\r\n"
|
"welcome" = "🤖 به ربات مدیریت <b>{{ .Hostname }}</b> خوش آمدید.\r\n"
|
||||||
"status" = "✅ ربات در حالت عادی است!"
|
"status" = "✅ ربات در حالت عادی است!"
|
||||||
"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!"
|
"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!"
|
||||||
|
"getID" = "🆔 شناسه شما: <code>{{ .ID }}</code>"
|
||||||
"helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n \r\nبرای جستجوی ورودیها (با آمار مشتری):\r\n<code>/inbound [توضیح]</code>"
|
"helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n \r\nبرای جستجوی ورودیها (با آمار مشتری):\r\n<code>/inbound [توضیح]</code>"
|
||||||
"helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفاده کنید:\r\n \r\n<code>/usage [UUID|رمز عبور]</code>\r\n \r\nاز UUID برای vmess/vless و از رمز عبور برای Trojan استفاده کنید."
|
"helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفاده کنید:\r\n \r\n<code>/usage [UUID|رمز عبور]</code>\r\n \r\nاز UUID برای vmess/vless و از رمز عبور برای Trojan استفاده کنید."
|
||||||
|
|
||||||
@@ -424,20 +430,21 @@
|
|||||||
"time" = "⏰ زمان: {{ .Time }}\r\n"
|
"time" = "⏰ زمان: {{ .Time }}\r\n"
|
||||||
"inbound" = "📍 ورودی: {{ .Remark }}\r\n"
|
"inbound" = "📍 ورودی: {{ .Remark }}\r\n"
|
||||||
"port" = "🔌 پورت: {{ .Port }}\r\n"
|
"port" = "🔌 پورت: {{ .Port }}\r\n"
|
||||||
"expire" = "📅 تاریخ انقضا: {{ .DateTime }}\r\n \r\n"
|
"expire" = "📅 تاریخ انقضا: {{ .Time }}\r\n"
|
||||||
"expireIn" = "📅 باقیمانده از انقضا: {{ .Time }}\r\n \r\n"
|
"expireIn" = "📅 باقیمانده از انقضا: {{ .Time }}\r\n"
|
||||||
"active" = "💡 فعال: {{ .Enable }}\r\n"
|
"active" = "💡 فعال: ✅\r\n"
|
||||||
|
"inactive" = "💡 فعال: ❌\r\n"
|
||||||
"email" = "📧 ایمیل: {{ .Email }}\r\n"
|
"email" = "📧 ایمیل: {{ .Email }}\r\n"
|
||||||
"upload" = "🔼 آپلود↑: {{ .Upload }}\r\n"
|
"upload" = "🔼 آپلود↑: {{ .Upload }}\r\n"
|
||||||
"download" = "🔽 دانلود↓: {{ .Download }}\r\n"
|
"download" = "🔽 دانلود↓: {{ .Download }}\r\n"
|
||||||
"total" = "🔄 کل: {{ .UpDown }} / {{ .Total }}\r\n"
|
"total" = "📊 کل: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
"TGUser" = "👤 کاربر تلگرام: {{ .TelegramID }}\r\n"
|
"TGUser" = "👤 کاربر تلگرام: {{ .TelegramID }}\r\n"
|
||||||
"exhaustedMsg" = "🚨 {{ .Type }} به اتمام رسیده است:\r\n"
|
"exhaustedMsg" = "🚨 {{ .Type }} به اتمام رسیده است:\r\n"
|
||||||
"exhaustedCount" = "🚨 تعداد {{ .Type }} به اتمام رسیده:\r\n"
|
"exhaustedCount" = "🚨 تعداد {{ .Type }} به اتمام رسیده:\r\n"
|
||||||
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
|
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
|
||||||
"depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n \r\n"
|
"depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n \r\n"
|
||||||
"backupTime" = "🗄 زمان پشتیبانگیری: {{ .Time }}\r\n"
|
"backupTime" = "🗄 زمان پشتیبانگیری: {{ .Time }}\r\n"
|
||||||
"refreshedOn" = "🔄🕒 تازهسازی شده در: {{ .Time }}\r\n"
|
"refreshedOn" = "\r\n📋🔄 تازهسازی شده در: {{ .Time }}\r\n \r\n"
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ بستن کیبورد"
|
"closeKeyboard" = "❌ بستن کیبورد"
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
"depleted" = "Исчерпано"
|
"depleted" = "Исчерпано"
|
||||||
"depletingSoon" = "Почти исчерпано"
|
"depletingSoon" = "Почти исчерпано"
|
||||||
"domainName" = "Домен"
|
"domainName" = "Домен"
|
||||||
"additional" = "Дополнительно"
|
|
||||||
"monitor" = "Порт IP"
|
"monitor" = "Порт IP"
|
||||||
"certificate" = "Сертификат"
|
"certificate" = "Сертификат"
|
||||||
"fail" = "Неудачно"
|
"fail" = "Неудачно"
|
||||||
@@ -49,6 +48,7 @@
|
|||||||
"clients" = "Клиенты"
|
"clients" = "Клиенты"
|
||||||
"usage" = "Использование"
|
"usage" = "Использование"
|
||||||
"secretToken" = "Секретный токен"
|
"secretToken" = "Секретный токен"
|
||||||
|
"remained" = "остались"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Статус системы"
|
"dashboard" = "Статус системы"
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"title" = "Статус системы"
|
"title" = "Статус системы"
|
||||||
"memory" = "Память"
|
"memory" = "Память"
|
||||||
"hard" = "Жесткий диск"
|
"hard" = "Жесткий диск"
|
||||||
"xrayStatus" = "Статус Xray"
|
"xrayStatus" = "Статус"
|
||||||
"stopXray" = "Остановить Xray"
|
"stopXray" = "Остановить Xray"
|
||||||
"restartXray" = "Рестарт Xray"
|
"restartXray" = "Рестарт Xray"
|
||||||
"xraySwitch" = "Переключить версию"
|
"xraySwitch" = "Переключить версию"
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
"setDefaultCert" = "Установить сертификат с панели"
|
"setDefaultCert" = "Установить сертификат с панели"
|
||||||
"xtlsDesc" = "Версия Xray должна быть не ниже 1.7.5"
|
"xtlsDesc" = "Версия Xray должна быть не ниже 1.7.5"
|
||||||
"realityDesc" = "Версия Xray должна быть не ниже 1.8.0"
|
"realityDesc" = "Версия Xray должна быть не ниже 1.8.0"
|
||||||
"telegramDesc" = "Используйте Telegram ID без @ или ID пользователя (вы можете получить его у @userinfobot)"
|
"telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)"
|
||||||
"subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких конфигураций"
|
"subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких конфигураций"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
@@ -221,6 +221,8 @@
|
|||||||
"TGBotSettings" = "Настройки Telegram бота"
|
"TGBotSettings" = "Настройки Telegram бота"
|
||||||
"panelListeningIP" = "IP адрес панели"
|
"panelListeningIP" = "IP адрес панели"
|
||||||
"panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP"
|
"panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP"
|
||||||
|
"panelListeningDomain" = "Домен прослушивания панели"
|
||||||
|
"panelListeningDomainDesc" = "По умолчанию оставьте пустым, чтобы отслеживать все домены и IP-адреса"
|
||||||
"panelPort" = "Порт панели"
|
"panelPort" = "Порт панели"
|
||||||
"panelPortDesc" = "Порт, используемый для отображения этой панели"
|
"panelPortDesc" = "Порт, используемый для отображения этой панели"
|
||||||
"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели"
|
"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели"
|
||||||
@@ -238,11 +240,13 @@
|
|||||||
"telegramToken" = "Токен Telegram бота"
|
"telegramToken" = "Токен Telegram бота"
|
||||||
"telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather"
|
"telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather"
|
||||||
"telegramChatId" = "Telegram ID админа бота"
|
"telegramChatId" = "Telegram ID админа бота"
|
||||||
"telegramChatIdDesc" = "Несколько Telegram ID, разделённых запятой. Используйте @userinfobot, чтобы получить Telegram ID"
|
"telegramChatIdDesc" = "Множественные идентификаторы чата, разделенные запятыми. Чтобы получить свои идентификаторы чатов, используйте @userinfobot или команду '/id' в боте."
|
||||||
"telegramNotifyTime" = "Частота уведомлений бота Telegram"
|
"telegramNotifyTime" = "Частота уведомлений бота Telegram"
|
||||||
"telegramNotifyTimeDesc" = "Используйте формат времени Crontab"
|
"telegramNotifyTimeDesc" = "Используйте формат времени Crontab"
|
||||||
"tgNotifyBackup" = "Резервное копирование базы данных"
|
"tgNotifyBackup" = "Резервное копирование базы данных"
|
||||||
"tgNotifyBackupDesc" = "Включать файл резервной копии базы данных с уведомлением об отчете"
|
"tgNotifyBackupDesc" = "Включать файл резервной копии базы данных с уведомлением об отчете"
|
||||||
|
"tgNotifyLogin" = "Уведомление о входе"
|
||||||
|
"tgNotifyLoginDesc" = "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель."
|
||||||
"sessionMaxAge" = "Продолжительность сессии"
|
"sessionMaxAge" = "Продолжительность сессии"
|
||||||
"sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)"
|
"sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)"
|
||||||
"expireTimeDiff" = "Порог истечения срока сессии для уведомления"
|
"expireTimeDiff" = "Порог истечения срока сессии для уведомления"
|
||||||
@@ -384,6 +388,7 @@
|
|||||||
"months" = "Месяцев"
|
"months" = "Месяцев"
|
||||||
"day" = "День"
|
"day" = "День"
|
||||||
"days" = "Дней"
|
"days" = "Дней"
|
||||||
|
"hours" = "Часов"
|
||||||
"unknown" = "Неизвестно"
|
"unknown" = "Неизвестно"
|
||||||
"inbounds" = "Входящие"
|
"inbounds" = "Входящие"
|
||||||
"clients" = "Клиенты"
|
"clients" = "Клиенты"
|
||||||
@@ -396,6 +401,7 @@
|
|||||||
"welcome" = "🤖 Добро пожаловать в бота управления <b>{{ .Hostname }}</b>.\r\n"
|
"welcome" = "🤖 Добро пожаловать в бота управления <b>{{ .Hostname }}</b>.\r\n"
|
||||||
"status" = "✅ Бот работает нормально!"
|
"status" = "✅ Бот работает нормально!"
|
||||||
"usage" = "❗ Пожалуйста, укажите текст для поиска!"
|
"usage" = "❗ Пожалуйста, укажите текст для поиска!"
|
||||||
|
"getID" = "🆔 Ваш ID: <code>{{ .ID }}</code>"
|
||||||
"helpAdminCommands" = "Поиск по электронной почте клиента:\r\n<code>/usage [Email]</code>\r\n \r\nПоиск входящих соединений (со статистикой клиента):\r\n<code>/inbound [Remark]</code>"
|
"helpAdminCommands" = "Поиск по электронной почте клиента:\r\n<code>/usage [Email]</code>\r\n \r\nПоиск входящих соединений (со статистикой клиента):\r\n<code>/inbound [Remark]</code>"
|
||||||
"helpClientCommands" = "Для получения статистики используйте следующую команду:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\nИспользуйте UUID для vmess/vless и пароль для Trojan."
|
"helpClientCommands" = "Для получения статистики используйте следующую команду:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\nИспользуйте UUID для vmess/vless и пароль для Trojan."
|
||||||
|
|
||||||
@@ -424,20 +430,21 @@
|
|||||||
"time" = "⏰ Время: {{ .Time }}\r\n"
|
"time" = "⏰ Время: {{ .Time }}\r\n"
|
||||||
"inbound" = "📍 Входящий поток: {{ .Remark }}\r\n"
|
"inbound" = "📍 Входящий поток: {{ .Remark }}\r\n"
|
||||||
"port" = "🔌 Порт: {{ .Port }}\r\n"
|
"port" = "🔌 Порт: {{ .Port }}\r\n"
|
||||||
"expire" = "📅 Дата окончания: {{ .DateTime }}\r\n \r\n"
|
"expire" = "📅 Дата окончания: {{ .Time }}\r\n"
|
||||||
"expireIn" = "📅 Окончание через: {{ .Time }}\r\n \r\n"
|
"expireIn" = "📅 Окончание через: {{ .Time }}\r\n"
|
||||||
"active" = "💡 Активен: {{ .Enable }}\r\n"
|
"active" = "💡 Активен: ✅ Да\r\n"
|
||||||
|
"inactive" = "💡 Активен: ❌ Нет\r\n"
|
||||||
"email" = "📧 Email: {{ .Email }}\r\n"
|
"email" = "📧 Email: {{ .Email }}\r\n"
|
||||||
"upload" = "🔼 Загрузка↑: {{ .Upload }}\r\n"
|
"upload" = "🔼 Исходящий трафик: ↑{{ .Upload }}\r\n"
|
||||||
"download" = "🔽 Скачивание↓: {{ .Download }}\r\n"
|
"download" = "🔽 Входящий трафик: ↓{{ .Download }}\r\n"
|
||||||
"total" = "🔄 Всего: {{ .UpDown }} / {{ .Total }}\r\n"
|
"total" = "📊 Всего: ↑↓{{ .UpDown }} из {{ .Total }}\r\n"
|
||||||
"TGUser" = "👤 Пользователь Telegram: {{ .TelegramID }}\r\n"
|
"TGUser" = "👤 Пользователь Telegram: {{ .TelegramID }}\r\n"
|
||||||
"exhaustedMsg" = "🚨 Исчерпаны {{ .Type }}:\r\n"
|
"exhaustedMsg" = "🚨 Исчерпаны {{ .Type }}:\r\n"
|
||||||
"exhaustedCount" = "🚨 Количество исчерпанных {{ .Type }}:\r\n"
|
"exhaustedCount" = "🚨 Количество исчерпанных {{ .Type }}:\r\n"
|
||||||
"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n"
|
"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n"
|
||||||
"depleteSoon" = "🔜 Скоро исчерпание: {{ .Deplete }}\r\n \r\n"
|
"depleteSoon" = "🔜 Скоро исчерпание: {{ .Deplete }}\r\n \r\n"
|
||||||
"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n"
|
"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n"
|
||||||
"refreshedOn" = "🔄🕒 Обновлено: {{ .Time }}\r\n"
|
"refreshedOn" = "\r\n📋🔄 Обновлено: {{ .Time }}\r\n \r\n"
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ Закрыть клавиатуру"
|
"closeKeyboard" = "❌ Закрыть клавиатуру"
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
"depleted" = "耗尽"
|
"depleted" = "耗尽"
|
||||||
"depletingSoon" = "即将耗尽"
|
"depletingSoon" = "即将耗尽"
|
||||||
"domainName" = "域名"
|
"domainName" = "域名"
|
||||||
"additional" = "额外"
|
|
||||||
"monitor" = "监听"
|
"monitor" = "监听"
|
||||||
"certificate" = "证书"
|
"certificate" = "证书"
|
||||||
"fail" = "失败"
|
"fail" = "失败"
|
||||||
@@ -49,6 +48,7 @@
|
|||||||
"clients" = "客户端"
|
"clients" = "客户端"
|
||||||
"usage" = "用法"
|
"usage" = "用法"
|
||||||
"secretToken" = "秘密令牌"
|
"secretToken" = "秘密令牌"
|
||||||
|
"remained" = "仍然存在"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "系统状态"
|
"dashboard" = "系统状态"
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"title" = "系统状态"
|
"title" = "系统状态"
|
||||||
"memory" = "内存"
|
"memory" = "内存"
|
||||||
"hard" = "硬盘"
|
"hard" = "硬盘"
|
||||||
"xrayStatus" = "xray 状态"
|
"xrayStatus" = "状态"
|
||||||
"stopXray" = "停止"
|
"stopXray" = "停止"
|
||||||
"restartXray" = "重启"
|
"restartXray" = "重启"
|
||||||
"xraySwitch" = "切换版本"
|
"xraySwitch" = "切换版本"
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
"setDefaultCert" = "从面板设置证书"
|
"setDefaultCert" = "从面板设置证书"
|
||||||
"xtlsDesc" = "Xray核心需要1.7.5"
|
"xtlsDesc" = "Xray核心需要1.7.5"
|
||||||
"realityDesc" = "Xray核心需要1.8.0及以上版本"
|
"realityDesc" = "Xray核心需要1.8.0及以上版本"
|
||||||
"telegramDesc" = "使用不带@的电报 ID 或聊天 ID(您可以在此处获取 @userinfobot)"
|
"telegramDesc" = "使用 Telegram ID,不包含 @ 符号或聊天 ID(可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)"
|
||||||
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称"
|
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
@@ -221,6 +221,8 @@
|
|||||||
"TGBotSettings" = "TG提醒相关设置"
|
"TGBotSettings" = "TG提醒相关设置"
|
||||||
"panelListeningIP" = "面板监听 IP"
|
"panelListeningIP" = "面板监听 IP"
|
||||||
"panelListeningIPDesc" = "默认留空监听所有 IP"
|
"panelListeningIPDesc" = "默认留空监听所有 IP"
|
||||||
|
"panelListeningDomain" = "面板监听域名"
|
||||||
|
"panelListeningDomainDesc" = "默认情况下留空以监视所有域名和 IP 地址"
|
||||||
"panelPort" = "面板监听端口"
|
"panelPort" = "面板监听端口"
|
||||||
"panelPortDesc" = "重启面板生效"
|
"panelPortDesc" = "重启面板生效"
|
||||||
"publicKeyPath" = "面板证书公钥文件路径"
|
"publicKeyPath" = "面板证书公钥文件路径"
|
||||||
@@ -238,11 +240,13 @@
|
|||||||
"telegramToken" = "电报机器人TOKEN"
|
"telegramToken" = "电报机器人TOKEN"
|
||||||
"telegramTokenDesc" = "重启面板生效"
|
"telegramTokenDesc" = "重启面板生效"
|
||||||
"telegramChatId" = "以逗号分隔的多个 chatID 重启面板生效"
|
"telegramChatId" = "以逗号分隔的多个 chatID 重启面板生效"
|
||||||
"telegramChatIdDesc" = "多个聊天 ID 以逗号分隔。使用@userinfobot 获取您的聊天 ID。重新启动面板以应用更改。"
|
"telegramChatIdDesc" = "多个聊天 ID 用逗号分隔。使用 @userinfobot 或在机器人中使用'/id'命令获取您的聊天 ID。"
|
||||||
"telegramNotifyTime" = "电报机器人通知时间"
|
"telegramNotifyTime" = "电报机器人通知时间"
|
||||||
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
||||||
"tgNotifyBackup" = "数据库备份"
|
"tgNotifyBackup" = "数据库备份"
|
||||||
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知"
|
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知"
|
||||||
|
"tgNotifyLogin" = "登录通知"
|
||||||
|
"tgNotifyLoginDesc" = "当有人试图登录您的面板时显示用户名、IP 地址和时间"
|
||||||
"sessionMaxAge" = "会话最大年龄"
|
"sessionMaxAge" = "会话最大年龄"
|
||||||
"sessionMaxAgeDesc" = "您可以保持登录状态的时间(单位:分钟)"
|
"sessionMaxAgeDesc" = "您可以保持登录状态的时间(单位:分钟)"
|
||||||
"expireTimeDiff" = "耗尽时间阈值"
|
"expireTimeDiff" = "耗尽时间阈值"
|
||||||
@@ -384,6 +388,7 @@
|
|||||||
"months" = "月"
|
"months" = "月"
|
||||||
"day" = "天"
|
"day" = "天"
|
||||||
"days" = "天"
|
"days" = "天"
|
||||||
|
"hours" = "小时"
|
||||||
"unknown" = "未知"
|
"unknown" = "未知"
|
||||||
"inbounds" = "入站连接"
|
"inbounds" = "入站连接"
|
||||||
"clients" = "客户端"
|
"clients" = "客户端"
|
||||||
@@ -396,6 +401,7 @@
|
|||||||
"welcome" = "🤖 欢迎来到<b>{{ .Hostname }}</b>管理机器人。\r\n"
|
"welcome" = "🤖 欢迎来到<b>{{ .Hostname }}</b>管理机器人。\r\n"
|
||||||
"status" = "✅ 机器人正常运行!"
|
"status" = "✅ 机器人正常运行!"
|
||||||
"usage" = "❗ 请输入要搜索的文本!"
|
"usage" = "❗ 请输入要搜索的文本!"
|
||||||
|
"getID" = "🆔 您的ID为:<code>{{ .ID }}</code>"
|
||||||
"helpAdminCommands" = "搜索客户端邮箱:\r\n<code>/usage [Email]</code>\r\n \r\n搜索入站连接(包含客户端统计信息):\r\n<code>/inbound [Remark]</code>"
|
"helpAdminCommands" = "搜索客户端邮箱:\r\n<code>/usage [Email]</code>\r\n \r\n搜索入站连接(包含客户端统计信息):\r\n<code>/inbound [Remark]</code>"
|
||||||
"helpClientCommands" = "要搜索统计信息,请使用以下命令:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\n对于vmess/vless,请使用UUID;对于Trojan,请使用密码。"
|
"helpClientCommands" = "要搜索统计信息,请使用以下命令:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\n对于vmess/vless,请使用UUID;对于Trojan,请使用密码。"
|
||||||
|
|
||||||
@@ -424,20 +430,21 @@
|
|||||||
"time" = "⏰ 时间:{{ .Time }}\r\n"
|
"time" = "⏰ 时间:{{ .Time }}\r\n"
|
||||||
"inbound" = "📍 入站:{{ .Remark }}\r\n"
|
"inbound" = "📍 入站:{{ .Remark }}\r\n"
|
||||||
"port" = "🔌 端口:{{ .Port }}\r\n"
|
"port" = "🔌 端口:{{ .Port }}\r\n"
|
||||||
"expire" = "📅 过期日期:{{ .DateTime }}\r\n \r\n"
|
"expire" = "📅 过期日期:{{ .Time }}\r\n"
|
||||||
"expireIn" = "📅 剩余时间:{{ .Time }}\r\n \r\n"
|
"expireIn" = "📅 剩余时间:{{ .Time }}\r\n"
|
||||||
"active" = "💡 激活:{{ .Enable }}\r\n"
|
"active" = "💡 激活:✅\r\n"
|
||||||
|
"inactive" = "💡 激活: ❌\r\n"
|
||||||
"email" = "📧 邮箱:{{ .Email }}\r\n"
|
"email" = "📧 邮箱:{{ .Email }}\r\n"
|
||||||
"upload" = "🔼 上传↑:{{ .Upload }}\r\n"
|
"upload" = "🔼 上传↑:{{ .Upload }}\r\n"
|
||||||
"download" = "🔽 下载↓:{{ .Download }}\r\n"
|
"download" = "🔽 下载↓:{{ .Download }}\r\n"
|
||||||
"total" = "🔄 总计:{{ .UpDown }} / {{ .Total }}\r\n"
|
"total" = "📊 总计:{{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
"TGUser" = "👤 电报用户:{{ .TelegramID }}\r\n"
|
"TGUser" = "👤 电报用户:{{ .TelegramID }}\r\n"
|
||||||
"exhaustedMsg" = "🚨 耗尽的{{ .Type }}:\r\n"
|
"exhaustedMsg" = "🚨 耗尽的{{ .Type }}:\r\n"
|
||||||
"exhaustedCount" = "🚨 耗尽的{{ .Type }}数量:\r\n"
|
"exhaustedCount" = "🚨 耗尽的{{ .Type }}数量:\r\n"
|
||||||
"disabled" = "🛑 禁用:{{ .Disabled }}\r\n"
|
"disabled" = "🛑 禁用:{{ .Disabled }}\r\n"
|
||||||
"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n \r\n"
|
"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n \r\n"
|
||||||
"backupTime" = "🗄 备份时间:{{ .Time }}\r\n"
|
"backupTime" = "🗄 备份时间:{{ .Time }}\r\n"
|
||||||
"refreshedOn" = "🔄🕒 刷新时间:{{ .Time }}\r\n"
|
"refreshedOn" = "\r\n📋🔄 刷新时间:{{ .Time }}\r\n \r\n"
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ 关闭键盘"
|
"closeKeyboard" = "❌ 关闭键盘"
|
||||||
|
|||||||
42
web/web.go
42
web/web.go
@@ -19,11 +19,12 @@ import (
|
|||||||
"x-ui/web/controller"
|
"x-ui/web/controller"
|
||||||
"x-ui/web/job"
|
"x-ui/web/job"
|
||||||
"x-ui/web/locale"
|
"x-ui/web/locale"
|
||||||
|
"x-ui/web/middleware"
|
||||||
"x-ui/web/network"
|
"x-ui/web/network"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
sessions "github.com/Calidity/gin-sessions"
|
||||||
"github.com/gin-contrib/sessions/cookie"
|
"github.com/Calidity/gin-sessions/cookie"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
)
|
)
|
||||||
@@ -144,28 +145,6 @@ func (s *Server) getHtmlTemplate(funcMap template.FuncMap) (*template.Template,
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func redirectMiddleware(basePath string) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
// Redirect from old '/xui' path to '/panel'
|
|
||||||
path := c.Request.URL.Path
|
|
||||||
redirects := map[string]string{
|
|
||||||
"panel/API": "panel/api",
|
|
||||||
"xui/API": "panel/api",
|
|
||||||
"xui": "panel",
|
|
||||||
}
|
|
||||||
for from, to := range redirects {
|
|
||||||
from, to = basePath+from, basePath+to
|
|
||||||
if strings.HasPrefix(path, from) {
|
|
||||||
newPath := to + path[len(from):]
|
|
||||||
c.Redirect(http.StatusMovedPermanently, newPath)
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) initRouter() (*gin.Engine, error) {
|
func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
if config.IsDebug() {
|
if config.IsDebug() {
|
||||||
gin.SetMode(gin.DebugMode)
|
gin.SetMode(gin.DebugMode)
|
||||||
@@ -177,6 +156,15 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
|
|
||||||
engine := gin.Default()
|
engine := gin.Default()
|
||||||
|
|
||||||
|
webDomain, err := s.settingService.GetWebDomain()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if webDomain != "" {
|
||||||
|
engine.Use(middleware.DomainValidatorMiddleware(webDomain))
|
||||||
|
}
|
||||||
|
|
||||||
secret, err := s.settingService.GetSecret()
|
secret, err := s.settingService.GetSecret()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -233,7 +221,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply the redirect middleware (`/xui` to `/panel`)
|
// Apply the redirect middleware (`/xui` to `/panel`)
|
||||||
engine.Use(redirectMiddleware(basePath))
|
engine.Use(middleware.RedirectMiddleware(basePath))
|
||||||
|
|
||||||
g := engine.Group(basePath)
|
g := engine.Group(basePath)
|
||||||
|
|
||||||
@@ -262,8 +250,8 @@ func (s *Server) startTask() {
|
|||||||
// Check the inbound traffic every 30 seconds that the traffic exceeds and expires
|
// Check the inbound traffic every 30 seconds that the traffic exceeds and expires
|
||||||
s.cron.AddJob("@every 30s", job.NewCheckInboundJob())
|
s.cron.AddJob("@every 30s", job.NewCheckInboundJob())
|
||||||
|
|
||||||
// check client ips from log file every 10 sec
|
// check client ips from log file every 20 sec
|
||||||
s.cron.AddJob("@every 10s", job.NewCheckClientIpJob())
|
s.cron.AddJob("@every 20s", job.NewCheckClientIpJob())
|
||||||
|
|
||||||
// Make a traffic condition every day, 8:30
|
// Make a traffic condition every day, 8:30
|
||||||
var entry cron.EntryID
|
var entry cron.EntryID
|
||||||
|
|||||||
251
x-ui.sh
251
x-ui.sh
@@ -518,9 +518,9 @@ install_acme() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ssl_cert_issue_main() {
|
ssl_cert_issue_main() {
|
||||||
echo "1) Get SSL"
|
echo -e "${green}\t1.${plain} Get SSL"
|
||||||
echo "2) Revoke"
|
echo -e "${green}\t2.${plain} Revoke"
|
||||||
echo "3) Force Renew"
|
echo -e "${green}\t3.${plain} Force Renew"
|
||||||
read -p "Choose an option: " choice
|
read -p "Choose an option: " choice
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1) ssl_cert_issue ;;
|
1) ssl_cert_issue ;;
|
||||||
@@ -631,10 +631,27 @@ ssl_cert_issue() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
warp_fixchatgpt() {
|
warp_cloudflare() {
|
||||||
curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash
|
echo -e "${green}\t1.${plain} install WARP"
|
||||||
echo ""
|
echo -e "${green}\t2.${plain} Account Type (free, plus, team)"
|
||||||
before_show_menu
|
echo -e "${green}\t3.${plain} Turn on/off WireProxy"
|
||||||
|
echo -e "${green}\t4.${plain} Uninstall WARP"
|
||||||
|
read -p "Choose an option: " choice
|
||||||
|
case "$choice" in
|
||||||
|
1)
|
||||||
|
bash <(curl -sSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh)
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
warp a
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
warp y
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
warp u
|
||||||
|
;;
|
||||||
|
*) echo "Invalid choice" ;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
run_speedtest() {
|
run_speedtest() {
|
||||||
@@ -642,24 +659,19 @@ run_speedtest() {
|
|||||||
if ! command -v speedtest &> /dev/null; then
|
if ! command -v speedtest &> /dev/null; then
|
||||||
# If not installed, install it
|
# If not installed, install it
|
||||||
local pkg_manager=""
|
local pkg_manager=""
|
||||||
local curl_install_cmd=""
|
|
||||||
local speedtest_install_script=""
|
local speedtest_install_script=""
|
||||||
|
|
||||||
if command -v dnf &> /dev/null; then
|
if command -v dnf &> /dev/null; then
|
||||||
pkg_manager="dnf"
|
pkg_manager="dnf"
|
||||||
curl_install_cmd="sudo dnf install -y curl"
|
|
||||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
|
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
|
||||||
elif command -v yum &> /dev/null; then
|
elif command -v yum &> /dev/null; then
|
||||||
pkg_manager="yum"
|
pkg_manager="yum"
|
||||||
curl_install_cmd="sudo yum install -y curl"
|
|
||||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
|
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
|
||||||
elif command -v apt-get &> /dev/null; then
|
elif command -v apt-get &> /dev/null; then
|
||||||
pkg_manager="apt-get"
|
pkg_manager="apt-get"
|
||||||
curl_install_cmd="sudo apt-get update && sudo apt-get install -y curl"
|
|
||||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
|
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
|
||||||
elif command -v apt &> /dev/null; then
|
elif command -v apt &> /dev/null; then
|
||||||
pkg_manager="apt"
|
pkg_manager="apt"
|
||||||
curl_install_cmd="sudo apt update && sudo apt install -y curl"
|
|
||||||
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
|
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -667,7 +679,6 @@ run_speedtest() {
|
|||||||
echo "Error: Package manager not found. You may need to install Speedtest manually."
|
echo "Error: Package manager not found. You may need to install Speedtest manually."
|
||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
$curl_install_cmd
|
|
||||||
curl -s $speedtest_install_script | sudo bash
|
curl -s $speedtest_install_script | sudo bash
|
||||||
sudo $pkg_manager install -y speedtest
|
sudo $pkg_manager install -y speedtest
|
||||||
fi
|
fi
|
||||||
@@ -677,6 +688,187 @@ run_speedtest() {
|
|||||||
speedtest
|
speedtest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iplimit_main() {
|
||||||
|
echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit"
|
||||||
|
echo -e "${green}\t2.${plain} Change Ban Duration"
|
||||||
|
echo -e "${green}\t3.${plain} Unban Everyone"
|
||||||
|
echo -e "${green}\t4.${plain} Check Logs"
|
||||||
|
echo -e "${green}\t5.${plain} Uninstall IP Limit"
|
||||||
|
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||||||
|
read -p "Choose an option: " choice
|
||||||
|
case "$choice" in
|
||||||
|
0)
|
||||||
|
show_menu ;;
|
||||||
|
1)
|
||||||
|
confirm "Proceed with installation of Fail2ban & IP Limit?" "y"
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
install_iplimit
|
||||||
|
else
|
||||||
|
iplimit_main
|
||||||
|
fi ;;
|
||||||
|
2)
|
||||||
|
read -rp "Please enter new Ban Duration in Minutes [default 5]: " NUM
|
||||||
|
if [[ $NUM =~ ^[0-9]+$ ]]; then
|
||||||
|
echo -e "\n[3x-ipl]\nenabled=true\nfilter=3x-ipl\naction=3x-ipl\nlogpath=/var/log/3xipl.log\nmaxretry=3\nfindtime=100\nbantime=${NUM}m" > /etc/fail2ban/jail.d/3x-ipl.conf
|
||||||
|
sudo systemctl restart fail2ban
|
||||||
|
echo -e "${green}Bantime set to ${NUM} minutes successfully.${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}${NUM} is not a number! Please, try again.${plain}"
|
||||||
|
fi
|
||||||
|
iplimit_main ;;
|
||||||
|
3)
|
||||||
|
confirm "Proceed with Unbanning everyone from IP Limit jail?" "y"
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
fail2ban-client reload --restart --unban 3x-ipl
|
||||||
|
echo -e "${green}All users Unbanned successfully.${plain}"
|
||||||
|
iplimit_main
|
||||||
|
else
|
||||||
|
echo -e "${yellow}Cancelled.${plain}"
|
||||||
|
fi
|
||||||
|
iplimit_main ;;
|
||||||
|
4)
|
||||||
|
if test -f "/var/log/3xipl-banned.log"; then
|
||||||
|
if [[ -s "/var/log/3xipl-banned.log" ]]; then
|
||||||
|
cat /var/log/3xipl-banned.log
|
||||||
|
else
|
||||||
|
echo -e "${red}Log file is empty.${plain}\n"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${red}Log file not found. Please Install Fail2ban and IP Limit first.${plain}\n"
|
||||||
|
iplimit_main
|
||||||
|
fi ;;
|
||||||
|
5)
|
||||||
|
remove_iplimit ;;
|
||||||
|
*) echo "Invalid choice" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
install_iplimit() {
|
||||||
|
if ! command -v fail2ban-client &>/dev/null; then
|
||||||
|
echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n"
|
||||||
|
# Check the OS and install necessary packages
|
||||||
|
case "${release}" in
|
||||||
|
ubuntu|debian)
|
||||||
|
sudo apt-get update && sudo apt-get install fail2ban -y ;;
|
||||||
|
centos)
|
||||||
|
sudo yum -y update && sudo yum -y install fail2ban ;;
|
||||||
|
fedora)
|
||||||
|
sudo dnf -y update && sudo dnf -y install fail2ban ;;
|
||||||
|
*)
|
||||||
|
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
||||||
|
exit 1 ;;
|
||||||
|
esac
|
||||||
|
echo -e "${green}Fail2ban installed successfully!${plain}\n"
|
||||||
|
else
|
||||||
|
echo -e "${yellow}Fail2ban is already installed.${plain}\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${green}Configuring IP Limit...${plain}\n"
|
||||||
|
|
||||||
|
#Check if [3x-ipl] exists in jail.local (just making sure there's no double config for jail)
|
||||||
|
if grep -qw '3x-ipl' /etc/fail2ban/jail.local || grep -qw '3x-ipl' /etc/fail2ban/jail.conf; then
|
||||||
|
echo -e "${red}Found conflicts in /etc/fail2ban/jail.conf or jail.local file!\nPlease manually remove anything related 3x-ipl in that files and try again.\nInstallation of IP Limit failed.${plain}\n"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#Check if log file exists
|
||||||
|
if ! test -f "/var/log/3xipl-banned.log"; then
|
||||||
|
touch /var/log/3xipl-banned.log
|
||||||
|
fi
|
||||||
|
|
||||||
|
#Check if service log file exists so fail2ban won't return error
|
||||||
|
if ! test -f "/var/log/3xipl.log"; then
|
||||||
|
touch /var/log/3xipl.log
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
echo -e "\n[3x-ipl]\nenabled=true\nfilter=3x-ipl\naction=3x-ipl\nlogpath=/var/log/3xipl.log\nmaxretry=3\nfindtime=100\nbantime=5m" > /etc/fail2ban/jail.d/3x-ipl.conf
|
||||||
|
|
||||||
|
sudo cat > /etc/fail2ban/filter.d/3x-ipl.conf << EOF
|
||||||
|
[Definition]
|
||||||
|
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
|
||||||
|
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*SRC\s*=\s*<ADDR>
|
||||||
|
ignoreregex =
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo cat > /etc/fail2ban/action.d/3x-ipl.conf << 'EOF'
|
||||||
|
[INCLUDES]
|
||||||
|
before = iptables-common.conf
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
actionstart = <iptables> -N f2b-<name>
|
||||||
|
<iptables> -A f2b-<name> -j <returntype>
|
||||||
|
<iptables> -I <chain> -p <protocol> -j f2b-<name>
|
||||||
|
|
||||||
|
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
|
||||||
|
<actionflush>
|
||||||
|
<iptables> -X f2b-<name>
|
||||||
|
|
||||||
|
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||||
|
|
||||||
|
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||||
|
echo "$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> /var/log/3xipl-banned.log
|
||||||
|
|
||||||
|
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||||
|
echo "$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> /var/log/3xipl-banned.log
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
#Launching fail2ban
|
||||||
|
if ! sudo systemctl is-active --quiet fail2ban; then
|
||||||
|
sudo systemctl start fail2ban
|
||||||
|
else
|
||||||
|
systemctl restart fail2ban
|
||||||
|
fi
|
||||||
|
sudo systemctl enable fail2ban
|
||||||
|
|
||||||
|
echo -e "${green}IP Limit installed and configured successfully!${plain}\n"
|
||||||
|
before_show_menu
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_iplimit(){
|
||||||
|
echo -e "${green}\t1.${plain} Only remove IP Limit configurations"
|
||||||
|
echo -e "${green}\t2.${plain} Uninstall Fail2ban and IP Limit"
|
||||||
|
echo -e "${green}\t0.${plain} Abort"
|
||||||
|
read -p "Choose an option: " num
|
||||||
|
case "$num" in
|
||||||
|
1)
|
||||||
|
rm -f /etc/fail2ban/filter.d/3x-ipl.conf
|
||||||
|
rm -f /etc/fail2ban/action.d/3x-ipl.conf
|
||||||
|
rm -f /etc/fail2ban/jail.d/3x-ipl.conf
|
||||||
|
sudo systemctl restart fail2ban
|
||||||
|
echo -e "${green}IP Limit removed successfully!${plain}\n"
|
||||||
|
before_show_menu ;;
|
||||||
|
2)
|
||||||
|
rm -f /etc/fail2ban/filter.d/3x-ipl.conf
|
||||||
|
rm -f /etc/fail2ban/action.d/3x-ipl.conf
|
||||||
|
rm -f /etc/fail2ban/jail.d/3x-ipl.conf
|
||||||
|
sudo systemctl stop fail2ban
|
||||||
|
sudo systemctl disable fail2ban
|
||||||
|
case "${release}" in
|
||||||
|
ubuntu|debian)
|
||||||
|
sudo apt-get remove fail2ban -y ;;
|
||||||
|
centos)
|
||||||
|
sudo yum -y remove fail2ban ;;
|
||||||
|
fedora)
|
||||||
|
sudo dnf -y remove fail2ban ;;
|
||||||
|
*)
|
||||||
|
echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n"
|
||||||
|
exit 1 ;;
|
||||||
|
esac
|
||||||
|
rm -rf /etc/fail2ban/*
|
||||||
|
echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n"
|
||||||
|
before_show_menu ;;
|
||||||
|
0)
|
||||||
|
echo -e "${yellow}Cancelled.${plain}\n"
|
||||||
|
iplimit_main ;;
|
||||||
|
*)
|
||||||
|
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
|
||||||
|
remove_iplimit ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
show_usage() {
|
show_usage() {
|
||||||
echo "x-ui control menu usages: "
|
echo "x-ui control menu usages: "
|
||||||
echo "------------------------------------------"
|
echo "------------------------------------------"
|
||||||
@@ -717,15 +909,17 @@ show_menu() {
|
|||||||
${green}13.${plain} Enable x-ui On System Startup
|
${green}13.${plain} Enable x-ui On System Startup
|
||||||
${green}14.${plain} Disable x-ui On System Startup
|
${green}14.${plain} Disable x-ui On System Startup
|
||||||
————————————————
|
————————————————
|
||||||
${green}15.${plain} Enable BBR
|
${green}15.${plain} SSL Certificate Management
|
||||||
${green}16.${plain} SSL Certificate Management
|
${green}16.${plain} IP Limit Management
|
||||||
${green}17.${plain} Update Geo Files
|
${green}17.${plain} WARP Management
|
||||||
${green}18.${plain} Active Firewall and open ports
|
————————————————
|
||||||
${green}19.${plain} Install WARP
|
${green}18.${plain} Enable BBR
|
||||||
${green}20.${plain} Speedtest by Ookla
|
${green}19.${plain} Update Geo Files
|
||||||
|
${green}20.${plain} Active Firewall and open ports
|
||||||
|
${green}21.${plain} Speedtest by Ookla
|
||||||
"
|
"
|
||||||
show_status
|
show_status
|
||||||
echo && read -p "Please enter your selection [0-20]: " num
|
echo && read -p "Please enter your selection [0-21]: " num
|
||||||
|
|
||||||
case "${num}" in
|
case "${num}" in
|
||||||
0)
|
0)
|
||||||
@@ -774,25 +968,28 @@ show_menu() {
|
|||||||
check_install && disable
|
check_install && disable
|
||||||
;;
|
;;
|
||||||
15)
|
15)
|
||||||
enable_bbr
|
|
||||||
;;
|
|
||||||
16)
|
|
||||||
ssl_cert_issue_main
|
ssl_cert_issue_main
|
||||||
;;
|
;;
|
||||||
|
16)
|
||||||
|
iplimit_main
|
||||||
|
;;
|
||||||
17)
|
17)
|
||||||
update_geo
|
warp_cloudflare
|
||||||
;;
|
;;
|
||||||
18)
|
18)
|
||||||
open_ports
|
enable_bbr
|
||||||
;;
|
;;
|
||||||
19)
|
19)
|
||||||
warp_fixchatgpt
|
update_geo
|
||||||
;;
|
;;
|
||||||
20)
|
20)
|
||||||
|
open_ports
|
||||||
|
;;
|
||||||
|
21)
|
||||||
run_speedtest
|
run_speedtest
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
LOGE "Please enter the correct number [0-20]"
|
LOGE "Please enter the correct number [0-21]"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|||||||
181
xray/api.go
Normal file
181
xray/api.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package xray
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
"x-ui/util/common"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/proxyman/command"
|
||||||
|
statsService "github.com/xtls/xray-core/app/stats/command"
|
||||||
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/proxy/shadowsocks"
|
||||||
|
"github.com/xtls/xray-core/proxy/trojan"
|
||||||
|
"github.com/xtls/xray-core/proxy/vless"
|
||||||
|
"github.com/xtls/xray-core/proxy/vmess"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XrayAPI struct {
|
||||||
|
HandlerServiceClient *command.HandlerServiceClient
|
||||||
|
StatsServiceClient *statsService.StatsServiceClient
|
||||||
|
grpcClient *grpc.ClientConn
|
||||||
|
isConnected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *XrayAPI) Init(apiPort int) (err error) {
|
||||||
|
if apiPort == 0 {
|
||||||
|
return common.NewError("xray api port wrong:", apiPort)
|
||||||
|
}
|
||||||
|
x.grpcClient, err = grpc.Dial(fmt.Sprintf("127.0.0.1:%v", apiPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
x.isConnected = true
|
||||||
|
|
||||||
|
hsClient := command.NewHandlerServiceClient(x.grpcClient)
|
||||||
|
ssClient := statsService.NewStatsServiceClient(x.grpcClient)
|
||||||
|
|
||||||
|
x.HandlerServiceClient = &hsClient
|
||||||
|
x.StatsServiceClient = &ssClient
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *XrayAPI) Close() {
|
||||||
|
x.grpcClient.Close()
|
||||||
|
x.HandlerServiceClient = nil
|
||||||
|
x.StatsServiceClient = nil
|
||||||
|
x.isConnected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]interface{}) error {
|
||||||
|
var account *serial.TypedMessage
|
||||||
|
switch Protocol {
|
||||||
|
case "vmess":
|
||||||
|
account = serial.ToTypedMessage(&vmess.Account{
|
||||||
|
Id: user["id"].(string),
|
||||||
|
})
|
||||||
|
case "vless":
|
||||||
|
account = serial.ToTypedMessage(&vless.Account{
|
||||||
|
Id: user["id"].(string),
|
||||||
|
Flow: user["flow"].(string),
|
||||||
|
})
|
||||||
|
case "trojan":
|
||||||
|
account = serial.ToTypedMessage(&trojan.Account{
|
||||||
|
Password: user["password"].(string),
|
||||||
|
})
|
||||||
|
case "shadowsocks":
|
||||||
|
account = serial.ToTypedMessage(&shadowsocks.Account{
|
||||||
|
Password: user["password"].(string),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
client := *x.HandlerServiceClient
|
||||||
|
|
||||||
|
_, err := client.AlterInbound(context.Background(), &command.AlterInboundRequest{
|
||||||
|
Tag: inboundTag,
|
||||||
|
Operation: serial.ToTypedMessage(&command.AddUserOperation{
|
||||||
|
User: &protocol.User{
|
||||||
|
Email: user["email"].(string),
|
||||||
|
Account: account,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *XrayAPI) RemoveUser(inboundTag string, email string) error {
|
||||||
|
client := *x.HandlerServiceClient
|
||||||
|
_, err := client.AlterInbound(context.Background(), &command.AlterInboundRequest{
|
||||||
|
Tag: inboundTag,
|
||||||
|
Operation: serial.ToTypedMessage(&command.RemoveUserOperation{
|
||||||
|
Email: email,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
||||||
|
if x.grpcClient == nil {
|
||||||
|
return nil, nil, common.NewError("xray api is not initialized")
|
||||||
|
}
|
||||||
|
var trafficRegex = regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
||||||
|
var ClientTrafficRegex = regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
||||||
|
|
||||||
|
client := *x.StatsServiceClient
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
defer cancel()
|
||||||
|
request := &statsService.QueryStatsRequest{
|
||||||
|
Reset_: reset,
|
||||||
|
}
|
||||||
|
resp, err := client.QueryStats(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
tagTrafficMap := map[string]*Traffic{}
|
||||||
|
emailTrafficMap := map[string]*ClientTraffic{}
|
||||||
|
|
||||||
|
clientTraffics := make([]*ClientTraffic, 0)
|
||||||
|
traffics := make([]*Traffic, 0)
|
||||||
|
for _, stat := range resp.GetStat() {
|
||||||
|
matchs := trafficRegex.FindStringSubmatch(stat.Name)
|
||||||
|
if len(matchs) < 3 {
|
||||||
|
|
||||||
|
matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name)
|
||||||
|
if len(matchs) < 3 {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
|
||||||
|
isUser := matchs[1] == "user"
|
||||||
|
email := matchs[2]
|
||||||
|
isDown := matchs[3] == "downlink"
|
||||||
|
if !isUser {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
traffic, ok := emailTrafficMap[email]
|
||||||
|
if !ok {
|
||||||
|
traffic = &ClientTraffic{
|
||||||
|
Email: email,
|
||||||
|
}
|
||||||
|
emailTrafficMap[email] = traffic
|
||||||
|
clientTraffics = append(clientTraffics, traffic)
|
||||||
|
}
|
||||||
|
if isDown {
|
||||||
|
traffic.Down = stat.Value
|
||||||
|
} else {
|
||||||
|
traffic.Up = stat.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
isInbound := matchs[1] == "inbound"
|
||||||
|
tag := matchs[2]
|
||||||
|
isDown := matchs[3] == "downlink"
|
||||||
|
if tag == "api" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
traffic, ok := tagTrafficMap[tag]
|
||||||
|
if !ok {
|
||||||
|
traffic = &Traffic{
|
||||||
|
IsInbound: isInbound,
|
||||||
|
Tag: tag,
|
||||||
|
}
|
||||||
|
tagTrafficMap[tag] = traffic
|
||||||
|
traffics = append(traffics, traffic)
|
||||||
|
}
|
||||||
|
if isDown {
|
||||||
|
traffic.Down = stat.Value
|
||||||
|
} else {
|
||||||
|
traffic.Up = stat.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return traffics, clientTraffics, nil
|
||||||
|
}
|
||||||
100
xray/process.go
100
xray/process.go
@@ -3,30 +3,22 @@ package xray
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"syscall"
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
|
|
||||||
"github.com/Workiva/go-datastructures/queue"
|
"github.com/Workiva/go-datastructures/queue"
|
||||||
statsservice "github.com/xtls/xray-core/app/stats/command"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var trafficRegex = regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
|
||||||
var ClientTrafficRegex = regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
|
||||||
|
|
||||||
func GetBinaryName() string {
|
func GetBinaryName() string {
|
||||||
return fmt.Sprintf("xray-%s-%s", runtime.GOOS, runtime.GOARCH)
|
return fmt.Sprintf("xray-%s-%s", runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
@@ -51,8 +43,8 @@ func GetIranPath() string {
|
|||||||
return config.GetBinFolderPath() + "/iran.dat"
|
return config.GetBinFolderPath() + "/iran.dat"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllowedIPsPath() string {
|
func GetBlockedIPsPath() string {
|
||||||
return config.GetBinFolderPath() + "/AllowedIPs"
|
return config.GetBinFolderPath() + "/BlockedIps"
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopProcess(p *Process) {
|
func stopProcess(p *Process) {
|
||||||
@@ -173,7 +165,7 @@ func (p *process) Start() (err error) {
|
|||||||
return common.NewErrorf("Failed to write configuration file: %v", err)
|
return common.NewErrorf("Failed to write configuration file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(GetBinaryPath(), "-c", configPath, "-restrictedIPsPath", GetAllowedIPsPath())
|
cmd := exec.Command(GetBinaryPath(), "-c", configPath, "-restrictedIPsPath", GetBlockedIPsPath())
|
||||||
p.cmd = cmd
|
p.cmd = cmd
|
||||||
|
|
||||||
stdReader, err := cmd.StdoutPipe()
|
stdReader, err := cmd.StdoutPipe()
|
||||||
@@ -236,87 +228,5 @@ func (p *process) Stop() error {
|
|||||||
if !p.IsRunning() {
|
if !p.IsRunning() {
|
||||||
return errors.New("xray is not running")
|
return errors.New("xray is not running")
|
||||||
}
|
}
|
||||||
return p.cmd.Process.Kill()
|
return p.cmd.Process.Signal(syscall.SIGTERM)
|
||||||
}
|
|
||||||
|
|
||||||
func (p *process) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
|
||||||
if p.apiPort == 0 {
|
|
||||||
return nil, nil, common.NewError("xray api port wrong:", p.apiPort)
|
|
||||||
}
|
|
||||||
conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%v", p.apiPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
client := statsservice.NewStatsServiceClient(conn)
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
|
||||||
defer cancel()
|
|
||||||
request := &statsservice.QueryStatsRequest{
|
|
||||||
Reset_: reset,
|
|
||||||
}
|
|
||||||
resp, err := client.QueryStats(ctx, request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
tagTrafficMap := map[string]*Traffic{}
|
|
||||||
emailTrafficMap := map[string]*ClientTraffic{}
|
|
||||||
|
|
||||||
clientTraffics := make([]*ClientTraffic, 0)
|
|
||||||
traffics := make([]*Traffic, 0)
|
|
||||||
for _, stat := range resp.GetStat() {
|
|
||||||
matchs := trafficRegex.FindStringSubmatch(stat.Name)
|
|
||||||
if len(matchs) < 3 {
|
|
||||||
|
|
||||||
matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name)
|
|
||||||
if len(matchs) < 3 {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
|
|
||||||
isUser := matchs[1] == "user"
|
|
||||||
email := matchs[2]
|
|
||||||
isDown := matchs[3] == "downlink"
|
|
||||||
if !isUser {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
traffic, ok := emailTrafficMap[email]
|
|
||||||
if !ok {
|
|
||||||
traffic = &ClientTraffic{
|
|
||||||
Email: email,
|
|
||||||
}
|
|
||||||
emailTrafficMap[email] = traffic
|
|
||||||
clientTraffics = append(clientTraffics, traffic)
|
|
||||||
}
|
|
||||||
if isDown {
|
|
||||||
traffic.Down = stat.Value
|
|
||||||
} else {
|
|
||||||
traffic.Up = stat.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
isInbound := matchs[1] == "inbound"
|
|
||||||
tag := matchs[2]
|
|
||||||
isDown := matchs[3] == "downlink"
|
|
||||||
if tag == "api" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
traffic, ok := tagTrafficMap[tag]
|
|
||||||
if !ok {
|
|
||||||
traffic = &Traffic{
|
|
||||||
IsInbound: isInbound,
|
|
||||||
Tag: tag,
|
|
||||||
}
|
|
||||||
tagTrafficMap[tag] = traffic
|
|
||||||
traffics = append(traffics, traffic)
|
|
||||||
}
|
|
||||||
if isDown {
|
|
||||||
traffic.Down = stat.Value
|
|
||||||
} else {
|
|
||||||
traffic.Up = stat.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return traffics, clientTraffics, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user