Compare commits

..

1 Commits
1.8.2 ... 1.6.2

Author SHA1 Message Date
Alireza Ahmadi
905ffda848 v1.6.2 2023-12-11 14:06:59 +01:00
116 changed files with 4081 additions and 7190 deletions

View File

@@ -50,6 +50,6 @@ jobs:
with: with:
context: . context: .
push: true push: true
platforms: linux/amd64,linux/arm64/v8, linux/arm/v7, linux/386 platforms: linux/amd64,linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,5 +1,4 @@
name: Release X-UI name: Release X-ui
on: on:
push: push:
tags: tags:
@@ -7,96 +6,127 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build: linuxamd64build:
strategy: name: build x-ui amd64 version
matrix:
platform:
- amd64
- arm64
- armv7
- 386
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repository - uses: actions/checkout@v4
uses: actions/checkout@v4 - name: Set up Go
- name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: '1.22' go-version: '1.21'
- name: build linux amd64 version
- name: Install dependencies
run: | run: |
sudo apt-get update CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
if [ "${{ matrix.platform }}" == "arm64" ]; then
sudo apt install gcc-aarch64-linux-gnu
elif [ "${{ matrix.platform }}" == "armv7" ]; then
sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "386" ]; then
sudo apt install gcc-i686-linux-gnu
fi
- name: Build x-ui
run: |
export CGO_ENABLED=1
export GOOS=linux
export GOARCH=${{ matrix.platform }}
if [ "${{ matrix.platform }}" == "arm64" ]; then
export GOARCH=arm64
export CC=aarch64-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "armv7" ]; then
export GOARCH=arm
export GOARM=7
export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "386" ]; then
export GOARCH=386
export CC=i686-linux-gnu-gcc
fi
go build -o xui-release -v main.go
mkdir x-ui mkdir x-ui
cp xui-release x-ui/ cp xui-release x-ui/xui-release
cp x-ui.service x-ui/ cp x-ui.service x-ui/x-ui.service
cp x-ui.sh x-ui/ cp x-ui.sh x-ui/x-ui.sh
mv x-ui/xui-release x-ui/x-ui cd x-ui
mkdir x-ui/bin mv xui-release x-ui
cd x-ui/bin mkdir bin
cd bin
# Download dependencies wget https://github.com/XTLS/Xray-core/releases/download/v1.8.6/Xray-linux-64.zip
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.10/" unzip Xray-linux-64.zip
if [ "${{ matrix.platform }}" == "amd64" ]; then rm -f Xray-linux-64.zip geoip.dat geosite.dat LICENSE README.md
wget ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip
rm -f Xray-linux-64.zip
elif [ "${{ matrix.platform }}" == "arm64" ]; then
wget ${Xray_URL}Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-arm64-v8a.zip
elif [ "${{ matrix.platform }}" == "armv7" ]; then
wget ${Xray_URL}Xray-linux-arm32-v7a.zip
unzip Xray-linux-arm32-v7a.zip
rm -f Xray-linux-arm32-v7a.zip
elif [ "${{ matrix.platform }}" == "386" ]; then
wget ${Xray_URL}Xray-linux-32.zip
unzip Xray-linux-32.zip
rm -f Xray-linux-32.zip
fi
rm -f geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/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 -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
mv xray xray-linux-${{ matrix.platform }} mv xray xray-linux-amd64
cd ../.. cd ..
cd ..
- name: Package - name: package
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui run: tar -zcvf x-ui-linux-amd64.tar.gz x-ui
- name: upload
- name: Upload files to GH release uses: svenstaro/upload-release-action@2.7.0
uses: svenstaro/upload-release-action@v2
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }} tag: ${{ github.ref }}
file: x-ui-linux-${{ matrix.platform }}.tar.gz file: x-ui-linux-amd64.tar.gz
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz asset_name: x-ui-linux-amd64.tar.gz
prerelease: true
linuxarm64build:
name: build x-ui arm64 version
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: build linux arm64 version
run: |
sudo apt-get update
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
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/xtls/xray-core/releases/download/v1.8.6/Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat LICENSE README.md
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
mv xray xray-linux-arm64
cd ..
cd ..
- name: package
run: tar -zcvf x-ui-linux-arm64.tar.gz x-ui
- name: upload
uses: svenstaro/upload-release-action@2.7.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: x-ui-linux-arm64.tar.gz
asset_name: x-ui-linux-arm64.tar.gz
prerelease: true
linuxs390xbuild:
name: build x-ui s390x version
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: build linux s390x version
run: |
sudo apt-get update
sudo apt install gcc-s390x-linux-gnu -y
CGO_ENABLED=1 GOOS=linux GOARCH=s390x CC=s390x-linux-gnu-gcc go build -o xui-release -v main.go
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/xtls/xray-core/releases/download/v1.8.6/Xray-linux-s390x.zip
unzip Xray-linux-s390x.zip
rm -f Xray-linux-s390x.zip geoip.dat geosite.dat LICENSE README.md
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
mv xray xray-linux-s390x
cd ..
cd ..
- name: package
run: tar -zcvf x-ui-linux-s390x.tar.gz x-ui
- name: upload
uses: svenstaro/upload-release-action@2.7.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: x-ui-linux-s390x.tar.gz
asset_name: x-ui-linux-s390x.tar.gz
prerelease: true prerelease: true

1
.gitignore vendored
View File

@@ -3,7 +3,6 @@
.cache .cache
.sync* .sync*
*.tar.gz *.tar.gz
*.log
access.log access.log
error.log error.log
tmp tmp

View File

@@ -1,29 +1,17 @@
#!/bin/sh #!/bin/sh
case $1 in if [ $1 == "amd64" ]; then
amd64) ARCH="64";
ARCH="64" FNAME="amd64";
FNAME="amd64" elif [ $1 == "arm64" ]; then
;; ARCH="arm64-v8a"
i386) FNAME="arm64";
ARCH="32" else
FNAME="i386" ARCH="64";
;; FNAME="amd64";
armv8 | arm64 | aarch64) fi
ARCH="arm64-v8a"
FNAME="arm64"
;;
armv7 | arm | arm32)
ARCH="arm32-v7a"
FNAME="arm32"
;;
*)
ARCH="64"
FNAME="amd64"
;;
esac
mkdir -p build/bin mkdir -p build/bin
cd build/bin cd build/bin
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.10/Xray-linux-${ARCH}.zip" wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.6/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md
mv xray "xray-linux-${FNAME}" mv xray "xray-linux-${FNAME}"

View File

@@ -1,4 +1,4 @@
FROM golang:1.22-alpine AS builder FROM golang:1.21-alpine AS builder
WORKDIR /app WORKDIR /app
ARG TARGETARCH ARG TARGETARCH
RUN apk --no-cache --update add build-base gcc wget unzip RUN apk --no-cache --update add build-base gcc wget unzip

279
README.md
View File

@@ -1,5 +1,5 @@
# X-UI # X-UI
**An Advanced Web Panel • Built on Xray Core** **Advanced GUI panel based on Xray Core supports multiple protocols and languages**
![](https://img.shields.io/github/v/release/alireza0/x-ui.svg) ![](https://img.shields.io/github/v/release/alireza0/x-ui.svg)
![](https://img.shields.io/docker/pulls/alireza7/x-ui.svg) ![](https://img.shields.io/docker/pulls/alireza7/x-ui.svg)
@@ -7,29 +7,32 @@
[![Downloads](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg) [![Downloads](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
> **Disclaimer:** This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment > **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
**If you think this project is helpful to you, you may wish to give a**:star2:
[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/alireza7) **If you think this project is helpful to you, you may wish to give a** :star2:
- USDT (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz` **Buy Me a Coffee :**
- USDT Tron (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
- Tezos (XTZ): - Tezos (XTZ):
`tz2Wnh2SsY1eezXrcLChu6idWpgdHzUFQcts` `tz2Wnh2SsY1eezXrcLChu6idWpgdHzUFQcts`
## Quick Overview ## Quick Look
| Features | Enable? | | Features | Enable? |
| -------------------------------------- | :----------------: | | -------------------------------------- | :----------------: |
| Multi-Protocol | :heavy_check_mark: | | Multi-Protocol | :heavy_check_mark: |
| Multi-Language | :heavy_check_mark: | | Multi-Language | :heavy_check_mark: |
| Multi-Client/Inbound | :heavy_check_mark: | | Multi-User Inbounds | :heavy_check_mark: |
| Advanced Traffic Routing Interface | :heavy_check_mark: | | Advanced Traffic Routing | :heavy_check_mark: |
| Client & Traffic & System Status | :heavy_check_mark: |
| Date & Traffic Cap Based on First Use | :heavy_check_mark: |
| REST API | :heavy_check_mark: | | REST API | :heavy_check_mark: |
| TG Bot (DB backup + admin + client) | :heavy_check_mark: | |Show Online Users | :heavy_check_mark: |
| Subscription Service (link + info) | :heavy_check_mark: | | Manage Users Traffic Data & Expiry Date| :heavy_check_mark: |
| Apply Expiry Date based on First Usage | :heavy_check_mark: |
| Telegram Bot (admin + clients) | :heavy_check_mark: |
| Database Backup using Telegram Bot | :heavy_check_mark: |
| Subscription Link + UserInfo | :heavy_check_mark: |
| Search in Deep | :heavy_check_mark: | | Search in Deep | :heavy_check_mark: |
| Dark/Light Theme | :heavy_check_mark: | | Dark/Light Theme | :heavy_check_mark: |
@@ -42,45 +45,22 @@ bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.s
## Install Custom Version ## Install Custom Version
**Step 1:** To install your desired version, add the version to the end of the installation command. e.g., ver `1.8.0`: To install your desired version you can add the version to the end of install command. Example for ver `0.5.2`:
```sh ```sh
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 1.8.0 bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.2
``` ```
## Manual Install & Upgrade ## Manual Install & Upgrade
<details> 1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases, generally choose Architecture `amd64`
<summary>Click for details</summary> 2. Then upload the compressed package to the server's `/root/` directory and login to the server with user `root`
### Usage > If your server cpu architecture is not `amd64` replace another architecture
1. To download the latest version of the compressed package directly to your server, run the following command:
```sh ```sh
ARCH=$(uname -m) ARCH=$(uname -m)
case "${ARCH}" in [[ "${ARCH}" == "s390x" ]] && XUI_ARCH="s390x" || [[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
*) XUI_ARCH="amd64" ;;
esac
wget https://github.com/alireza0/x-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)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
*) XUI_ARCH="amd64" ;;
esac
cd /root/ cd /root/
rm x-ui/ /usr/local/x-ui/ /usr/bin/x-ui -rf rm x-ui/ /usr/local/x-ui/ /usr/bin/x-ui -rf
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
@@ -93,35 +73,15 @@ systemctl enable x-ui
systemctl restart x-ui systemctl restart x-ui
``` ```
</details> ## Install Using Docker
## Install using Docker 1. Install Docker
<details>
<summary>Click for details</summary>
### Usage
**Step 1:** Install Docker
```shell ```shell
curl -fsSL https://get.docker.com | sh curl -fsSL https://get.docker.com | sh
``` ```
**Step 2:** Clone the Project Repository: 2. Install X-UI
```sh
git clone https://github.com/alireza0/x-ui.git
cd x-ui
```
**Step 3:** Start the Service
```sh
docker compose up -d
```
OR
```shell ```shell
mkdir x-ui && cd x-ui mkdir x-ui && cd x-ui
@@ -134,32 +94,12 @@ docker run -itd \
alireza7/x-ui:latest alireza7/x-ui:latest
``` ```
update to latest version
```sh
cd x-ui
docker compose down
docker compose pull x-ui
docker compose up -d
```
remove x-ui from docker
```sh
docker stop x-ui
docker rm x-ui
cd --
rm -r x-ui
```
> Build your own image > Build your own image
```shell ```shell
docker build -t x-ui . docker build -t x-ui .
``` ```
</details>
## Languages ## Languages
- English - English
@@ -170,21 +110,22 @@ docker build -t x-ui .
## Features ## Features
- Supports protocols including VLESS, VMess, Trojan, Shadowsocks, Dokodemo-door, SOCKS, HTTP, Wireguard - Supported protocols: VMess, VLESS, Trojan, Shadowsocks, Dokodemo-door, SOCKS, HTTP
- Supports XTLS protocols, including Vision and REALITY - Support XTLS native encryptions (Vision, REALITY)
- An advanced interface for routing traffic, incorporating PROXY Protocol, Reverse, External, and Transparent Proxy, along with Multi-Domain, SSL Certificate, and Port - Support advanced JSON editor GUI for Xray-Core configuration
- Support auto generate Cloudflare WARP using Wireguard outbound - Support advanced GUI for routing traffic (Reverse and Transparent proxy, Multi-Domain, Multi-Certificate, Multi-Port per inbound)
- An interactive JSON interface for Xray template configuration - Support Multi-User per inbound
- An advanced interface for inbound and outbound configuration - Support applying traffic data limits and expiry dates per user/inbound
- Clients traffic cap and expiration date based on first use - Support system status monitoring
- Displays online clients, traffic statistics, and system status monitoring - Support deep database search
- Deep database search - Show traffic statistics
- Displays depleted clients with expired dates or exceeded traffic cap - Show online users
- Subscription service with (multi)link - Show users with expired date or exceeded traffic limits
- Importing and exporting databases - Support subscription (multi) link
- One-Click SSL certificate application and automatic renewal - Support import/export database
- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate) - Support One-Click SSL certificate application and automatic renewal
- Dark/Light theme - Support HTTPS for panel (self-provided domain name + SSL certificate)
- Support Dark/Light theme UI
## Recommended OS ## Recommended OS
@@ -193,13 +134,12 @@ docker build -t x-ui .
- Debian 10+ - Debian 10+
- Fedora 36+ - Fedora 36+
## Preview ## Screenshots
![inbounds](./media/inbounds.png) ![inbounds](./media/inbounds.png)
![Dark inbounds](./media/inbounds-dark.png) ![Dark inbounds](./media/inbounds-dark.png)
![outbounds](./media/outbounds.png) ![outbounds](./media/outbounds.png)
![rules](./media/rules.png) ![rules](./media/rules.png)
![warp](./media/warp.png)
## API Routes ## API Routes
@@ -259,17 +199,6 @@ docker build -t x-ui .
<details> <details>
<summary>Click for details</summary> <summary>Click for details</summary>
### Cloudflare
The admin management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
- Cloudflare registered email
- Cloudflare Global API Key
- The domain name has been resolved to the current server through cloudflare
**Step 1:** Run the`x-ui`command on the server's terminal and then choose `17`. Then enter the information as requested.
### Certbot ### Certbot
```bash ```bash
@@ -289,42 +218,40 @@ certbot certonly --standalone --register-unsafely-without-email --non-interactiv
### Usage ### Usage
The web panel supports daily traffic, panel login, database backup, system status, client info, and other notification and functions through the Telegram Bot. To use the bot, you need to set the bot-related parameters in the panel, including: 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:
- Telegram Token - Tg robot Token
- Admin Chat ID(s) - Tg robot ChatId
- Notification Time (in cron syntax) - Tg robot cycle runtime, in crontab syntax
- Database Backup - Tg robot Expiration threshold
- CPU Load Threshold Notification - Tg robot Traffic threshold
- Tg robot Enable send backup in cycle runtime
**Crontab Time Format** - Tg robot Enable CPU usage alarm threshold
Reference syntax: Reference syntax:
- `*/30 * * * *` - Notify every 30 minutes, every hour - 30 \* \* \* \* \* //Notify at the 30s of each point
- `30 * * * * *` - Notify at the 30th second of each minute - 0 \*/10 \* \* \* \* //Notify at the first second of each 10 minutes
- `0 */10 * * * *` - Notify at the start of every 10 minutes - @hourly // hourly notification
- `@hourly` - Hourly notification - @daily // Daily notification (00:00 in the morning)
- `@daily` - Daily notification (00:00 AM) - @every 8h // notify every 8 hours
- `@every 8h` - Notify every 8 hours
For more info about [Crontab](https://acquia.my.site.com/s/article/360004224494-Cron-time-string-format)
### Features ### Features
- Periodic reporting - Report periodic
- Login notifications - Login notification
- CPU load threshold notifications - CPU threshold notification
- Advance notifications for expiration time and traffic - Threshold for Expiration time and Traffic to report in advance
- Client reporting menu with Telegram ID or username in configurations - Support client report menu if client's telegram ID or telegram UserName added to the user's configurations
- Anonymous traffic reports, search by UUID (VLESS/VMess) or Password (Trojan/Shadowsocks) - Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously
- Menu-based bot - Menu based bot
- Client search by email (admin only) - Search client by email ( only admin )
- Inbound checks - Check all inbounds
- System status check - Check server status
- Depleted client checks - Check depleted users
- Backup on request and in periodic reports - Receive backup by request and in periodic reports
- Multilingual support - Multi language bot
</details> </details>
## Troubleshoots ## Troubleshoots
@@ -334,58 +261,50 @@ For more info about [Crontab](https://acquia.my.site.com/s/article/360004224494-
### Enable Traffic Usage ### Enable Traffic Usage
If you are upgrading from an older version or other forks and find that data traffic usage for clients may not work by default, follow the steps below to enable it: Please be aware if you upgrade from an old X-UI version or other forks, by default data traffic usage for users may not work! it's recommended to follow below steps for enabeling:
**Step 1: Locate the Configuration Section** 1. Find this section in config file
Find the following section in the config file: ```json
"policy": {
"system": {
```
2. Add below section just after ` "policy": {` :
```json
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
```
- The final output is like:
```json ```json
"policy": { "policy": {
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
"system": { "system": {
// Other policy configurations "statsInboundDownlink": true,
"statsInboundUplink": true
} }
}, },
"routing": {
``` ```
**Step 2: Add the Required Configuration**
Add the following section just after `"policy": {`: 3. Save and restart panel
```json
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
```
**Step 3: Final Configuration**
Your final config should look like this:
```json
"policy": {
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
// Other routing configurations
},
```
**Step 4: Save and Restart**
Save your changes and restart the Xray Service
</details> </details>
## A Special Thanks to ## a Special Thanks to
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/) - [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
- [MHSanaei](https://github.com/MHSanaei) - [MHSanaei](https://github.com/MHSanaei)

View File

@@ -1 +1 @@
1.8.2 1.6.2

View File

@@ -6,7 +6,6 @@ import (
"io/fs" "io/fs"
"os" "os"
"path" "path"
"x-ui/config" "x-ui/config"
"x-ui/database/model" "x-ui/database/model"
"x-ui/xray" "x-ui/xray"

View File

@@ -2,7 +2,6 @@ package model
import ( import (
"fmt" "fmt"
"x-ui/util/json_util" "x-ui/util/json_util"
"x-ui/xray" "x-ui/xray"
) )
@@ -37,7 +36,7 @@ type Inbound struct {
// config part // config part
Listen string `json:"listen" form:"listen"` Listen string `json:"listen" form:"listen"`
Port int `json:"port" form:"port"` Port int `json:"port" form:"port" gorm:"unique"`
Protocol Protocol `json:"protocol" form:"protocol"` Protocol Protocol `json:"protocol" form:"protocol"`
Settings string `json:"settings" form:"settings"` Settings string `json:"settings" form:"settings"`
StreamSettings string `json:"streamSettings" form:"streamSettings"` StreamSettings string `json:"streamSettings" form:"streamSettings"`

106
go.mod
View File

@@ -1,92 +1,94 @@
module x-ui module x-ui
go 1.22.0 go 1.21.4
require ( require (
github.com/Calidity/gin-sessions v1.3.1 github.com/Workiva/go-datastructures v1.1.1
github.com/gin-contrib/sessions v0.0.4
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/goccy/go-json v0.10.2 github.com/goccy/go-json v0.10.2
github.com/nicksnyder/go-i18n/v2 v2.4.0 github.com/nicksnyder/go-i18n/v2 v2.3.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.1 github.com/pelletier/go-toml/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.24.3 github.com/shirou/gopsutil/v3 v3.23.11
github.com/xtls/xray-core v1.8.10 github.com/xtls/xray-core v1.8.6
go.uber.org/atomic v1.11.0 go.uber.org/atomic v1.11.0
golang.org/x/text v0.14.0 golang.org/x/text v0.14.0
google.golang.org/grpc v1.63.2 google.golang.org/grpc v1.59.0
gorm.io/driver/sqlite v1.5.5 gorm.io/driver/sqlite v1.5.4
gorm.io/gorm v1.25.9 gorm.io/gorm v1.25.5
) )
require github.com/chenzhuoyu/iasm v0.9.1 // indirect
require ( require (
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/bytedance/sonic v1.11.3 // indirect github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/cloudflare/circl v1.3.7 // indirect github.com/cloudflare/circl v1.3.6 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/gzip v1.0.0 github.com/gaukas/godicttls v0.0.4 // indirect
github.com/gin-contrib/gzip v0.0.6
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.3.0 // 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.19.0 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/sessions v1.2.2 // indirect github.com/gorilla/sessions v1.2.1 // indirect
github.com/gorilla/websocket v1.5.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect
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.17.7 // indirect github.com/klauspost/compress v1.17.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // 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/onsi/ginkgo/v2 v2.16.0 // indirect github.com/onsi/ginkgo/v2 v2.13.1 // 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-20240219145905-2259734c190a // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/quic-go/quic-go v0.42.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/refraction-networking/utls v1.6.3 // indirect github.com/quic-go/quic-go v0.40.0 // indirect
github.com/refraction-networking/utls v1.5.4 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/sagernet/sing v0.3.8 // indirect github.com/sagernet/sing v0.2.17 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect github.com/sagernet/sing-shadowsocks v0.2.5 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // 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.13 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.7.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.12 // indirect github.com/ugorji/go/codec v1.2.11 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.3.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
golang.org/x/arch v0.7.0 // indirect golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.21.0 // indirect golang.org/x/crypto v0.15.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/mod v0.16.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.22.0 // indirect golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.18.0 // indirect golang.org/x/sys v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.4.0 // indirect
golang.org/x/tools v0.19.0 // indirect golang.org/x/tools v0.15.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/protobuf v1.33.0 // indirect google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
lukechampine.com/blake3 v1.2.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect

326
go.sum
View File

@@ -10,29 +10,29 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/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/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns= github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
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/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.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=
@@ -44,36 +44,50 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gin-contrib/gzip v1.0.0 h1:UKN586Po/92IDX6ie5CWLgMI81obiIp5nSP85T3wlTk= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v1.0.0/go.mod h1:CtG7tQrPB3vIBo6Gat9FVUsis+1emjvQqd66ME5TdnE= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
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.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
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.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
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.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -81,16 +95,20 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
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/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -98,21 +116,21 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= 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/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -123,88 +141,109 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.2/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.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
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/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-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0= github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
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.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 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/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM= github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU=
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
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/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0 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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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-20240219145905-2259734c190a h1:XCUtNgBnZfUBhdfCX2QK+fslr9vevSsUg3W3peZwlak= github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM= github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc= github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs= github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o=
github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
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/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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sagernet/sing v0.3.8 h1:gm4JKalPhydMYX2zFOTnnd4TXtM/16WFRqSjMepYQQk= github.com/sagernet/sing v0.2.17 h1:vMPKb3MV0Aa5ws4dCJkRI8XEjrsUcDn810czd0FwmzI=
github.com/sagernet/sing v0.3.8/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= github.com/sagernet/sing v0.2.17/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s= github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM= github.com/sagernet/sing-shadowsocks v0.2.5/go.mod h1:MGWGkcU2xW2G2mfArT9/QqpVLOGU+dBaahZCtPHdt7A=
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/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ=
github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
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.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -236,28 +275,32 @@ github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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.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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
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/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
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/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/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
@@ -269,35 +312,40 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI= github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI=
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE= github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.10 h1:qxae6gSteonpPI7EZyOyqw5HmRVRzmU07qs0l1GNqz4= github.com/xtls/xray-core v1.8.6 h1:tr3nk/fZnFfCsmgZv7B3RC72N5qUC88oMGVLlybDey8=
github.com/xtls/xray-core v1.8.10/go.mod h1:Mc1t+kLBPE5a1EpsUNKjMLviGz3Y0XywxeEraJZAMlI= github.com/xtls/xray-core v1.8.6/go.mod h1:hj2EB8rtcLdlTC8zxiWm5xL+C0k2Aie9Pk0mXtDEP6U=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -306,8 +354,12 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -317,43 +369,61 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/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-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/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-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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@@ -366,32 +436,39 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 h1:em/y72n4XlYRtayY/cVj6pnVzHa//BDA1BdoO+z9mdE= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-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.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o= gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
@@ -400,7 +477,6 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@@ -23,16 +23,25 @@ else
fi fi
echo "The OS release is: $release" echo "The OS release is: $release"
arch() { arch=$(arch)
case "$(uname -m)" in
x86_64 | x64 | amd64) echo 'amd64' ;; if [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then
i*86 | x86) echo '386' ;; arch="amd64"
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;; elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then
armv7* | armv7 | arm) echo 'armv7' ;; arch="arm64"
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;; elif [[ $arch == "s390x" ]]; then
esac arch="s390x"
} else
echo "arch: $(arch)" arch="amd64"
echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}"
fi
echo "arch: ${arch}"
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead, if there is something wrong, please get in touch with me!"
exit -1
fi
os_version="" os_version=""
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1) os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
@@ -41,7 +50,7 @@ if [[ "${release}" == "centos" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "ubuntu" ]]; then elif [[ "${release}" == "ubuntu" ]]; then
if [[ ${os_version} -lt 20 ]]; then if [[ ${os_version} -lt 20 ]]; then
echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1 echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1
fi fi
@@ -59,18 +68,13 @@ else
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1 echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
fi fi
install_dependencies() {
case "${release}" in install_base() {
centos) if [[ "${release}" == "centos" ]] || [[ "${release}" == "fedora" ]] ; then
yum -y update && yum install -y -q wget curl tar tzdata yum install wget curl tar -y
;; else
fedora) apt install wget curl tar -y
dnf -y update && dnf install -y -q wget curl tar tzdata fi
;;
*)
apt-get update && apt install -y -q wget curl tar tzdata
;;
esac
} }
#This function will be called when user installed x-ui out of sercurity #This function will be called when user installed x-ui out of sercurity
@@ -109,21 +113,6 @@ config_after_install() {
} }
install_x-ui() { install_x-ui() {
# checks if the installation backup dir exist. if existed then ask user if they want to restore it else continue installation.
if [[ -e /usr/local/x-ui-backup/ ]]; then
read -p "Failed installation detected. Do you want to restore previously installed version? [y/n]? ": restore_confirm
if [[ "${restore_confirm}" == "y" || "${restore_confirm}" == "Y" ]]; then
systemctl stop x-ui
mv /usr/local/x-ui-backup/x-ui.db /etc/x-ui/ -f
mv /usr/local/x-ui-backup/ /usr/local/x-ui/ -f
systemctl start x-ui
echo -e "${green}previous installed x-ui restored successfully${plain}, it is up and running now..."
exit 0
else
echo -e "Continuing installing x-ui ..."
fi
fi
cd /usr/local/ cd /usr/local/
if [ $# == 0 ]; then if [ $# == 0 ]; then
@@ -133,45 +122,36 @@ install_x-ui() {
exit 1 exit 1
fi fi
echo -e "Got x-ui latest version: ${last_version}, beginning the installation..." echo -e "Got x-ui latest version: ${last_version}, beginning the installation..."
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo -e "${red}Downloading x-ui failed, please be sure that your server can access Github ${plain}" echo -e "${red}Dowanloading x-ui failed, please be sure that your server can access Github ${plain}"
exit 1 exit 1
fi fi
else else
last_version=$1 last_version=$1
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz" url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz"
echo -e "Beginning to install x-ui v$1" echo -e "Begining to install x-ui v$1"
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url} wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url}
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo -e "${red}download x-ui v$1 failed,please check the version exists${plain}" echo -e "${red}dowanload x-ui v$1 failed,please check the verison exists${plain}"
exit 1 exit 1
fi fi
fi fi
if [[ -e /usr/local/x-ui/ ]]; then if [[ -e /usr/local/x-ui/ ]]; then
systemctl stop x-ui systemctl stop x-ui
mv /usr/local/x-ui/ /usr/local/x-ui-backup/ -f rm /usr/local/x-ui/ -rf
cp /etc/x-ui/x-ui.db /usr/local/x-ui-backup/ -f
fi fi
tar zxvf x-ui-linux-$(arch).tar.gz tar zxvf x-ui-linux-${arch}.tar.gz
rm x-ui-linux-$(arch).tar.gz -f rm x-ui-linux-${arch}.tar.gz -f
cd x-ui cd x-ui
chmod +x x-ui chmod +x x-ui bin/xray-linux-${arch}
# Check the system's architecture and rename the file accordingly
if [[ $(arch) == "armv7" ]]; then
mv bin/xray-linux-$(arch) bin/xray-linux-arm
chmod +x bin/xray-linux-arm
fi
chmod +x x-ui bin/xray-linux-$(arch)
cp -f x-ui.service /etc/systemd/system/ cp -f x-ui.service /etc/systemd/system/
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/alireza0/x-ui/main/x-ui.sh wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/alireza0/x-ui/main/x-ui.sh
chmod +x /usr/local/x-ui/x-ui.sh chmod +x /usr/local/x-ui/x-ui.sh
chmod +x /usr/bin/x-ui chmod +x /usr/bin/x-ui
config_after_install config_after_install
rm /usr/local/x-ui-backup/ -rf
#echo -e "If it is a new installation, the default web port is ${green}54321${plain}, The username and password are ${green}admin${plain} by default" #echo -e "If it is a new installation, the default web port is ${green}54321${plain}, The username and password are ${green}admin${plain} by default"
#echo -e "Please make sure that this port is not occupied by other procedures,${yellow} And make sure that port 54321 has been released${plain}" #echo -e "Please make sure that this port is not occupied by other procedures,${yellow} And make sure that port 54321 has been released${plain}"
# echo -e "If you want to modify the 54321 to other ports and enter the x-ui command to modify it, you must also ensure that the port you modify is also released" # echo -e "If you want to modify the 54321 to other ports and enter the x-ui command to modify it, you must also ensure that the port you modify is also released"
@@ -183,24 +163,22 @@ install_x-ui() {
systemctl start x-ui systemctl start x-ui
echo -e "${green}x-ui v${last_version}${plain} installation finished, it is up and running now..." echo -e "${green}x-ui v${last_version}${plain} installation finished, it is up and running now..."
echo -e "" echo -e ""
echo "X-UI Control Menu Usage" echo -e "x-ui control menu usages: "
echo "------------------------------------------" echo -e "----------------------------------------------"
echo "SUBCOMMANDS:" echo -e "x-ui - Enter Admin menu"
echo "x-ui - Admin Management Script" echo -e "x-ui start - Start x-ui"
echo "x-ui start - Start" echo -e "x-ui stop - Stop x-ui"
echo "x-ui stop - Stop" echo -e "x-ui restart - Restart x-ui"
echo "x-ui restart - Restart" echo -e "x-ui status - Show x-ui status"
echo "x-ui status - Current Status" echo -e "x-ui enable - Enable x-ui on system startup"
echo "x-ui enable - Enable Autostart on OS Startup" echo -e "x-ui disable - Disable x-ui on system startup"
echo "x-ui disable - Disable Autostart on OS Startup" echo -e "x-ui log - Check x-ui logs"
echo "x-ui log - Check Logs" echo -e "x-ui update - Update x-ui"
echo "x-ui update - Update" echo -e "x-ui install - Install x-ui"
echo "x-ui install - Install" echo -e "x-ui uninstall - Uninstall x-ui"
echo "x-ui uninstall - Uninstall" echo -e "----------------------------------------------"
echo "x-ui help - Control Menu Usage"
echo "------------------------------------------"
} }
echo -e "${green}Running...${plain}" echo -e "${green}Excuting...${plain}"
install_dependencies install_base
install_x-ui $1 install_x-ui $1

View File

@@ -8,14 +8,12 @@ import (
"github.com/op/go-logging" "github.com/op/go-logging"
) )
var ( var logger *logging.Logger
logger *logging.Logger var logBuffer []struct {
logBuffer []struct { time string
time string level logging.Level
level logging.Level log string
log string }
}
)
func init() { func init() {
InitLogger(logging.INFO) InitLogger(logging.INFO)

View File

@@ -8,7 +8,6 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
_ "unsafe" _ "unsafe"
"x-ui/config" "x-ui/config"
"x-ui/database" "x-ui/database"
"x-ui/logger" "x-ui/logger"
@@ -319,7 +318,7 @@ func main() {
updateTgbotEnableSts(enabletgbot) updateTgbotEnableSts(enabletgbot)
} }
default: default:
fmt.Println("Invalid subcommands") fmt.Println("except 'run' or 'setting' subcommands")
fmt.Println() fmt.Println()
runCmd.Usage() runCmd.Usage()
fmt.Println() fmt.Println()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -1,87 +0,0 @@
{
"remarks": "",
"dns": {
"tag": "dns_out",
"queryStrategy": "UseIP",
"servers": [
{
"address": "8.8.8.8",
"skipFallback": false
}
]
},
"inbounds": [
{
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true,
"userLevel": 8
},
"sniffing": {
"destOverride": [
"http",
"tls",
"fakedns"
],
"enabled": true
},
"tag": "socks"
},
{
"port": 10809,
"protocol": "http",
"settings": {
"userLevel": 8
},
"tag": "http"
}
],
"log": {
"loglevel": "warning"
},
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIP"
}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {
"response": {
"type": "http"
}
}
}
],
"policy": {
"levels": {
"8": {
"connIdle": 300,
"downlinkOnly": 1,
"handshake": 4,
"uplinkOnly": 1
}
},
"system": {
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"network": "tcp,udp",
"outboundTag": "proxy"
}
]
},
"stats": {}
}

View File

@@ -7,7 +7,6 @@ import (
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"x-ui/config" "x-ui/config"
"x-ui/logger" "x-ui/logger"
"x-ui/util/common" "x-ui/util/common"
@@ -48,6 +47,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine := gin.Default() engine := gin.Default()
subPath, err := s.settingService.GetSubPath()
if err != nil {
return nil, err
}
subDomain, err := s.settingService.GetSubDomain() subDomain, err := s.settingService.GetSubDomain()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -57,62 +61,15 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine.Use(middleware.DomainValidatorMiddleware(subDomain)) engine.Use(middleware.DomainValidatorMiddleware(subDomain))
} }
LinksPath, err := s.settingService.GetSubPath() g := engine.Group(subPath)
if err != nil {
return nil, err
}
JsonPath, err := s.settingService.GetSubJsonPath() s.sub = NewSUBController(g)
if err != nil {
return nil, err
}
Encrypt, err := s.settingService.GetSubEncrypt()
if err != nil {
return nil, err
}
ShowInfo, err := s.settingService.GetSubShowInfo()
if err != nil {
return nil, err
}
RemarkModel, err := s.settingService.GetRemarkModel()
if err != nil {
RemarkModel = "-ieo"
}
SubUpdates, err := s.settingService.GetSubUpdates()
if err != nil {
SubUpdates = "10"
}
SubJsonFragment, err := s.settingService.GetSubJsonFragment()
if err != nil {
SubJsonFragment = ""
}
SubJsonMux, err := s.settingService.GetSubJsonMux()
if err != nil {
SubJsonMux = ""
}
SubJsonRules, err := s.settingService.GetSubJsonRules()
if err != nil {
SubJsonRules = ""
}
g := engine.Group("/")
s.sub = NewSUBController(
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
SubJsonFragment, SubJsonMux, SubJsonRules)
return engine, nil return engine, nil
} }
func (s *Server) Start() (err error) { func (s *Server) Start() (err error) {
// This is an anonymous function, no function name //This is an anonymous function, no function name
defer func() { defer func() {
if err != nil { if err != nil {
s.Stop() s.Stop()
@@ -157,19 +114,21 @@ func (s *Server) Start() (err error) {
if certFile != "" || keyFile != "" { if certFile != "" || keyFile != "" {
cert, err := tls.LoadX509KeyPair(certFile, keyFile) cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err == nil { if err != nil {
c := &tls.Config{ listener.Close()
Certificates: []tls.Certificate{cert}, return err
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
logger.Info("sub server run https on", listener.Addr())
} else {
logger.Error("error in loading certificates: ", err)
logger.Info("sub server run http on", listener.Addr())
} }
c := &tls.Config{
Certificates: []tls.Certificate{cert},
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
}
if certFile != "" || keyFile != "" {
logger.Info("Sub server run https on", listener.Addr())
} else { } else {
logger.Info("sub server run http on", listener.Addr()) logger.Info("Sub server run http on", listener.Addr())
} }
s.listener = listener s.listener = listener

View File

@@ -3,59 +3,34 @@ package sub
import ( import (
"encoding/base64" "encoding/base64"
"strings" "strings"
"x-ui/web/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type SUBController struct { type SUBController struct {
subPath string subService SubService
subJsonPath string settingService service.SettingService
subEncrypt bool
updateInterval string
subService *SubService
subJsonService *SubJsonService
} }
func NewSUBController( func NewSUBController(g *gin.RouterGroup) *SUBController {
g *gin.RouterGroup, a := &SUBController{}
subPath string,
jsonPath string,
encrypt bool,
showInfo bool,
rModel string,
update string,
jsonFragment string,
jsonMux string,
jsonRules string,
) *SUBController {
sub := NewSubService(showInfo, rModel)
a := &SUBController{
subPath: subPath,
subJsonPath: jsonPath,
subEncrypt: encrypt,
updateInterval: update,
subService: sub,
subJsonService: NewSubJsonService(jsonFragment, jsonMux, jsonRules, sub),
}
a.initRouter(g) a.initRouter(g)
return a return a
} }
func (a *SUBController) initRouter(g *gin.RouterGroup) { func (a *SUBController) initRouter(g *gin.RouterGroup) {
gLink := g.Group(a.subPath) g = g.Group("/")
gJson := g.Group(a.subJsonPath)
gLink.GET(":subid", a.subs) g.GET("/:subid", a.subs)
gJson.GET(":subid", a.subJsons)
} }
func (a *SUBController) subs(c *gin.Context) { func (a *SUBController) subs(c *gin.Context) {
subEncrypt, _ := a.settingService.GetSubEncrypt()
subShowInfo, _ := a.settingService.GetSubShowInfo()
subId := c.Param("subid") subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0] host := strings.Split(c.Request.Host, ":")[0]
subs, header, err := a.subService.GetSubs(subId, host) subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo)
if err != nil || len(subs) == 0 { if err != nil || len(subs) == 0 {
c.String(400, "Error!") c.String(400, "Error!")
} else { } else {
@@ -65,31 +40,14 @@ func (a *SUBController) subs(c *gin.Context) {
} }
// Add headers // Add headers
c.Writer.Header().Set("Subscription-Userinfo", header) c.Writer.Header().Set("Subscription-Userinfo", headers[0])
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval) c.Writer.Header().Set("Profile-Update-Interval", headers[1])
c.Writer.Header().Set("Profile-Title", subId) c.Writer.Header().Set("Profile-Title", headers[2])
if a.subEncrypt { if subEncrypt {
c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
} else { } else {
c.String(200, result) c.String(200, result)
} }
} }
} }
func (a *SUBController) subJsons(c *gin.Context) {
subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0]
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
if err != nil || len(jsonSub) == 0 {
c.String(400, "Error!")
} else {
// Add headers
c.Writer.Header().Set("Subscription-Userinfo", header)
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
c.Writer.Header().Set("Profile-Title", subId)
c.String(200, jsonSub)
}
}

View File

@@ -1,385 +0,0 @@
package sub
import (
_ "embed"
"encoding/json"
"fmt"
"strings"
"x-ui/database/model"
"x-ui/logger"
"x-ui/util/json_util"
"x-ui/util/random"
"x-ui/web/service"
"x-ui/xray"
)
//go:embed default.json
var defaultJson string
type SubJsonService struct {
configJson map[string]interface{}
defaultOutbounds []json_util.RawMessage
fragment string
mux string
inboundService service.InboundService
SubService *SubService
}
func NewSubJsonService(fragment string, mux string, rules string, subService *SubService) *SubJsonService {
var configJson map[string]interface{}
var defaultOutbounds []json_util.RawMessage
json.Unmarshal([]byte(defaultJson), &configJson)
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
for _, defaultOutbound := range outboundSlices {
jsonBytes, _ := json.Marshal(defaultOutbound)
defaultOutbounds = append(defaultOutbounds, jsonBytes)
}
}
if rules != "" {
var newRules []interface{}
routing, _ := configJson["routing"].(map[string]interface{})
defaultRules, _ := routing["rules"].([]interface{})
json.Unmarshal([]byte(rules), &newRules)
defaultRules = append(newRules, defaultRules...)
routing["rules"] = defaultRules
configJson["routing"] = routing
}
if fragment != "" {
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
}
return &SubJsonService{
configJson: configJson,
defaultOutbounds: defaultOutbounds,
fragment: fragment,
mux: mux,
SubService: subService,
}
}
func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) {
inbounds, err := s.SubService.getInboundsBySubId(subId)
if err != nil || len(inbounds) == 0 {
return "", "", err
}
var header string
var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic
var configArray []json_util.RawMessage
// Prepare Inbounds
for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound)
if err != nil {
logger.Error("SubJsonService - GetClients: Unable to get clients from inbound")
}
if clients == nil {
continue
}
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
listen, port, streamSettings, err := s.SubService.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
if err == nil {
inbound.Listen = listen
inbound.Port = port
inbound.StreamSettings = streamSettings
}
}
for _, client := range clients {
if client.Enable && client.SubID == subId {
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
newConfigs := s.getConfig(inbound, client, host)
configArray = append(configArray, newConfigs...)
}
}
}
if len(configArray) == 0 {
return "", "", nil
}
// Prepare statistics
for index, clientTraffic := range clientTraffics {
if index == 0 {
traffic.Up = clientTraffic.Up
traffic.Down = clientTraffic.Down
traffic.Total = clientTraffic.Total
if clientTraffic.ExpiryTime > 0 {
traffic.ExpiryTime = clientTraffic.ExpiryTime
}
} else {
traffic.Up += clientTraffic.Up
traffic.Down += clientTraffic.Down
if traffic.Total == 0 || clientTraffic.Total == 0 {
traffic.Total = 0
} else {
traffic.Total += clientTraffic.Total
}
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
traffic.ExpiryTime = 0
}
}
}
// Combile outbounds
var finalJson []byte
if len(configArray) == 1 {
finalJson, _ = json.MarshalIndent(configArray[0], "", " ")
} else {
finalJson, _ = json.MarshalIndent(configArray, "", " ")
}
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
return string(finalJson), header, nil
}
func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client, host string) []json_util.RawMessage {
var newJsonArray []json_util.RawMessage
stream := s.streamData(inbound.StreamSettings)
externalProxies, ok := stream["externalProxy"].([]interface{})
if !ok || len(externalProxies) == 0 {
externalProxies = []interface{}{
map[string]interface{}{
"forceTls": "same",
"dest": host,
"port": float64(inbound.Port),
"remark": "",
},
}
}
delete(stream, "externalProxy")
for _, ep := range externalProxies {
extPrxy := ep.(map[string]interface{})
inbound.Listen = extPrxy["dest"].(string)
inbound.Port = int(extPrxy["port"].(float64))
newStream := stream
switch extPrxy["forceTls"].(string) {
case "tls":
if newStream["security"] != "tls" {
newStream["security"] = "tls"
newStream["tslSettings"] = map[string]interface{}{}
}
case "none":
if newStream["security"] != "none" {
newStream["security"] = "none"
delete(newStream, "tslSettings")
}
}
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
var newOutbounds []json_util.RawMessage
switch inbound.Protocol {
case "vmess", "vless":
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
case "trojan", "shadowsocks":
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
}
newOutbounds = append(newOutbounds, s.defaultOutbounds...)
newConfigJson := make(map[string]interface{})
for key, value := range s.configJson {
newConfigJson[key] = value
}
newConfigJson["outbounds"] = newOutbounds
newConfigJson["remarks"] = s.SubService.genRemark(inbound, client.Email, extPrxy["remark"].(string))
newConfig, _ := json.MarshalIndent(newConfigJson, "", " ")
newJsonArray = append(newJsonArray, newConfig)
}
return newJsonArray
}
func (s *SubJsonService) streamData(stream string) map[string]interface{} {
var streamSettings map[string]interface{}
json.Unmarshal([]byte(stream), &streamSettings)
security, _ := streamSettings["security"].(string)
if security == "tls" {
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{}))
} else if security == "reality" {
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{}))
}
delete(streamSettings, "sockopt")
if s.fragment != "" {
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`)
}
// remove proxy protocol
network, _ := streamSettings["network"].(string)
switch network {
case "tcp":
streamSettings["tcpSettings"] = s.removeAcceptProxy(streamSettings["tcpSettings"])
case "ws":
streamSettings["wsSettings"] = s.removeAcceptProxy(streamSettings["wsSettings"])
case "httpupgrade":
streamSettings["httpupgradeSettings"] = s.removeAcceptProxy(streamSettings["httpupgradeSettings"])
}
return streamSettings
}
func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} {
netSettings, ok := setting.(map[string]interface{})
if ok {
delete(netSettings, "acceptProxyProtocol")
}
return netSettings
}
func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
tlsData := make(map[string]interface{}, 1)
tlsClientSettings, _ := tData["settings"].(map[string]interface{})
tlsData["serverName"] = tData["serverName"]
tlsData["alpn"] = tData["alpn"]
if allowInsecure, ok := tlsClientSettings["allowInsecure"].(bool); ok {
tlsData["allowInsecure"] = allowInsecure
}
if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
tlsData["fingerprint"] = fingerprint
}
return tlsData
}
func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
rltyData := make(map[string]interface{}, 1)
rltyClientSettings, _ := rData["settings"].(map[string]interface{})
rltyData["show"] = false
rltyData["publicKey"] = rltyClientSettings["publicKey"]
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
// Set random data
rltyData["spiderX"] = "/" + random.Seq(15)
shortIds, ok := rData["shortIds"].([]interface{})
if ok && len(shortIds) > 0 {
rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
} else {
rltyData["shortId"] = ""
}
serverNames, ok := rData["serverNames"].([]interface{})
if ok && len(serverNames) > 0 {
rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
} else {
rltyData["serverName"] = ""
}
return rltyData
}
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
outbound := Outbound{}
usersData := make([]UserVnext, 1)
usersData[0].ID = client.ID
usersData[0].Level = 8
if inbound.Protocol == model.VLESS {
usersData[0].Flow = client.Flow
usersData[0].Encryption = "none"
}
vnextData := make([]VnextSetting, 1)
vnextData[0] = VnextSetting{
Address: inbound.Listen,
Port: inbound.Port,
Users: usersData,
}
outbound.Protocol = string(inbound.Protocol)
outbound.Tag = "proxy"
if s.mux != "" {
outbound.Mux = json_util.RawMessage(s.mux)
}
outbound.StreamSettings = streamSettings
outbound.Settings = OutboundSettings{
Vnext: vnextData,
}
result, _ := json.MarshalIndent(outbound, "", " ")
return result
}
func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
outbound := Outbound{}
serverData := make([]ServerSetting, 1)
serverData[0] = ServerSetting{
Address: inbound.Listen,
Port: inbound.Port,
Level: 8,
Password: client.Password,
}
if inbound.Protocol == model.Shadowsocks {
var inboundSettings map[string]interface{}
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
method, _ := inboundSettings["method"].(string)
serverData[0].Method = method
// server password in multi-user 2022 protocols
if strings.HasPrefix(method, "2022") {
if serverPassword, ok := inboundSettings["password"].(string); ok {
serverData[0].Password = fmt.Sprintf("%s:%s", serverPassword, client.Password)
}
}
}
outbound.Protocol = string(inbound.Protocol)
outbound.Tag = "proxy"
if s.mux != "" {
outbound.Mux = json_util.RawMessage(s.mux)
}
outbound.StreamSettings = streamSettings
outbound.Settings = OutboundSettings{
Servers: serverData,
}
result, _ := json.MarshalIndent(outbound, "", " ")
return result
}
type Outbound struct {
Protocol string `json:"protocol"`
Tag string `json:"tag"`
StreamSettings json_util.RawMessage `json:"streamSettings"`
Mux json_util.RawMessage `json:"mux,omitempty"`
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
Settings OutboundSettings `json:"settings,omitempty"`
}
type OutboundSettings struct {
Vnext []VnextSetting `json:"vnext,omitempty"`
Servers []ServerSetting `json:"servers,omitempty"`
}
type VnextSetting struct {
Address string `json:"address"`
Port int `json:"port"`
Users []UserVnext `json:"users"`
}
type UserVnext struct {
Encryption string `json:"encryption,omitempty"`
Flow string `json:"flow,omitempty"`
ID string `json:"id"`
Level int `json:"level"`
}
type ServerSetting struct {
Password string `json:"password"`
Level int `json:"level"`
Address string `json:"address"`
Port int `json:"port"`
Flow string `json:"flow,omitempty"`
Method string `json:"method,omitempty"`
}

View File

@@ -6,12 +6,10 @@ import (
"net/url" "net/url"
"strings" "strings"
"time" "time"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
"x-ui/util/common" "x-ui/util/common"
"x-ui/util/random"
"x-ui/web/service" "x-ui/web/service"
"x-ui/xray" "x-ui/xray"
@@ -19,46 +17,50 @@ import (
) )
type SubService struct { type SubService struct {
address string address string
showInfo bool showInfo bool
remarkModel string remarkModel string
inboundService service.InboundService inboundService service.InboundService
settingService service.SettingService
} }
func NewSubService(showInfo bool, remarkModel string) *SubService { func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
return &SubService{
showInfo: showInfo,
remarkModel: remarkModel,
}
}
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
s.address = host s.address = host
s.showInfo = showInfo
var result []string var result []string
var header string var headers []string
var traffic xray.ClientTraffic var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic var clientTraffics []xray.ClientTraffic
inbounds, err := s.getInboundsBySubId(subId) inbounds, err := s.getInboundsBySubId(subId)
if err != nil { if err != nil {
return nil, "", err return nil, nil, err
}
s.remarkModel, err = s.settingService.GetRemarkModel()
if err != nil {
s.remarkModel = "-ieo"
} }
// Prepare Inbounds
for _, inbound := range inbounds { for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound) clients, err := s.inboundService.GetClients(inbound)
if err != nil { if err != nil {
logger.Error("SubService - GetClients: Unable to get clients from inbound") logger.Error("SubService - GetSub: Unable to get clients from inbound")
} }
if clients == nil { if clients == nil {
continue continue
} }
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' { if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings) fallbackMaster, err := s.getFallbackMaster(inbound.Listen)
if err == nil { if err == nil {
inbound.Listen = listen inbound.Listen = fallbackMaster.Listen
inbound.Port = port inbound.Port = fallbackMaster.Port
inbound.StreamSettings = streamSettings var stream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
var masterStream map[string]interface{}
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
stream["security"] = masterStream["security"]
stream["tlsSettings"] = masterStream["tlsSettings"]
stream["externalProxy"] = masterStream["externalProxy"]
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
inbound.StreamSettings = string(modifiedStream)
} }
} }
for _, client := range clients { for _, client := range clients {
@@ -69,8 +71,6 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
} }
} }
} }
// Prepare statistics
for index, clientTraffic := range clientTraffics { for index, clientTraffic := range clientTraffics {
if index == 0 { if index == 0 {
traffic.Up = clientTraffic.Up traffic.Up = clientTraffic.Up
@@ -92,8 +92,11 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
} }
} }
} }
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000) headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
return result, header, nil updateInterval, _ := s.settingService.GetSubUpdates()
headers = append(headers, fmt.Sprintf("%d", updateInterval))
headers = append(headers, subId)
return result, headers, nil
} }
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
@@ -122,7 +125,7 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
return xray.ClientTraffic{} return xray.ClientTraffic{}
} }
func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) { func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
db := database.GetDB() db := database.GetDB()
var inbound *model.Inbound var inbound *model.Inbound
err := db.Model(model.Inbound{}). err := db.Model(model.Inbound{}).
@@ -130,19 +133,9 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest). Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
Find(&inbound).Error Find(&inbound).Error
if err != nil { if err != nil {
return "", 0, "", err return nil, err
} }
return inbound, nil
var stream map[string]interface{}
json.Unmarshal([]byte(streamSettings), &stream)
var masterStream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
stream["security"] = masterStream["security"]
stream["tlsSettings"] = masterStream["tlsSettings"]
stream["externalProxy"] = masterStream["externalProxy"]
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
return inbound.Listen, inbound.Port, string(modifiedStream), nil
} }
func (s *SubService) getLink(inbound *model.Inbound, email string) string { func (s *SubService) getLink(inbound *model.Inbound, email string) string {
@@ -194,12 +187,8 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
case "ws": case "ws":
ws, _ := stream["wsSettings"].(map[string]interface{}) ws, _ := stream["wsSettings"].(map[string]interface{})
obj["path"] = ws["path"].(string) obj["path"] = ws["path"].(string)
if host, ok := ws["host"].(string); ok && len(host) > 0 { headers, _ := ws["headers"].(map[string]interface{})
obj["host"] = host obj["host"] = searchHost(headers)
} else {
headers, _ := ws["headers"].(map[string]interface{})
obj["host"] = searchHost(headers)
}
case "http": case "http":
obj["net"] = "h2" obj["net"] = "h2"
http, _ := stream["httpSettings"].(map[string]interface{}) http, _ := stream["httpSettings"].(map[string]interface{})
@@ -213,15 +202,10 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
obj["path"], _ = quic["key"].(string) obj["path"], _ = quic["key"].(string)
case "grpc": case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{}) grpc, _ := stream["grpcSettings"].(map[string]interface{})
obj["path"], _ = grpc["serviceName"].(string) obj["path"] = grpc["serviceName"].(string)
obj["authority"], _ = grpc["authority"].(string)
if grpc["multiMode"].(bool) { if grpc["multiMode"].(bool) {
obj["type"] = "multi" obj["type"] = "multi"
} }
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
obj["path"] = httpupgrade["path"].(string)
obj["host"] = httpupgrade["host"].(string)
} }
security, _ := stream["security"].(string) security, _ := stream["security"].(string)
@@ -338,12 +322,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
case "ws": case "ws":
ws, _ := stream["wsSettings"].(map[string]interface{}) ws, _ := stream["wsSettings"].(map[string]interface{})
params["path"] = ws["path"].(string) params["path"] = ws["path"].(string)
if host, ok := ws["host"].(string); ok && len(host) > 0 { headers, _ := ws["headers"].(map[string]interface{})
params["host"] = host params["host"] = searchHost(headers)
} else {
headers, _ := ws["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
case "http": case "http":
http, _ := stream["httpSettings"].(map[string]interface{}) http, _ := stream["httpSettings"].(map[string]interface{})
params["path"] = http["path"].(string) params["path"] = http["path"].(string)
@@ -357,14 +337,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
case "grpc": case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{}) grpc, _ := stream["grpcSettings"].(map[string]interface{})
params["serviceName"] = grpc["serviceName"].(string) params["serviceName"] = grpc["serviceName"].(string)
params["authority"], _ = grpc["authority"].(string)
if grpc["multiMode"].(bool) { if grpc["multiMode"].(bool) {
params["mode"] = "multi" params["mode"] = "multi"
} }
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
params["path"] = httpupgrade["path"].(string)
params["host"] = httpupgrade["host"].(string)
} }
security, _ := stream["security"].(string) security, _ := stream["security"].(string)
@@ -407,21 +382,25 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if realitySetting != nil { if realitySetting != nil {
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok { if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
sNames, _ := sniValue.([]interface{}) sNames, _ := sniValue.([]interface{})
params["sni"] = sNames[random.Num(len(sNames))].(string) params["sni"], _ = sNames[0].(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(realitySetting, "shortIds"); ok { if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
shortIds, _ := sidValue.([]interface{}) shortIds, _ := sidValue.([]interface{})
params["sid"] = shortIds[random.Num(len(shortIds))].(string) params["sid"], _ = shortIds[0].(string)
} }
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok { if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
if fp, ok := fpValue.(string); ok && len(fp) > 0 { if fp, ok := fpValue.(string); ok && len(fp) > 0 {
params["fp"] = fp params["fp"] = fp
} }
} }
params["spx"] = "/" + random.Seq(15) if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
params["spx"] = spx
}
}
} }
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
@@ -429,10 +408,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
} }
} }
if security != "tls" && security != "reality" {
params["security"] = "none"
}
externalProxies, _ := stream["externalProxy"].([]interface{}) externalProxies, _ := stream["externalProxy"].([]interface{})
if len(externalProxies) > 0 { if len(externalProxies) > 0 {
@@ -528,12 +503,8 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
case "ws": case "ws":
ws, _ := stream["wsSettings"].(map[string]interface{}) ws, _ := stream["wsSettings"].(map[string]interface{})
params["path"] = ws["path"].(string) params["path"] = ws["path"].(string)
if host, ok := ws["host"].(string); ok && len(host) > 0 { headers, _ := ws["headers"].(map[string]interface{})
params["host"] = host params["host"] = searchHost(headers)
} else {
headers, _ := ws["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
case "http": case "http":
http, _ := stream["httpSettings"].(map[string]interface{}) http, _ := stream["httpSettings"].(map[string]interface{})
params["path"] = http["path"].(string) params["path"] = http["path"].(string)
@@ -547,14 +518,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
case "grpc": case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{}) grpc, _ := stream["grpcSettings"].(map[string]interface{})
params["serviceName"] = grpc["serviceName"].(string) params["serviceName"] = grpc["serviceName"].(string)
params["authority"], _ = grpc["authority"].(string)
if grpc["multiMode"].(bool) { if grpc["multiMode"].(bool) {
params["mode"] = "multi" params["mode"] = "multi"
} }
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
params["path"] = httpupgrade["path"].(string)
params["host"] = httpupgrade["host"].(string)
} }
security, _ := stream["security"].(string) security, _ := stream["security"].(string)
@@ -593,28 +559,28 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if realitySetting != nil { if realitySetting != nil {
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok { if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
sNames, _ := sniValue.([]interface{}) sNames, _ := sniValue.([]interface{})
params["sni"] = sNames[random.Num(len(sNames))].(string) params["sni"], _ = sNames[0].(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(realitySetting, "shortIds"); ok { if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
shortIds, _ := sidValue.([]interface{}) shortIds, _ := sidValue.([]interface{})
params["sid"] = shortIds[random.Num(len(shortIds))].(string) params["sid"], _ = shortIds[0].(string)
} }
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok { if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
if fp, ok := fpValue.(string); ok && len(fp) > 0 { if fp, ok := fpValue.(string); ok && len(fp) > 0 {
params["fp"] = fp params["fp"] = fp
} }
} }
params["spx"] = "/" + random.Seq(15) if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
params["spx"] = spx
}
}
} }
} }
if security != "tls" && security != "reality" {
params["security"] = "none"
}
externalProxies, _ := stream["externalProxy"].([]interface{}) externalProxies, _ := stream["externalProxy"].([]interface{})
if len(externalProxies) > 0 { if len(externalProxies) > 0 {
@@ -714,12 +680,8 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
case "ws": case "ws":
ws, _ := stream["wsSettings"].(map[string]interface{}) ws, _ := stream["wsSettings"].(map[string]interface{})
params["path"] = ws["path"].(string) params["path"] = ws["path"].(string)
if host, ok := ws["host"].(string); ok && len(host) > 0 { headers, _ := ws["headers"].(map[string]interface{})
params["host"] = host params["host"] = searchHost(headers)
} else {
headers, _ := ws["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
case "http": case "http":
http, _ := stream["httpSettings"].(map[string]interface{}) http, _ := stream["httpSettings"].(map[string]interface{})
params["path"] = http["path"].(string) params["path"] = http["path"].(string)
@@ -733,14 +695,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
case "grpc": case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{}) grpc, _ := stream["grpcSettings"].(map[string]interface{})
params["serviceName"] = grpc["serviceName"].(string) params["serviceName"] = grpc["serviceName"].(string)
params["authority"], _ = grpc["authority"].(string)
if grpc["multiMode"].(bool) { if grpc["multiMode"].(bool) {
params["mode"] = "multi" params["mode"] = "multi"
} }
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
params["path"] = httpupgrade["path"].(string)
params["host"] = httpupgrade["host"].(string)
} }
security, _ := stream["security"].(string) security, _ := stream["security"].(string)

View File

@@ -3,7 +3,6 @@ package common
import ( import (
"errors" "errors"
"fmt" "fmt"
"x-ui/logger" "x-ui/logger"
) )

View File

@@ -5,14 +5,12 @@ import (
"time" "time"
) )
var ( var numSeq [10]rune
numSeq [10]rune var lowerSeq [26]rune
lowerSeq [26]rune var upperSeq [26]rune
upperSeq [26]rune var numLowerSeq [36]rune
numLowerSeq [36]rune var numUpperSeq [36]rune
numUpperSeq [36]rune var allSeq [62]rune
allSeq [62]rune
)
func init() { func init() {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
@@ -43,7 +41,3 @@ func Seq(n int) string {
} }
return string(runes) return string(runes)
} }
func Num(n int) int {
return rand.Intn(n)
}

View File

@@ -538,7 +538,7 @@
var on = function(emitter, type, f) { var on = function(emitter, type, f) {
if (emitter.addEventListener) { if (emitter.addEventListener) {
emitter.addEventListener(type, f, { passive: false }); emitter.addEventListener(type, f, false);
} else if (emitter.attachEvent) { } else if (emitter.attachEvent) {
emitter.attachEvent("on" + type, f); emitter.attachEvent("on" + type, f);
} else { } else {

View File

@@ -55,7 +55,7 @@ style attribute {
} }
.ant-table-tbody > tr > td, .ant-table-tbody > tr > td,
.ant-table-thead > tr > th { .ant-table-thead > tr > th {
padding: 12px 8px; padding: 12px 16px;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
.ant-table-thead > tr > th { .ant-table-thead > tr > th {
@@ -132,13 +132,6 @@ style attribute {
margin: 0.5rem; margin: 0.5rem;
padding: 0.5rem; padding: 0.5rem;
} }
.ant-modal-body {
padding: 10px;
}
.ant-form-item-label {
line-height: 1.5;
padding: 8px 0 0;
}
} }
.ant-layout-content { .ant-layout-content {
@@ -416,10 +409,6 @@ style attribute {
background-color: white; background-color: white;
} }
.ant-form-item {
margin-bottom: 0;
}
.ant-setting-textarea { .ant-setting-textarea {
margin-top: 1.5rem; margin-top: 1.5rem;
} }
@@ -683,8 +672,7 @@ style attribute {
border-color: #9ea2a8; border-color: #9ea2a8;
} }
.dark .ant-table-row-expand-icon:hover, .dark .ant-table-row-expand-icon:hover {
.dark .ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:hover {
color: #0e49b5; color: #0e49b5;
background-color: #fff0; background-color: #fff0;
border-color: #0e49b5; border-color: #0e49b5;
@@ -1039,12 +1027,6 @@ li.ant-select-dropdown-menu-item:empty:after {
color: rgba(255, 255, 255, 0.25); color: rgba(255, 255, 255, 0.25);
} }
.dark .ant-message-notice-content {
background-color: #222d42;
border: 1px solid #2c3950;
color: rgba(255, 255, 255, 0.65);
}
.ant-input-number-handler-wrap { .ant-input-number-handler-wrap {
border-radius: 0; border-radius: 0;
} }
@@ -1056,29 +1038,3 @@ li.ant-select-dropdown-menu-item:empty:after {
.ant-input-number { .ant-input-number {
overflow: clip; overflow: clip;
} }
.ant-modal-body,
.ant-collapse-content>.ant-collapse-content-box {
overflow-x: auto;
}
.dark .ant-dropdown-menu-item:hover,
.dark .ant-dropdown-menu-submenu-title:hover,
.dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),
.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) {
background-color: #313f5a;
}
.ant-select-dropdown,
.ant-popover-inner {
overflow-x: hidden;
}
.ant-popover-inner-content {
max-height: 400px;
overflow-y: auto;
}
.ant-input-group-addon:not(:first-child):not(:last-child), .ant-input-group-wrap:not(:first-child):not(:last-child), .ant-input-group>.ant-input:not(:first-child):not(:last-child) {
border-radius: 0rem 1rem 1rem 0rem;
}

BIN
web/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -14,17 +14,3 @@ axios.interceptors.request.use(
}, },
(error) => Promise.reject(error), (error) => Promise.reject(error),
); );
axios.interceptors.response.use(
(response) => response,
(error) => {
if (error.response) {
const statusCode = error.response.status;
// Check the status code
if (statusCode === 401) { // Unauthorized
return window.location.reload();
}
}
return Promise.reject(error);
}
);

View File

@@ -56,10 +56,6 @@ class DBInbound {
return this.protocol === Protocols.HTTP; return this.protocol === Protocols.HTTP;
} }
get isWireguard() {
return this.protocol === Protocols.WIREGUARD;
}
get address() { get address() {
let address = location.hostname; let address = location.hostname;
if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") { if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") {

View File

@@ -8,7 +8,6 @@ const Protocols = {
Shadowsocks: "shadowsocks", Shadowsocks: "shadowsocks",
Socks: "socks", Socks: "socks",
HTTP: "http", HTTP: "http",
Wireguard: "wireguard"
}; };
const SSMethods = { const SSMethods = {
@@ -47,34 +46,18 @@ const ALPN_OPTION = {
HTTP1: "http/1.1", HTTP1: "http/1.1",
}; };
const OutboundDomainStrategies = [ const outboundDomainStrategies = [
"AsIs", "AsIs",
"UseIP", "UseIP",
"UseIPv4", "UseIPv4",
"UseIPv6", "UseIPv6"
"UseIPv6v4", ]
"UseIPv4v6",
"ForceIP",
"ForceIPv6v4",
"ForceIPv6",
"ForceIPv4v6",
"ForceIPv4"
];
const WireguardDomainStrategy = [
"ForceIP",
"ForceIPv4",
"ForceIPv4v6",
"ForceIPv6",
"ForceIPv6v4"
];
Object.freeze(Protocols); Object.freeze(Protocols);
Object.freeze(SSMethods); Object.freeze(SSMethods);
Object.freeze(TLS_FLOW_CONTROL); Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(ALPN_OPTION); Object.freeze(ALPN_OPTION);
Object.freeze(OutboundDomainStrategies); Object.freeze(outboundDomainStrategies);
Object.freeze(WireguardDomainStrategy);
class CommonClass { class CommonClass {
@@ -194,14 +177,14 @@ class WsStreamSettings extends CommonClass {
static fromJson(json={}) { static fromJson(json={}) {
return new WsStreamSettings( return new WsStreamSettings(
json.path, json.path,
json.host, json.headers && !ObjectUtil.isEmpty(json.headers.Host) ? json.headers.Host : '',
); );
} }
toJson() { toJson() {
return { return {
path: this.path, path: this.path,
host: this.host, headers: ObjectUtil.isEmpty(this.host) ? undefined : {Host: this.host},
}; };
} }
} }
@@ -257,48 +240,24 @@ class QuicStreamSettings extends CommonClass {
} }
class GrpcStreamSettings extends CommonClass { class GrpcStreamSettings extends CommonClass {
constructor(serviceName="", authority="", multiMode=false) { constructor(serviceName="", multiMode=false) {
super(); super();
this.serviceName = serviceName; this.serviceName = serviceName;
this.authority = authority;
this.multiMode = multiMode; this.multiMode = multiMode;
} }
static fromJson(json={}) { static fromJson(json={}) {
return new GrpcStreamSettings(json.serviceName, json.authority, json.multiMode); return new GrpcStreamSettings(json.serviceName, json.multiMode);
} }
toJson() { toJson() {
return { return {
serviceName: this.serviceName, serviceName: this.serviceName,
authority: this.authority,
multiMode: this.multiMode, multiMode: this.multiMode,
} }
} }
} }
class HttpUpgradeStreamSettings extends CommonClass {
constructor(path='/', host='') {
super();
this.path = path;
this.host = host;
}
static fromJson(json={}) {
return new HttpUpgradeStreamSettings(
json.path,
json.host,
);
}
toJson() {
return {
path: this.path,
host: this.host,
};
}
}
class TlsStreamSettings extends CommonClass { class TlsStreamSettings extends CommonClass {
constructor(serverName='', constructor(serverName='',
alpn=[], alpn=[],
@@ -357,36 +316,7 @@ class RealityStreamSettings extends CommonClass {
spiderX: this.spiderX, spiderX: this.spiderX,
}; };
} }
} };
class SockoptStreamSettings extends CommonClass {
constructor(dialerProxy = "", tcpFastOpen = false, tcpKeepAliveInterval = 0, tcpNoDelay = false) {
super();
this.dialerProxy = dialerProxy;
this.tcpFastOpen = tcpFastOpen;
this.tcpKeepAliveInterval = tcpKeepAliveInterval;
this.tcpNoDelay = tcpNoDelay;
}
static fromJson(json = {}) {
if (Object.keys(json).length === 0) return undefined;
return new SockoptStreamSettings(
json.dialerProxy,
json.tcpFastOpen,
json.tcpKeepAliveInterval,
json.tcpNoDelay,
);
}
toJson() {
return {
dialerProxy: this.dialerProxy,
tcpFastOpen: this.tcpFastOpen,
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
tcpNoDelay: this.tcpNoDelay,
};
}
}
class StreamSettings extends CommonClass { class StreamSettings extends CommonClass {
constructor(network='tcp', constructor(network='tcp',
@@ -399,8 +329,6 @@ class StreamSettings extends CommonClass {
httpSettings=new HttpStreamSettings(), httpSettings=new HttpStreamSettings(),
quicSettings=new QuicStreamSettings(), quicSettings=new QuicStreamSettings(),
grpcSettings=new GrpcStreamSettings(), grpcSettings=new GrpcStreamSettings(),
httpupgradeSettings=new HttpUpgradeStreamSettings(),
sockopt = undefined,
) { ) {
super(); super();
this.network = network; this.network = network;
@@ -413,8 +341,6 @@ class StreamSettings extends CommonClass {
this.http = httpSettings; this.http = httpSettings;
this.quic = quicSettings; this.quic = quicSettings;
this.grpc = grpcSettings; this.grpc = grpcSettings;
this.httpupgrade = httpupgradeSettings;
this.sockopt = sockopt;
} }
get isTls() { get isTls() {
@@ -425,14 +351,6 @@ class StreamSettings extends CommonClass {
return this.security === "reality"; return this.security === "reality";
} }
get sockoptSwitch() {
return this.sockopt != undefined;
}
set sockoptSwitch(value) {
this.sockopt = value ? new SockoptStreamSettings() : undefined;
}
static fromJson(json={}) { static fromJson(json={}) {
return new StreamSettings( return new StreamSettings(
json.network, json.network,
@@ -445,8 +363,6 @@ class StreamSettings extends CommonClass {
HttpStreamSettings.fromJson(json.httpSettings), HttpStreamSettings.fromJson(json.httpSettings),
QuicStreamSettings.fromJson(json.quicSettings), QuicStreamSettings.fromJson(json.quicSettings),
GrpcStreamSettings.fromJson(json.grpcSettings), GrpcStreamSettings.fromJson(json.grpcSettings),
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
SockoptStreamSettings.fromJson(json.sockopt),
); );
} }
@@ -463,37 +379,6 @@ class StreamSettings extends CommonClass {
httpSettings: network === 'http' ? this.http.toJson() : undefined, httpSettings: network === 'http' ? this.http.toJson() : undefined,
quicSettings: network === 'quic' ? this.quic.toJson() : undefined, quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined, grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
};
}
}
class Mux extends CommonClass {
constructor(enabled = false, concurrency = 8, xudpConcurrency = 16, xudpProxyUDP443 = "reject") {
super();
this.enabled = enabled;
this.concurrency = concurrency;
this.xudpConcurrency = xudpConcurrency;
this.xudpProxyUDP443 = xudpProxyUDP443;
}
static fromJson(json = {}) {
if (Object.keys(json).length === 0) return undefined;
return new Mux(
json.enabled,
json.concurrency,
json.xudpConcurrency,
json.xudpProxyUDP443,
);
}
toJson() {
return {
enabled: this.enabled,
concurrency: this.concurrency,
xudpConcurrency: this.xudpConcurrency,
xudpProxyUDP443: this.xudpProxyUDP443,
}; };
} }
} }
@@ -504,16 +389,12 @@ class Outbound extends CommonClass {
protocol=Protocols.VMess, protocol=Protocols.VMess,
settings=null, settings=null,
streamSettings = new StreamSettings(), streamSettings = new StreamSettings(),
sendThrough,
mux = new Mux(),
) { ) {
super(); super();
this.tag = tag; this.tag = tag;
this._protocol = protocol; this._protocol = protocol;
this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings; this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings;
this.stream = streamSettings; this.stream = streamSettings;
this.sendThrough = sendThrough;
this.mux = mux;
} }
get protocol() { get protocol() {
@@ -527,8 +408,8 @@ class Outbound extends CommonClass {
} }
canEnableTls() { canEnableTls() {
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false; if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade"].includes(this.stream.network); return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
} }
//this is used for xtls-rprx-vision //this is used for xtls-rprx-vision
@@ -548,10 +429,6 @@ class Outbound extends CommonClass {
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol); return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol);
} }
canEnableMux() {
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, Protocols.HTTP, Protocols.Socks].includes(this.protocol);
}
hasVnext() { hasVnext() {
return [Protocols.VMess, Protocols.VLESS].includes(this.protocol); return [Protocols.VMess, Protocols.VLESS].includes(this.protocol);
} }
@@ -582,26 +459,15 @@ class Outbound extends CommonClass {
json.protocol, json.protocol,
Outbound.Settings.fromJson(json.protocol, json.settings), Outbound.Settings.fromJson(json.protocol, json.settings),
StreamSettings.fromJson(json.streamSettings), StreamSettings.fromJson(json.streamSettings),
json.sendThrough,
Mux.fromJson(json.mux),
) )
} }
toJson() { toJson() {
var stream;
if (this.canEnableStream()) {
stream = this.stream.toJson();
} else {
if (this.stream?.sockopt)
stream = { sockopt: this.stream.sockopt.toJson() };
}
return { return {
tag: this.tag == '' ? undefined : this.tag, tag: this.tag == '' ? undefined : this.tag,
protocol: this.protocol, protocol: this.protocol,
settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings, settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings,
streamSettings: stream, streamSettings: this.canEnableStream() ? this.stream.toJson() : undefined,
sendThrough: this.sendThrough != "" ? this.sendThrough : undefined,
mux: this.mux?.enabled ? this.mux : undefined,
}; };
} }
@@ -610,7 +476,7 @@ class Outbound extends CommonClass {
if(data.length !=2) return null; if(data.length !=2) return null;
switch(data[0].toLowerCase()){ switch(data[0].toLowerCase()){
case Protocols.VMess: case Protocols.VMess:
return this.fromVmessLink(JSON.parse(Base64.decode(data[1]))); return this.fromVmessLink(JSON.parse(atob(data[1])));
case Protocols.VLESS: case Protocols.VLESS:
case Protocols.Trojan: case Protocols.Trojan:
case 'ss': case 'ss':
@@ -627,8 +493,8 @@ class Outbound extends CommonClass {
if (network === 'tcp') { if (network === 'tcp') {
stream.tcp = new TcpStreamSettings( stream.tcp = new TcpStreamSettings(
json.type, json.type,
json.host ?? '', json.host ? json.host.split(','): [],
json.path ?? ''); json.path ? json.path.split(','): []);
} else if (network === 'kcp') { } else if (network === 'kcp') {
stream.kcp = new KcpStreamSettings(); stream.kcp = new KcpStreamSettings();
stream.type = json.type; stream.type = json.type;
@@ -639,16 +505,14 @@ class Outbound extends CommonClass {
stream.network = 'http' stream.network = 'http'
stream.http = new HttpStreamSettings( stream.http = new HttpStreamSettings(
json.path, json.path,
json.host); json.host ? json.host.split(',') : []);
} else if (network === 'quic') { } else if (network === 'quic') {
stream.quic = new QuicStreamSettings( stream.quic = new QuicStreamSettings(
json.host ? json.host : 'none', json.host ? json.host : 'none',
json.path, json.path,
json.type ? json.type : 'none'); json.type ? json.type : 'none');
} else if (network === 'grpc') { } else if (network === 'grpc') {
stream.grpc = new GrpcStreamSettings(json.path, json.authority, json.type == 'multi'); stream.grpc = new GrpcStreamSettings(json.path, json.type == 'multi');
} else if (network === 'httpupgrade') {
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path,json.host);
} }
if(json.tls && json.tls == 'tls'){ if(json.tls && json.tls == 'tls'){
@@ -659,22 +523,21 @@ class Outbound extends CommonClass {
json.allowInsecure); json.allowInsecure);
} }
const port = json.port * 1;
return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, port, json.id), stream); return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, json.port, json.id), stream);
} }
static fromParamLink(link){ static fromParamLink(link){
const url = new URL(link); const url = new URL(link);
let type = url.searchParams.get('type') ?? 'tcp'; let type = url.searchParams.get('type');
let security = url.searchParams.get('security') ?? 'none'; let security = url.searchParams.get('security') ?? 'none';
let stream = new StreamSettings(type, security); let stream = new StreamSettings(type, security);
let headerType = url.searchParams.get('headerType') ?? undefined; let headerType = url.searchParams.get('headerType');
let host = url.searchParams.get('host') ?? undefined; let host = url.searchParams.get('host');
let path = url.searchParams.get('path') ?? undefined; let path = url.searchParams.get('path');
if (type === 'tcp' || type === 'none') { if (type === 'tcp') {
stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path); stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path);
} else if (type === 'kcp') { } else if (type === 'kcp') {
stream.kcp = new KcpStreamSettings(); stream.kcp = new KcpStreamSettings();
@@ -690,12 +553,7 @@ class Outbound extends CommonClass {
url.searchParams.get('key') ?? '', url.searchParams.get('key') ?? '',
headerType ?? 'none'); headerType ?? 'none');
} else if (type === 'grpc') { } else if (type === 'grpc') {
stream.grpc = new GrpcStreamSettings( stream.grpc = new GrpcStreamSettings(url.searchParams.get('serviceName') ?? '', url.searchParams.get('mode') == 'multi');
url.searchParams.get('serviceName') ?? '',
url.searchParams.get('authority') ?? '',
url.searchParams.get('mode') == 'multi');
} else if (type === 'httpupgrade') {
stream.httpupgrade = new HttpUpgradeStreamSettings(path,host);
} }
if(security == 'tls'){ if(security == 'tls'){
@@ -712,10 +570,13 @@ class Outbound extends CommonClass {
let sni=url.searchParams.get('sni') ?? ''; let sni=url.searchParams.get('sni') ?? '';
let sid=url.searchParams.get('sid') ?? ''; let sid=url.searchParams.get('sid') ?? '';
let spx=url.searchParams.get('spx') ?? ''; let spx=url.searchParams.get('spx') ?? '';
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx); stream.tls = new RealityStreamSettings(pbk, fp, sni, sid, spx);
} }
const regex = /([^@]+):\/\/([^@]+)@(.+):(\d+)(.*)$/; let data = link.split('?');
if(data.length != 2) return null;
const regex = /([^@]+):\/\/([^@]+)@([^:]+):(\d+)\?(.*)$/;
const match = link.match(regex); const match = link.match(regex);
if (!match) return null; if (!match) return null;
@@ -764,7 +625,6 @@ Outbound.Settings = class extends CommonClass {
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings(); case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
case Protocols.Socks: return new Outbound.SocksSettings(); case Protocols.Socks: return new Outbound.SocksSettings();
case Protocols.HTTP: return new Outbound.HttpSettings(); case Protocols.HTTP: return new Outbound.HttpSettings();
case Protocols.Wireguard: return new Outbound.WireguardSettings();
default: return null; default: return null;
} }
} }
@@ -780,7 +640,6 @@ Outbound.Settings = class extends CommonClass {
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json); case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json); case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json); case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
default: return null; default: return null;
} }
} }
@@ -829,7 +688,7 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
Outbound.BlackholeSettings = class extends CommonClass { Outbound.BlackholeSettings = class extends CommonClass {
constructor(type) { constructor(type) {
super(); super();
this.type = type; this.type;
} }
static fromJson(json={}) { static fromJson(json={}) {
@@ -980,22 +839,22 @@ Outbound.ShadowsocksSettings = class extends CommonClass {
} }
}; };
Outbound.SocksSettings = class extends CommonClass { Outbound.SocksSettings = class extends CommonClass {
constructor(address, port, user, pass) { constructor(address, port, user, password) {
super(); super();
this.address = address; this.address = address;
this.port = port; this.port = port;
this.user = user; this.user = user;
this.pass = pass; this.password = password;
} }
static fromJson(json={}) { static fromJson(json={}) {
let servers = json.servers; servers = json.servers;
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}]; if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
return new Outbound.SocksSettings( return new Outbound.SocksSettings(
servers[0].address, servers[0].address,
servers[0].port, servers[0].port,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user, ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass, ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password,
); );
} }
@@ -1004,28 +863,28 @@ Outbound.SocksSettings = class extends CommonClass {
servers: [{ servers: [{
address: this.address, address: this.address,
port: this.port, port: this.port,
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, pass: this.pass}], users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, password: this.password}],
}], }],
}; };
} }
}; };
Outbound.HttpSettings = class extends CommonClass { Outbound.HttpSettings = class extends CommonClass {
constructor(address, port, user, pass) { constructor(address, port, user, password) {
super(); super();
this.address = address; this.address = address;
this.port = port; this.port = port;
this.user = user; this.user = user;
this.pass = pass; this.password = password;
} }
static fromJson(json={}) { static fromJson(json={}) {
let servers = json.servers; servers = json.servers;
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}]; if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
return new Outbound.HttpSettings( return new Outbound.HttpSettings(
servers[0].address, servers[0].address,
servers[0].port, servers[0].port,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user, ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass, ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password,
); );
} }
@@ -1034,89 +893,8 @@ Outbound.HttpSettings = class extends CommonClass {
servers: [{ servers: [{
address: this.address, address: this.address,
port: this.port, port: this.port,
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, pass: this.pass}], users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, password: this.password}],
}], }],
}; };
} }
}; };
Outbound.WireguardSettings = class extends CommonClass {
constructor(
mtu=1420, secretKey='',
address='', workers=2, domainStrategy='', reserved='',
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
super();
this.mtu = mtu;
this.secretKey = secretKey;
this.pubKey = secretKey.length>0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
this.address = address instanceof Array ? address.join(',') : address;
this.workers = workers;
this.domainStrategy = domainStrategy;
this.reserved = reserved instanceof Array ? reserved.join(',') : reserved;
this.peers = peers;
this.kernelMode = kernelMode;
}
addPeer() {
this.peers.push(new Outbound.WireguardSettings.Peer());
}
delPeer(index) {
this.peers.splice(index, 1);
}
static fromJson(json={}){
return new Outbound.WireguardSettings(
json.mtu,
json.secretKey,
json.address,
json.workers,
json.domainStrategy,
json.reserved,
json.peers.map(peer => Outbound.WireguardSettings.Peer.fromJson(peer)),
json.kernelMode,
);
}
toJson() {
return {
mtu: this.mtu?? undefined,
secretKey: this.secretKey,
address: this.address ? this.address.split(",") : [],
workers: this.workers?? undefined,
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
reserved: this.reserved ? this.reserved.split(",").map(Number) : undefined,
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
kernelMode: this.kernelMode,
};
}
};
Outbound.WireguardSettings.Peer = class extends CommonClass {
constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
super();
this.publicKey = publicKey;
this.psk = psk;
this.allowedIPs = allowedIPs;
this.endpoint = endpoint;
this.keepAlive = keepAlive;
}
static fromJson(json={}){
return new Outbound.WireguardSettings.Peer(
json.publicKey,
json.preSharedKey,
json.allowedIPs,
json.endpoint,
json.keepAlive
);
}
toJson() {
return {
publicKey: this.publicKey,
preSharedKey: this.psk.length>0 ? this.psk : undefined,
allowedIPs: this.allowedIPs ? this.allowedIPs : undefined,
endpoint: this.endpoint,
keepAlive: this.keepAlive?? undefined,
};
}
};

View File

@@ -24,18 +24,13 @@ class AllSetting {
this.subListen = ""; this.subListen = "";
this.subPort = "2096"; this.subPort = "2096";
this.subPath = "/sub/"; this.subPath = "/sub/";
this.subJsonPath = "/json/";
this.subDomain = ""; this.subDomain = "";
this.subCertFile = ""; this.subCertFile = "";
this.subKeyFile = ""; this.subKeyFile = "";
this.subUpdates = 0; this.subUpdates = 0;
this.subEncrypt = true; this.subEncrypt = true;
this.subShowInfo = false; this.subShowInfo = false;
this.subURI = ""; this.subURI = '';
this.subJsonURI = "";
this.subJsonFragment = "";
this.subJsonMux = "";
this.subJsonRules = "";
this.timeLocation = "Asia/Tehran"; this.timeLocation = "Asia/Tehran";

View File

@@ -6,7 +6,6 @@ const Protocols = {
DOKODEMO: 'dokodemo-door', DOKODEMO: 'dokodemo-door',
SOCKS: 'socks', SOCKS: 'socks',
HTTP: 'http', HTTP: 'http',
WIREGUARD: 'wireguard',
}; };
const SSMethods = { const SSMethods = {
@@ -206,6 +205,15 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
this.headers.push({ name: name, value: value }); this.headers.push({ name: name, value: value });
} }
getHeader(name) {
for (const header of this.headers) {
if (header.name.toLowerCase() === name.toLowerCase()) {
return header.value;
}
}
return null;
}
removeHeader(index) { removeHeader(index) {
this.headers.splice(index, 1); this.headers.splice(index, 1);
} }
@@ -221,7 +229,6 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
toJson() { toJson() {
return { return {
version: this.version,
method: this.method, method: this.method,
path: ObjectUtil.clone(this.path), path: ObjectUtil.clone(this.path),
headers: XrayCommonClass.toV2Headers(this.headers), headers: XrayCommonClass.toV2Headers(this.headers),
@@ -323,11 +330,10 @@ class KcpStreamSettings extends XrayCommonClass {
} }
class WsStreamSettings extends XrayCommonClass { class WsStreamSettings extends XrayCommonClass {
constructor(acceptProxyProtocol=false, path='/', host='', headers=[]) { constructor(acceptProxyProtocol=false, path='/', headers=[]) {
super(); super();
this.acceptProxyProtocol = acceptProxyProtocol; this.acceptProxyProtocol = acceptProxyProtocol;
this.path = path; this.path = path;
this.host = host;
this.headers = headers; this.headers = headers;
} }
@@ -335,6 +341,15 @@ class WsStreamSettings extends XrayCommonClass {
this.headers.push({ name: name, value: value }); this.headers.push({ name: name, value: value });
} }
getHeader(name) {
for (const header of this.headers) {
if (header.name.toLowerCase() === name.toLowerCase()) {
return header.value;
}
}
return null;
}
removeHeader(index) { removeHeader(index) {
this.headers.splice(index, 1); this.headers.splice(index, 1);
} }
@@ -343,7 +358,6 @@ class WsStreamSettings extends XrayCommonClass {
return new WsStreamSettings( return new WsStreamSettings(
json.acceptProxyProtocol, json.acceptProxyProtocol,
json.path, json.path,
json.host,
XrayCommonClass.toHeaders(json.headers), XrayCommonClass.toHeaders(json.headers),
); );
} }
@@ -352,7 +366,6 @@ class WsStreamSettings extends XrayCommonClass {
return { return {
acceptProxyProtocol: this.acceptProxyProtocol, acceptProxyProtocol: this.acceptProxyProtocol,
path: this.path, path: this.path,
host: this.host,
headers: XrayCommonClass.toV2Headers(this.headers, false), headers: XrayCommonClass.toV2Headers(this.headers, false),
}; };
} }
@@ -393,7 +406,7 @@ class HttpStreamSettings extends XrayCommonClass {
class QuicStreamSettings extends XrayCommonClass { class QuicStreamSettings extends XrayCommonClass {
constructor(security='none', constructor(security='none',
key=RandomUtil.randomSeq(10), type='none') { key='', type='none') {
super(); super();
this.security = security; this.security = security;
this.key = key; this.key = key;
@@ -420,62 +433,24 @@ class QuicStreamSettings extends XrayCommonClass {
} }
class GrpcStreamSettings extends XrayCommonClass { class GrpcStreamSettings extends XrayCommonClass {
constructor(serviceName='', authority='', multiMode=false) { constructor(serviceName="", multiMode=false) {
super(); super();
this.serviceName = serviceName; this.serviceName = serviceName;
this.authority = authority;
this.multiMode = multiMode; this.multiMode = multiMode;
} }
static fromJson(json={}) { static fromJson(json={}) {
return new GrpcStreamSettings(json.serviceName, json.authority, json.multiMode); return new GrpcStreamSettings(json.serviceName, json.multiMode);
} }
toJson() { toJson() {
return { return {
serviceName: this.serviceName, serviceName: this.serviceName,
authority: this.authority,
multiMode: this.multiMode, multiMode: this.multiMode,
} }
} }
} }
class HttpUpgradeStreamSettings extends XrayCommonClass {
constructor(acceptProxyProtocol=false, path='/', host='', headers=[]) {
super();
this.acceptProxyProtocol = acceptProxyProtocol;
this.path = path;
this.host = host;
this.headers = headers;
}
addHeader(name, value) {
this.headers.push({ name: name, value: value });
}
removeHeader(index) {
this.headers.splice(index, 1);
}
static fromJson(json={}) {
return new HttpUpgradeStreamSettings(
json.acceptProxyProtocol,
json.path,
json.host,
XrayCommonClass.toHeaders(json.headers),
);
}
toJson() {
return {
acceptProxyProtocol: this.acceptProxyProtocol,
path: this.path,
host: this.host,
headers: XrayCommonClass.toV2Headers(this.headers, false),
};
}
}
class TlsStreamSettings extends XrayCommonClass { class TlsStreamSettings extends XrayCommonClass {
constructor(serverName='', constructor(serverName='',
minVersion = TLS_VERSION_OPTION.TLS12, minVersion = TLS_VERSION_OPTION.TLS12,
@@ -483,7 +458,7 @@ class TlsStreamSettings extends XrayCommonClass {
cipherSuites = '', cipherSuites = '',
rejectUnknownSni = false, rejectUnknownSni = false,
certificates=[new TlsStreamSettings.Cert()], certificates=[new TlsStreamSettings.Cert()],
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1], alpn=[],
settings=new TlsStreamSettings.Settings()) { settings=new TlsStreamSettings.Settings()) {
super(); super();
this.sni = serverName; this.sni = serverName;
@@ -728,7 +703,6 @@ class StreamSettings extends XrayCommonClass {
httpSettings=new HttpStreamSettings(), httpSettings=new HttpStreamSettings(),
quicSettings=new QuicStreamSettings(), quicSettings=new QuicStreamSettings(),
grpcSettings=new GrpcStreamSettings(), grpcSettings=new GrpcStreamSettings(),
httpupgradeSettings=new HttpUpgradeStreamSettings(),
sockopt = undefined, sockopt = undefined,
) { ) {
super(); super();
@@ -743,7 +717,6 @@ class StreamSettings extends XrayCommonClass {
this.http = httpSettings; this.http = httpSettings;
this.quic = quicSettings; this.quic = quicSettings;
this.grpc = grpcSettings; this.grpc = grpcSettings;
this.httpupgrade = httpupgradeSettings;
this.sockopt = sockopt; this.sockopt = sockopt;
} }
@@ -792,7 +765,6 @@ class StreamSettings extends XrayCommonClass {
HttpStreamSettings.fromJson(json.httpSettings), HttpStreamSettings.fromJson(json.httpSettings),
QuicStreamSettings.fromJson(json.quicSettings), QuicStreamSettings.fromJson(json.quicSettings),
GrpcStreamSettings.fromJson(json.grpcSettings), GrpcStreamSettings.fromJson(json.grpcSettings),
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
SockoptStreamSettings.fromJson(json.sockopt), SockoptStreamSettings.fromJson(json.sockopt),
); );
} }
@@ -811,7 +783,6 @@ class StreamSettings extends XrayCommonClass {
httpSettings: network === 'http' ? this.http.toJson() : undefined, httpSettings: network === 'http' ? this.http.toJson() : undefined,
quicSettings: network === 'quic' ? this.quic.toJson() : undefined, quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined, grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined, sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
}; };
} }
@@ -841,7 +812,7 @@ class Sniffing extends XrayCommonClass {
class Inbound extends XrayCommonClass { class Inbound extends XrayCommonClass {
constructor(port=RandomUtil.randomIntRange(10000, 60000), constructor(port=RandomUtil.randomIntRange(10000, 60000),
listen='', listen='',
protocol=Protocols.VLESS, protocol=Protocols.VMESS,
settings=null, settings=null,
streamSettings=new StreamSettings(), streamSettings=new StreamSettings(),
tag='', tag='',
@@ -916,17 +887,13 @@ class Inbound extends XrayCommonClass {
return this.network === "http"; return this.network === "http";
} }
get isHttpupgrade() {
return this.network === "httpupgrade";
}
// Shadowsocks // Shadowsocks
get method() { get method() {
switch (this.protocol) { switch (this.protocol) {
case Protocols.SHADOWSOCKS: case Protocols.SHADOWSOCKS:
return this.settings.method; return this.settings.method;
default: default:
return ''; return "";
} }
} }
get isSSMultiUser() { get isSSMultiUser() {
@@ -939,27 +906,16 @@ class Inbound extends XrayCommonClass {
get serverName() { get serverName() {
if (this.stream.isTls) return this.stream.tls.sni; if (this.stream.isTls) return this.stream.tls.sni;
if (this.stream.isReality) return this.stream.reality.serverNames; if (this.stream.isReality) return this.stream.reality.serverNames;
return ''; return "";
}
getHeader(obj, name) {
for (const header of obj.headers) {
if (header.name.toLowerCase() === name.toLowerCase()) {
return header.value;
}
}
return null;
} }
get host() { get host() {
if (this.isTcp) { if (this.isTcp) {
return this.getHeader(this.stream.tcp.request, 'host'); return this.stream.tcp.request.getHeader("Host");
} else if (this.isWs) {
return this.stream.ws.getHeader("Host");
} else if (this.isH2) { } else if (this.isH2) {
return this.stream.http.host[0]; return this.stream.http.host[0];
} else if (this.isWs) {
return this.stream.ws.host?.length>0 ? this.stream.ws.host : this.getHeader(this.stream.ws, 'host');
} else if (this.isHttpupgrade) {
return this.stream.httpupgrade.host;
} }
return null; return null;
} }
@@ -971,8 +927,6 @@ class Inbound extends XrayCommonClass {
return this.stream.ws.path; return this.stream.ws.path;
} else if (this.isH2) { } else if (this.isH2) {
return this.stream.http.path; return this.stream.http.path;
} else if (this.isHttpupgrade) {
return this.stream.httpupgrade.path;
} }
return null; return null;
} }
@@ -1008,7 +962,7 @@ class Inbound extends XrayCommonClass {
canEnableTls() { canEnableTls() {
if(![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false; if(![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade"].includes(this.network); return ["tcp", "ws", "http", "quic", "grpc"].includes(this.network);
} }
//this is used for xtls-rprx-vision //this is used for xtls-rprx-vision
@@ -1028,6 +982,10 @@ class Inbound extends XrayCommonClass {
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol); return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
} }
canSniffing() {
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
}
reset() { reset() {
this.port = RandomUtil.randomIntRange(10000, 60000); this.port = RandomUtil.randomIntRange(10000, 60000);
this.listen = ''; this.listen = '';
@@ -1053,24 +1011,29 @@ class Inbound extends XrayCommonClass {
type: 'none', type: 'none',
tls: security, tls: security,
}; };
const network = this.stream.network; let network = this.stream.network;
if (network === 'tcp') { if (network === 'tcp') {
const tcp = this.stream.tcp; let tcp = this.stream.tcp;
obj.type = tcp.type; obj.type = tcp.type;
if (tcp.type === 'http') { if (tcp.type === 'http') {
const request = tcp.request; let request = tcp.request;
obj.path = request.path.join(','); obj.path = request.path.join(',');
const host = this.getHeader(request,'host'); let index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
if (host) obj.host = host; if (index >= 0) {
obj.host = request.headers[index].value;
}
} }
} else if (network === 'kcp') { } else if (network === 'kcp') {
const kcp = this.stream.kcp; let kcp = this.stream.kcp;
obj.type = kcp.type; obj.type = kcp.type;
obj.path = kcp.seed; obj.path = kcp.seed;
} else if (network === 'ws') { } else if (network === 'ws') {
const ws = this.stream.ws; let ws = this.stream.ws;
obj.path = ws.path; obj.path = ws.path;
obj.host = ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host'); let index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
if (index >= 0) {
obj.host = ws.headers[index].value;
}
} else if (network === 'http') { } else if (network === 'http') {
obj.net = 'h2'; obj.net = 'h2';
obj.path = this.stream.http.path; obj.path = this.stream.http.path;
@@ -1081,14 +1044,9 @@ class Inbound extends XrayCommonClass {
obj.path = this.stream.quic.key; obj.path = this.stream.quic.key;
} else if (network === 'grpc') { } else if (network === 'grpc') {
obj.path = this.stream.grpc.serviceName; obj.path = this.stream.grpc.serviceName;
obj.authority = this.stream.grpc.authority;
if (this.stream.grpc.multiMode){ if (this.stream.grpc.multiMode){
obj.type = 'multi' obj.type = 'multi'
} }
} else if (network === 'httpupgrade') {
const httpupgrade = this.stream.httpupgrade;
obj.path = httpupgrade.path;
obj.host = httpupgrade.host;
} }
if (security === 'tls') { if (security === 'tls') {
@@ -1137,7 +1095,11 @@ class Inbound extends XrayCommonClass {
case "ws": case "ws":
const ws = this.stream.ws; const ws = this.stream.ws;
params.set("path", ws.path); params.set("path", ws.path);
params.set("host", ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host')); const index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
if (index >= 0) {
const host = ws.headers[index].value;
params.set("host", host);
}
break; break;
case "http": case "http":
const http = this.stream.http; const http = this.stream.http;
@@ -1153,16 +1115,10 @@ class Inbound extends XrayCommonClass {
case "grpc": case "grpc":
const grpc = this.stream.grpc; const grpc = this.stream.grpc;
params.set("serviceName", grpc.serviceName); params.set("serviceName", grpc.serviceName);
params.set("authority", grpc.authority);
if(grpc.multiMode){ if(grpc.multiMode){
params.set("mode", "multi"); params.set("mode", "multi");
} }
break; break;
case "httpupgrade":
const httpupgrade = this.stream.httpupgrade;
params.set("path", httpupgrade.path);
params.set("host", httpupgrade.host);
break;
} }
if (security === 'tls') { if (security === 'tls') {
@@ -1182,7 +1138,7 @@ class Inbound extends XrayCommonClass {
} }
} }
else if (security === 'reality') { if (security === 'reality') {
params.set("security", "reality"); params.set("security", "reality");
params.set("pbk", this.stream.reality.settings.publicKey); params.set("pbk", this.stream.reality.settings.publicKey);
params.set("fp", this.stream.reality.settings.fingerprint); params.set("fp", this.stream.reality.settings.fingerprint);
@@ -1200,10 +1156,6 @@ 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) {
@@ -1241,7 +1193,11 @@ class Inbound extends XrayCommonClass {
case "ws": case "ws":
const ws = this.stream.ws; const ws = this.stream.ws;
params.set("path", ws.path); params.set("path", ws.path);
params.set("host", ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host')); const index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
if (index >= 0) {
const host = ws.headers[index].value;
params.set("host", host);
}
break; break;
case "http": case "http":
const http = this.stream.http; const http = this.stream.http;
@@ -1257,16 +1213,10 @@ class Inbound extends XrayCommonClass {
case "grpc": case "grpc":
const grpc = this.stream.grpc; const grpc = this.stream.grpc;
params.set("serviceName", grpc.serviceName); params.set("serviceName", grpc.serviceName);
params.set("authority", grpc.authority);
if(grpc.multiMode){ if(grpc.multiMode){
params.set("mode", "multi"); params.set("mode", "multi");
} }
break; break;
case "httpupgrade":
const httpupgrade = this.stream.httpupgrade;
params.set("path", httpupgrade.path);
params.set("host", httpupgrade.host);
break;
} }
if (security === 'tls') { if (security === 'tls') {
@@ -1324,7 +1274,11 @@ class Inbound extends XrayCommonClass {
case "ws": case "ws":
const ws = this.stream.ws; const ws = this.stream.ws;
params.set("path", ws.path); params.set("path", ws.path);
params.set("host", ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host')); const index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
if (index >= 0) {
const host = ws.headers[index].value;
params.set("host", host);
}
break; break;
case "http": case "http":
const http = this.stream.http; const http = this.stream.http;
@@ -1340,16 +1294,10 @@ class Inbound extends XrayCommonClass {
case "grpc": case "grpc":
const grpc = this.stream.grpc; const grpc = this.stream.grpc;
params.set("serviceName", grpc.serviceName); params.set("serviceName", grpc.serviceName);
params.set("authority", grpc.authority);
if(grpc.multiMode){ if(grpc.multiMode){
params.set("mode", "multi"); params.set("mode", "multi");
} }
break; break;
case "httpupgrade":
const httpupgrade = this.stream.httpupgrade;
params.set("path", httpupgrade.path);
params.set("host", httpupgrade.host);
break;
} }
if (security === 'tls') { if (security === 'tls') {
@@ -1366,7 +1314,7 @@ class Inbound extends XrayCommonClass {
} }
} }
else if (security === 'reality') { if (security === 'reality') {
params.set("security", "reality"); params.set("security", "reality");
params.set("pbk", this.stream.reality.settings.publicKey); params.set("pbk", this.stream.reality.settings.publicKey);
params.set("fp", this.stream.reality.settings.fingerprint); params.set("fp", this.stream.reality.settings.fingerprint);
@@ -1380,9 +1328,6 @@ class Inbound extends XrayCommonClass {
params.set("spx", this.stream.reality.settings.spiderX); params.set("spx", this.stream.reality.settings.spiderX);
} }
} }
else {
params.set("security", "none");
}
const link = `trojan://${clientPassword}@${address}:${port}`; const link = `trojan://${clientPassword}@${address}:${port}`;
const url = new URL(link); const url = new URL(link);
@@ -1393,28 +1338,6 @@ class Inbound extends XrayCommonClass {
return url.toString(); return url.toString();
} }
getWireguardLink(address, port, remark, peerId) {
let txt = `[Interface]\n`
txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n`
txt += `Address = ${this.settings.peers[peerId].allowedIPs[0]}\n`
txt += `DNS = 1.1.1.1, 9.9.9.9\n`
if (this.settings.mtu) {
txt += `MTU = ${this.settings.mtu}\n`
}
txt += `\n# ${remark}\n`
txt += `[Peer]\n`
txt += `PublicKey = ${this.settings.pubKey}\n`
txt += `AllowedIPs = 0.0.0.0/0, ::/0\n`
txt += `Endpoint = ${address}:${port}`
if (this.settings.peers[peerId].psk) {
txt += `\nPresharedKey = ${this.settings.peers[peerId].psk}`
}
if (this.settings.peers[peerId].keepAlive) {
txt += `\nPersistentKeepalive = ${this.settings.peers[peerId].keepAlive}\n`
}
return txt;
}
genLink(address='', port=this.port, forceTls='same', remark='', client) { genLink(address='', port=this.port, forceTls='same', remark='', client) {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VMESS: case Protocols.VMESS:
@@ -1438,7 +1361,7 @@ class Inbound extends XrayCommonClass {
const orderChars = remarkModel.slice(1); const orderChars = remarkModel.slice(1);
let orders = { let orders = {
'i': remark, 'i': remark,
'e': email, 'e': client ? client.email : '',
'o': '', 'o': '',
}; };
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){ if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
@@ -1461,7 +1384,6 @@ class Inbound extends XrayCommonClass {
} }
genInboundLinks(remark = '', remarkModel = '-ieo') { genInboundLinks(remark = '', remarkModel = '-ieo') {
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
if(this.clients){ if(this.clients){
let links = []; let links = [];
this.clients.forEach((client) => { this.clients.forEach((client) => {
@@ -1471,14 +1393,7 @@ class Inbound extends XrayCommonClass {
}); });
return links.join('\r\n'); return links.join('\r\n');
} else { } else {
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(addr, this.port, 'same', remark); if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(this.listen, this.port, 'same', remark);
if(this.protocol == Protocols.WIREGUARD) {
let links = [];
this.settings.peers.forEach((p,index) => {
links.push(this.getWireguardLink(addr,this.port,remark + remarkModel.charAt(0) + (index+1),index));
});
return links.join('\r\n');
}
return ''; return '';
} }
} }
@@ -1529,7 +1444,6 @@ Inbound.Settings = class extends XrayCommonClass {
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol); case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(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);
case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol);
default: return null; default: return null;
} }
} }
@@ -1543,7 +1457,6 @@ Inbound.Settings = class extends XrayCommonClass {
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json); case Protocols.DOKODEMO: return Inbound.DokodemoSettings.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);
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
default: return null; default: return null;
} }
} }
@@ -1617,7 +1530,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
); );
} }
get _expiryTime() { get _expiryTime() {
if (this.expiryTime === 0 || this.expiryTime === '') { if (this.expiryTime === 0 || this.expiryTime === "") {
return null; return null;
} }
if (this.expiryTime < 0){ if (this.expiryTime < 0){
@@ -1627,7 +1540,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
} }
set _expiryTime(t) { set _expiryTime(t) {
if (t == null || t === '') { if (t == null || t === "") {
this.expiryTime = 0; this.expiryTime = 0;
} else { } else {
this.expiryTime = t.valueOf(); this.expiryTime = t.valueOf();
@@ -1710,7 +1623,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
} }
get _expiryTime() { get _expiryTime() {
if (this.expiryTime === 0 || this.expiryTime === '') { if (this.expiryTime === 0 || this.expiryTime === "") {
return null; return null;
} }
if (this.expiryTime < 0){ if (this.expiryTime < 0){
@@ -1720,7 +1633,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
} }
set _expiryTime(t) { set _expiryTime(t) {
if (t == null || t === '') { if (t == null || t === "") {
this.expiryTime = 0; this.expiryTime = 0;
} else { } else {
this.expiryTime = t.valueOf(); this.expiryTime = t.valueOf();
@@ -1735,7 +1648,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
} }
}; };
Inbound.VLESSSettings.Fallback = class extends XrayCommonClass { Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
constructor(name='', alpn='', path='', dest='', xver=0) { constructor(name="", alpn='', path='', dest='', xver=0) {
super(); super();
this.name = name; this.name = name;
this.alpn = alpn; this.alpn = alpn;
@@ -1844,7 +1757,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
} }
get _expiryTime() { get _expiryTime() {
if (this.expiryTime === 0 || this.expiryTime === '') { if (this.expiryTime === 0 || this.expiryTime === "") {
return null; return null;
} }
if (this.expiryTime < 0){ if (this.expiryTime < 0){
@@ -1854,7 +1767,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
} }
set _expiryTime(t) { set _expiryTime(t) {
if (t == null || t === '') { if (t == null || t === "") {
this.expiryTime = 0; this.expiryTime = 0;
} else { } else {
this.expiryTime = t.valueOf(); this.expiryTime = t.valueOf();
@@ -1871,7 +1784,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
}; };
Inbound.TrojanSettings.Fallback = class extends XrayCommonClass { Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
constructor(name='', alpn='', path='', dest='', xver=0) { constructor(name="", alpn='', path='', dest='', xver=0) {
super(); super();
this.name = name; this.name = name;
this.alpn = alpn; this.alpn = alpn;
@@ -1986,7 +1899,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
} }
get _expiryTime() { get _expiryTime() {
if (this.expiryTime === 0 || this.expiryTime === '') { if (this.expiryTime === 0 || this.expiryTime === "") {
return null; return null;
} }
if (this.expiryTime < 0){ if (this.expiryTime < 0){
@@ -1996,7 +1909,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
} }
set _expiryTime(t) { set _expiryTime(t) {
if (t == null || t === '') { if (t == null || t === "") {
this.expiryTime = 0; this.expiryTime = 0;
} else { } else {
this.expiryTime = t.valueOf(); this.expiryTime = t.valueOf();
@@ -2134,81 +2047,3 @@ Inbound.HttpSettings.HttpAccount = class extends XrayCommonClass {
return new Inbound.HttpSettings.HttpAccount(json.user, json.pass); return new Inbound.HttpSettings.HttpAccount(json.user, json.pass);
} }
}; };
Inbound.WireguardSettings = class extends XrayCommonClass {
constructor(protocol, mtu=1420, secretKey=Wireguard.generateKeypair().privateKey, peers=[new Inbound.WireguardSettings.Peer()], kernelMode=false) {
super(protocol);
this.mtu = mtu;
this.secretKey = secretKey;
this.pubKey = secretKey.length>0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
this.peers = peers;
this.kernelMode = kernelMode;
}
addPeer() {
this.peers.push(new Inbound.WireguardSettings.Peer(null,null,'',['10.0.0.' + (this.peers.length+2)]));
}
delPeer(index) {
this.peers.splice(index, 1);
}
static fromJson(json={}){
return new Inbound.WireguardSettings(
Protocols.WIREGUARD,
json.mtu,
json.secretKey,
json.peers.map(peer => Inbound.WireguardSettings.Peer.fromJson(peer)),
json.kernelMode,
);
}
toJson() {
return {
mtu: this.mtu?? undefined,
secretKey: this.secretKey,
peers: Inbound.WireguardSettings.Peer.toJsonArray(this.peers),
kernelMode: this.kernelMode,
};
}
};
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.2/32'], keepAlive=0) {
super();
this.privateKey = privateKey
this.publicKey = publicKey;
if (!this.publicKey){
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
}
this.psk = psk;
allowedIPs.forEach((a,index) => {
if (a.length>0 && !a.includes('/')) allowedIPs[index] += '/32';
})
this.allowedIPs = allowedIPs;
this.keepAlive = keepAlive;
}
static fromJson(json={}){
return new Inbound.WireguardSettings.Peer(
json.privateKey,
json.publicKey,
json.preSharedKey,
json.allowedIPs,
json.keepAlive
);
}
toJson() {
this.allowedIPs.forEach((a,index) => {
if (a.length>0 && !a.includes('/')) this.allowedIPs[index] += '/32';
});
return {
privateKey: this.privateKey,
publicKey: this.publicKey,
preSharedKey: this.psk.length>0 ? this.psk : undefined,
allowedIPs: this.allowedIPs,
keepAlive: this.keepAlive?? undefined,
};
}
};

View File

@@ -20,14 +20,6 @@ function sizeFormat(size) {
} }
} }
function cpuCoreFormat(cores) {
if (cores === 1) {
return "1 Core";
} else {
return cores + " Cores";
}
}
function base64(str) { function base64(str) {
return Base64.encode(str); return Base64.encode(str);
} }

View File

@@ -136,9 +136,9 @@ class RandomUtil {
} }
static randomShortId() { static randomShortId() {
let shortIds = new Array(24).fill(''); let shortIds = ['','','',''];
for (var ii = 0; ii < 24; ii++) { for (var ii = 0; ii < 4; ii++) {
for (var jj = 0; jj < this.randomInt(16); jj++){ for (var jj = 0; jj < this.randomInt(8); jj++){
let randomNum = this.randomInt(256); let randomNum = this.randomInt(256);
shortIds[ii] += ('0' + randomNum.toString(16)).slice(-2) shortIds[ii] += ('0' + randomNum.toString(16)).slice(-2)
} }
@@ -296,190 +296,3 @@ class ObjectUtil {
return true; return true;
} }
} }
class Wireguard {
static gf(init) {
var r = new Float64Array(16);
if (init) {
for (var i = 0; i < init.length; ++i)
r[i] = init[i];
}
return r;
}
static pack(o, n) {
var b, m = this.gf(), t = this.gf();
for (var i = 0; i < 16; ++i)
t[i] = n[i];
this.carry(t);
this.carry(t);
this.carry(t);
for (var j = 0; j < 2; ++j) {
m[0] = t[0] - 0xffed;
for (var i = 1; i < 15; ++i) {
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
m[i - 1] &= 0xffff;
}
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
b = (m[15] >> 16) & 1;
m[14] &= 0xffff;
this.cswap(t, m, 1 - b);
}
for (var i = 0; i < 16; ++i) {
o[2 * i] = t[i] & 0xff;
o[2 * i + 1] = t[i] >> 8;
}
}
static carry(o) {
var c;
for (var i = 0; i < 16; ++i) {
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536);
o[i] &= 0xffff;
}
}
static cswap(p, q, b) {
var t, c = ~(b - 1);
for (var i = 0; i < 16; ++i) {
t = c & (p[i] ^ q[i]);
p[i] ^= t;
q[i] ^= t;
}
}
static add(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] + b[i]) | 0;
}
static subtract(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] - b[i]) | 0;
}
static multmod(o, a, b) {
var t = new Float64Array(31);
for (var i = 0; i < 16; ++i) {
for (var j = 0; j < 16; ++j)
t[i + j] += a[i] * b[j];
}
for (var i = 0; i < 15; ++i)
t[i] += 38 * t[i + 16];
for (var i = 0; i < 16; ++i)
o[i] = t[i];
this.carry(o);
this.carry(o);
}
static invert(o, i) {
var c = this.gf();
for (var a = 0; a < 16; ++a)
c[a] = i[a];
for (var a = 253; a >= 0; --a) {
this.multmod(c, c, c);
if (a !== 2 && a !== 4)
this.multmod(c, c, i);
}
for (var a = 0; a < 16; ++a)
o[a] = c[a];
}
static clamp(z) {
z[31] = (z[31] & 127) | 64;
z[0] &= 248;
}
static generatePublicKey(privateKey) {
var r, z = new Uint8Array(32);
var a = this.gf([1]),
b = this.gf([9]),
c = this.gf(),
d = this.gf([1]),
e = this.gf(),
f = this.gf(),
_121665 = this.gf([0xdb41, 1]),
_9 = this.gf([9]);
for (var i = 0; i < 32; ++i)
z[i] = privateKey[i];
this.clamp(z);
for (var i = 254; i >= 0; --i) {
r = (z[i >>> 3] >>> (i & 7)) & 1;
this.cswap(a, b, r);
this.cswap(c, d, r);
this.add(e, a, c);
this.subtract(a, a, c);
this.add(c, b, d);
this.subtract(b, b, d);
this.multmod(d, e, e);
this.multmod(f, a, a);
this.multmod(a, c, a);
this.multmod(c, b, e);
this.add(e, a, c);
this.subtract(a, a, c);
this.multmod(b, a, a);
this.subtract(c, d, f);
this.multmod(a, c, _121665);
this.add(a, a, d);
this.multmod(c, c, a);
this.multmod(a, d, f);
this.multmod(d, b, _9);
this.multmod(b, e, e);
this.cswap(a, b, r);
this.cswap(c, d, r);
}
this.invert(c, c);
this.multmod(a, a, c);
this.pack(z, a);
return z;
}
static generatePresharedKey() {
var privateKey = new Uint8Array(32);
window.crypto.getRandomValues(privateKey);
return privateKey;
}
static generatePrivateKey() {
var privateKey = this.generatePresharedKey();
this.clamp(privateKey);
return privateKey;
}
static encodeBase64(dest, src) {
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]);
for (var i = 0; i < 4; ++i)
dest[i] = input[i] + 65 +
(((25 - input[i]) >> 8) & 6) -
(((51 - input[i]) >> 8) & 75) -
(((61 - input[i]) >> 8) & 15) +
(((62 - input[i]) >> 8) & 3);
}
static keyToBase64(key) {
var i, base64 = new Uint8Array(44);
for (i = 0; i < 32 / 3; ++i)
this.encodeBase64(base64.subarray(i * 4), key.subarray(i * 3));
this.encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]));
base64[43] = 61;
return String.fromCharCode.apply(null, base64);
}
static keyFromBase64(encoded) {
const binaryStr = atob(encoded);
const bytes = new Uint8Array(binaryStr.length);
for (let i = 0; i < binaryStr.length; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
return bytes;
}
static generateKeypair(secretKey='') {
var privateKey = secretKey.length>0 ? this.keyFromBase64(secretKey) : this.generatePrivateKey();
var publicKey = this.generatePublicKey(privateKey);
return {
publicKey: this.keyToBase64(publicKey),
privateKey: secretKey.length>0 ? secretKey : this.keyToBase64(privateKey)
};
}
}

View File

@@ -22,35 +22,81 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
g = g.Group("/xui/API/inbounds") g = g.Group("/xui/API/inbounds")
g.Use(a.checkLogin) g.Use(a.checkLogin)
g.GET("/", a.inbounds)
g.GET("/get/:id", a.inbound)
g.GET("/getClientTraffics/:email", a.getClientTraffics)
g.POST("/add", a.addInbound)
g.POST("/del/:id", a.delInbound)
g.POST("/update/:id", a.updateInbound)
g.POST("/addClient", a.addInboundClient)
g.POST("/:id/delClient/:clientId", a.delInboundClient)
g.POST("/updateClient/:clientId", a.updateInboundClient)
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
g.POST("/resetAllTraffics", a.resetAllTraffics)
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
g.POST("/delDepletedClients/:id", a.delDepletedClients)
g.GET("/createbackup", a.createBackup)
g.POST("/onlines", a.onlines)
a.inboundController = NewInboundController(g) a.inboundController = NewInboundController(g)
}
inboundRoutes := []struct { func (a *APIController) inbounds(c *gin.Context) {
Method string a.inboundController.getInbounds(c)
Path string }
Handler gin.HandlerFunc
}{
{"GET", "/createbackup", a.createBackup},
{"GET", "/", a.inboundController.getInbounds},
{"GET", "/get/:id", a.inboundController.getInbound},
{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics},
{"POST", "/add", a.inboundController.addInbound},
{"POST", "/del/:id", a.inboundController.delInbound},
{"POST", "/update/:id", a.inboundController.updateInbound},
{"POST", "/addClient", a.inboundController.addInboundClient},
{"POST", "/:id/delClient/:clientId", a.inboundController.delInboundClient},
{"POST", "/updateClient/:clientId", a.inboundController.updateInboundClient},
{"POST", "/:id/resetClientTraffic/:email", a.inboundController.resetClientTraffic},
{"POST", "/resetAllTraffics", a.inboundController.resetAllTraffics},
{"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics},
{"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients},
{"POST", "/onlines", a.inboundController.onlines},
}
for _, route := range inboundRoutes { func (a *APIController) inbound(c *gin.Context) {
g.Handle(route.Method, route.Path, route.Handler) a.inboundController.getInbound(c)
} }
func (a *APIController) getClientTraffics(c *gin.Context) {
a.inboundController.getClientTraffics(c)
}
func (a *APIController) addInbound(c *gin.Context) {
a.inboundController.addInbound(c)
}
func (a *APIController) delInbound(c *gin.Context) {
a.inboundController.delInbound(c)
}
func (a *APIController) updateInbound(c *gin.Context) {
a.inboundController.updateInbound(c)
}
func (a *APIController) addInboundClient(c *gin.Context) {
a.inboundController.addInboundClient(c)
}
func (a *APIController) delInboundClient(c *gin.Context) {
a.inboundController.delInboundClient(c)
}
func (a *APIController) updateInboundClient(c *gin.Context) {
a.inboundController.updateInboundClient(c)
}
func (a *APIController) resetClientTraffic(c *gin.Context) {
a.inboundController.resetClientTraffic(c)
}
func (a *APIController) resetAllTraffics(c *gin.Context) {
a.inboundController.resetAllTraffics(c)
}
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
a.inboundController.resetAllClientTraffics(c)
}
func (a *APIController) delDepletedClients(c *gin.Context) {
a.inboundController.delDepletedClients(c)
} }
func (a *APIController) createBackup(c *gin.Context) { func (a *APIController) createBackup(c *gin.Context) {
a.Tgbot.SendBackupToAdmins() a.Tgbot.SendBackupToAdmins()
} }
func (a *APIController) onlines(c *gin.Context) {
a.inboundController.onlines(c)
}

View File

@@ -2,7 +2,6 @@ package controller
import ( import (
"net/http" "net/http"
"x-ui/logger" "x-ui/logger"
"x-ui/web/locale" "x-ui/web/locale"
"x-ui/web/session" "x-ui/web/session"
@@ -10,12 +9,13 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type BaseController struct{} type BaseController struct {
}
func (a *BaseController) checkLogin(c *gin.Context) { func (a *BaseController) checkLogin(c *gin.Context) {
if !session.IsLogin(c) { if !session.IsLogin(c) {
if isAjax(c) { if isAjax(c) {
pureJsonMsg(c, http.StatusUnauthorized, false, I18nWeb(c, "pages.login.loginAgain")) pureJsonMsg(c, false, I18nWeb(c, "pages.login.loginAgain"))
} else { } else {
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
} }

View File

@@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"x-ui/database/model" "x-ui/database/model"
"x-ui/web/service" "x-ui/web/service"
"x-ui/web/session" "x-ui/web/session"
@@ -64,7 +63,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)
@@ -84,11 +82,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
} }
user := session.GetLoginUser(c) user := session.GetLoginUser(c)
inbound.UserId = user.Id inbound.UserId = user.Id
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
} else {
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
}
needRestart := false needRestart := false
inbound, needRestart, err = a.inboundService.AddInbound(inbound) inbound, needRestart, err = a.inboundService.AddInbound(inbound)
@@ -150,7 +144,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
return return
} }
jsonMsg(c, "Client(s) added", nil) jsonMsg(c, "Client(s) added", nil)
if needRestart { if err == nil && needRestart {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }
} }
@@ -171,7 +165,7 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
return return
} }
jsonMsg(c, "Client deleted", nil) jsonMsg(c, "Client deleted", nil)
if needRestart { if err == nil && needRestart {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }
} }
@@ -194,7 +188,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
return return
} }
jsonMsg(c, "Client updated", nil) jsonMsg(c, "Client updated", nil)
if needRestart { if err == nil && needRestart {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }
} }
@@ -215,7 +209,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
return return
} }
jsonMsg(c, "traffic reseted", nil) jsonMsg(c, "traffic reseted", nil)
if needRestart { if err == nil && needRestart {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }
} }
@@ -272,11 +266,7 @@ func (a *InboundController) importInbound(c *gin.Context) {
user := session.GetLoginUser(c) user := session.GetLoginUser(c)
inbound.Id = 0 inbound.Id = 0
inbound.UserId = user.Id inbound.UserId = user.Id
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
} else {
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
}
for index := range inbound.ClientStats { for index := range inbound.ClientStats {
inbound.ClientStats[index].Id = 0 inbound.ClientStats[index].Id = 0

View File

@@ -3,7 +3,6 @@ package controller
import ( import (
"net/http" "net/http"
"time" "time"
"x-ui/logger" "x-ui/logger"
"x-ui/web/service" "x-ui/web/service"
"x-ui/web/session" "x-ui/web/session"
@@ -48,15 +47,15 @@ func (a *IndexController) login(c *gin.Context) {
var form LoginForm var form LoginForm
err := c.ShouldBind(&form) err := c.ShouldBind(&form)
if err != nil { if err != nil {
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.invalidFormData")) pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
return return
} }
if form.Username == "" { if form.Username == "" {
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyUsername")) pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
return return
} }
if form.Password == "" { if form.Password == "" {
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyPassword")) pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
return return
} }
@@ -65,7 +64,7 @@ func (a *IndexController) login(c *gin.Context) {
if user == nil { if user == nil {
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password) logger.Infof("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, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword")) pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
return return
} else { } else {
logger.Infof("%s login success ,Ip Address: %s\n", form.Username, getRemoteIp(c)) logger.Infof("%s login success ,Ip Address: %s\n", form.Username, getRemoteIp(c))

View File

@@ -5,7 +5,6 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"time" "time"
"x-ui/web/global" "x-ui/web/global"
"x-ui/web/service" "x-ui/web/service"

View File

@@ -3,7 +3,6 @@ package controller
import ( import (
"errors" "errors"
"time" "time"
"x-ui/web/entity" "x-ui/web/entity"
"x-ui/web/service" "x-ui/web/service"
"x-ui/web/session" "x-ui/web/session"

View File

@@ -4,7 +4,6 @@ import (
"net" "net"
"net/http" "net/http"
"strings" "strings"
"x-ui/config" "x-ui/config"
"x-ui/logger" "x-ui/logger"
"x-ui/web/entity" "x-ui/web/entity"
@@ -49,11 +48,18 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
c.JSON(http.StatusOK, m) c.JSON(http.StatusOK, m)
} }
func pureJsonMsg(c *gin.Context, statusCode int, success bool, msg string) { func pureJsonMsg(c *gin.Context, success bool, msg string) {
c.JSON(statusCode, entity.Msg{ if success {
Success: success, c.JSON(http.StatusOK, entity.Msg{
Msg: msg, Success: true,
}) Msg: msg,
})
} else {
c.JSON(http.StatusOK, entity.Msg{
Success: false,
Msg: msg,
})
}
} }
func html(c *gin.Context, name string, title string, data gin.H) { func html(c *gin.Context, name string, title string, data gin.H) {

View File

@@ -26,7 +26,6 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
g.POST("/update", a.updateSetting) g.POST("/update", a.updateSetting)
g.GET("/getXrayResult", a.getXrayResult) g.GET("/getXrayResult", a.getXrayResult)
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig) g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
g.POST("/warp/:action", a.warp)
} }
func (a *XraySettingController) getXraySetting(c *gin.Context) { func (a *XraySettingController) getXraySetting(c *gin.Context) {
@@ -62,24 +61,3 @@ func (a *XraySettingController) getDefaultXrayConfig(c *gin.Context) {
func (a *XraySettingController) getXrayResult(c *gin.Context) { func (a *XraySettingController) getXrayResult(c *gin.Context) {
jsonObj(c, a.XrayService.GetXrayResult(), nil) jsonObj(c, a.XrayService.GetXrayResult(), nil)
} }
func (a *XraySettingController) warp(c *gin.Context) {
action := c.Param("action")
var resp string
var err error
switch action {
case "data":
resp, err = a.XraySettingService.GetWarp()
case "config":
resp, err = a.XraySettingService.GetWarpConfig()
case "reg":
skey := c.PostForm("privateKey")
pkey := c.PostForm("publicKey")
resp, err = a.XraySettingService.RegWarp(skey, pkey)
case "license":
license := c.PostForm("license")
resp, err = a.XraySettingService.SetWarpLicence(license)
}
jsonObj(c, resp, err)
}

View File

@@ -5,7 +5,6 @@ import (
"net" "net"
"strings" "strings"
"time" "time"
"x-ui/util/common" "x-ui/util/common"
) )
@@ -47,11 +46,6 @@ type AllSetting struct {
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
SubURI string `json:"subURI" form:"subURI"` SubURI string `json:"subURI" form:"subURI"`
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
} }
func (s *AllSetting) CheckValid() error { func (s *AllSetting) CheckValid() error {
@@ -109,13 +103,6 @@ func (s *AllSetting) CheckValid() error {
s.SubPath += "/" s.SubPath += "/"
} }
if !strings.HasPrefix(s.SubJsonPath, "/") {
s.SubJsonPath = "/" + s.SubJsonPath
}
if !strings.HasSuffix(s.SubJsonPath, "/") {
s.SubJsonPath += "/"
}
_, err := time.LoadLocation(s.TimeLocation) _, err := time.LoadLocation(s.TimeLocation)
if err != nil { if err != nil {
return common.NewError("time location not exist:", s.TimeLocation) return common.NewError("time location not exist:", s.TimeLocation)

View File

@@ -7,10 +7,8 @@ import (
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
) )
var ( var webServer WebServer
webServer WebServer var subServer SubServer
subServer SubServer
)
type WebServer interface { type WebServer interface {
GetCron() *cron.Cron GetCron() *cron.Cron

View File

@@ -7,6 +7,8 @@
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css"> <link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css"> <link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}"> <link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
<link rel=”icon” type=”image/x-icon” href="{{ .base_path }}assets/favicon.ico">
<link rel="shortcut icon" type="image/x-icon" href="{{ .base_path }}assets/favicon.ico">
<style> <style>
[v-cloak] { [v-cloak] {
display: none; display: none;
@@ -28,5 +30,4 @@
</style> </style>
<title>{{ .host }}-{{ i18n .title}}</title> <title>{{ .host }}-{{ i18n .title}}</title>
</head> </head>
<div id="message"></div>
{{end}} {{end}}

View File

@@ -1,7 +1,6 @@
{{define "promptModal"}} {{define "promptModal"}}
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title" <a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
:closable="true" @ok="promptModal.ok" :mask-closable="false" :closable="true" @ok="promptModal.ok" :mask-closable="false"
:confirm-loading="promptModal.confirmLoading"
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme"> :ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
<a-input id="prompt-modal-input" :type="promptModal.type" <a-input id="prompt-modal-input" :type="promptModal.type"
v-model="promptModal.value" v-model="promptModal.value"
@@ -18,7 +17,6 @@
value: '', value: '',
okText: '{{ i18n "sure"}}', okText: '{{ i18n "sure"}}',
visible: false, visible: false,
confirmLoading: false,
keyEnter(e) { keyEnter(e) {
if (this.type !== 'textarea') { if (this.type !== 'textarea') {
e.preventDefault(); e.preventDefault();
@@ -32,6 +30,7 @@
} }
}, },
ok() { ok() {
promptModal.close();
promptModal.confirm(promptModal.value); promptModal.confirm(promptModal.value);
}, },
confirm() {}, confirm() {},
@@ -54,10 +53,7 @@
}, },
close() { close() {
this.visible = false; this.visible = false;
}, }
loading(loading=true) {
this.confirmLoading = loading;
},
}; };
const promptModalApp = new Vue({ const promptModalApp = new Vue({

View File

@@ -5,23 +5,13 @@
width="300px" :class="themeSwitcher.currentTheme"> width="300px" :class="themeSwitcher.currentTheme">
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag> <a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
<template v-if="app.subSettings.enable && qrModal.subId"> <template v-if="app.subSettings.enable && qrModal.subId">
<a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider> <a-divider>Subscription</a-divider>
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" <canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas>
id="qrCode-sub"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
</canvas>
<a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider>
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))"
id="qrCode-subJson"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
</canvas>
</template> </template>
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider> <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<template v-for="(row, index) in qrModal.qrcodes"> <template v-for="(row, index) in qrModal.qrcodes">
<a-tag color="blue" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag> <a-tag color="blue" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" <canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
:id="'qrCode-'+index"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;"></canvas>
</template> </template>
</a-modal> </a-modal>
@@ -42,21 +32,12 @@
this.client = client; this.client = client;
this.subId = ''; this.subId = '';
this.qrcodes = []; this.qrcodes = [];
if (this.inbound.protocol == Protocols.WIREGUARD){ this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l,index) =>{ this.qrcodes.push({
this.qrcodes.push({ remark: l.remark,
remark: "Peer " + (index+1), link: l.link
link: l
});
}); });
} else { });
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
this.qrcodes.push({
remark: l.remark,
link: l.link
});
});
}
this.visible = true; this.visible = true;
}, },
close: function () { close: function () {
@@ -89,16 +70,12 @@
}, },
genSubLink(subID) { genSubLink(subID) {
return app.subSettings.subURI+subID+'?name='+subID; return app.subSettings.subURI+subID+'?name='+subID;
},
genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID;
} }
}, },
updated() { updated() {
if (qrModal.client && 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));
this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
} }
qrModal.qrcodes.forEach((element, index) => { qrModal.qrcodes.forEach((element, index) => {
this.setQrCode("qrCode-" + index, element.link); this.setQrCode("qrCode-" + index, element.link);

View File

@@ -1,16 +1,14 @@
{{define "textModal"}} {{define "textModal"}}
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title" <a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
:closable="true" :closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
:class="themeSwitcher.currentTheme"> :ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}" :class="themeSwitcher.currentTheme">
<template slot="footer"> <a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" icon="download" :href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)" :download="txtModal.fileName">
:download="txtModal.fileName">[[ txtModal.fileName ]] {{ i18n "download" }} [[ txtModal.fileName ]]
</a-button> </a-button>
<a-button type="primary" id="copy-btn">{{ i18n "copy" }}</a-button>
</template>
<a-input type="textarea" v-model="txtModal.content" <a-input type="textarea" v-model="txtModal.content"
:autosize="{ minRows: 10, maxRows: 20}"></a-input> :autosize="{ minRows: 10, maxRows: 20}"></a-input>
</a-modal> </a-modal>
<script> <script>
@@ -29,7 +27,7 @@
this.visible = true; this.visible = true;
textModalApp.$nextTick(() => { textModalApp.$nextTick(() => {
if (this.clipboard === null) { if (this.clipboard === null) {
this.clipboard = new ClipboardJS('#copy-btn', { this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
text: () => this.content, text: () => this.content,
}); });
this.clipboard.on('success', () => { this.clipboard.on('success', () => {

View File

@@ -27,7 +27,6 @@
text-align: center; text-align: center;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 100%;
} }
.title { .title {
font-size: 32px; font-size: 32px;
@@ -37,6 +36,7 @@
overflow: hidden; overflow: hidden;
} }
#login { #login {
animation: charge 0.5s both;
background-color: #fff; background-color: #fff;
border-radius: 2rem; border-radius: 2rem;
padding: 3rem; padding: 3rem;
@@ -45,6 +45,24 @@
#login:hover { #login:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
} }
@keyframes charge {
from {
transform: translateY(5rem);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes wave {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.wave { .wave {
opacity: 0.6; opacity: 0.6;
position: absolute; position: absolute;
@@ -52,20 +70,20 @@
left: 50%; left: 50%;
width: 6000px; width: 6000px;
height: 6000px; height: 6000px;
background-color: rgba(0, 135, 113, 0.08); background-color: rgba(14, 73, 181, 0.08);
margin-left: -3000px; margin-left: -3000px;
transform-origin: 50% 48%; transform-origin: 50% 48%;
border-radius: 46%; border-radius: 46%;
animation: wave 72s infinite linear;
pointer-events: none; pointer-events: none;
rotate: 125deg;
} }
.wave2 { .wave2 {
opacity: 0.4; animation: wave 88s infinite linear;
rotate: 70deg; opacity: 0.3;
} }
.wave3 { .wave3 {
opacity: 0.2; animation: wave 80s infinite linear;
rotate: 90deg; opacity: 0.1;
} }
.under { .under {
background-color: #dce9f5; background-color: #dce9f5;
@@ -82,8 +100,120 @@
.dark h1 { .dark h1 {
color: rgba(255, 255, 255, 0.85); color: rgba(255, 255, 255, 0.85);
} }
.ant-form-item { .ant-btn-primary-login {
margin-bottom: 16px; color: #0e49b5;
background-color: #edf4fa;
border-color: #a9c5e7;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
box-shadow: none;
width: 100%;
}
.ant-btn-primary-login:focus,
.ant-btn-primary-login:hover {
color: #fff;
background-color: #0c3f9d;
border-color: #0e49b5;
background-image: linear-gradient(
270deg,
rgba(123, 199, 77, 0) 30%,
#2f67c2,
rgba(123, 199, 77, 0) 100%
);
background-repeat: no-repeat;
animation: ma-bg-move ease-in-out 5s infinite;
background-position-x: -500px;
width: 95%;
animation-delay: -0.5s;
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
}
.ant-btn-primary-login.active,
.ant-btn-primary-login:active {
color: #fff;
background-color: #04308f;
border-color: #04308f;
}
@keyframes ma-bg-move {
0% {
background-position: -500px 0;
}
50% {
background-position: 1000px 0;
}
100% {
background-position: 1000px 0;
}
}
.wave-btn-bg {
position: relative;
border-radius: 25px;
width: 100%;
}
.dark .wave-btn-bg {
color: #fff;
position: relative;
background-color: #0e49b5;
border: 2px double transparent;
background-origin: border-box;
background-clip: padding-box, border-box;
background-size: 300%;
animation: wave-btn-tara 4s ease infinite;
transition: all 0.5s ease;
width: 100%;
}
.dark .wave-btn-bg-cl {
background-image: linear-gradient(rgba(13, 14, 33, 0), rgba(13, 14, 33, 0)),
radial-gradient(circle at left top, #0e49b5, #387eff, #0e49b5) !important;
border-radius: 3em;
}
.dark .wave-btn-bg-cl:hover {
width: 95%;
}
.dark .wave-btn-bg-cl:before {
position: absolute;
content: "";
top: -5px;
left: -5px;
bottom: -5px;
right: -5px;
z-index: -1;
background: inherit;
background-size: inherit;
border-radius: 4em;
opacity: 0;
transition: 0.5s;
}
.dark .wave-btn-bg-cl:hover::before {
opacity: 1;
filter: blur(20px);
animation: wave-btn-tara 8s linear infinite;
}
@keyframes wave-btn-tara {
to {
background-position: 300%;
}
}
.dark .ant-btn-primary-login {
font-size: 14px;
color: #fff;
text-align: center;
background-image: linear-gradient(
rgba(13, 14, 33, 0.45),
rgba(13, 14, 33, 0.35)
);
border-radius: 2rem;
border: none;
outline: none;
background-color: transparent;
height: 46px;
position: relative;
white-space: nowrap;
cursor: pointer;
touch-action: manipulation;
padding: 0 15px;
width: 100%;
animation: none;
background-position-x: 0;
box-shadow: none;
} }
</style> </style>
<body> <body>
@@ -97,7 +227,7 @@
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;"> <a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
<a-row type="flex" justify="center"> <a-row type="flex" justify="center">
<a-col> <a-col>
<h2 class="title" style="text-align: center;">{{ i18n "pages.login.title" }}</h2> <h1 class="title">{{ i18n "pages.login.title" }}</h1>
</a-col> </a-col>
</a-row> </a-row>
<a-row type="flex" justify="center"> <a-row type="flex" justify="center">
@@ -116,10 +246,12 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-row justify="center" class="centered"> <a-row justify="center" class="centered">
<a-button type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined" <div class="wave-btn-bg wave-btn-bg-cl">
:style="{ fontWeight: 'bold', width: loading ? '50px' : '100%', display: 'inline-block' }"> <a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
[[ loading ? '' : '{{ i18n "login" }}' ]] :style="loading ? { width: '50px' } : { display: 'inline-block' }">
</a-button> [[ loading ? '' : '{{ i18n "login" }}' ]]
</a-button>
</div>
</a-row> </a-row>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>

View File

@@ -2,105 +2,170 @@
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok" <a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false" :confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme"> :ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.client.method" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" :dropdown-class-name="themeSwitcher.currentTheme"> <tr>
<a-select-option :value="0">Random</a-select-option> <td>{{ i18n "pages.client.method" }}</td>
<a-select-option :value="1">Random+Prefix</a-select-option> <td>
<a-select-option :value="2">Random+Prefix+Num</a-select-option> <a-form-item>
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option> <a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option> <a-select-option :value="0">Random</a-select-option>
</a-select> <a-select-option :value="1">Random+Prefix</a-select-option>
</a-form-item> <a-select-option :value="2">Random+Prefix+Num</a-select-option>
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1"> <a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number> <a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
</a-form-item> </a-select>
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1"> </a-form-item>
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number> </td>
</a-form-item> </tr>
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0"> <tr v-if="clientsBulkModal.emailMethod>1">
<a-input v-model="clientsBulkModal.emailPrefix"></a-input> <td>{{ i18n "pages.client.first" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "pages.client.postfix" }}' v-if="clientsBulkModal.emailMethod>2"> <a-form-item>
<a-input v-model="clientsBulkModal.emailPostfix"></a-input> <a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2"> </td>
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number> </tr>
</a-form-item> <tr v-if="clientsBulkModal.emailMethod>1">
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.canEnableTlsFlow()"> <td>{{ i18n "pages.client.last" }}</td>
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme"> <td>
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> <a-form-item>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
</a-select> </a-form-item>
</a-form-item> </td>
<a-form-item v-if="app.subSettings.enable"> </tr>
<template slot="label"> <tr v-if="clientsBulkModal.emailMethod>0">
<a-tooltip> <td>{{ i18n "pages.client.prefix" }}</td>
<template slot="title"> <td>
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span> <a-form-item>
</template> <a-input v-model="clientsBulkModal.emailPrefix" style="width: 250px"></a-input>
Subscription </a-form-item>
<a-icon @click="clientsBulkModal.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon> </td>
</a-tooltip> </tr>
</template> <tr v-if="clientsBulkModal.emailMethod>2">
<a-input v-model.trim="clientsBulkModal.subId"></a-input> <td>{{ i18n "pages.client.postfix" }}</td>
</a-form-item> <td>
<a-form-item v-if="app.tgBotEnable"> <a-form-item>
<template slot="label"> <a-input v-model="clientsBulkModal.emailPostfix" style="width: 250px"></a-input>
<a-tooltip> </a-form-item>
<template slot="title"> </td>
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span> </tr>
</template> <tr v-if="clientsBulkModal.emailMethod < 2">
Telegram ID <td>{{ i18n "pages.client.clientCount" }}</td>
<a-icon type="question-circle"></a-icon> <td>
</a-tooltip> <a-form-item>
</template> <a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
<a-input v-model.trim="clientsBulkModal.tgId"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item> </tr>
<template slot="label"> <tr v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
<a-tooltip> <td>Flow</td>
<template slot="title"> <td>
<span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> <a-form-item>
</template> <a-select v-model="clientsBulkModal.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
{{ i18n "pages.inbounds.totalFlow" }} <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-icon type="question-circle"></a-icon> <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-tooltip> </a-select>
</template> </a-form-item>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number> GB </td>
</a-form-item> </tr>
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'> <tr v-if="app.subSettings.enable">
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch> <td>Subscription
</a-form-item> <a-tooltip>
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart"> <template slot="title">
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number> <span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
</a-form-item> </template>
<a-form-item v-else> <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
<template slot="label"> </a-tooltip>
<a-tooltip> </td>
<template slot="title"> <td>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> <a-form-item>
</template> <a-input v-model.trim="clientsBulkModal.subId" style="width: 250px"></a-input>
{{ i18n "pages.inbounds.expireDate" }} </a-form-item>
<a-icon type="question-circle"></a-icon> </td>
</a-tooltip> </tr>
</template> <tr v-if="app.tgBotEnable">
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <td>Telegram ID
:dropdown-class-name="themeSwitcher.currentTheme" <a-tooltip>
v-model="clientsBulkModal.expiryTime"></a-date-picker> <template slot="title">
</a-form-item> <span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
<a-form-item v-if="clientsBulkModal.expiryTime != 0"> </template>
<template slot="label"> <a-icon type="question-circle" theme="filled"></a-icon>
<span>{{ i18n "pages.client.renew" }}</span> </a-tooltip>
<a-tooltip> </td>
<template slot="title"> <td>
<span>{{ i18n "pages.client.renewDesc" }}</span> <a-form-item>
</template> <a-input v-model.trim="clientsBulkModal.tgId" style="width: 250px"></a-input>
<a-icon type="question-circle"></a-icon> </a-form-item>
</a-tooltip> </td>
</template> </tr>
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number> <tr>
</a-form-item> <td>
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.expiryTime != 0">
<td>
<span>{{ i18n "pages.client.renew" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.client.renewDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
</a-modal> </a-modal>
<script> <script>
@@ -155,7 +220,7 @@
} }
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id); ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
}, },
show({ title='', okText='{{ i18n "confirm" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) { show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
this.visible = true; this.visible = true;
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;
@@ -189,7 +254,7 @@
clientsBulkModal.visible = false; clientsBulkModal.visible = false;
clientsBulkModal.loading(false); clientsBulkModal.loading(false);
}, },
loading(loading=true) { loading(loading) {
clientsBulkModal.confirmLoading = loading; clientsBulkModal.confirmLoading = loading;
}, },
}; };

View File

@@ -29,7 +29,7 @@
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id); ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
} }
}, },
show({ title='', okText='{{ i18n "confirm" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) { show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
this.visible = true; this.visible = true;
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;
@@ -70,7 +70,7 @@
clientModal.visible = false; clientModal.visible = false;
clientModal.loading(false); clientModal.loading(false);
}, },
loading(loading=true) { loading(loading) {
clientModal.confirmLoading = loading; clientModal.confirmLoading = loading;
}, },
}; };

View File

@@ -1,23 +1,23 @@
{{define "menuItems"}} {{define "menuItems"}}
<a-menu-item key="{{ .base_path }}xui/"> <a-menu-item key="{{ .base_path }}xui/">
<a-icon type="dashboard"></a-icon> <a-icon type="dashboard"></a-icon>
<span><strong>{{ i18n "menu.dashboard"}}</strong></span> <span>{{ i18n "menu.dashboard"}}</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}xui/inbounds"> <a-menu-item key="{{ .base_path }}xui/inbounds">
<a-icon type="user"></a-icon> <a-icon type="user"></a-icon>
<span><strong>{{ i18n "menu.inbounds"}}</strong></span> <span>{{ i18n "menu.inbounds"}}</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}xui/settings"> <a-menu-item key="{{ .base_path }}xui/settings">
<a-icon type="setting"></a-icon> <a-icon type="setting"></a-icon>
<span><strong>{{ i18n "menu.settings"}}</strong></span> <span>{{ i18n "menu.settings"}}</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}xui/xray"> <a-menu-item key="{{ .base_path }}xui/xray">
<a-icon type="tool"></a-icon> <a-icon type="tool"></a-icon>
<span><strong>{{ i18n "menu.xray"}}</strong></span> <span>{{ i18n "menu.xray"}}</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}logout"> <a-menu-item key="{{ .base_path }}logout">
<a-icon type="logout"></a-icon> <a-icon type="logout"></a-icon>
<span><strong>{{ i18n "menu.logout"}}</strong></span> <span>{{ i18n "menu.logout"}}</span>
</a-menu-item> </a-menu-item>
{{end}} {{end}}

View File

@@ -15,7 +15,7 @@
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input> <a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
</template> </template>
<template v-else-if="type === 'number'"> <template v-else-if="type === 'number'">
<a-input-number :value="value" @change="value => $emit('input', value)" :min="min" :max="max" :step="step" style="width: 100%;"></a-input-number> <a-input-number :value="value" @change="value => $emit('input', value)" :min="min" :step="step" style="width: 100%;"></a-input-number>
</template> </template>
<template v-else-if="type === 'switch'"> <template v-else-if="type === 'switch'">
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch> <a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
@@ -28,7 +28,7 @@
{{define "component/setting"}} {{define "component/setting"}}
<script> <script>
Vue.component('setting-list-item', { Vue.component('setting-list-item', {
props: ["type", "title", "desc", "value", "min", "max", "step", "placeholder"], props: ["type", "title", "desc", "value", "min", "step", "placeholder"],
template: `{{template "component/settingListItem"}}`, template: `{{template "component/settingListItem"}}`,
}); });
</script> </script>

View File

@@ -19,7 +19,6 @@
toggleTheme() { toggleTheme() {
this.isDarkTheme = !this.isDarkTheme; this.isDarkTheme = !this.isDarkTheme;
localStorage.setItem('dark-mode', this.isDarkTheme); localStorage.setItem('dark-mode', this.isDarkTheme);
document.getElementById('message').className = themeSwitcher.currentTheme;
}, },
}; };
} }
@@ -30,10 +29,6 @@
props: [], props: [],
template: `{{template "component/themeSwitchTemplate"}}`, template: `{{template "component/themeSwitchTemplate"}}`,
data: () => ({ themeSwitcher }), data: () => ({ themeSwitcher }),
mounted() {
this.$message.config({getContainer: () => document.getElementById('message')});
document.getElementById('message').className = themeSwitcher.currentTheme;
}
}); });
</script> </script>
{{end}} {{end}}

View File

@@ -1,86 +0,0 @@
{{define "dnsModal"}}
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
:closable="true" :mask-closable="false"
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
<a-button size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')">+</a-button>
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
<a-input v-model.trim="dnsModal.dnsServer.domains[index]">
<a-button size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
<a-select
v-model="dnsModal.dnsServer.queryStrategy"
style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
[[ l ]]
</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
<script>
const dnsModal = {
title: '',
visible: false,
okText: '{{ i18n "confirm" }}',
isEdit: false,
confirm: null,
dnsServer: {
address: "localhost",
domains: [],
queryStrategy: 'UseIP',
},
ok() {
domains = dnsModal.dnsServer.domains.filter(d => d.length>0);
dnsModal.dnsServer.domains = domains;
newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address;
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
},
show({ title='', okText='{{ i18n "confirm" }}', dnsServer, confirm=(dnsServer)=>{}, isEdit=false }) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if(isEdit) {
if (typeof dnsServer == 'object'){
this.dnsServer = dnsServer;
} else {
this.dnsServer.address = dnsServer?? '';
}
} else {
this.dnsServer = {
address: "localhost",
domains: [],
queryStrategy: 'UseIP',
}
}
this.isEdit = isEdit;
},
close() {
dnsModal.visible = false;
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#dns-modal',
data: {
dnsModal: dnsModal,
},
computed: {
isAdvanced: {
get: function () { return dnsModal.dnsServer.domains.length>0 }
}
}
});
</script>
{{end}}

View File

@@ -1,57 +0,0 @@
{{define "fakednsModal"}}
<a-modal id="fakedns-modal" v-model="fakednsModal.visible" :title="fakednsModal.title" @ok="fakednsModal.ok"
:closable="true" :mask-closable="false"
:ok-text="fakednsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.fakedns.ipPool" }}'>
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.fakedns.poolSize" }}'>
<a-input type="number" min="1" v-model.trim="fakednsModal.fakeDns.poolSize"></a-input>
</a-form-item>
</a-form>
</a-modal>
<script>
const fakednsModal = {
title: '',
visible: false,
okText: '{{ i18n "confirm" }}',
isEdit: false,
confirm: null,
fakeDns: {
ipPool: "198.18.0.0/16",
poolSize: 65535,
},
ok() {
ObjectUtil.execute(fakednsModal.confirm, fakednsModal.fakeDns);
},
show({ title='', okText='{{ i18n "confirm" }}', fakeDns, confirm=(fakeDns)=>{}, isEdit=false }) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if(isEdit) {
this.fakeDns = fakeDns;
} else {
this.fakeDns = {
ipPool: "198.18.0.0/16",
poolSize: 65535,
}
}
this.isEdit = isEdit;
},
close() {
fakednsModal.visible = false;
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#fakedns-modal',
data: {
fakednsModal: fakednsModal,
}
});
</script>
{{end}}

View File

@@ -1,125 +1,170 @@
{{define "form/client"}} {{define "form/client"}}
<a-form layout="horizontal" v-if="client" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline" v-if="client">
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'> <table width="100%" class="ant-table-tbody">
<a-switch v-model="client.enable"></a-switch> <tr>
</a-form-item> <td>{{ i18n "pages.inbounds.enable" }}</td>
<a-form-item> <td>
<template slot="label"> <a-form-item>
<a-tooltip> <a-switch v-model="client.enable"></a-switch>
<template slot="title"> </a-form-item>
<span>{{ i18n "pages.inbounds.emailDesc" }}</span> </td>
</template> </tr>
{{ i18n "pages.inbounds.email" }} <tr>
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon> <td>
</a-tooltip> <span>{{ i18n "pages.inbounds.email" }}</span>
</template> <a-tooltip>
<a-input v-model.trim="client.email"></a-input> <template slot="title">
</a-form-item> <span>{{ i18n "pages.inbounds.emailDesc" }}</span>
<a-form-item v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS"> </template>
<template slot="label"> <a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
<a-tooltip> </a-tooltip>
<template slot="title"> </td>
<span>{{ i18n "reset" }}</span> <td>
</template> <a-form-item>
{{ i18n "password" }} <a-input v-model.trim="client.email" style="width: 250px"></a-input>
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon> </a-form-item>
<a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)" type="sync"></a-icon> </td>
</a-tooltip> </tr>
</template> <tr v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
<a-input v-model.trim="client.password"></a-input> <td>password
</a-form-item> <a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
<a-form-item v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS"> </td>
<template slot="label"> <td>
<a-tooltip> <a-form-item>
<template slot="title"> <a-input v-model.trim="client.password" style="width: 250px"></a-input>
<span>{{ i18n "reset" }}</span> </a-form-item>
</template> </td>
ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon> </tr>
</a-tooltip> <tr v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
</template> <td>ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon></td>
<a-input v-model.trim="client.id"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item v-if="client.email && app.subSettings.enable"> <a-input v-model.trim="client.id" style="width: 250px"></a-input>
<template slot="label"> </a-form-item>
<a-tooltip> </td>
<template slot="title"> </tr>
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span> <tr v-if="client.email && app.subSettings.enable">
</template> <td>Subscription
Subscription <a-tooltip>
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon> <template slot="title">
</a-tooltip> <span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
</template> </template>
<a-input v-model.trim="client.subId"></a-input> <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
</a-form-item> </a-tooltip>
<a-form-item v-if="client.email && app.tgBotEnable"> </td>
<template slot="label"> <td>
<a-tooltip> <a-form-item>
<template slot="title"> <a-input v-model.trim="client.subId" style="width: 250px"></a-input>
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span> </a-form-item>
</template> </td>
Telegram ID </tr>
<a-icon type="question-circle"></a-icon> <tr v-if="client.email && app.tgBotEnable">
</a-tooltip> <td>Telegram ID
</template> <a-tooltip>
<a-input v-model.trim="client.tgId"></a-input> <template slot="title">
</a-form-item> <span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
<a-form-item v-if="inbound.canEnableTlsFlow()" label='Flow'> </template>
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme"> <a-icon type="question-circle" theme="filled"></a-icon>
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> </a-tooltip>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> </td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-form-item> <a-input v-model.trim="client.tgId" style="width: 250px"></a-input>
<template slot="label"> </a-form-item>
<a-tooltip> </td>
<template slot="title"> </tr>
<span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> <tr v-if="inbound.canEnableTlsFlow()">
</template> <td>Flow</td>
{{ i18n "pages.inbounds.totalFlow" }} <td>
<a-icon type="question-circle"></a-icon> <a-form-item>
</a-tooltip> <a-select v-model="client.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
</template> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> GB <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-form-item> </a-select>
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'> </a-form-item>
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)"> </td>
[[ sizeFormat(clientStats.up) ]] / </tr>
[[ sizeFormat(clientStats.down) ]] <tr>
([[ sizeFormat(clientStats.up + clientStats.down) ]]) <td>
</a-tag> <span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip> <a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template> <template slot="title">
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon> 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</a-tooltip> </template>
</a-form-item> <a-icon type="question-circle" theme="filled"></a-icon>
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'> </a-tooltip>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch> </td>
</a-form-item> <td>
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'> <a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number> <a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item v-else> </td>
<template slot="label"> </tr>
<a-tooltip> <tr v-if="isEdit && clientStats">
<template slot="title">{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</template> <td>{{ i18n "usage" }}</td>
{{ i18n "pages.inbounds.expireDate" }} <td>
<a-icon type="question-circle"></a-icon> <a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
</a-tooltip> [[ sizeFormat(clientStats.up) ]] /
</template> [[ sizeFormat(clientStats.down) ]]
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" ([[ sizeFormat(clientStats.up + clientStats.down) ]])
:dropdown-class-name="themeSwitcher.currentTheme" </a-tag>
v-model="client._expiryTime"></a-date-picker> <a-tooltip>
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag> <template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
</a-form-item> <a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon>
<a-form-item v-if="client.expiryTime != 0"> </a-tooltip>
<template slot="label"> </td>
<a-tooltip> </tr>
<template slot="title">{{ i18n "pages.client.renewDesc" }}</template> <tr>
{{ i18n "pages.client.renew" }} <td>{{ i18n "pages.client.delayedStart" }}</td>
<a-icon type="question-circle"></a-icon> <td>
</a-tooltip> <a-form-item>
</template> <a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
<a-input-number v-model.number="client.reset" :min="0"></a-input-number> </a-form-item>
</a-form-item> </td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
</a-form-item>
</td>
</tr>
<tr v-if="client.expiryTime != 0">
<td>
<span>{{ i18n "pages.client.renew" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.client.renewDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model.number="client.reset" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,63 +1,91 @@
{{define "form/inbound"}} {{define "form/inbound"}}
<!-- base --> <!-- base -->
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label='{{ i18n "enable" }}'> <table width="100%" class="ant-table-tbody">
<a-switch v-model="dbInbound.enable"></a-switch> <tr>
</a-form-item> <td>{{ i18n "enable" }}</td>
<a-form-item label='{{ i18n "remark" }}'> <td>
<a-input v-model.trim="dbInbound.remark"></a-input> <a-form-item>
</a-form-item> <a-switch v-model="dbInbound.enable"></a-switch>
</a-form-item>
<a-form-item label='{{ i18n "protocol" }}'> </td>
<a-select v-model="inbound.protocol" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme"> </tr>
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option> <tr>
</a-select> <td>{{ i18n "remark" }}</td>
</a-form-item> <td>
<a-form-item>
<a-form-item> <a-input v-model.trim="dbInbound.remark" style="width: 250px;"></a-input>
<template slot="label"> </a-form-item>
<a-tooltip> </td>
</tr>
<tr>
<td>{{ i18n "protocol" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "monitor" }}
<a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.monitorDesc" }}</span> <span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
</template> </template>
{{ i18n "monitor" }} <a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon> </a-tooltip>
</a-tooltip> </td>
</template> <td>
<a-input v-model.trim="inbound.listen"></a-input> <a-form-item>
</a-form-item> <a-input v-model.trim="inbound.listen" style="width: 250px;"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'> </td>
<a-input-number v-model.number="inbound.port"></a-input-number> </tr>
</a-form-item> <tr>
<td>{{ i18n "pages.inbounds.port" }}</td>
<a-form-item> <td>
<template slot="label"> <a-form-item>
<a-tooltip> <a-input-number v-model.number="inbound.port"></a-input-number>
<template slot="title"> </a-form-item>
<span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> </td>
</template> </tr>
{{ i18n "pages.inbounds.totalFlow" }} <tr>
<a-icon type="question-circle"></a-icon> <td>
</a-tooltip> <span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
</template> <a-tooltip>
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number> GB <template slot="title">
</a-form-item> 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-form-item> <a-icon type="question-circle" theme="filled"></a-icon>
<template slot="label"> </a-tooltip>
<a-tooltip> </td>
<template slot="title"> <td>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> <a-form-item>
</template> <a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
{{ i18n "pages.inbounds.expireDate" }} </a-form-item>
<a-icon type="question-circle"></a-icon> </td>
</a-tooltip> </tr>
</template> <tr>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <td>
:dropdown-class-name="themeSwitcher.currentTheme" <span>{{ i18n "pages.inbounds.expireDate" }}</span>
v-model="dbInbound._expiryTime"></a-date-picker> <a-tooltip>
</a-form-item> <template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
<!-- vmess settings --> <!-- vmess settings -->
@@ -95,11 +123,6 @@
{{template "form/http"}} {{template "form/http"}}
</template> </template>
<!-- wireguard -->
<template v-if="inbound.protocol === Protocols.WIREGUARD">
{{template "form/wireguard"}}
</template>
<!-- stream settings --> <!-- stream settings -->
<template v-if="inbound.canEnableStream()"> <template v-if="inbound.canEnableStream()">
{{template "form/streamSettings"}} {{template "form/streamSettings"}}
@@ -112,7 +135,7 @@
</template> </template>
<!-- sniffing --> <!-- sniffing -->
<template> <template v-if="inbound.canSniffing()">
{{template "form/sniffing"}} {{template "form/sniffing"}}
</template> </template>
{{end}} {{end}}

View File

@@ -2,455 +2,535 @@
<!-- base --> <!-- base -->
<a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }"> <a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
<a-tab-pane key="1" tab="Form"> <a-tab-pane key="1" tab="Form">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label='{{ i18n "protocol" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="outbound.protocol" :dropdown-class-name="themeSwitcher.currentTheme"> <tr>
<a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option> <td>{{ i18n "protocol" }}</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}' has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'"> <a-select v-model="outbound.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-input v-model.trim="outbound.tag" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input> <a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
</a-form-item> </a-select>
<a-form-item label='{{ i18n "pages.xray.outbound.sendThrough" }}'> </a-form-item>
<a-input v-model="outbound.sendThrough"></a-input> </td>
</a-form-item> </tr>
<tr>
<td>{{ i18n "pages.xray.outbound.tag" }}</td>
<td>
<a-form-item has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
<a-input v-model.trim="outbound.tag" style="width: 250px" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
</a-form-item>
</td>
</tr>
<!-- freedom settings--> <!-- freedom settings-->
<template v-if="outbound.protocol === Protocols.Freedom"> <template v-if="outbound.protocol === Protocols.Freedom">
<a-form-item label='Strategy'> <tr>
<a-select <td>Strategy</td>
v-model="outbound.settings.domainStrategy" <td>
:dropdown-class-name="themeSwitcher.currentTheme"> <a-form-item>
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option> <a-select
</a-select> v-model="outbound.settings.domainStrategy"
</a-form-item> style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-form-item label='Fragment'> <a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
<a-switch </a-select>
:checked="Object.keys(outbound.settings.fragment).length >0" </a-form-item>
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}"> </td>
</a-switch> </tr>
</a-form-item> <tr>
<td>Fragment</td>
<td>
<a-form-item>
<a-switch
:checked="Object.keys(outbound.settings.fragment).length >0"
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
</a-switch>
</a-form-item>
</td>
</tr>
<template v-if="Object.keys(outbound.settings.fragment).length >0"> <template v-if="Object.keys(outbound.settings.fragment).length >0">
<a-form-item label='Packets'> <tr>
<a-select <td>Packets</td>
v-model="outbound.settings.fragment.packets" <td>
:dropdown-class-name="themeSwitcher.currentTheme"> <a-form-item>
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option> <a-select
</a-select> v-model="outbound.settings.fragment.packets"
</a-form-item> style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-form-item label='Length'> <a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
<a-input v-model.trim="outbound.settings.fragment.length"></a-input> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Interval'> </td>
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input> </tr>
</a-form-item> <tr>
<td>Length</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.settings.fragment.length" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Interval</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.settings.fragment.interval" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
</template> </template>
</template> </template>
<!-- blackhole settings --> <!-- blackhole settings -->
<template v-if="outbound.protocol === Protocols.Blackhole"> <template v-if="outbound.protocol === Protocols.Blackhole">
<a-form-item label='Response Type'> <tr>
<a-select <td>Response Type</td>
v-model="outbound.settings.type" <td>
:dropdown-class-name="themeSwitcher.currentTheme"> <a-form-item>
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option> <a-select
</a-select> v-model="outbound.settings.type"
</a-form-item> style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</template> </template>
<!-- dns settings --> <!-- dns settings -->
<template v-if="outbound.protocol === Protocols.DNS"> <template v-if="outbound.protocol === Protocols.DNS">
<a-form-item label='{{ i18n "pages.inbounds.network" }}'> <tr>
<a-select <td>{{ i18n "pages.inbounds.network" }}</td>
v-model="outbound.settings.network" <td>
:dropdown-class-name="themeSwitcher.currentTheme"> <a-form-item>
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option> <a-select
</a-select> v-model="outbound.settings.network"
</a-form-item> style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
</template> <a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
</a-select>
<!-- wireguard settings --> </a-form-item>
<template v-if="outbound.protocol === Protocols.Wireguard"> </td>
<a-form-item> </tr>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
{{ i18n "pages.xray.outbound.address" }} <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.address"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync"
@click="[outbound.settings.pubKey, outbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
</a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.secretKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input disabled v-model="outbound.settings.pubKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.domainStrategy" }}'>
<a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="wds in ['', ...WireguardDomainStrategy]" :value="wds">[[ wds ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="outbound.settings.mtu"></a-input-number>
</a-form-item>
<a-form-item label='Workers'>
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
Reserved <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model="outbound.settings.reserved"></a-input>
</a-form-item>
<a-form-item label="Peers">
<a-button type="primary" size="small" @click="outbound.settings.addPeer()">+</a-button>
</a-form-item>
<a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
Peer [[ index + 1 ]]
<a-icon v-if="outbound.settings.peers.length>1" type="delete" @click="() => outbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
<a-input v-model.trim="peer.endpoint"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'>
<a-input v-model.trim="peer.psk"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button>
</template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]">
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input>
</a-form-item>
</a-form>
</template> </template>
<!-- Address + Port --> <!-- Address + Port -->
<template v-if="outbound.hasAddressPort()"> <template v-if="outbound.hasAddressPort()">
<a-form-item label='{{ i18n "pages.inbounds.address" }}'> <tr>
<a-input v-model.trim="outbound.settings.address"></a-input> <td>{{ i18n "pages.inbounds.address" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'> <a-form-item>
<a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number> <a-input v-model.trim="outbound.settings.address" style="width: 250px"></a-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.port" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
</a-form-item>
</td>
</tr>
</template> </template>
<!-- Vnext (vless/vmess) settings --> <!-- Vnext (vless/vmess) settings -->
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)"> <template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
<a-form-item label='ID'> <tr>
<a-input v-model.trim="outbound.settings.id"></a-input> <td>ID</td>
</a-form-item> <td>
<a-form-item>
<a-input v-model.trim="outbound.settings.id" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<!-- vless settings --> <!-- vless settings -->
<template v-if="outbound.canEnableTlsFlow()"> <template v-if="outbound.canEnableTlsFlow()">
<a-form-item label='Flow'> <tr>
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme"> <td>Flow</td>
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> <td>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-form-item>
</a-select> <a-select v-model="outbound.settings.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
</a-form-item> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</template> </template>
</template> </template>
<!-- Servers (trojan/shadowsocks/socks/http) settings --> <!-- Servers (trojan/shadowsocks/socks/http) settings -->
<template v-if="outbound.hasServers()"> <template v-if="outbound.hasServers()">
<!-- http / socks --> <tr v-if="outbound.hasUsername()">
<template v-if="outbound.hasUsername()"> <td>{{ i18n "username" }}</td>
<a-form-item label='{{ i18n "username" }}'> <td>
<a-input v-model.trim="outbound.settings.user"></a-input> <a-form-item>
</a-form-item> <a-input v-model.trim="outbound.settings.user" style="width: 250px"></a-input>
<a-form-item label='{{ i18n "password" }}'> </a-form-item>
<a-input v-model.trim="outbound.settings.pass"></a-input> </td>
</a-form-item> </tr>
</template> <tr>
<!-- trojan/shadowsocks --> <td>{{ i18n "password" }}</td>
<template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)"> <td>
<a-form-item label='{{ i18n "password" }}'> <a-form-item>
<a-input v-model.trim="outbound.settings.password"></a-input> <a-input v-model.trim="outbound.settings.password" style="width: 250px"></a-input>
</a-form-item> </a-form-item>
</template> </td>
</tr>
<!-- shadowsocks --> <!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks"> <template v-if="outbound.protocol === Protocols.Shadowsocks">
<a-form-item label='{{ i18n "encryption" }}'> <tr>
<a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme"> <td>{{ i18n "encryption" }}</td>
<a-select-option v-for="(method, method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option> <td>
</a-select> <a-form-item>
</a-form-item> <a-select v-model="outbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-form-item label='UDP over TCP'> <a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
<a-switch v-model="outbound.settings.uot"></a-switch> </a-select>
</a-form-item> </a-form-item>
</template> </td>
</tr>
<tr>
<td>UDP over TCP</td>
<td>
<a-form-item>
<a-switch v-model="outbound.settings.uot"></a-switch>
</a-form-item>
</td>
</tr>
</template>
</template> </template>
<!-- stream settings --> <!-- stream settings -->
<template v-if="outbound.canEnableStream()"> <template v-if="outbound.canEnableStream()">
<a-form-item label='{{ i18n "transmission" }}'> <tr>
<a-select v-model="outbound.stream.network" @change="streamNetworkChange" <td>{{ i18n "transmission" }}</td>
:dropdown-class-name="themeSwitcher.currentTheme"> <td>
<a-select-option value="tcp">TCP</a-select-option> <a-form-item>
<a-select-option value="kcp">mKCP</a-select-option> <a-select v-model="outbound.stream.network" @change="streamNetworkChange"
<a-select-option value="ws">WebSocket</a-select-option> style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="http">HTTP/2</a-select-option> <a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="quic">QUIC</a-select-option> <a-select-option value="kcp">KCP</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option> <a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="httpupgrade">HttpUpgrade</a-select-option> <a-select-option value="http">HTTP2</a-select-option>
</a-select> <a-select-option value="quic">QUIC</a-select-option>
</a-select> <a-select-option value="grpc">gRPC</a-select-option>
</a-form-item> </a-select>
</a-form-item>
</td>
</tr>
<template v-if="outbound.stream.network === 'tcp'"> <template v-if="outbound.stream.network === 'tcp'">
<a-form-item label='HTTP {{ i18n "camouflage" }}'> <tr>
<a-switch :checked="outbound.stream.tcp.type === 'http'" <td>http {{ i18n "camouflage" }}</td>
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'"> <td>
</a-switch> <a-form-item>
</a-form-item> <a-switch
:checked="outbound.stream.tcp.type === 'http'"
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch>
</a-form-item>
</td>
</tr>
<template v-if="outbound.stream.tcp.type == 'http'"> <template v-if="outbound.stream.tcp.type == 'http'">
<a-form-item label='{{ i18n "host" }}'> <tr>
<a-input v-model.trim="outbound.stream.tcp.host"></a-input> <td>{{ i18n "host" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "path" }}'> <a-form-item>
<a-input v-model.trim="outbound.stream.tcp.path"></a-input> <a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.host"></a-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td>
<a-form-item>
<a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.path"></a-input>
</a-form-item>
</td>
</tr>
</template> </template>
</template> </template>
<!-- kcp --> <!-- kcp -->
<template v-if="outbound.stream.network === 'kcp'"> <template v-if="outbound.stream.network === 'kcp'">
<a-form-item label='{{ i18n "camouflage" }}'> <tr>
<a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme"> <td>{{ i18n "camouflage" }}</td>
<a-select-option value="none">None</a-select-option> <td>
<a-select-option value="srtp">SRTP</a-select-option> <a-form-item>
<a-select-option value="utp">uTP</a-select-option> <a-select v-model="outbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="wechat-video">WeChat</a-select-option> <a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option> <a-select-option value="srtp">srtp (video call)</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option> <a-select-option value="utp">utp (BT download)</a-select-option>
<a-select-option value="dns">DNS</a-select-option> <a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
</a-select> <a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
</a-form-item> <a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
<a-form-item label='{{ i18n "password" }}'> </a-select>
<a-input v-model="outbound.stream.kcp.seed"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item label='MTU'> </tr>
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number> <tr>
</a-form-item> <td>{{ i18n "password" }}</td>
<a-form-item label='TTI (ms)'> <td>
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number> <a-form-item>
</a-form-item> <a-input v-model="outbound.stream.kcp.seed" style="width: 250px;"></a-input>
<a-form-item label='Uplink (MB/s)'> </a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number> </td>
</a-form-item> </tr>
<a-form-item label='Downlink (MB/s)'> <tr>
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number> <td>mtu</td>
</a-form-item> <td>
<a-form-item label='Congestion'> <a-form-item>
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch> <a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Read Buffer (MB)'> </td>
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number> </tr>
</a-form-item> <tr>
<a-form-item label='Write Buffer (MB)'> <td>tti (ms)</td>
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number> <td>
</a-form-item> <a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>uplink capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>downlink capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>congestion</td>
<td>
<a-form-item>
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>read buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>write buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
</template> </template>
<!-- ws --> <!-- ws -->
<template v-if="outbound.stream.network === 'ws'"> <template v-if="outbound.stream.network === 'ws'">
<a-form-item label='{{ i18n "host" }}'> <tr>
<a-input v-model="outbound.stream.ws.host"></a-input> <td>{{ i18n "host" }}</td>
</a-form-item> <td><a-form-item><a-input style="width: 250px" v-model="outbound.stream.ws.host"></a-input></a-form-item></td>
<a-form-item label='{{ i18n "path" }}'> </tr>
<a-input v-model.trim="outbound.stream.ws.path"></a-input> <tr>
</a-form-item> <td>{{ i18n "path" }}</td>
<td><a-form-item><a-input style="width: 250px;" v-model.trim="outbound.stream.ws.path"></a-input></a-form-item></td>
</tr>
</template> </template>
<!-- http --> <!-- http -->
<template v-if="outbound.stream.network === 'http'"> <template v-if="outbound.stream.network === 'http'">
<a-form-item label='{{ i18n "host" }}'> <tr>
<a-input v-model.trim="outbound.stream.http.host"></a-input> <td>{{ i18n "host" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "path" }}'> <a-form-item>
<a-input v-model.trim="outbound.stream.http.path"></a-input> <a-input v-model.trim="outbound.stream.http.host" style="width: 250px;"></a-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.http.path" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
</template> </template>
<!-- quic --> <!-- quic -->
<template v-if="outbound.stream.network === 'quic'"> <template v-if="outbound.stream.network === 'quic'">
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'> <tr>
<a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme"> <td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
<a-select-option value="none">None</a-select-option> <td>
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option> <a-form-item>
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option> <a-select v-model="outbound.stream.quic.security" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
</a-select> <a-select-option value="none">none</a-select-option>
</a-form-item> <a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
<a-form-item label='{{ i18n "password" }}'> <a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
<a-input v-model.trim="outbound.stream.quic.key"></a-input> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "camouflage" }}'> </td>
<a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme"> </tr>
<a-select-option value="none">None</a-select-option> <tr>
<a-select-option value="srtp">SRTP</a-select-option> <td>{{ i18n "password" }}</td>
<a-select-option value="utp">uTP</a-select-option> <td>
<a-select-option value="wechat-video">WeChat</a-select-option> <a-form-item>
<a-select-option value="dtls">DTLS 1.2</a-select-option> <a-input v-model.trim="outbound.stream.quic.key" style="width: 250px;"></a-input>
<a-select-option value="wireguard">WireGuard</a-select-option> </a-form-item>
</a-select> </td>
</a-form-item> </tr>
<tr>
<td>{{ i18n "camouflage" }}</td>
<td>
<a-form-item>
<a-select v-model="outbound.stream.quic.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="srtp">srtp (video call)</a-select-option>
<a-select-option value="utp">utp (BT download)</a-select-option>
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</template> </template>
<!-- grpc --> <!-- grpc -->
<template v-if="outbound.stream.network === 'grpc'"> <template v-if="outbound.stream.network === 'grpc'">
<a-form-item label='Service Name'> <tr>
<a-input v-model.trim="outbound.stream.grpc.serviceName"></a-input> <td>serviceName</td>
</a-form-item> <td>
<a-form-item label='Authority'> <a-form-item>
<a-input v-model.trim="outbound.stream.grpc.authority"></a-input> <a-input v-model.trim="outbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Multi Mode'> </td>
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch> </tr>
</a-form-item> <tr>
</template> <td>MultiMode</td>
<td>
<!-- httpupgrade --> <a-form-item>
<template v-if="outbound.stream.network === 'httpupgrade'"> <a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
<a-form-item label='{{ i18n "host" }}'> </a-form-item>
<a-input v-model="outbound.stream.httpupgrade.host"></a-input> </td>
</a-form-item> </tr>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
</a-form-item>
</template> </template>
</template> </template>
<!-- tls settings --> <!-- tls settings -->
<template v-if="outbound.canEnableTls()"> <template v-if="outbound.canEnableTls()">
<a-form-item label='{{ i18n "security" }}'> <tr>
<a-radio-group v-model="outbound.stream.security" button-style="solid"> <td>{{ i18n "security" }}</td>
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button> <td>
<a-radio-button value="tls">TLS</a-radio-button> <a-form-item>
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button> <a-radio-group v-model="outbound.stream.security" button-style="solid">
</a-radio-group> <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
</a-form-item> <a-radio-button value="tls">TLS</a-radio-button>
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
</a-radio-group>
</a-form-item>
</td>
</tr>
<template v-if="outbound.stream.isTls"> <template v-if="outbound.stream.isTls">
<a-form-item label="SNI" placeholder="Server Name Indication"> <tr>
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input> <td>SNI</td>
</a-form-item> <td>
<a-form-item label="uTLS"> <a-form-item placeholder="Server Name Indication">
<a-select v-model="outbound.stream.tls.fingerprint" <a-input v-model.trim="outbound.stream.tls.serverName" style="width: 250px"></a-input>
:dropdown-class-name="themeSwitcher.currentTheme"> </a-form-item>
<a-select-option value=''>None</a-select-option> </td>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> </tr>
</a-select> <tr>
</a-form-item> <td>uTLS</td>
<a-form-item label="ALPN"> <td>
<a-select <a-form-item>
mode="multiple" <a-select v-model="outbound.stream.tls.fingerprint"
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme" <a-select-option value=''>None</a-select-option>
v-model="outbound.stream.tls.alpn"> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option> </a-select>
</a-select> </a-form-item>
</a-form-item> </td>
<a-form-item label="Allow Insecure"> </tr>
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch> <tr>
</a-form-item> <td>ALPN</td>
<td>
<a-form-item>
<a-select
mode="multiple"
style="width: 250px"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="outbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Allow insecure</td>
<td>
<a-form-item>
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
</a-form-item>
</td>
</tr>
</template> </template>
<!-- reality settings --> <!-- reality settings -->
<template v-if="outbound.stream.isReality"> <template v-if="outbound.stream.isReality">
<a-form-item label="SNI"> <tr>
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input> <td>{{ i18n "domainName" }}</td>
</a-form-item> <td>
<a-form-item label="uTLS"> <a-form-item>
<a-select v-model="outbound.stream.reality.fingerprint" <a-input v-model.trim="outbound.stream.reality.serverName" style="width: 250px"></a-input>
:dropdown-class-name="themeSwitcher.currentTheme"> </a-form-item>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> </td>
</a-select> </tr>
</a-form-item> <tr>
<a-form-item label="Short ID"> <td>uTLS</td>
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item label="SpiderX"> <a-select v-model="outbound.stream.reality.fingerprint"
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input> style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
</a-form-item> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
<a-form-item label="Public Key"> </a-select>
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>Short Id</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>SpiderX</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Public Key</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.reality.publicKey" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
</template> </template>
</template> </template>
</table>
<!-- sockopt settings -->
<a-form-item label="Sockopts">
<a-switch v-model="outbound.stream.sockoptSwitch"></a-switch>
</a-form-item>
<template v-if="outbound.stream.sockoptSwitch">
<a-form-item label="Dialer Proxy">
<a-select v-model="outbound.stream.sockopt.dialerProxy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="TCP Fast Open">
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item>
<a-form-item label="Keep Alive Interval">
<a-input-number v-model="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP No-Delay">
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch>
</a-form-item>
</template>
<!-- mux settings -->
<template v-if="outbound.canEnableMux()">
<a-form-item label="Mux">
<a-switch v-model="outbound.mux.enabled"></a-switch>
</a-form-item>
<template v-if="outbound.mux.enabled">
<a-form-item label="Concurrency">
<a-input-number v-model="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
</a-form-item>
<a-form-item label="xudp Concurrency">
<a-input-number v-model="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
</a-form-item>
<a-form-item label="xudp UDP 443">
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="c in ['reject', 'allow', 'skip']" :value="c">[[ c ]]</a-select-option>
</a-select>
</a-form-item>
</template>
</template>
</a-form> </a-form>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab="JSON" force-render="true"> <a-tab-pane key="2" tab="JSON" force-render="true">

View File

@@ -1,20 +1,42 @@
{{define "form/dokodemo"}} {{define "form/dokodemo"}}
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.settings.address"></a-input> <tr>
</a-form-item> <td>{{ i18n "pages.inbounds.targetAddress"}}</td>
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'> <td>
<a-input-number v-model.number="inbound.settings.port"></a-input-number> <a-form-item>
</a-form-item> <a-input v-model.trim="inbound.settings.address"></a-input>
<a-form-item label='{{ i18n "pages.inbounds.network"}}'> </a-form-item>
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme"> </td>
<a-select-option value="tcp,udp">TCP+UDP</a-select-option> </tr>
<a-select-option value="tcp">TCP</a-select-option> <tr>
<a-select-option value="udp">UDP</a-select-option> <td>{{ i18n "pages.inbounds.destinationPort"}}</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-form-item label='Follow Redirect'> <a-input-number v-model.number="inbound.settings.port"></a-input-number>
<a-switch v-model="inbound.settings.followRedirect"></a-switch> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.network"}}</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
<a-select-option value="tcp">tcp</a-select-option>
<a-select-option value="udp">udp</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>FollowRedirect</td>
<td>
<a-form-item>
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,5 +1,5 @@
{{define "form/http"}} {{define "form/http"}}
<a-form> <a-form layout="inline">
<table style="width: 100%; text-align: center; margin-bottom: 10px;"> <table style="width: 100%; text-align: center; margin-bottom: 10px;">
<tr> <tr>
<td width="45%">{{ i18n "username" }}</td> <td width="45%">{{ i18n "username" }}</td>

View File

@@ -1,5 +1,6 @@
{{define "form/shadowsocks"}} {{define "form/shadowsocks"}}
<template v-if="inbound.isSSMultiUser"> <a-form layout="inline">
<template v-if="inbound.isSSMultiUser">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'> <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}} {{template "form/client"}}
@@ -19,32 +20,40 @@
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
</template> </template>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <table width="100%" class="ant-table-tbody">
<a-form-item label='{{ i18n "encryption" }}'> <tr>
<a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme"> <td>{{ i18n "encryption" }}</td>
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option> <td>
</a-select> <a-form-item>
</a-form-item> <a-select v-model="inbound.settings.method" style="width: 250px;" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
<a-form-item v-if="inbound.isSS2022"> <a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
<template slot="label"> </a-select>
<a-tooltip> </a-form-item>
<template slot="title"> </td>
<span>{{ i18n "reset" }}</span> </tr>
</template> <tr v-if="inbound.isSS2022">
{{ i18n "password" }} <td>{{ i18n "password" }}
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon> <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
</a-tooltip> </td>
</template> <td>
<a-input v-model.trim="inbound.settings.password"></a-input> <a-form-item>
</a-form-item> <a-input v-model.trim="inbound.settings.password" style="width: 250px"></a-input>
<a-form-item label='{{ i18n "pages.inbounds.network" }}'> </a-form-item>
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme"> </td>
<a-select-option value="tcp,udp">TCP+UDP</a-select-option> </tr>
<a-select-option value="tcp">TCP</a-select-option> <tr>
<a-select-option value="udp">UDP</a-select-option> <td>{{ i18n "pages.inbounds.network" }}</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
<a-select-option value="tcp">tcp</a-select-option>
<a-select-option value="udp">udp</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
<a-divider style="margin:0;"></a-divider>
{{end}} {{end}}

View File

@@ -1,33 +1,52 @@
{{define "form/socks"}} {{define "form/socks"}}
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'> <table width="100%" class="ant-table-tbody">
<a-switch v-model="inbound.settings.udp"></a-switch> <tr>
</a-form-item> <td style="width: 30%;">{{ i18n "password" }}</td>
<a-form-item label="IP" v-if="inbound.settings.udp"> <td>
<a-input v-model.trim="inbound.settings.ip"></a-input> <a-form-item>
</a-form-item> <a-switch :checked="inbound.settings.auth === 'password'"
<a-form-item label='{{ i18n "password" }}'> @change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
<a-switch :checked="inbound.settings.auth === 'password'" </a-form-item>
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch> </td>
</a-form-item> </tr>
<template v-if="inbound.settings.auth === 'password'"> <tr v-if="inbound.settings.auth === 'password'">
<table style="width: 100%; text-align: center; margin-bottom: 10px;"> <td colspan="2">
<tr> <table style="width: 100%; text-align: center; margin-bottom: 10px;">
<td width="45%">{{ i18n "username" }}</td> <tr>
<td width="45%">{{ i18n "password" }}</td> <td width="45%">{{ i18n "username" }}</td>
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td> <td width="45%">{{ i18n "password" }}</td>
</tr> <td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td>
</table> </tr>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;"> </table>
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'> <a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
</a-input> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'> </a-input>
<template slot="addonAfter"> <a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button> <template slot="addonAfter">
</template> <a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
</a-input> </template>
</a-input-group> </a-input>
</template> </a-input-group>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.enable" }} udp</td>
<td>
<a-form-item>
<a-switch v-model="inbound.settings.udp"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.settings.udp">
<td>IP</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.settings.ip"></a-input>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,4 +1,5 @@
{{define "form/trojan"}} {{define "form/trojan"}}
<a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'> <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}} {{template "form/client"}}
@@ -31,27 +32,54 @@
</a-form> </a-form>
<!-- trojan fallbacks --> <!-- trojan fallbacks -->
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
<a-divider style="margin:0;"> <a-divider style="margin:0;">
Fallback [[ index + 1 ]] fallback[[ index + 1 ]]
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)" <a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/> style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider> </a-divider>
<a-form-item label='SNI'> <table width="100%">
<a-input v-model="fallback.name"></a-input> <tr>
</a-form-item> <td style="width: 20%;">Name</td>
<a-form-item label='ALPN'> <td>
<a-input v-model="fallback.alpn"></a-input> <a-form-item>
</a-form-item> <a-input v-model="fallback.name" style="width: 250px"></a-input>
<a-form-item label='Path'> </a-form-item>
<a-input v-model="fallback.path"></a-input> </td>
</a-form-item> </tr>
<a-form-item label='Dest'> <tr>
<a-input v-model="fallback.dest"></a-input> <td>Alpn</td>
</a-form-item> <td>
<a-form-item label='xVer'> <a-form-item>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number> <a-input v-model="fallback.alpn" style="width: 250px"></a-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>Path</td>
<td>
<a-form-item>
<a-input v-model="fallback.path" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Dest</td>
<td>
<a-form-item>
<a-input v-model="fallback.dest" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>xVer</td>
<td>
<a-form-item>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
<a-divider style="margin:0;"></a-divider> <a-divider style="margin:0;"></a-divider>
</template> </template>

View File

@@ -1,4 +1,5 @@
{{define "form/vless"}} {{define "form/vless"}}
<a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'> <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}} {{template "form/client"}}
@@ -33,27 +34,54 @@
</a-form> </a-form>
<!-- vless fallbacks --> <!-- vless fallbacks -->
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
<a-divider style="margin:0;"> <a-divider style="margin:0;">
Fallback [[ index + 1 ]] fallback[[ index + 1 ]]
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)" <a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/> style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider> </a-divider>
<a-form-item label='SNI'> <table width="100%">
<a-input v-model="fallback.name"></a-input> <tr>
</a-form-item> <td style="width: 20%;">Name</td>
<a-form-item label='ALPN'> <td>
<a-input v-model="fallback.alpn"></a-input> <a-form-item>
</a-form-item> <a-input v-model="fallback.name" style="width: 250px"></a-input>
<a-form-item label='Path'> </a-form-item>
<a-input v-model="fallback.path"></a-input> </td>
</a-form-item> </tr>
<a-form-item label='Dest'> <tr>
<a-input v-model="fallback.dest"></a-input> <td>Alpn</td>
</a-form-item> <td>
<a-form-item label='xVer'> <a-form-item>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number> <a-input v-model="fallback.alpn" style="width: 250px"></a-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>Path</td>
<td>
<a-form-item>
<a-input v-model="fallback.path" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Dest</td>
<td>
<a-form-item>
<a-input v-model="fallback.dest" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>xVer</td>
<td>
<a-form-item>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
<a-divider style="margin:0;"></a-divider> <a-divider style="margin:0;"></a-divider>
</template> </template>

View File

@@ -1,4 +1,5 @@
{{define "form/vmess"}} {{define "form/vmess"}}
<a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'> <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}} {{template "form/client"}}

View File

@@ -1,80 +0,0 @@
{{define "form/wireguard"}}
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync"
@click="[inbound.settings.pubKey, inbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
</a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.settings.secretKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input disabled v-model="inbound.settings.pubKey"></a-input>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="inbound.settings.kernelMode"></a-switch>
</a-form-item>
<a-form-item label="Peers">
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
</a-form-item>
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
Peer [[ index + 1 ]]
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.privateKey"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.publicKey" }}
</template>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.psk" }}
<a-icon @click="peer.psk = Wireguard.keyToBase64(Wireguard.generatePresharedKey())"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.psk"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button>
</template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]">
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input>
</a-form-item>
</a-form>
</a-form>
{{end}}

View File

@@ -1,19 +1,19 @@
{{define "form/sniffing"}} {{define "form/sniffing"}}
<a-divider style="margin:0;"></a-divider> <a-divider style="margin:0;"></a-divider>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item> <a-form-item>
<span slot="label"> <span slot="label">
Sniffing sniffing
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span> <span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
</template> </template>
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</span> </span>
<a-switch v-model="inbound.sniffing.enabled"></a-switch> <a-switch v-model="inbound.sniffing.enabled"></a-switch>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item>
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled"> <a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox> <a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
</a-checkbox-group> </a-checkbox-group>

View File

@@ -1,26 +1,32 @@
{{define "form/externalProxy"}} {{define "form/externalProxy"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-divider style="margin:0;"></a-divider> <a-divider style="margin:0;"></a-divider>
<a-form-item label="External Proxy"> <a-form-item label="External Proxy">
<a-switch v-model="externalProxy"></a-switch> <a-switch v-model="externalProxy"></a-switch>
<a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button> <a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
</a-form-item> </a-form-item>
<a-input-group style="margin: 5px 0;" compact v-for="(row, index) in inbound.stream.externalProxy"> <table width="100%" class="ant-table-tbody" v-if="externalProxy" style="margin-bottom:5px">
<template> <tr style="line-height: 40px;">
<a-tooltip title="Force TLS"> <td width="100%">
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme"> <a-input-group style="margin: 0 5px;" compact v-for="(row, index) in inbound.stream.externalProxy">
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option> <template>
<a-select-option value="none">{{ i18n "none" }}</a-select-option> <a-tooltip title="Force TLS">
<a-select-option value="tls">TLS</a-select-option> <a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
</a-select> <a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
</a-tooltip> <a-select-option value="none">{{ i18n "none" }}</a-select-option>
</template> <a-select-option value="tls">TLS</a-select-option>
<a-input style="width: 35%; border-radius: 0;" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input> </a-select>
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'> </a-tooltip>
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number> </template>
</a-tooltip> <a-input style="width: 35%" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
<a-input style="width: 20%; border-radius: 0;" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input> <a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button> <a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
</a-input-group> </a-tooltip>
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
</a-input-group>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,13 +1,22 @@
{{define "form/streamGRPC"}} {{define "form/streamGRPC"}}
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label="Service Name"> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input> <tr>
</a-form-item> <td>serviceName</td>
<a-form-item label="Authority"> <td>
<a-input v-model.trim="inbound.stream.grpc.authority"></a-input> <a-form-item>
</a-form-item> <a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
<a-form-item label="Multi Mode"> </a-form-item>
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch> </td>
</a-form-item> </tr>
<tr>
<td>MultiMode</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,19 +1,24 @@
{{define "form/streamHTTP"}} {{define "form/streamHTTP"}}
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label='{{ i18n "path" }}'> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.stream.http.path"></a-input> <tr>
</a-form-item> <td>{{ i18n "path" }}</td>
<a-form-item> <td>
<template slot="label">{{ i18n "host" }} <a-form-item>
<a-button size="small" @click="inbound.stream.http.addHost()">+</a-button> <a-input v-model.trim="inbound.stream.http.path" style="width: 250px;"></a-input>
</template> </a-form-item>
<template v-for="(host, index) in inbound.stream.http.host"> </td>
<a-input v-model.trim="inbound.stream.http.host[index]"> </tr>
<a-button size="small" slot="addonAfter" <tr>
@click="inbound.stream.http.removeHost(index)" <td>host</td>
v-if="inbound.stream.http.host.length>1">-</a-button> <td>
</a-input> <a-form-item>
</template> <a-row v-for="(host, index) in inbound.stream.http.host">
</a-form-item> <a-input v-model.trim="inbound.stream.http.host[index]" style="width: 250px;"></a-input>
</a-row>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,26 +0,0 @@
{{define "form/streamHTTPUPGRADE"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="PROXY Protocol">
<a-switch v-model="inbound.stream.httpupgrade.acceptProxyProtocol"></a-switch>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="inbound.stream.httpupgrade.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
</a-form>
{{end}}

View File

@@ -1,48 +1,85 @@
{{define "form/streamKCP"}} {{define "form/streamKCP"}}
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label='{{ i18n "camouflage" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme"> <tr>
<a-select-option value="none">None</a-select-option> <td>{{ i18n "camouflage" }}</td>
<a-select-option value="srtp">SRTP</a-select-option> <td>
<a-select-option value="utp">uTP</a-select-option> <a-form-item>
<a-select-option value="wechat-video">WeChat</a-select-option> <a-select v-model="inbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="dtls">DTLS 1.2</a-select-option> <a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option> <a-select-option value="srtp">srtp (video call)</a-select-option>
<a-select-option value="dns">DNS</a-select-option> <a-select-option value="utp">utp (BT download)</a-select-option>
</a-select> <a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
</a-form-item> <a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
<a-form-item> <a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
<template slot="label"> </a-select>
<a-tooltip> </a-form-item>
<template slot="title"> </td>
<span>{{ i18n "reset" }}</span> </tr>
</template> <tr>
{{ i18n "password" }} <td>{{ i18n "password" }}</td>
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)"type="sync"> </a-icon> <td>
</a-tooltip> <a-form-item>
</template> <a-input v-model="inbound.stream.kcp.seed" style="width: 250px;"></a-input>
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item label='MTU'> </tr>
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number> <tr>
</a-form-item> <td>mtu</td>
<a-form-item label='TTI (ms)'> <td>
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number> <a-form-item>
</a-form-item> <a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
<a-form-item label='Uplink (MB/s)'> </a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number> </td>
</a-form-item> </tr>
<a-form-item label='Downlink (MB/s)'> <tr>
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number> <td>tti (ms)</td>
</a-form-item> <td>
<a-form-item label='Congestion'> <a-form-item>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch> <a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Read Buffer (MB)'> </td>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number> </tr>
</a-form-item> <tr>
<a-form-item label='Write Buffer (MB)'> <td>uplink capacity (MB/S)</td>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number> <td>
</a-form-item> <a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>downlink capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>congestion</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>read buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>write buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,33 +1,41 @@
{{define "form/streamQUIC"}} {{define "form/streamQUIC"}}
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme"> <tr>
<a-select-option value="none">None</a-select-option> <td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option> <td>
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option> <a-form-item>
</a-select> <a-select v-model="inbound.stream.quic.security" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
</a-form-item> <a-select-option value="none">none</a-select-option>
<a-form-item> <a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
<template slot="label"> <a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
<a-tooltip> </a-select>
<template slot="title"> </a-form-item>
<span>{{ i18n "reset" }}</span> </td>
</template> </tr>
{{ i18n "password" }} <tr>
<a-icon @click="inbound.stream.quic.key = RandomUtil.randomSeq(10)"type="sync"> </a-icon> <td>{{ i18n "password" }}</td>
</a-tooltip> <td>
</template> <a-form-item>
<a-input v-model.trim="inbound.stream.quic.key"></a-input> <a-input v-model.trim="inbound.stream.quic.key" style="width: 250px;"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "camouflage" }}'> </td>
<a-select v-model="inbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme"> </tr>
<a-select-option value="none">None</a-select-option> <tr>
<a-select-option value="srtp">SRTP</a-select-option> <td>{{ i18n "camouflage" }}</td>
<a-select-option value="utp">uTP</a-select-option> <td>
<a-select-option value="wechat-video">WeChat</a-select-option> <a-form-item>
<a-select-option value="dtls">DTLS 1.2</a-select-option> <a-select v-model="inbound.stream.quic.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="wireguard">WireGuard</a-select-option> <a-select-option value="none">none (not camouflage)</a-select-option>
</a-select> <a-select-option value="srtp">srtp (video call)</a-select-option>
</a-form-item> <a-select-option value="utp">utp (BT download)</a-select-option>
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,16 +1,15 @@
{{define "form/streamSettings"}} {{define "form/streamSettings"}}
<!-- select stream network --> <!-- select stream network -->
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label="{{ i18n "transmission" }}"> <a-form-item label="{{ i18n "transmission" }}">
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" <a-select v-model="inbound.stream.network" @change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme"> style="width: 150px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option> <a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option> <a-select-option value="kcp">KCP</a-select-option>
<a-select-option value="ws">WebSocket</a-select-option> <a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="http">HTTP/2</a-select-option> <a-select-option value="http">HTTP2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option> <a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option> <a-select-option value="grpc">gRPC</a-select-option>
<a-select-option value="httpupgrade">HttpUpgrade</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-form> </a-form>
@@ -44,12 +43,6 @@
<template v-if="inbound.stream.network === 'grpc'"> <template v-if="inbound.stream.network === 'grpc'">
{{template "form/streamGRPC"}} {{template "form/streamGRPC"}}
</template> </template>
<!-- httpupgrade -->
<template v-if="inbound.stream.network === 'httpupgrade'">
{{template "form/streamHTTPUPGRADE"}}
</template>
<!-- sockopt --> <!-- sockopt -->
<template> <template>
{{template "form/streamSockopt"}} {{template "form/streamSockopt"}}

View File

@@ -1,26 +1,46 @@
{{define "form/streamSockopt"}} {{define "form/streamSockopt"}}
<a-divider style="margin:0;"></a-divider> <a-divider style="margin:0;"></a-divider>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label="TPROXY"> <a-form-item label="Transparent Proxy">
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch> <a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
</a-form-item> </a-form-item>
<template v-if="inbound.stream.sockoptSwitch"> <table width="100%" class="ant-table-tbody" v-if="inbound.stream.sockoptSwitch">
<a-form-item label="PROXY Protocol"> <tr>
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch> <td>Accept Proxy Protocol</td>
</a-form-item> <td>
<a-form-item label="TCP Fast Open"> <a-form-item>
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch> <a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Route Mark"> </td>
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number> </tr>
</a-form-item> <tr>
<a-form-item label="TPROXY"> <td>TCP FastOpen</td>
<a-select v-model="inbound.stream.sockopt.tproxy" :dropdown-class-name="themeSwitcher.currentTheme"> <td>
<a-select-option value="off">Off</a-select-option> <a-form-item>
<a-select-option value="redirect">Redirect</a-select-option> <a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
<a-select-option value="tproxy">TPROXY</a-select-option> </a-form-item>
</a-select> </td>
</a-form-item> </tr>
</template> <tr>
<td>Route Mark</td>
<td>
<a-form-item>
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>T-Proxy</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="off">OFF</a-select-option>
<a-select-option value="redirect">Redirect</a-select-option>
<a-select-option value="tproxy">T-Proxy</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,82 +1,113 @@
{{define "form/streamTCP"}} {{define "form/streamTCP"}}
<!-- tcp type --> <!-- tcp type -->
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label="PROXY Protocol" v-if="inbound.canEnableTls()"> <a-form-item label="Accept Proxy Protocol" v-if="inbound.canEnableTls()">
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch> <a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='HTTP {{ i18n "camouflage" }}'> <a-form-item label="http {{ i18n "camouflage" }}">
<a-switch :checked="inbound.stream.tcp.type === 'http'" <a-switch
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'"> :checked="inbound.stream.tcp.type === 'http'"
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch> </a-switch>
</a-form-item> </a-form-item>
</a-form> </a-form>
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }" <!-- tcp request -->
:wrapper-col="{ md: {span:14} }"> <a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
<!-- tcp request --> <table width="100%" class="ant-table-tbody">
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider> <tr>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'> <td>{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}</td>
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.method" }}'> <a-input v-model.trim="inbound.stream.tcp.request.version" style="width: 200px;"></a-input>
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item> </tr>
<template slot="label">{{ i18n "pages.inbounds.stream.tcp.path" }} <tr>
<a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button> <td>{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}</td>
</template> <td>
<template v-for="(path, index) in inbound.stream.tcp.request.path"> <a-form-item>
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"> <a-input v-model.trim="inbound.stream.tcp.request.method" style="width: 200px;"></a-input>
<a-button size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)" </a-form-item>
v-if="inbound.stream.tcp.request.path.length>1">-</a-button> </td>
</a-input> </tr>
</template> <tr>
</a-form-item> <td style="vertical-align: top; padding-top: 10px;">{{ i18n "pages.inbounds.stream.tcp.requestPath" }}
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'> <a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button>
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('', '')">+</a-button> </td>
</a-form-item> <td>
<a-form-item :wrapper-col="{span:24}"> <a-form-item>
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers"> <a-row v-for="(path, index) in inbound.stream.tcp.request.path">
<a-input style="width: 50%" v-model.trim="header.name" <a-input v-model.trim="inbound.stream.tcp.request.path[index]" style="width: 200px;">
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'> <a-button size="small" slot="addonAfter"
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> @click="inbound.stream.tcp.request.removePath(index)"
</a-input> v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
<a-input style="width: 50%" v-model.trim="header.value" </a-input>
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> </a-row>
<a-button slot="addonAfter" size="small" </a-form-item>
@click="inbound.stream.tcp.request.removeHeader(index)">-</a-button> </td>
</a-input> </tr>
</a-input-group> <tr>
</a-form-item> <td colspan="2" width="100%">
<a-form-item>
<!-- tcp response --> <span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.response" }}</a-divider> <a-button size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.addHeader('', '')">+</a-button>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'> <a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input> <a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
</a-form-item> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.status" }}'> </a-input>
<a-input v-model.trim="inbound.stream.tcp.response.status"></a-input> <a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
</a-form-item> <a-button slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.statusDescription" }}'> </a-input>
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input> </a-input-group>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'> </td>
<a-button size="small" </tr>
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button> <!-- tcp response -->
</a-form-item> <tr>
<a-form-item :wrapper-col="{span:24}"> <td>{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}</td>
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers"> <td>
<a-input style="width: 50%" v-model.trim="header.name" <a-form-item>
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'> <a-input v-model.trim="inbound.stream.tcp.response.version" style="width: 200px;"></a-input>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> </a-form-item>
</a-input> </td>
<a-input style="width: 50%" v-model.trim="header.value" </tr>
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <tr>
<template slot="addonAfter"> <td>{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}</td>
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button> <td>
</template> <a-form-item>
</a-input> <a-input v-model.trim="inbound.stream.tcp.response.status" style="width: 200px;"></a-input>
</a-input-group> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.response.reason" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2" width="100%">
<a-form-item>
<span>{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}:</span>
<a-button size="small" style="margin-left: 10px"
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,18 +1,16 @@
{{define "form/streamWS"}} {{define "form/streamWS"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label="PROXY Protocol"> <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>
<br>
<a-form-item label='{{ i18n "path" }}'> <a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.ws.path"></a-input> <a-input v-model.trim="inbound.stream.ws.path"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "host" }}'> <br>
<a-input v-model.trim="inbound.stream.ws.host"></a-input> <a-form-item style="width: 100%;">
</a-form-item> <span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'> <a-button size="small" style="margin-left: 10px" @click="inbound.stream.ws.addHeader()">+</a-button>
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'> <a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>

View File

@@ -1,135 +1,251 @@
{{define "form/tlsSettings"}} {{define "form/tlsSettings"}}
<!-- tls enable --> <!-- tls enable -->
<a-form v-if="inbound.canEnableTls()" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form v-if="inbound.canEnableTls()" layout="inline">
<a-divider style="margin:0;"></a-divider> <a-divider style="margin:0;"></a-divider>
<a-form-item label='{{ i18n "security" }}'> <table width="100%" class="ant-table-tbody">
<a-radio-group v-model="inbound.stream.security" button-style="solid"> <tr>
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button> <td colspan="2">
<a-radio-button value="tls">TLS</a-radio-button> <a-form-item label='{{ i18n "security" }}'>
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button> <a-radio-group v-model="inbound.stream.security" button-style="solid">
</a-radio-group> <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
</a-form-item> <a-radio-button value="tls">TLS</a-radio-button>
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
</a-radio-group>
</a-form-item>
</td>
</tr>
<!-- tls settings --> <!-- tls settings -->
<template v-if="inbound.stream.isTls"> <template v-if="inbound.stream.isTls">
<a-form-item label="SNI" placeholder="Server Name Indication"> <tr>
<a-input v-model.trim="inbound.stream.tls.sni"></a-input> <td>SNI</td>
</a-form-item> <td>
<a-form-item label="Cipher Suites"> <a-form-item placeholder="Server Name Indication">
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme"> <a-input v-model.trim="inbound.stream.tls.sni" style="width: 250px"></a-input>
<a-select-option value="">Auto</a-select-option> </a-form-item>
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option> </td>
</a-select> </tr>
</a-form-item> <tr>
<a-form-item label="Min/Max Version"> <td>CipherSuites</td>
<a-input-group compact> <td>
<a-select v-model="inbound.stream.tls.minVersion" style="width:100px;" :dropdown-class-name="themeSwitcher.currentTheme"> <a-form-item>
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option> <a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
</a-select> <a-select-option value="">auto</a-select-option>
<a-select v-model="inbound.stream.tls.maxVersion" style="width:100px;" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option> </a-select>
</a-select> </a-form-item>
</a-input-group> </td>
</a-form-item> </tr>
<a-form-item label="uTLS"> <tr>
<a-select v-model="inbound.stream.tls.settings.fingerprint" <td>Min/Max Version</td>
:dropdown-class-name="themeSwitcher.currentTheme"> <td>
<a-select-option value=''>None</a-select-option> <a-form-item>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-input-group compact>
</a-select> <a-select style="width: 125px" v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.currentTheme">
</a-form-item> <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
<a-form-item label="ALPN"> </a-select>
<a-select <a-select style="width: 125px" v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.currentTheme">
mode="multiple" <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
:dropdown-class-name="themeSwitcher.currentTheme" </a-select>
v-model="inbound.stream.tls.alpn"> </a-input-group>
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option> </a-form-item>
</a-select> </td>
</a-form-item> </tr>
<a-form-item label="Allow Insecure"> <tr>
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch> <td>uTLS</td>
</a-form-item> <td>
<a-form-item label="Reject Unknown SNI"> <a-form-item>
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch> <a-select v-model="inbound.stream.tls.settings.fingerprint"
</a-form-item> style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>ALPN</td>
<td>
<a-form-item>
<a-select
mode="multiple"
style="width: 250px"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="inbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Allow insecure</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>Reject Unknown SNI</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
</a-form-item>
</td>
</tr>
<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" }}'> <tr>
<a-radio-group v-model="cert.useFile" button-style="solid"> <td>{{ i18n "certificate" }}</td>
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button> <td>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button> <a-form-item>
</a-radio-group> <a-radio-group v-model="cert.useFile" button-style="solid">
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button> <a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button> <a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
</a-form-item> </a-radio-group>
<template v-if="cert.useFile"> <a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'> <a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'> </tr>
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input> <template v-if="cert.useFile">
</a-form-item> <tr>
<a-form-item label=" "> <td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button> <td>
</a-form-item> <a-form-item>
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyPath" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td></td>
<td>
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
</td>
</tr>
</template> </template>
<template v-else> <template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'> <tr>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input> <td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'> <a-form-item>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input> <a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyContent" }}</td>
<td>
<a-form-item>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
</a-form-item>
</td>
</tr>
</template> </template>
<a-form-item label='OCSP stapling'> <tr>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number> <td>ocspStapling</td>
</a-form-item> <td>
<a-form-item>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
</template> </template>
</template> </template>
<!-- reality settings --> <!-- reality settings -->
<template v-if="inbound.stream.isReality"> <template v-if="inbound.stream.isReality">
<a-form-item label='Show'> <tr>
<a-switch v-model="inbound.stream.reality.show"></a-switch> <td>Show</td>
</a-form-item> <td>
<a-form-item label='Xver'> <a-form-item>
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number> <a-switch v-model="inbound.stream.reality.show"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='uTLS'> </td>
<a-select v-model="inbound.stream.reality.settings.fingerprint" </tr>
:dropdown-class-name="themeSwitcher.currentTheme"> <tr>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <td>Xver</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-form-item label='Dest'> <a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
<a-input v-model.trim="inbound.stream.reality.dest"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item label='SNI'> </tr>
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input> <tr>
</a-form-item> <td>uTLS</td>
<a-form-item> <td>
<template slot="label"> <a-form-item>
<a-tooltip> <a-select v-model="inbound.stream.reality.settings.fingerprint"
<template slot="title"> style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
<span>{{ i18n "reset" }}</span> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</template> </a-select>
Short IDs </a-form-item>
</td>
</tr>
<tr>
<td>Dest</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Server Names</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Short Ids
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId().join(',')" type="sync"> <a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId().join(',')" type="sync">
</a-icon> </td>
</template> <td>
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input> <a-form-item>
</a-form-item> <a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
<a-form-item label='SpiderX'> </a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input> </td>
</a-form-item> </tr>
<a-form-item label='Private Key'> <tr>
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input> <td>SpiderX</td>
</a-form-item> <td>
<a-form-item label='Public Key'> <a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input> <a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label=" "> </td>
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button> </tr>
</a-form-item> <tr>
<td>Private Key</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Public Key</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td></td>
<td>
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button>
</td>
</tr>
</template> </template>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -54,9 +54,7 @@
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template> <template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
<template v-else-if="isClientOnline(client.email)">{{ i18n "online" }}</template> <template v-else-if="isClientOnline(client.email)">{{ i18n "online" }}</template>
</template> </template>
<a-badge <a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
:class="isClientOnline(client.email)? 'online-animation' : ''"
:color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
</a-badge> </a-badge>
</a-tooltip> </a-tooltip>
[[ client.email ]] [[ client.email ]]
@@ -88,12 +86,13 @@
<td width="120px" v-else-if="client.totalGB > 0"> <td width="120px" v-else-if="client.totalGB > 0">
<a-progress :stroke-color="clientStatsColor(record, client.email)" <a-progress :stroke-color="clientStatsColor(record, client.email)"
:show-info="false" :show-info="false"
:status="isClientEnabled(record, client.email)? 'exception' : ''" :status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
:percent="statsProgress(record, client.email)"/> :percent="statsProgress(record, client.email)"/>
</td> </td>
<td width="120px" v-else class="infinite-bar"> <td width="120px" v-else class="infinite-bar">
<a-progress <a-progress
:show-info="false" :show-info="false"
:status="isClientOnline(client.email)? 'active' : ''"
:percent="100"></a-progress> :percent="100"></a-progress>
</td> </td>
<td width="60px"> <td width="60px">
@@ -118,7 +117,7 @@
</td> </td>
<td width="120px" class="infinite-bar"> <td width="120px" class="infinite-bar">
<a-progress :show-info="false" <a-progress :show-info="false"
:status="isClientEnabled(record, client.email)? 'exception' : ''" :status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
:percent="expireProgress(client.expiryTime, client.reset)"/> :percent="expireProgress(client.expiryTime, client.reset)"/>
</td> </td>
<td width="60px">[[ client.reset + "d" ]]</td> <td width="60px">[[ client.reset + "d" ]]</td>
@@ -203,13 +202,14 @@
</template> </template>
<a-progress :stroke-color="clientStatsColor(record, client.email)" <a-progress :stroke-color="clientStatsColor(record, client.email)"
:show-info="false" :show-info="false"
:status="isClientEnabled(record, client.email)? 'exception' : ''" :status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
:percent="statsProgress(record, client.email)"/> :percent="statsProgress(record, client.email)"/>
</a-popover> </a-popover>
</td> </td>
<td width="120px" v-else class="infinite-bar"> <td width="120px" v-else class="infinite-bar">
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'" <a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'"
:show-info="false" :show-info="false"
:status="isClientOnline(client.email)? 'active' : ''"
:percent="100"></a-progress> :percent="100"></a-progress>
</td> </td>
<td width="80px"> <td width="80px">
@@ -235,7 +235,7 @@
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span> <span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
</template> </template>
<a-progress :show-info="false" <a-progress :show-info="false"
:status="isClientEnabled(record, client.email)? 'exception' : ''" :status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
:percent="expireProgress(client.expiryTime, client.reset)"/> :percent="expireProgress(client.expiryTime, client.reset)"/>
</a-popover> </a-popover>
</td> </td>

View File

@@ -7,40 +7,28 @@
width="600px" width="600px"
:class="themeSwitcher.currentTheme" :class="themeSwitcher.currentTheme"
> >
<a-row> <table style="margin-bottom: 10px; width: 100%;">
<a-col :xs="24" :md="12"> <tr><td>
<table> <table>
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="purple">[[ dbInbound.protocol ]]</a-tag></td></tr> <tr><td>{{ i18n "protocol" }}</td><td><a-tag color="purple">[[ dbInbound.protocol ]]</a-tag></td></tr>
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td> <tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag>[[ dbInbound.address ]]</a-tag></td></tr>
<a-tooltip :title="[[ dbInbound.address ]]">
<a-tag class="info-large-tag">[[ dbInbound.address ]]</a-tag>
</a-tooltip>
</td></tr>
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag>[[ dbInbound.port ]]</a-tag></td></tr> <tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag>[[ dbInbound.port ]]</a-tag></td></tr>
</table> </table>
</a-col> </td>
<a-col :xs="24" :md="12"> <td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<table> <table>
<tr> <tr>
<td>{{ i18n "transmission" }}</td><td><a-tag color="blue">[[ inbound.network ]]</a-tag></td> <td>{{ i18n "transmission" }}</td><td><a-tag color="blue">[[ inbound.network ]]</a-tag></td>
</tr> </tr>
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade"> <template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
<tr> <tr>
<td>{{ i18n "host" }}</td> <td>{{ i18n "host" }}</td>
<td v-if="inbound.host"> <td v-if="inbound.host"><a-tag>[[ inbound.host ]]</a-tag></td>
<a-tooltip :title="[[ inbound.host ]]">
<a-tag class="info-large-tag">[[ inbound.host ]]</a-tag>
</a-tooltip>
</td>
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr> <td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
</tr> </tr>
<tr> <tr>
<td>{{ i18n "path" }}</td> <td>{{ i18n "path" }}</td>
<td v-if="inbound.path"> <td v-if="inbound.path"><a-tag>[[ inbound.path ]]</a-tag></td>
<a-tooltip :title="[[ inbound.path ]]">
<a-tag class="info-large-tag">[[ inbound.path ]]</a-tag>
</a-tooltip>
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td> <td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
</tr> </tr>
</template> </template>
@@ -57,35 +45,30 @@
</template> </template>
<template v-if="inbound.isGrpc"> <template v-if="inbound.isGrpc">
<tr><td>grpc serviceName</td><td> <tr><td>grpc serviceName</td><td><a-tag>[[ inbound.serviceName ]]</a-tag></td></tr>
<a-tooltip :title="[[ inbound.serviceName ]]">
<a-tag class="info-large-tag">[[ inbound.serviceName ]]</a-tag>
</a-tooltip>
<tr><td>grpc multiMode</td><td><a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr> <tr><td>grpc multiMode</td><td><a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
</template> </template>
</table> </table>
</template> </td></tr>
</a-col> <tr colspan="2" v-if="dbInbound.hasLink()">
<template v-if="dbInbound.hasLink()"> <td>
{{ i18n "security" }} {{ i18n "security" }}
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag> <a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
<br /> <br />
<template v-if="inbound.stream.security != 'none'"> <template v-if="inbound.stream.security != 'none'">
{{ i18n "domainName" }} {{ i18n "domainName" }}
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</template> </template>
</template> </td>
</tr>
</table>
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;"> <table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
<tr> <tr>
<td>{{ i18n "encryption" }}</td> <td>{{ i18n "encryption" }}</td>
<td><a-tag color="blue">[[ inbound.settings.method ]]</a-tag></td> <td><a-tag color="blue">[[ inbound.settings.method ]]</a-tag></td>
</tr><tr v-if="inbound.isSS2022"> </tr><tr v-if="inbound.isSS2022">
<td>{{ i18n "password" }}</td> <td>{{ i18n "password" }}</td>
<td> <td><a-tag>[[ inbound.settings.password ]]</a-tag></td>
<a-tooltip :title="[[ inbound.settings.password ]]">
<a-tag class="info-large-tag">[[ inbound.settings.password ]]</a-tag>
</a-tooltip>
</td>
</tr><tr> </tr><tr>
<td>{{ i18n "pages.inbounds.network" }}</td> <td>{{ i18n "pages.inbounds.network" }}</td>
<td><a-tag color="blue">[[ inbound.settings.network ]]</a-tag></td> <td><a-tag color="blue">[[ inbound.settings.network ]]</a-tag></td>
@@ -107,12 +90,8 @@
<td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td> <td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td>
</tr> </tr>
<tr v-if="infoModal.clientSettings.password"> <tr v-if="infoModal.clientSettings.password">
<td>{{ i18n "password" }}</td> <td>Password</td>
<td> <td><a-tag>[[ infoModal.clientSettings.password ]]</a-tag></td>
<a-tooltip :title="[[ infoModal.clientSettings.password ]]">
<a-tag class="info-large-tag">[[ infoModal.clientSettings.password ]]</a-tag>
</a-tooltip>
</td>
</tr> </tr>
<tr> <tr>
<td>{{ i18n "status" }}</td> <td>{{ i18n "status" }}</td>
@@ -139,7 +118,7 @@
<tr> <tr>
<td> <td>
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> <a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
[[ getRemStats() ]] [[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
</a-tag> </a-tag>
</td> </td>
<td> <td>
@@ -160,10 +139,10 @@
</tr> </tr>
</table> </table>
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId"> <template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
<a-divider>Subscription URL</a-divider> <a-divider>Subscription link</a-divider>
<a-row> <a-row>
<a-col :sx="24" :md="22">SUB: <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col> <a-col :span="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
<a-col :sx="24" :md="2" style="text-align: right;"> <a-col :span="2">
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"> <button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
<a-icon type="snippets"></a-icon> <a-icon type="snippets"></a-icon>
@@ -171,22 +150,12 @@
</a-tooltip> </a-tooltip>
</a-col> </a-col>
</a-row> </a-row>
<a-row>
<a-col :sx="24" :md="22">JSON: <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a></a-col>
<a-col :sx="24" :md="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)">
<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 ID</a-divider> <a-divider>Telegram ID</a-divider>
<a-row> <a-row>
<a-col :sx="24" :md="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col> <a-col :span="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
<a-col :sx="24" :md="2" style="text-align: right;"> <a-col :span="2">
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)"> <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> <a-icon type="snippets"></a-icon>
@@ -198,8 +167,8 @@
<template v-if="dbInbound.hasLink()"> <template v-if="dbInbound.hasLink()">
<a-divider>URL</a-divider> <a-divider>URL</a-divider>
<a-row v-for="(link,index) in infoModal.links"> <a-row v-for="(link,index) in infoModal.links">
<a-col :sx="24" :md="22"><a-tag color="blue">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col> <a-col :span="22"><a-tag color="blue">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
<a-col :sx="24" :md="2" style="text-align: right;"> <a-col :span="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'> <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)"> <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> <a-icon type="snippets"></a-icon>
@@ -270,71 +239,6 @@
<td><a-tag color="blue">[[ account.pass ]]</a-tag></td> <td><a-tag color="blue">[[ account.pass ]]</a-tag></td>
</tr> </tr>
</table> </table>
<table v-if="dbInbound.isWireguard" style="margin-bottom: 10px; width: 100%;">
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
<td>[[ inbound.settings.secretKey ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
<td>[[ inbound.settings.pubKey ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>MTU</td>
<td>[[ inbound.settings.mtu ]]</td>
</tr>
<tr>
<td>Kernel Mode</td>
<td>[[ inbound.settings.kernelMode ]]</td>
</tr>
<template v-for="(peer, index) in inbound.settings.peers">
<tr>
<td colspan="2"><a-divider>Peer [[ index + 1 ]]</a-divider></td>
</tr>
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
<td>[[ peer.privateKey ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
<td>[[ peer.publicKey ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
<td>[[ peer.psk ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
<td>[[ peer.allowedIPs.join(",") ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>Keep Alive</td>
<td>[[ peer.keepAlive ]]</td>
</tr>
<tr>
<td colspan="2">
<a-row>
<a-col :span="22" style="overflow-wrap: anywhere;">
<a-tag color="blue">Config</a-tag>
<div
v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)"
style="border-radius: 1rem; padding: 0.5rem;"
class="client-table-odd-row"></div>
</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, infoModal.links[index])">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip>
</a-col>
</a-row>
</td>
</tr>
</table>
</template>
</template> </template>
</a-modal> </a-modal>
<script> <script>
@@ -351,7 +255,6 @@
index: null, index: null,
isExpired: false, isExpired: false,
subLink: '', subLink: '',
subJsonLink: '',
tgLink: '', tgLink: '',
show(dbInbound, index) { show(dbInbound, index) {
this.index = index; this.index = index;
@@ -360,15 +263,10 @@
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null; this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry; this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : []; this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
if (this.inbound.protocol == Protocols.WIREGUARD){ this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
} else {
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
}
if (this.clientSettings) { if (this.clientSettings) {
if (this.clientSettings.subId) { if (this.clientSettings.subId) {
this.subLink = this.genSubLink(this.clientSettings.subId); this.subLink = this.genSubLink(this.clientSettings.subId);
this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
} }
if (this.clientSettings.tgId) { if (this.clientSettings.tgId) {
this.tgLink = "https://t.me/" + this.clientSettings.tgId; this.tgLink = "https://t.me/" + this.clientSettings.tgId;
@@ -381,9 +279,6 @@
}, },
genSubLink(subID) { genSubLink(subID) {
return app.subSettings.subURI+subID+'?name='+subID; return app.subSettings.subURI+subID+'?name='+subID;
},
genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID+'?name='+subID;;
} }
}; };
@@ -423,11 +318,7 @@
}, },
statsColor(stats) { statsColor(stats) {
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total); return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
}, }
getRemStats() {
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down;
return remained>0 ? sizeFormat(remained) : '-';
},
}, },
}); });

View File

@@ -10,7 +10,7 @@
title: '', title: '',
visible: false, visible: false,
confirmLoading: false, confirmLoading: false,
okText: '{{ i18n "confirm" }}', okText: '{{ i18n "sure" }}',
isEdit: false, isEdit: false,
confirm: null, confirm: null,
inbound: new Inbound(), inbound: new Inbound(),
@@ -18,7 +18,7 @@
ok() { ok() {
ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound); ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound);
}, },
show({ title='', okText='{{ i18n "confirm" }}', inbound=null, dbInbound=null, confirm=(inbound, dbInbound)=>{}, isEdit=false }) { show({ title='', okText='{{ i18n "sure" }}', inbound=null, dbInbound=null, confirm=(inbound, dbInbound)=>{}, isEdit=false }) {
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;
if (inbound) { if (inbound) {
@@ -39,7 +39,7 @@
inModal.visible = false; inModal.visible = false;
inModal.loading(false); inModal.loading(false);
}, },
loading(loading=true) { loading(loading) {
inModal.confirmLoading = loading; inModal.confirmLoading = loading;
}, },
}; };

View File

@@ -43,10 +43,6 @@
0%, 50%, 100% { transform: scale(1); opacity: 1; } 0%, 50%, 100% { transform: scale(1); opacity: 1; }
10% { transform: scale(1.5); opacity: .2; } 10% { transform: scale(1.5); opacity: .2; }
} }
.info-large-tag {
max-width: 200px;
overflow: hidden;
}
</style> </style>
<body> <body>
@@ -68,15 +64,15 @@
<a-card hoverable> <a-card hoverable>
<a-row> <a-row>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :xs="24" :sm="24" :lg="12">
<strong>{{ i18n "pages.inbounds.totalDownUp" }}:</strong> {{ i18n "pages.inbounds.totalDownUp" }}:
<a-tag color="blue">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag> <a-tag color="blue">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
</a-col> </a-col>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :xs="24" :sm="24" :lg="12">
<strong>{{ i18n "pages.inbounds.totalUsage" }}:</strong> {{ i18n "pages.inbounds.totalUsage" }}:
<a-tag color="blue">[[ sizeFormat(total.up + total.down) ]]</a-tag> <a-tag color="blue">[[ sizeFormat(total.up + total.down) ]]</a-tag>
</a-col> </a-col>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :xs="24" :sm="24" :lg="12">
<strong>{{ i18n "pages.inbounds.inboundCount" }}:</strong> {{ i18n "pages.inbounds.inboundCount" }}:
<a-tag color="blue">[[ dbInbounds.length ]]</a-tag> <a-tag color="blue">[[ dbInbounds.length ]]</a-tag>
</a-col> </a-col>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :xs="24" :sm="24" :lg="12">
@@ -84,7 +80,7 @@
<div> <div>
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"> <a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
</a-back-top> </a-back-top>
<strong>{{ i18n "clients" }}:</strong> {{ i18n "clients" }}:
<a-tag color="blue">[[ total.clients ]]</a-tag> <a-tag color="blue">[[ total.clients ]]</a-tag>
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
@@ -137,10 +133,6 @@
<a-icon type="export"></a-icon> <a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export" }} {{ i18n "pages.inbounds.export" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="subs" v-if="subSettings.enable">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
</a-menu-item>
<a-menu-item key="resetInbounds"> <a-menu-item key="resetInbounds">
<a-icon type="reload"></a-icon> <a-icon type="reload"></a-icon>
{{ i18n "pages.inbounds.resetAllTraffic" }} {{ i18n "pages.inbounds.resetAllTraffic" }}
@@ -149,7 +141,7 @@
<a-icon type="file-done"></a-icon> <a-icon type="file-done"></a-icon>
{{ i18n "pages.inbounds.resetAllClientTraffics" }} {{ i18n "pages.inbounds.resetAllClientTraffics" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;"> <a-menu-item key="delDepletedClients">
<a-icon type="rest"></a-icon> <a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }} {{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item> </a-menu-item>
@@ -168,9 +160,9 @@
</a-col> </a-col>
</a-row> </a-row>
</div> </div>
<div :style="isMobile ? '' : 'display: flex; align-items: center; justify-content: flex-start;'"> <div style="display: flex; align-items: center; justify-content: flex-start;">
<a-switch v-model="enableFilter" <a-switch v-model="enableFilter"
:style="isMobile ? 'margin-bottom: .5rem; display: flex;' : 'margin-right: .5rem;'" style="margin-right: .5rem;"
@change="toggleFilter"> @change="toggleFilter">
<a-icon slot="checkedChildren" type="search"></a-icon> <a-icon slot="checkedChildren" type="search"></a-icon>
<a-icon slot="unCheckedChildren" type="filter"></a-icon> <a-icon slot="unCheckedChildren" type="filter"></a-icon>
@@ -185,7 +177,7 @@
</a-radio-group> </a-radio-group>
</div> </div>
<a-back-top></a-back-top> <a-back-top></a-back-top>
<a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id" <a-table :columns="isMobile ? mobileColums : columns" :row-key="dbInbound => dbInbound.id"
:data-source="searchedInbounds" :data-source="searchedInbounds"
:scroll="isMobile ? {} : { x: 1000 }" :scroll="isMobile ? {} : { x: 1000 }"
:pagination=pagination(searchedInbounds) :pagination=pagination(searchedInbounds)
@@ -203,7 +195,7 @@
<a-icon type="edit"></a-icon> <a-icon type="edit"></a-icon>
{{ i18n "edit" }} {{ i18n "edit" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard"> <a-menu-item key="qrcode" v-if="dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser">
<a-icon type="qrcode"></a-icon> <a-icon type="qrcode"></a-icon>
{{ i18n "qrCode" }} {{ i18n "qrCode" }}
</a-menu-item> </a-menu-item>
@@ -224,11 +216,7 @@
<a-icon type="export"></a-icon> <a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export"}} {{ i18n "pages.inbounds.export"}}
</a-menu-item> </a-menu-item>
<a-menu-item key="subs" v-if="subSettings.enable"> <a-menu-item key="delDepletedClients">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
</a-menu-item>
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
<a-icon type="rest"></a-icon> <a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }} {{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item> </a-menu-item>
@@ -244,7 +232,7 @@
</a-menu-item> </a-menu-item>
<a-menu-item key="clipboard"> <a-menu-item key="clipboard">
<a-icon type="copy"></a-icon> <a-icon type="copy"></a-icon>
{{ i18n "pages.inbounds.exportInbound" }} {{ i18n "pages.inbounds.copyToClipboard" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="clone"> <a-menu-item key="clone">
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}} <a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
@@ -255,7 +243,7 @@
</span> </span>
</a-menu-item> </a-menu-item>
<a-menu-item v-if="isMobile"> <a-menu-item v-if="isMobile">
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch> <a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
{{ i18n "pages.inbounds.enable" }} {{ i18n "pages.inbounds.enable" }}
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
@@ -322,7 +310,7 @@
</a-popover> </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,dbInbound.enable)"></a-switch> <a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
</template> </template>
<template slot="expiryTime" slot-scope="text, dbInbound"> <template slot="expiryTime" slot-scope="text, dbInbound">
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme"> <a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
@@ -502,7 +490,7 @@
scopedSlots: { customRender: 'expiryTime' }, scopedSlots: { customRender: 'expiryTime' },
}]; }];
const mobileColumns = [{ const mobileColums = [{
title: "ID", title: "ID",
align: 'right', align: 'right',
dataIndex: "id", dataIndex: "id",
@@ -526,73 +514,19 @@
}]; }];
const innerColumns = [ const innerColumns = [
{ { title: '{{ i18n "pages.inbounds.operate" }}', width: 50, scopedSlots: { customRender: 'actions' } },
title: '{{ i18n "pages.inbounds.operate" }}', { title: '{{ i18n "pages.inbounds.enable" }}', width: 20, scopedSlots: { customRender: 'enable' } },
width: 50, { title: '{{ i18n "online" }}', width: 20, scopedSlots: { customRender: 'online' } },
scopedSlots: { customRender: 'actions' }, { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
}, { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
{ { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
title: '{{ i18n "pages.inbounds.enable" }}', ];
width: 20,
scopedSlots: { customRender: 'enable' },
},
{
title: '{{ i18n "online" }}',
width: 20,
scopedSlots: { customRender: 'online' },
},
{
title: '{{ i18n "pages.inbounds.client" }}',
dataIndex: 'email',
width: 70,
scopedSlots: { customRender: 'client' },
sorter: (a, b) => {
const clientA = a.email || '';
const clientB = b.email || '';
return clientA.localeCompare(clientB, undefined, { sensitivity: 'base' });
},
},
{
title: '{{ i18n "pages.inbounds.traffic" }}',
dataIndex: 'traffic',
width: 80,
align: 'center',
scopedSlots: { customRender: 'traffic' },
},
{
title: '{{ i18n "pages.inbounds.expireDate" }}',
width: 70,
align: 'center',
scopedSlots: { customRender: 'expiryTime' },
},
];
const innerMobileColumns = [ const innerMobileColumns = [
{ { title: '{{ i18n "pages.inbounds.operate" }}', width: 10, align: 'center', scopedSlots: { customRender: 'actionMenu' } },
title: '{{ i18n "pages.inbounds.operate" }}', { title: '{{ i18n "pages.inbounds.client" }}', width: 90, align: 'left', scopedSlots: { customRender: 'client' } },
width: 10, { title: '{{ i18n "pages.inbounds.info" }}', width: 10, align: 'center', scopedSlots: { customRender: 'info' } },
align: 'center', ];
scopedSlots: { customRender: 'actionMenu' },
},
{
title: '{{ i18n "pages.inbounds.client" }}',
dataIndex: 'email',
width: 90,
align: 'left',
scopedSlots: { customRender: 'client' },
sorter: (a, b) => {
const clientA = a.email || '';
const clientB = b.email || '';
return clientA.localeCompare(clientB, undefined, { sensitivity: 'base' });
},
},
{
title: '{{ i18n "pages.inbounds.info" }}',
width: 10,
align: 'center',
scopedSlots: { customRender: 'info' },
},
];
const app = new Vue({ const app = new Vue({
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],
@@ -618,8 +552,7 @@
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000, refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
subSettings: { subSettings: {
enable : false, enable : false,
subURI : '', subURI : ''
subJsonURI : '',
}, },
remarkModel: '-ieo', remarkModel: '-ieo',
tgBotEnable: false, tgBotEnable: false,
@@ -664,8 +597,7 @@
this.tgBotEnable = tgBotEnable; this.tgBotEnable = tgBotEnable;
this.subSettings = { this.subSettings = {
enable : subEnable, enable : subEnable,
subURI: subURI, subURI: subURI
subJsonURI: subJsonURI
}; };
this.pageSize = pageSize; this.pageSize = pageSize;
this.remarkModel = remarkModel; this.remarkModel = remarkModel;
@@ -702,12 +634,8 @@
clientCount = clients.length; clientCount = clients.length;
if (dbInbound.enable) { if (dbInbound.enable) {
clients.forEach(client => { clients.forEach(client => {
if (client.enable) { client.enable ? active.push(client.email) : deactive.push(client.email);
active.push(client.email); if(this.isClientOnline(client.email)) online.push(client.email);
if(this.isClientOnline(client.email)) online.push(client.email);
} else {
deactive.push(client.email);
}
}); });
clientStats.forEach(client => { clientStats.forEach(client => {
if (!client.enable) { if (!client.enable) {
@@ -795,9 +723,6 @@
case "export": case "export":
this.exportAllLinks(); this.exportAllLinks();
break; break;
case "subs":
this.exportAllSubs();
break;
case "resetInbounds": case "resetInbounds":
this.resetAllTraffic(); this.resetAllTraffic();
break; break;
@@ -829,9 +754,6 @@
case "export": case "export":
this.inboundLinks(dbInbound.id); this.inboundLinks(dbInbound.id);
break; break;
case "subs":
this.exportSubs(dbInbound.id);
break;
case "clipboard": case "clipboard":
this.copyToClipboard(dbInbound.id); this.copyToClipboard(dbInbound.id);
break; break;
@@ -858,7 +780,9 @@
okText: '{{ i18n "pages.inbounds.create"}}', okText: '{{ i18n "pages.inbounds.create"}}',
cancelText: '{{ i18n "close" }}', cancelText: '{{ i18n "close" }}',
confirm: async (inbound, dbInbound) => { confirm: async (inbound, dbInbound) => {
await this.addInbound(inbound, dbInbound, inModal); inModal.loading();
await this.addInbound(inbound, dbInbound);
inModal.close();
}, },
isEdit: false isEdit: false
}); });
@@ -873,7 +797,9 @@
inbound: inbound, inbound: inbound,
dbInbound: dbInbound, dbInbound: dbInbound,
confirm: async (inbound, dbInbound) => { confirm: async (inbound, dbInbound) => {
inModal.loading();
await this.updateInbound(inbound, dbInbound); await this.updateInbound(inbound, dbInbound);
inModal.close();
}, },
isEdit: true isEdit: true
}); });
@@ -906,8 +832,8 @@
port: RandomUtil.randomIntRange(10000, 60000), port: RandomUtil.randomIntRange(10000, 60000),
protocol: baseInbound.protocol, protocol: baseInbound.protocol,
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(), settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
streamSettings: baseInbound.stream.toString(), streamSettings: baseInbound.stream.toString(),
sniffing: baseInbound.sniffing.toString(), sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
}; };
await this.submit('/xui/inbound/add', data, inModal); await this.submit('/xui/inbound/add', data, inModal);
}, },
@@ -926,7 +852,7 @@
settings: inbound.settings.toString(), settings: inbound.settings.toString(),
}; };
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString(); if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
data.sniffing = inbound.sniffing.toString(); if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
await this.submit('/xui/inbound/add', data, inModal); await this.submit('/xui/inbound/add', data, inModal);
}, },
@@ -945,7 +871,7 @@
settings: inbound.settings.toString(), settings: inbound.settings.toString(),
}; };
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString(); if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
data.sniffing = inbound.sniffing.toString(); if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal); await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
}, },
@@ -956,7 +882,9 @@
okText: '{{ i18n "pages.client.submitAdd"}}', okText: '{{ i18n "pages.client.submitAdd"}}',
dbInbound: dbInbound, dbInbound: dbInbound,
confirm: async (clients, dbInboundId) => { confirm: async (clients, dbInboundId) => {
await this.addClient(clients, dbInboundId, clientModal); clientModal.loading();
await this.addClient(clients, dbInboundId);
clientModal.close();
}, },
isEdit: false isEdit: false
}); });
@@ -968,7 +896,9 @@
okText: '{{ i18n "pages.client.bulk"}}', okText: '{{ i18n "pages.client.bulk"}}',
dbInbound: dbInbound, dbInbound: dbInbound,
confirm: async (clients, dbInboundId) => { confirm: async (clients, dbInboundId) => {
await this.addClient(clients, dbInboundId, clientsBulkModal); clientsBulkModal.loading();
await this.addClient(clients, dbInboundId);
clientsBulkModal.close();
}, },
}); });
}, },
@@ -997,19 +927,19 @@
default: return clients.findIndex(item => item.id === client.id && item.email === client.email); default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
} }
}, },
async addClient(clients, dbInboundId, modal) { async addClient(clients, dbInboundId) {
const data = { const data = {
id: dbInboundId, id: dbInboundId,
settings: '{"clients": [' + clients.toString() + ']}', settings: '{"clients": [' + clients.toString() + ']}',
}; };
await this.submit(`/xui/inbound/addClient`, data, modal); await this.submit(`/xui/inbound/addClient`, data);
}, },
async updateClient(client, dbInboundId, clientId) { async updateClient(client, dbInboundId, clientId) {
const data = { const data = {
id: dbInboundId, id: dbInboundId,
settings: '{"clients": [' + client.toString() + ']}', settings: '{"clients": [' + client.toString() + ']}',
}; };
await this.submit(`/xui/inbound/updateClient/${clientId}`, data, clientModal); await this.submit(`/xui/inbound/updateClient/${clientId}`, data);
}, },
resetTraffic(dbInboundId) { resetTraffic(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
@@ -1096,9 +1026,8 @@
newDbInbound = this.checkFallback(dbInbound); newDbInbound = this.checkFallback(dbInbound);
infoModal.show(newDbInbound, index); infoModal.show(newDbInbound, index);
}, },
switchEnable(dbInboundId,state) { switchEnable(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
dbInbound.enable = state;
this.submit(`/xui/inbound/update/${dbInboundId}`, dbInbound); this.submit(`/xui/inbound/update/${dbInboundId}`, dbInbound);
}, },
async switchEnableClient(dbInboundId, client) { async switchEnableClient(dbInboundId, client) {
@@ -1112,8 +1041,8 @@
await this.updateClient(clients[index], dbInboundId, clientId); await this.updateClient(clients[index], dbInboundId, clientId);
this.loading(false); this.loading(false);
}, },
async submit(url, data, modal) { async submit(url, data) {
const msg = await HttpUtil.postWithModal(url, data, modal); const msg = await HttpUtil.postWithModal(url, data);
if (msg.success) { if (msg.success) {
await this.getDBInbounds(); await this.getDBInbounds();
} }
@@ -1160,7 +1089,7 @@
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}', title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}', content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
class: themeSwitcher.currentTheme, class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}', okText: '{{ i18n "reset"}}',
cancelText: '{{ i18n "cancel"}}', cancelText: '{{ i18n "cancel"}}',
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId), onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
}) })
@@ -1248,22 +1177,6 @@
newDbInbound = this.checkFallback(dbInbound); newDbInbound = this.checkFallback(dbInbound);
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(this.remarkModel), newDbInbound.remark); txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(this.remarkModel), newDbInbound.remark);
}, },
exportSubs(dbInboundId) {
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const clients = this.getInboundClients(dbInbound);
let subLinks = []
if (clients != null){
clients.forEach(c => {
if (c.subId && c.subId.length>0){
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
}
})
}
txtModal.show(
'{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
[...new Set(subLinks)].join('\n'),
dbInbound.remark + "-Subs");
},
importInbound() { importInbound() {
promptModal.open({ promptModal.open({
title: '{{ i18n "pages.inbounds.importInbound" }}', title: '{{ i18n "pages.inbounds.importInbound" }}',
@@ -1272,27 +1185,11 @@
okText: '{{ i18n "pages.inbounds.import" }}', okText: '{{ i18n "pages.inbounds.import" }}',
confirm: async (dbInboundText) => { confirm: async (dbInboundText) => {
await this.submit('/xui/inbound/import', {data: dbInboundText}, promptModal); await this.submit('/xui/inbound/import', {data: dbInboundText}, promptModal);
promptModal.close();
}, },
}); });
}, },
exportAllSubs() { exportAllLinks() {
let subLinks = []
for (const dbInbound of this.dbInbounds) {
const clients = this.getInboundClients(dbInbound);
if (clients != null){
clients.forEach(c => {
if (c.subId && c.subId.length>0){
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
}
})
}
}
txtModal.show(
'{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
[...new Set(subLinks)].join('\r\n'),
'All-Inbounds-Subs');
},
exportAllLinks() {
let copyText = []; let copyText = [];
for (const dbInbound of this.dbInbounds) { for (const dbInbound of this.dbInbounds) {
copyText.push(dbInbound.genInboundLinks(this.remarkModel)); copyText.push(dbInbound.genInboundLinks(this.remarkModel));

View File

@@ -44,14 +44,14 @@
<a-progress type="dashboard" status="normal" <a-progress type="dashboard" status="normal"
:stroke-color="status.cpu.color" :stroke-color="status.cpu.color"
:percent="status.cpu.percent"></a-progress> :percent="status.cpu.percent"></a-progress>
<div><strong>CPU:</strong> [[ cpuCoreFormat(status.cpuCount) ]]</div> <div>CPU: ([[ status.cpuCount ]]core)</div>
</a-col> </a-col>
<a-col :span="12" style="text-align: center"> <a-col :span="12" style="text-align: center">
<a-progress type="dashboard" status="normal" <a-progress type="dashboard" status="normal"
:stroke-color="status.mem.color" :stroke-color="status.mem.color"
:percent="status.mem.percent"></a-progress> :percent="status.mem.percent"></a-progress>
<div> <div>
<strong>{{ i18n "pages.index.memory"}}:</strong> [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]] {{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
</div> </div>
</a-col> </a-col>
</a-row> </a-row>
@@ -63,7 +63,7 @@
:stroke-color="status.swap.color" :stroke-color="status.swap.color"
:percent="status.swap.percent"></a-progress> :percent="status.swap.percent"></a-progress>
<div> <div>
<strong>Swap:</strong> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]] Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
</div> </div>
</a-col> </a-col>
<a-col :span="12" style="text-align: center"> <a-col :span="12" style="text-align: center">
@@ -71,7 +71,7 @@
:stroke-color="status.disk.color" :stroke-color="status.disk.color"
:percent="status.disk.percent"></a-progress> :percent="status.disk.percent"></a-progress>
<div> <div>
<strong>{{ i18n "pages.index.hard"}}:</strong> [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]] {{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
</div> </div>
</a-col> </a-col>
</a-row> </a-row>
@@ -84,100 +84,85 @@
<a-row> <a-row>
<a-col :sm="24" :md="12"> <a-col :sm="24" :md="12">
<a-card hoverable> <a-card hoverable>
<strong>{{ i18n "pages.inbounds.stream.tcp.version" }}:</strong> X-UI: <a href="https://github.com/alireza0/x-ui/releases" target="_blank"><a-tag color="blue">{{ .cur_ver }}</a-tag></a>
<a href="https://github.com/alireza0/x-ui/releases" target="_blank"> Xray:
<a-tag color="purple" style="cursor: pointer;">X-UI {{ .cur_ver }}</a-tag>
</a>
<a-tooltip title='{{ i18n "pages.index.xraySwitch" }}'> <a-tooltip title='{{ i18n "pages.index.xraySwitch" }}'>
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">Xray [[ status.xray.version ]]</a-tag> <a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
</a-tooltip> </a-tooltip>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :md="12">
<a-card hoverable> <a-card hoverable>
<strong>{{ i18n "pages.index.operationHours" }}:</strong> {{ i18n "pages.index.operationHours" }}:
<a-tooltip> Xray
<template slot="title"> <a-tag color="blue">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
{{ i18n "pages.index.xrayoperationHoursDesc" }} OS
</template>
<a-tag color="blue" style="margin-right: 3px;">Xray [[ formatSecond(status.appStats.uptime) ]]</a-tag>
</a-tooltip>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.operationHoursDesc" }} {{ i18n "pages.index.operationHoursDesc" }}
</template> </template>
<a-tag color="blue">OS [[ formatSecond(status.uptime) ]]</a-tag> <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
<a-tag color="blue">[[ formatSecond(status.uptime) ]]</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :md="12">
<a-card hoverable> <a-card hoverable>
<strong>{{ i18n "pages.index.xrayStatus" }}:</strong> {{ i18n "pages.index.xrayStatus" }}:
<a-tag :color="status.xray.color" style="margin-right: 3px;"><strong>[[ status.xray.state ]]</strong></a-tag> <a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
<a-popover v-if="status.xray.state === State.Error" <a-popover v-if="status.xray.state === State.Error"
:overlay-class-name="themeSwitcher.currentTheme"> :overlay-class-name="themeSwitcher.currentTheme">
<span slot="title" style="font-size: 12pt">An error occurred while running Xray <span slot="title" style="font-size: 12pt">Error in running xray-core
<a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag> <a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
</span> </span>
<template slot="content"> <template slot="content">
<p style="max-width: 400px" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p> <p style="max-width: 400px" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
</template> </template>
<a-icon type="exclamation-circle"></a-icon> <a-icon type="question-circle" theme="filled"></a-icon>
</a-popover> </a-popover>
<a-tag color="purple" style="cursor: pointer; margin-right: 3px;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
<a-tag color="purple" style="cursor: pointer; margin-right: 3px;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :md="12">
<a-card hoverable> <a-card hoverable>
<strong>{{ i18n "menu.link" }}:</strong> {{ i18n "menu.link" }}:
<a-tag color="purple" style="cursor: pointer; margin-right: 3px;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
<a-tag color="purple" style="cursor: pointer; margin-right: 3px;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :md="12">
<a-card hoverable> <a-card hoverable>
<strong>{{ i18n "pages.index.systemLoad" }}:</strong> {{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
<a-tooltip> </a-card>
<template slot="title"> </a-col>
{{ i18n "pages.index.systemLoadDesc" }} <a-col :sm="24" :md="12">
</template> <a-card hoverable>
<a-tag color="blue">[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]</a-tag> {{ i18n "usage"}}:
Memory: [[ sizeFormat(status.appStats.mem) ]] -
Threads: [[ status.appStats.threads ]]
</a-tooltip> </a-tooltip>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :md="12">
<a-card hoverable> <a-card hoverable>
<strong>{{ i18n "usage" }}:</strong> Host: [[ status.hostInfo.hostname ]] -
<a-tag color="blue" style="margin-right: 3px;">RAM [[ sizeFormat(status.appStats.mem) ]]</a-tag> <template v-if="status.hostInfo.ipv4">IPv4:
<a-tag color="blue">Threads [[ status.appStats.threads ]]</a-tag>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-card hoverable>
<strong>{{ i18n "pages.index.serverInfo" }}:</strong>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.hostname" }} [[ status.hostInfo.ipv4 ]]
</template> </template>
<a-tag color="blue" style="margin-right: 3px;">[[ status.hostInfo.hostname ]]</a-tag> <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
<template v-if="status.hostInfo.ipv4">
<a-tooltip>
<template slot="title">
[[ status.hostInfo.ipv4 ]]
</template>
<a-tag color="blue" style="margin-right: 3px;">IPv4</a-tag>
</a-tooltip>
</template> </template>
<template v-if="status.hostInfo.ipv6"> <template v-if="status.hostInfo.ipv6">IPv6:
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
[[ status.hostInfo.ipv6 ]] [[ status.hostInfo.ipv6 ]]
</template> </template>
<a-tag color="blue" style="margin-right: 3px;">IPv6</a-tag> <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
</a-card> </a-card>
</a-col> </a-col>
@@ -185,21 +170,21 @@
<a-card hoverable> <a-card hoverable>
<a-row> <a-row>
<a-col :span="12"> <a-col :span="12">
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.connectionTcpCountDesc" }} {{ i18n "pages.index.connectionTcpCountDesc" }}
</template> </template>
<strong>TCP:</Strong> <a-tag>[[ status.tcpCount ]]</a-tag> <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.connectionUdpCountDesc" }} {{ i18n "pages.index.connectionUdpCountDesc" }}
</template> </template>
<strong>UDP:</strong> <a-tag>[[ status.udpCount ]]</a-tag> <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</a-col> </a-col>
</a-row> </a-row>
@@ -210,20 +195,22 @@
<a-row> <a-row>
<a-col :span="12"> <a-col :span="12">
<a-icon type="arrow-up"></a-icon> <a-icon type="arrow-up"></a-icon>
[[ sizeFormat(status.netIO.up) ]] / S
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.upSpeed" }} {{ i18n "pages.index.upSpeed" }}
</template> </template>
<strong>Up:</strong> [[ sizeFormat(status.netIO.up) ]]/s <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
<a-icon type="arrow-down"></a-icon> <a-icon type="arrow-down"></a-icon>
[[ sizeFormat(status.netIO.down) ]] / S
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.downSpeed" }} {{ i18n "pages.index.downSpeed" }}
</template> </template>
<strong>Down:</strong> [[ sizeFormat(status.netIO.down) ]]/s <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</a-col> </a-col>
</a-row> </a-row>
@@ -234,20 +221,22 @@
<a-row> <a-row>
<a-col :span="12"> <a-col :span="12">
<a-icon type="cloud-upload"></a-icon> <a-icon type="cloud-upload"></a-icon>
[[ sizeFormat(status.netTraffic.sent) ]]
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.totalSent" }} {{ i18n "pages.index.totalSent" }}
</template> </template>
<strong>Out:</strong> [[ sizeFormat(status.netTraffic.sent) ]] <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
<a-icon type="cloud-download"></a-icon> <a-icon type="cloud-download"></a-icon>
[[ sizeFormat(status.netTraffic.recv) ]]
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.totalReceive" }} {{ i18n "pages.index.totalReceive" }}
</template> </template>
<strong>In:</strong> [[ sizeFormat(status.netTraffic.recv) ]] <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</a-col> </a-col>
</a-row> </a-row>
@@ -262,10 +251,8 @@
:closable="true" @ok="() => versionModal.visible = false" :closable="true" @ok="() => versionModal.visible = false"
:class="themeSwitcher.currentTheme" :class="themeSwitcher.currentTheme"
footer=""> footer="">
<a-alert type="warning" style="margin-bottom: 12px; width: fit-content" <h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
message='{{ i18n "pages.index.xraySwitchClickDesk" }}' <h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
show-icon
></a-alert>
<template v-for="version, index in versionModal.versions"> <template v-for="version, index in versionModal.versions">
<a-tag :color="index % 2 == 0 ? 'purple' : 'blue'" <a-tag :color="index % 2 == 0 ? 'purple' : 'blue'"
style="margin: 10px" @click="switchV2rayVersion(version)"> style="margin: 10px" @click="switchV2rayVersion(version)">
@@ -274,53 +261,52 @@
</template> </template>
</a-modal> </a-modal>
<a-modal id="log-modal" v-model="logModal.visible" <a-modal id="log-modal" v-model="logModal.visible" title="Logs"
:closable="true" @cancel="() => logModal.visible = false" :closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
:class="themeSwitcher.currentTheme" :class="themeSwitcher.currentTheme"
width="800px" footer=""> width="800px"
<template slot="title"> footer="">
{{ i18n "pages.index.logs" }}
<a-icon :spin="logModal.loading"
type="sync"
style="vertical-align: middle; margin-left: 10px;"
:disabled="logModal.loading"
@click="openLogs()">
</a-icon>
</template>
<a-form layout="inline"> <a-form layout="inline">
<a-form-item> <a-form-item label="Count">
<a-input-group compact> <a-select v-model="logModal.rows"
<a-select v-model="logModal.rows" style="width:70px;" style="width: 80px"
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme"> @change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
<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>
<a-select-option value="50">50</a-select-option> <a-select-option value="50">50</a-select-option>
<a-select-option value="100">100</a-select-option> <a-select-option value="100">100</a-select-option>
</a-select> </a-select>
<a-select v-model="logModal.level" style="width:100px;" </a-form-item>
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme"> <a-form-item label="Log Level">
<a-select-option value="debug">Debug</a-select-option> <a-select v-model="logModal.level"
<a-select-option value="info">Info</a-select-option> style="width: 120px"
<a-select-option value="warning">Warning</a-select-option> @change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="err">Error</a-select-option> <a-select-option value="debug">Debug</a-select-option>
</a-select> <a-select-option value="info">Info</a-select-option>
</a-input-group> <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 label="SysLog">
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox> <a-button class="ant-btn ant-btn-primary" :loading="logModal.loading" @click="openLogs()"><a-icon :spin="logModal.loading" type="sync"></a-icon> Reload</a-button>
</a-form-item> </a-form-item>
<a-form-item style="float: right;"> <a-form-item>
<a-button type="primary" icon="download" <a-button type="primary" style="margin-bottom: 10px;"
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs?.join('\n'))" download="x-ui.log"> :href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs)" download="x-ui.log">
{{ i18n "download" }} x-ui.log
</a-button> </a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.formattedLogs"></div> <div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.logs"></div>
</a-modal> </a-modal>
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title" <a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
:closable="true" footer="" :closable="true"
:class="themeSwitcher.currentTheme"> :class="themeSwitcher.currentTheme"
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content" <a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
:message="backupModal.description" :message="backupModal.description"
show-icon show-icon
@@ -342,9 +328,9 @@
{{template "textModal"}} {{template "textModal"}}
<script> <script>
const State = { const State = {
Running: "Running", Running: "running",
Stop: "Stop", Stop: "stop",
Error: "Error", Error: "error",
} }
Object.freeze(State); Object.freeze(State);
@@ -412,7 +398,7 @@
this.xray = data.xray; this.xray = data.xray;
switch (this.xray.state) { switch (this.xray.state) {
case State.Running: case State.Running:
this.xray.color = 'blue'; this.xray.color = "blue";
break; break;
case State.Stop: case State.Stop:
this.xray.color = "orange"; this.xray.color = "orange";
@@ -440,15 +426,14 @@
const logModal = { const logModal = {
visible: false, visible: false,
logs: [], logs: '',
rows: 20, rows: 20,
level: 'info', level: 'info',
syslog: false, syslog: false,
loading: false, loading: false,
show(logs) { show(logs) {
this.visible = true; this.visible = true;
this.logs = logs; this.logs = logs? this.formatLogs(logs) : "No Record...";
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
}, },
formatLogs(logs) { formatLogs(logs) {
let formattedLogs = ''; let formattedLogs = '';

View File

@@ -16,7 +16,6 @@
} }
.ant-tabs-bar { .ant-tabs-bar {
font-weight: bold;
margin: 0; margin: 0;
} }
@@ -44,17 +43,13 @@
<a-layout-content> <a-layout-content>
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'> <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
<transition name="list" appear> <transition name="list" appear>
<a-alert type="error" v-if="confAlerts.length>0" style="margin: 10px 5px;" <a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
message='{{ i18n "secAlertTitle" }}' message='{{ i18n "secAlertTitle" }}'
color="red" color="red"
show-icon description='{{ i18n "secAlertSsl" }}'
closable show-icon closable
> >
<template slot="description"> </a-alert>
<b>{{ i18n "secAlertConf" }}</b>
<ul><li v-for="a in confAlerts">[[ a ]]</li></ul>
</template>
</a-alert>
</transition> </transition>
<a-space direction="vertical"> <a-space direction="vertical">
<a-card hoverable style="margin-bottom: .5rem;"> <a-card hoverable style="margin-bottom: .5rem;">
@@ -136,25 +131,45 @@
</a-col> </a-col>
</a-row> </a-row>
</a-list-item> </a-list-item>
</a-list> </a-list>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab='{{ i18n "pages.settings.userSettings"}}'> <a-tab-pane key="2" tab='{{ i18n "pages.settings.userSettings"}}'>
<a-form layout="horizontal" :colon="false" style="float: left; margin: 10px 0;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }"> <a-form style="padding: 20px;" layout="inline">
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'> <table cellpadding="2">
<a-input v-model="user.oldUsername"></a-input> <tr>
</a-form-item> <td>{{ i18n "pages.settings.oldUsername"}}:</td>
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'> <td>
<password-input v-model="user.oldPassword"></password-input> <a-form-item>
</a-form-item> <a-input v-model="user.oldUsername" style="width: 200px"></a-input>
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'> </a-form-item>
<a-input v-model="user.newUsername"></a-input> </td>
</a-form-item> </tr>
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'> <tr>
<password-input v-model="user.newPassword"></password-input> <td>{{ i18n "pages.settings.currentPassword"}}:</td>
</a-form-item> <td>
<a-form-item label=" "> <a-form-item>
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button> <password-input v-model="user.oldPassword" style="width: 200px"></password-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.settings.newUsername"}}:</td>
<td>
<a-form-item>
<a-input v-model="user.newUsername" style="width: 200px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.settings.newPassword"}}:</td>
<td>
<a-form-item>
<password-input v-model="user.newPassword" style="width: 200px"></password-input>
</a-form-item>
</td>
</tr>
</table>
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
</a-form> </a-form>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'> <a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
@@ -171,6 +186,7 @@
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<a-list-item-meta title="Telegram Bot Language" /> <a-list-item-meta title="Telegram Bot Language" />
</a-col> </a-col>
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<template> <template>
<a-select <a-select
@@ -204,68 +220,6 @@
<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>
</a-list> </a-list>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable">
<a-list item-layout="horizontal">
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.settings.fragment"}}' desc='{{ i18n "pages.settings.fragmentDesc"}}' v-model="fragment"></setting-list-item>
<setting-list-item type="switch" title='Mux' v-model="enableMux"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.directCountryConfigs"}}' desc='{{ i18n "pages.xray.directCountryConfigsDesc"}}' v-model="enableDirect"></setting-list-item>
</a-list>
<a-collapse>
<a-collapse-panel header='{{ i18n "pages.settings.fragment"}}' v-if="fragment">
<a-list-item style="padding: 20px">
<a-row>
<a-col :lg="24" :xl="12">
<a-list-item-meta title='Packets'/>
</a-col>
<a-col :lg="24" :xl="12">
<a-select
v-model="fragmentPackets"
style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p" :label="p" v-for="p in ['1-1', '1-3', 'tlshello']">
[[ p ]]
</a-select-option>
</a-select>
</a-col>
</a-row>
</a-list-item>
<setting-list-item type="text" title='Length' v-model="fragmentLength" placeholder="100-200"></setting-list-item>
<setting-list-item type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='Mux' v-if="enableMux">
<setting-list-item type="number" title='Concurrency' v-model="muxConcurrency" :min="-1" :max="1024"></setting-list-item>
<setting-list-item type="number" title='xudp Concurrency' v-model="muxXudpConcurrency" :min="-1" :max="1024"></setting-list-item>
<a-list-item style="padding: 20px">
<a-row>
<a-col :lg="24" :xl="12">
<a-list-item-meta title='xudp UDP 443'/>
</a-col>
<a-col :lg="24" :xl="12">
<a-select
v-model="muxXudpProxyUDP443"
style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p" :label="p" v-for="p in ['reject', 'allow', 'skip']">
[[ p ]]
</a-select-option>
</a-select>
</a-col>
</a-row>
</a-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.directCountryConfigs"}}' v-if="enableDirect">
<a-list-item style="padding: 20px">
<a-checkbox-group
v-model="directCountries"
name="Countries"
:options="countryOptions"
/>
</a-list-item>
</a-collapse-panel>
</a-collapse>
</a-tab-pane>
</a-tabs> </a-tabs>
</a-space> </a-space>
</a-spin> </a-spin>
@@ -291,61 +245,10 @@
saveBtnDisable: true, saveBtnDisable: true,
user: {}, user: {},
lang: getLang(), lang: getLang(),
showAlert: false,
remarkModels: {i:'Inbound',e:'Email',o:'Other'}, remarkModels: {i:'Inbound',e:'Email',o:'Other'},
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'], remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
remarkSample: '', remarkSample: '',
defaultFragment: {
tag: "fragment",
protocol: "freedom",
settings: {
domainStrategy: "AsIs",
fragment: {
packets: "tlshello",
length: "100-200",
interval: "10-20"
}
},
streamSettings: {
sockopt: {
tcpKeepAliveIdle: 100,
tcpNoDelay: true
}
}
},
defaultMux: {
enabled: true,
concurrency: 8,
xudpConcurrency: 16,
xudpProxyUDP443: "reject"
},
defaultRules: [
{
type: "field",
outboundTag: "direct",
domain: [
"geosite:category-ir",
"geosite:cn"
],
"enabled": true
},
{
type: "field",
outboundTag: "direct",
ip: [
"geoip:private",
"geoip:ir",
"geoip:cn"
],
enabled: true
},
],
countryOptions: [
{ label: 'Private IP/Domain', value: 'private' },
{ label: '🇮🇷 Iran', value: 'ir' },
{ label: '🇨🇳 China', value: 'cn' },
{ label: '🇷🇺 Russia', value: 'ru' },
{ label: '🇻🇳 Vietnam', value: 'vn' },
],
get remarkModel() { get remarkModel() {
rm = this.allSetting.remarkModel; rm = this.allSetting.remarkModel;
return rm.length>1 ? rm.substring(1).split('') : []; return rm.length>1 ? rm.substring(1).split('') : [];
@@ -406,7 +309,7 @@
title: '{{ i18n "pages.settings.restartPanel" }}', title: '{{ i18n "pages.settings.restartPanel" }}',
content: '{{ i18n "pages.settings.restartPanelDesc" }}', content: '{{ i18n "pages.settings.restartPanelDesc" }}',
class: themeSwitcher.currentTheme, class: themeSwitcher.currentTheme,
okText: '{{ i18n "confirm" }}', okText: '{{ i18n "sure" }}',
cancelText: '{{ i18n "cancel" }}', cancelText: '{{ i18n "cancel" }}',
onOk: () => resolve(), onOk: () => resolve(),
}); });
@@ -426,117 +329,10 @@
} }
} }
}, },
computed: {
fragment: {
get: function() { return this.allSetting?.subJsonFragment != ""; },
set: function (v) {
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
}
},
fragmentPackets: {
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.packets : ""; },
set: function(v) {
if (v != ""){
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.packets = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
fragmentLength: {
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
set: function(v) {
if (v != ""){
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.length = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
fragmentInterval: {
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
set: function(v) {
if (v != ""){
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.interval = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
enableMux: {
get: function() { return this.allSetting?.subJsonMux != ""; },
set: function (v) {
this.allSetting.subJsonMux = v ? JSON.stringify(this.defaultMux) : "";
}
},
muxConcurrency: {
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).concurrency : -1; },
set: function(v) {
newMux = JSON.parse(this.allSetting.subJsonMux);
newMux.concurrency = v;
this.allSetting.subJsonMux = JSON.stringify(newMux);
}
},
muxXudpConcurrency: {
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpConcurrency : -1; },
set: function(v) {
newMux = JSON.parse(this.allSetting.subJsonMux);
newMux.xudpConcurrency = v;
this.allSetting.subJsonMux = JSON.stringify(newMux);
}
},
muxXudpProxyUDP443: {
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpProxyUDP443 : "reject"; },
set: function(v) {
newMux = JSON.parse(this.allSetting.subJsonMux);
newMux.xudpProxyUDP443 = v;
this.allSetting.subJsonMux = JSON.stringify(newMux);
}
},
enableDirect: {
get: function() { return this.allSetting?.subJsonRules != ""; },
set: function (v) {
this.allSetting.subJsonRules = v ? JSON.stringify(this.defaultRules) : "";
}
},
directCountries: {
get: function() {
if (!this.enableDirect) return [];
rules = JSON.parse(this.allSetting.subJsonRules);
return Array.isArray(rules) ? rules[1].ip.map(d => d.replace("geoip:","")) : [];
},
set: function (v) {
rules = JSON.parse(this.allSetting.subJsonRules);
if (!Array.isArray(rules)) return;
rules[0].domain = [];
rules[1].ip = [];
v.forEach(d => {
category = ["cn","private"].includes(d) ? "" : "category-";
rules[0].domain.push("geosite:"+category+d);
rules[1].ip.push("geoip:"+d);
});
this.allSetting.subJsonRules = JSON.stringify(rules);
}
},
confAlerts: {
get: function() {
if (!this.allSetting) return [];
var alerts = []
if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}');
if (this.allSetting.webPort == 54321) alerts.push('{{ i18n "secAlertPanelPort" }}');
panelPath = window.location.pathname.split('/').length<4
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}');
if (this.allSetting.subEnable) {
subPath = this.allSetting.subURI.length >0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
subJsonPath = this.allSetting.subJsonURI.length >0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
}
return alerts
}
}
},
async mounted() { async mounted() {
if (window.location.protocol !== "https:") {
this.showAlert = true;
}
await this.getAllSetting(); await this.getAllSetting();
while (true) { while (true) {
await PromiseUtil.sleep(1000); await PromiseUtil.sleep(1000);

View File

@@ -1,204 +0,0 @@
{{define "warpModal"}}
<a-modal id="warp-modal" v-model="warpModal.visible" title="Cloudflare WARP"
:confirm-loading="warpModal.confirmLoading" :closable="true" :mask-closable="true"
:footer="null" :class="themeSwitcher.currentTheme">
<template v-if="ObjectUtil.isEmpty(warpModal.warpData)">
<a-button icon="api" @click="register" :loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.create" }}</a-button>
</template>
<template v-else>
<table style="margin: 5px 0; width: 100%;">
<tr class="client-table-odd-row">
<td>Access Token</td>
<td>[[ warpModal.warpData.access_token ]]</td>
</tr>
<tr>
<td>Device ID</td>
<td>[[ warpModal.warpData.device_id ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>License Key</td>
<td>[[ warpModal.warpData.license_key ]]</td>
</tr>
<tr>
<td>Private Key</td>
<td>[[ warpModal.warpData.private_key ]]</td>
</tr>
</table>
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
<a-collapse style="margin: 10px 0;">
<a-collapse-panel header='WARP/WARP+ License Key'>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Key">
<a-input v-model="warpPlus"></a-input>
<a-button @click="updateLicense(warpPlus)" :disabled="warpPlus.length<26" :loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.update" }}</a-button>
</a-form-item>
</a-form>
</a-collapse-panel>
</a-collapse>
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.accountInfo" }}</a-divider>
<a-button icon="sync" @click="getConfig" style="margin-top: 5px; margin-bottom: 10px;" :loading="warpModal.confirmLoading" type="primary">{{ i18n "info" }}</a-button>
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig)">
<table style="width: 100%">
<tr class="client-table-odd-row">
<td>Device Name</td>
<td>[[ warpModal.warpConfig.name ]]</td>
</tr>
<tr>
<td>Device Model</td>
<td>[[ warpModal.warpConfig.model ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>Device Enabled</td>
<td>[[ warpModal.warpConfig.enabled ]]</td>
</tr>
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account)">
<tr>
<td>Account Type</td>
<td>[[ warpModal.warpConfig.account.account_type ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>Role</td>
<td>[[ warpModal.warpConfig.account.role ]]</td>
</tr>
<tr>
<td>WARP+ Data</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>Quota</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.quota) ]]</td>
</tr>
<tr v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account.usage)">
<td>Usage</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.usage) ]]</td>
</tr>
</template>
</table>
<a-divider style="margin: 10px 0;">{{ i18n "pages.xray.outbound.outboundStatus" }}</a-divider>
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<template v-if="warpOutboundIndex>=0">
<a-tag color="green" style="line-height: 31px;">{{ i18n "enabled" }}</a-tag>
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading" type="danger">{{ i18n "reset" }}</a-button>
</template>
<template v-else>
<a-tag color="orange" style="line-height: 31px;">{{ i18n "disabled" }}</a-tag>
<a-button @click="addOutbound" :loading="warpModal.confirmLoading" type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
</template>
</a-form-item>
</a-form>
</template>
</template>
</a-modal>
<script>
const warpModal = {
visible: false,
confirmLoading: false,
warpData: null,
warpConfig: null,
warpOutbound: null,
show() {
this.visible = true;
this.warpConfig = null;
this.getData();
},
close() {
this.visible = false;
this.loading(false);
},
loading(loading=true) {
this.confirmLoading = loading;
},
async getData(){
this.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/data');
this.loading(false);
if (msg.success) {
this.warpData = msg.obj.length>0 ? JSON.parse(msg.obj): null;
}
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#warp-modal',
data: {
warpModal: warpModal,
warpPlus: '',
},
methods: {
collectConfig() {
config = warpModal.warpConfig.config;
peer = config.peers[0];
if(config){
warpModal.warpOutbound = Outbound.fromJson({
tag: 'warp',
protocol: Protocols.Wireguard,
settings: {
mtu: 1420,
secretKey: warpModal.warpData.private_key,
address: Object.values(config.interface.addresses),
domainStrategy: 'ForceIP',
peers: [{
publicKey: peer.public_key,
endpoint: peer.endpoint.host,
}],
kernelMode: false
}
});
}
},
async register(){
warpModal.loading(true);
keys = Wireguard.generateKeypair();
const msg = await HttpUtil.post('/xui/xray/warp/reg',keys);
if (msg.success) {
resp = JSON.parse(msg.obj);
warpModal.warpData = resp.data;
warpModal.warpConfig = resp.config;
this.collectConfig();
}
warpModal.loading(false);
},
async updateLicense(l){
warpModal.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/license',{license: l});
if (msg.success) {
warpModal.warpData = JSON.parse(msg.obj);
warpModal.warpConfig = null;
this.warpPlus = '';
}
warpModal.loading(false);
},
async getConfig(){
warpModal.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/config');
warpModal.loading(false);
if (msg.success) {
warpModal.warpConfig = JSON.parse(msg.obj);
this.collectConfig();
}
},
addOutbound(){
app.templateSettings.outbounds.push(warpModal.warpOutbound.toJson());
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close();
},
resetOutbound(){
app.templateSettings.outbounds[this.warpOutboundIndex] = warpModal.warpOutbound.toJson();
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close();
}
},
computed: {
warpOutboundIndex: {
get: function() {
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1;
}
}
}
});
</script>
{{end}}

File diff suppressed because it is too large Load Diff

View File

@@ -1,114 +0,0 @@
{{define "balancerModal"}}
<a-modal
id="balancer-modal"
v-model="balancerModal.visible"
:title="balancerModal.title"
@ok="balancerModal.ok"
:confirm-loading="balancerModal.confirmLoading"
:ok-button-props="{ props: { disabled: !balancerModal.isValid } }"
:closable="true"
:mask-closable="false"
:ok-text="balancerModal.okText"
cancel-text='{{ i18n "close" }}'
:class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
:validate-status="balancerModal.duplicateTag? 'warning' : 'success'">
<a-input v-model.trim="balancerModal.balancer.tag" @change="balancerModal.check()"
placeholder='{{ i18n "pages.xray.balancer.tagDesc" }}'></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.balancer.balancerStrategy" }}'>
<a-select v-model="balancerModal.balancer.strategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="random">Random</a-select-option>
<a-select-option value="roundRobin">Round Robin</a-select-option>
<a-select-option value="leastLoad">Least Load</a-select-option>
<a-select-option value="leastPing">Least Ping</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback :validate-status="balancerModal.emptySelector? 'warning' : 'success'">
<a-select v-model="balancerModal.balancer.selector" mode="tags" @change="balancerModal.checkSelector()"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</table>
</a-form>
</a-modal>
<script>
const balancerModal = {
title: '',
visible: false,
confirmLoading: false,
okText: '{{ i18n "sure" }}',
isEdit: false,
confirm: null,
duplicateTag: false,
emptySelector: false,
balancer: {
tag: '',
strategy: 'random',
selector: []
},
outboundTags: [],
balancerTags:[],
ok() {
if (balancerModal.balancer.selector.length == 0) {
balancerModal.emptySelector = true;
return;
}
balancerModal.emptySelector = false;
ObjectUtil.execute(balancerModal.confirm, balancerModal.balancer);
},
show({ title = '', okText = '{{ i18n "sure" }}', balancerTags = [], balancer, confirm = (balancer) => { }, isEdit = false }) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if (isEdit) {
balancerModal.balancer = balancer;
} else {
balancerModal.balancer = {
tag: '',
strategy: 'random',
selector: []
};
}
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
this.isEdit = isEdit;
this.check();
this.checkSelector();
},
close() {
this.visible = false;
this.loading(false);
},
loading(loading=true) {
this.confirmLoading = loading;
},
check() {
if (this.balancer.tag == '' || this.balancerTags.includes(this.balancer.tag)) {
this.duplicateTag = true;
this.isValid = false;
} else {
this.duplicateTag = false;
this.isValid = true;
}
},
checkSelector() {
this.emptySelector = this.balancer.selector.length == 0;
}
};
new Vue({
delimiters: ['[[', ']]'],
el: '#balancer-modal',
data: {
balancerModal: balancerModal
},
methods: {
}
});
</script>
{{end}}

View File

@@ -11,7 +11,7 @@
title: '', title: '',
visible: false, visible: false,
confirmLoading: false, confirmLoading: false,
okText: '{{ i18n "confirm" }}', okText: '{{ i18n "sure" }}',
isEdit: false, isEdit: false,
confirm: null, confirm: null,
outbound: new Outbound(), outbound: new Outbound(),
@@ -25,7 +25,7 @@
ok() { ok() {
ObjectUtil.execute(outModal.confirm, outModal.outbound.toJson()); ObjectUtil.execute(outModal.confirm, outModal.outbound.toJson());
}, },
show({ title='', okText='{{ i18n "confirm" }}', outbound, confirm=(outbound)=>{}, isEdit=false, tags=[] }) { show({ title='', okText='{{ i18n "sure" }}', outbound, confirm=(outbound)=>{}, isEdit=false, tags=[] }) {
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;
this.confirm = confirm; this.confirm = confirm;
@@ -42,7 +42,7 @@
outModal.visible = false; outModal.visible = false;
outModal.loading(false); outModal.loading(false);
}, },
loading(loading=true) { loading(loading) {
outModal.confirmLoading = loading; outModal.confirmLoading = loading;
}, },
check(){ check(){

View File

@@ -2,42 +2,79 @@
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok" <a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false" :confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme"> :ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme"> <tr>
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option> <td>{{ i18n "pages.xray.outbound.type" }}</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}'> <a-select v-model="reverseModal.reverse.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-input v-model.trim="reverseModal.reverse.tag"></a-input> <a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
</a-form-item> </a-select>
<a-form-item label='{{ i18n "pages.xray.outbound.domain" }}'> </a-form-item>
<a-input v-model.trim="reverseModal.reverse.domain"></a-input> </td>
</a-form-item> </tr>
<tr>
<td>{{ i18n "pages.xray.outbound.tag" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="reverseModal.reverse.tag" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.outbound.domain" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="reverseModal.reverse.domain" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<template v-if="reverseModal.reverse.type=='bridge'"> <template v-if="reverseModal.reverse.type=='bridge'">
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'> <tr>
<a-select v-model="reverseModal.rules[0].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme"> <td>{{ i18n "pages.xray.outbound.intercon" }}</td>
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option> <td>
</a-select> <a-form-item>
</a-form-item> <a-select v-model="reverseModal.rules[0].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-form-item label='{{ i18n "pages.xray.rules.outbound" }}'> <a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
<a-select v-model="reverseModal.rules[1].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme"> </a-select>
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option> </a-form-item>
</a-select> </td>
</a-form-item> </tr>
<tr>
<td>{{ i18n "pages.xray.rules.outbound" }}</td>
<td>
<a-form-item>
<a-select v-model="reverseModal.rules[1].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</template> </template>
<template v-else> <template v-else>
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'> <tr>
<a-checkbox-group <td>{{ i18n "pages.xray.outbound.intercon" }}</td>
v-model="reverseModal.rules[0].inboundTag" <td>
:options="reverseModal.inboundTags"></a-checkbox-group> <a-form-item>
</a-form-item> <a-checkbox-group
<a-form-item label='{{ i18n "pages.xray.rules.inbound" }}'> v-model="reverseModal.rules[0].inboundTag"
<a-checkbox-group :options="reverseModal.inboundTags"></a-checkbox-group>
v-model="reverseModal.rules[1].inboundTag" </a-form-item>
:options="reverseModal.inboundTags"></a-checkbox-group> </td>
</a-form-item> </tr>
<tr>
<td>{{ i18n "pages.xray.rules.inbound" }}</td>
<td>
<a-form-item>
<a-checkbox-group
v-model="reverseModal.rules[1].inboundTag"
:options="reverseModal.inboundTags"></a-checkbox-group>
</a-form-item>
</td>
</tr>
</template> </template>
</table>
</a-form> </a-form>
</a-modal> </a-modal>
<script> <script>
@@ -45,7 +82,7 @@
title: '', title: '',
visible: false, visible: false,
confirmLoading: false, confirmLoading: false,
okText: '{{ i18n "confirm" }}', okText: '{{ i18n "sure" }}',
isEdit: false, isEdit: false,
confirm: null, confirm: null,
reverse: { reverse: {
@@ -73,7 +110,7 @@
} }
ObjectUtil.execute(reverseModal.confirm, reverseModal.reverse, reverseModal.rules); ObjectUtil.execute(reverseModal.confirm, reverseModal.reverse, reverseModal.rules);
}, },
show({ title='', okText='{{ i18n "confirm" }}', reverse, rules, confirm=(reverse, rules)=>{}, isEdit=false }) { show({ title='', okText='{{ i18n "sure" }}', reverse, rules, confirm=(reverse, rules)=>{}, isEdit=false }) {
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;
this.confirm = confirm; this.confirm = confirm;
@@ -113,14 +150,13 @@
this.isEdit = isEdit; this.isEdit = isEdit;
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag); this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
this.inboundTags.push(...app.inboundTags); this.inboundTags.push(...app.inboundTags);
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag); this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
}, },
close() { close() {
reverseModal.visible = false; reverseModal.visible = false;
reverseModal.loading(false); reverseModal.loading(false);
}, },
loading(loading=true) { loading(loading) {
reverseModal.confirmLoading = loading; reverseModal.confirmLoading = loading;
}, },
}; };
@@ -131,6 +167,8 @@
data: { data: {
reverseModal: reverseModal, reverseModal: reverseModal,
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'}, reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
},
methods: {
} }
}); });

View File

@@ -2,124 +2,149 @@
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok" <a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false" :confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme"> :ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="inline">
<a-form-item label='Domain Matcher'> <table width="100%" class="ant-table-tbody">
<a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme"> <tr>
<td style="width: 30%;">Domain Matcher</td>
<td>
<a-form-item>
<a-select v-model="ruleModal.rule.domainMatcher" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option> <a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item> </td>
<template slot="label"> </tr>
<tr>
<td>Source IPs
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template>
Source IPs <a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </td>
<a-input v-model.trim="ruleModal.rule.source"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item> <a-input v-model.trim="ruleModal.rule.source" style="width: 250px"></a-input>
<template slot="label"> </a-form-item>
</td>
</tr>
<tr>
<td>Source Port
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template>
Source Port <a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </td>
<a-input v-model.trim="ruleModal.rule.sourcePort"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item label='Network'> <a-input v-model.trim="ruleModal.rule.sourcePort" style="width: 250px"></a-input>
<a-select v-model="ruleModal.rule.network" :dropdown-class-name="themeSwitcher.currentTheme"> </a-form-item>
<a-select-option v-for="x in ['','tcp','udp','tcp,udp']" :value="x">[[ x ]]</a-select-option> </td>
</a-select> </tr>
</a-form-item> <tr>
<a-form-item label='Protocol'> <td>Network</td>
<a-select v-model="ruleModal.rule.protocol" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"> <td>
<a-select-option v-for="x in ['http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option> <a-form-item>
</a-select> <a-select v-model="ruleModal.rule.network" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
</a-form-item> <a-select-option v-for="x in ['','tcp','tdp','tcp,udp']" :value="x">[[ x ]]</a-select-option>
<a-form-item label='Attributes'> </a-select>
<a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button> </a-form-item>
</a-form-item> </td>
<a-form-item :wrapper-col="{span: 24}"> </tr>
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs"> <tr>
<a-input style="width: 50%" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'> <td>Protocol</td>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <td>
</a-input> <a-form-item>
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-select v-model="ruleModal.rule.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button> <a-select-option v-for="x in ['','http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
</a-input> </a-select>
</a-input-group> </a-form-item>
</a-form-item> </td>
<a-form-item> </tr>
<template slot="label"> <tr>
<td colspan="2">
<a-form-item>
<span>Attributes</span>
<a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button>
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
<a-input style="width: 50%" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
<tr>
<td>IP
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template>
IP <a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </td>
<a-input v-model.trim="ruleModal.rule.ip"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item> <a-input v-model.trim="ruleModal.rule.ip" style="width: 250px"></a-input>
<template slot="label"> </a-form-item>
</td>
</tr>
<tr>
<td>Domain
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template>
Domain <a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </td>
<a-input v-model.trim="ruleModal.rule.domain"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item> <a-input v-model.trim="ruleModal.rule.domain" style="width: 250px"></a-input>
<template slot="label"> </a-form-item>
</td>
</tr>
<tr>
<td>Port
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template>
User <a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </td>
<a-input v-model.trim="ruleModal.rule.user"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item> <a-input v-model.trim="ruleModal.rule.port" style="width: 250px"></a-input>
<template slot="label"> </a-form-item>
<a-tooltip> </td>
<template slot="title"> </tr>
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <tr>
</template> <td>Inbound Tags</td>
Port <a-icon type="question-circle"></a-icon> <td>
</a-tooltip> <a-form-item>
</template> <a-select v-model="ruleModal.rule.inboundTag" mode="multiple" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-input v-model.trim="ruleModal.rule.port"></a-input> <a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-form-item> </a-select>
<a-form-item label='Inbound Tags'> </a-form-item>
<a-select v-model="ruleModal.rule.inboundTag" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"> </td>
<a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option> </tr>
</a-select> <tr>
</a-form-item> <td>Outbound Tag</td>
<a-form-item label='Outbound Tag'> <td>
<a-select v-model="ruleModal.rule.outboundTag" :dropdown-class-name="themeSwitcher.currentTheme"> <a-form-item>
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option> <a-select v-model="ruleModal.rule.outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
</a-select> <a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-form-item> </a-select>
<a-form-item> </a-form-item>
<template slot="label"> </td>
<a-tooltip> </tr>
<template slot="title">
<span>{{ i18n "pages.xray.balancer.balancerDesc" }}</span>
</template>
Balancer Tag <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-select v-model="ruleModal.rule.balancerTag" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.balancerTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</table> </table>
</a-form> </a-form>
</a-modal> </a-modal>
@@ -129,7 +154,7 @@
title: '', title: '',
visible: false, visible: false,
confirmLoading: false, confirmLoading: false,
okText: '{{ i18n "confirm" }}', okText: '{{ i18n "sure" }}',
isEdit: false, isEdit: false,
confirm: null, confirm: null,
rule: { rule: {
@@ -146,7 +171,6 @@
protocol: [], protocol: [],
attrs: [], attrs: [],
outboundTag: "", outboundTag: "",
balancerTag: "",
}, },
inboundTags: [], inboundTags: [],
outboundTags: [], outboundTags: [],
@@ -156,7 +180,7 @@
newRule = ruleModal.getResult(); newRule = ruleModal.getResult();
ObjectUtil.execute(ruleModal.confirm, newRule); ObjectUtil.execute(ruleModal.confirm, newRule);
}, },
show({ title='', okText='{{ i18n "confirm" }}', rule, confirm=(rule)=>{}, isEdit=false }) { show({ title='', okText='{{ i18n "sure" }}', rule, confirm=(rule)=>{}, isEdit=false }) {
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;
this.confirm = confirm; this.confirm = confirm;
@@ -174,7 +198,6 @@
this.rule.protocol = rule.protocol; this.rule.protocol = rule.protocol;
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : []; this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
this.rule.outboundTag = rule.outboundTag; this.rule.outboundTag = rule.outboundTag;
this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : "";
} else { } else {
this.rule = { this.rule = {
domainMatcher: "", domainMatcher: "",
@@ -189,29 +212,24 @@
protocol: [], protocol: [],
attrs: [], attrs: [],
outboundTag: "", outboundTag: "",
balancerTag: "",
} }
} }
this.isEdit = isEdit; this.isEdit = isEdit;
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag); this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
this.inboundTags.push(...app.inboundTags); this.inboundTags.push(...app.inboundTags);
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag) this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
if(app.templateSettings.reverse){ if(app.templateSettings.reverse){
if(app.templateSettings.reverse.bridges) { if(app.templateSettings.reverse.bridges) {
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag)); this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
} }
if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag)); if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
} }
if (app.templateSettings.routing && app.templateSettings.routing.balancers) {
this.balancerTags = [ "", ...app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
}
}, },
close() { close() {
ruleModal.visible = false; ruleModal.visible = false;
ruleModal.loading(false); ruleModal.loading(false);
}, },
loading(loading=true) { loading(loading) {
ruleModal.confirmLoading = loading; ruleModal.confirmLoading = loading;
}, },
getResult() { getResult() {
@@ -230,8 +248,7 @@
rule.inboundTag = value.inboundTag; rule.inboundTag = value.inboundTag;
rule.protocol = value.protocol; rule.protocol = value.protocol;
rule.attrs = Object.fromEntries(value.attrs); rule.attrs = Object.fromEntries(value.attrs);
rule.outboundTag = value.outboundTag == "" ? undefined : value.outboundTag; rule.outboundTag = value.outboundTag;
rule.balancerTag = value.balancerTag == "" ? undefined : value.balancerTag;
for (const [key, value] of Object.entries(rule)) { for (const [key, value] of Object.entries(rule)) {
if ( if (

View File

@@ -3,7 +3,6 @@ package job
import ( import (
"strconv" "strconv"
"time" "time"
"x-ui/web/service" "x-ui/web/service"
"github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/cpu"

View File

@@ -31,4 +31,5 @@ func (j *XrayTrafficJob) Run() {
if needRestart { if needRestart {
j.xrayService.SetToNeedRestart() j.xrayService.SetToNeedRestart()
} }
} }

View File

@@ -4,7 +4,6 @@ import (
"embed" "embed"
"io/fs" "io/fs"
"strings" "strings"
"x-ui/logger" "x-ui/logger"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -13,11 +12,9 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
) )
var ( var i18nBundle *i18n.Bundle
i18nBundle *i18n.Bundle var LocalizerWeb *i18n.Localizer
LocalizerWeb *i18n.Localizer var LocalizerBot *i18n.Localizer
LocalizerBot *i18n.Localizer
)
type I18nType string type I18nType string
@@ -82,6 +79,7 @@ func I18n(i18nType I18nType, key string, params ...string) string {
MessageID: key, MessageID: key,
TemplateData: templateData, TemplateData: templateData,
}) })
if err != nil { if err != nil {
logger.Errorf("Failed to localize message: %v", err) logger.Errorf("Failed to localize message: %v", err)
return "" return ""
@@ -137,6 +135,7 @@ func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error {
_, err = i18nBundle.ParseMessageFileBytes(data, path) _, err = i18nBundle.ParseMessageFileBytes(data, path)
return err return err
}) })
if err != nil { if err != nil {
return err return err
} }

View File

@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"strings" "strings"
"time" "time"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
@@ -39,25 +38,9 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
return inbounds, nil return inbounds, nil
} }
func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) { func (s *InboundService) checkPortExist(port int, ignoreId int) (bool, error) {
db := database.GetDB() db := database.GetDB()
if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" { db = db.Model(model.Inbound{}).Where("port = ?", port)
db = db.Model(model.Inbound{}).Where("port = ?", port)
} else {
db = db.Model(model.Inbound{}).
Where("port = ?", port).
Where(
db.Model(model.Inbound{}).Where(
"listen = ?", listen,
).Or(
"listen = \"\"",
).Or(
"listen = \"0.0.0.0\"",
).Or(
"listen = \"::\"",
).Or(
"listen = \"::0\""))
}
if ignoreId > 0 { if ignoreId > 0 {
db = db.Where("id != ?", ignoreId) db = db.Where("id != ?", ignoreId)
} }
@@ -91,6 +74,7 @@ func (s *InboundService) getAllEmails() ([]string, error) {
FROM inbounds, FROM inbounds,
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
`).Scan(&emails).Error `).Scan(&emails).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -151,7 +135,7 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
} }
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, bool, error) {
exist, err := s.checkPortExist(inbound.Listen, inbound.Port, 0) exist, err := s.checkPortExist(inbound.Port, 0)
if err != nil { if err != nil {
return inbound, false, err return inbound, false, err
} }
@@ -172,23 +156,6 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
return inbound, false, err return inbound, false, err
} }
// Secure client ID
for _, client := range clients {
if inbound.Protocol == "trojan" {
if client.Password == "" {
return inbound, false, common.NewError("empty client ID")
}
} else if inbound.Protocol == "shadowsocks" {
if client.Email == "" {
return inbound, false, common.NewError("empty client ID")
}
} else {
if client.ID == "" {
return inbound, false, common.NewError("empty client ID")
}
}
}
db := database.GetDB() db := database.GetDB()
tx := db.Begin() tx := db.Begin()
defer func() { defer func() {
@@ -271,7 +238,7 @@ func (s *InboundService) GetInbound(id int) (*model.Inbound, error) {
} }
func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, bool, error) {
exist, err := s.checkPortExist(inbound.Listen, inbound.Port, inbound.Id) exist, err := s.checkPortExist(inbound.Port, inbound.Id)
if err != nil { if err != nil {
return inbound, false, err return inbound, false, err
} }
@@ -314,11 +281,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
oldInbound.Settings = inbound.Settings oldInbound.Settings = inbound.Settings
oldInbound.StreamSettings = inbound.StreamSettings oldInbound.StreamSettings = inbound.StreamSettings
oldInbound.Sniffing = inbound.Sniffing oldInbound.Sniffing = inbound.Sniffing
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
} else {
oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
}
needRestart := false needRestart := false
s.xrayApi.Init(p.GetAPIPort()) s.xrayApi.Init(p.GetAPIPort())
@@ -416,23 +379,6 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
return false, err return false, err
} }
// Secure client ID
for _, client := range clients {
if oldInbound.Protocol == "trojan" {
if client.Password == "" {
return false, common.NewError("empty client ID")
}
} else if oldInbound.Protocol == "shadowsocks" {
if client.Email == "" {
return false, common.NewError("empty client ID")
}
} else {
if client.ID == "" {
return false, common.NewError("empty client ID")
}
}
}
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 {
@@ -516,9 +462,9 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
client_key = "email" client_key = "email"
} }
interfaceClients := settings["clients"].([]interface{}) inerfaceClients := settings["clients"].([]interface{})
var newClients []interface{} var newClients []interface{}
for _, client := range interfaceClients { for _, client := range inerfaceClients {
c := client.(map[string]interface{}) c := client.(map[string]interface{})
c_id := c[client_key].(string) c_id := c[client_key].(string)
if c_id == clientId { if c_id == clientId {
@@ -528,10 +474,6 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
} }
} }
if len(newClients) == 0 {
return false, common.NewError("no client remained in Inbound")
}
settings["clients"] = newClients settings["clients"] = newClients
newSettings, err := json.MarshalIndent(settings, "", " ") newSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil { if err != nil {
@@ -587,19 +529,15 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
} }
oldEmail := "" oldEmail := ""
newClientId := ""
clientIndex := 0 clientIndex := 0
for index, oldClient := range oldClients { for index, oldClient := range oldClients {
oldClientId := "" oldClientId := ""
if oldInbound.Protocol == "trojan" { if oldInbound.Protocol == "trojan" {
oldClientId = oldClient.Password oldClientId = oldClient.Password
newClientId = clients[0].Password
} else if oldInbound.Protocol == "shadowsocks" { } else if oldInbound.Protocol == "shadowsocks" {
oldClientId = oldClient.Email oldClientId = oldClient.Email
newClientId = clients[0].Email
} else { } else {
oldClientId = oldClient.ID oldClientId = oldClient.ID
newClientId = clients[0].ID
} }
if clientId == oldClientId { if clientId == oldClientId {
oldEmail = oldClient.Email oldEmail = oldClient.Email
@@ -608,11 +546,6 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
} }
} }
// Validate new client ID
if newClientId == "" {
return false, common.NewError("empty client ID")
}
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 {
@@ -984,7 +917,7 @@ func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error
s.xrayApi.Init(p.GetAPIPort()) s.xrayApi.Init(p.GetAPIPort())
for _, tag := range tags { for _, tag := range tags {
err1 := s.xrayApi.DelInbound(tag) err1 := s.xrayApi.DelInbound(tag)
if err1 == nil { if err == nil {
logger.Debug("Inbound disabled by api:", tag) logger.Debug("Inbound disabled by api:", tag)
} else { } else {
logger.Debug("Error in disabling inbound by api:", err1) logger.Debug("Error in disabling inbound by api:", err1)
@@ -1064,7 +997,10 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
clientTraffic.Reset = client.Reset clientTraffic.Reset = client.Reset
result := tx.Create(&clientTraffic) result := tx.Create(&clientTraffic)
err := result.Error err := result.Error
return err if err != nil {
return err
}
return nil
} }
func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error { func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error {
@@ -1075,12 +1011,13 @@ func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *mod
"email": client.Email, "email": client.Email,
"total": client.TotalGB, "total": client.TotalGB,
"expiry_time": client.ExpiryTime, "expiry_time": client.ExpiryTime,
"reset": client.Reset, "reset": client.Reset})
})
err := result.Error err := result.Error
return err if err != nil {
return err
}
return nil
} }
func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error { func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error {
return tx.Where("email = ?", email).Delete(xray.ClientTraffic{}).Error return tx.Where("email = ?", email).Delete(xray.ClientTraffic{}).Error
} }
@@ -1161,7 +1098,11 @@ func (s *InboundService) ResetAllClientTraffics(id int) error {
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0}) Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
err := result.Error err := result.Error
return err
if err != nil {
return err
}
return nil
} }
func (s *InboundService) ResetAllTraffics() error { func (s *InboundService) ResetAllTraffics() error {
@@ -1172,7 +1113,11 @@ func (s *InboundService) ResetAllTraffics() error {
Updates(map[string]interface{}{"up": 0, "down": 0}) Updates(map[string]interface{}{"up": 0, "down": 0})
err := result.Error err := result.Error
return err
if err != nil {
return err
}
return nil
} }
func (s *InboundService) DelDepletedClients(id int) (err error) { func (s *InboundService) DelDepletedClients(id int) (err error) {
@@ -1371,7 +1316,7 @@ func (s *InboundService) MigrationRequirements() {
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings) json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
clients, ok := settings["clients"].([]interface{}) clients, ok := settings["clients"].([]interface{})
if ok { if ok {
// Fix Client configuration problems // Fix Clinet configuration problems
var newClients []interface{} var newClients []interface{}
for client_index := range clients { for client_index := range clients {
c := clients[client_index].(map[string]interface{}) c := clients[client_index].(map[string]interface{})

View File

@@ -4,11 +4,11 @@ import (
"os" "os"
"syscall" "syscall"
"time" "time"
"x-ui/logger" "x-ui/logger"
) )
type PanelService struct{} type PanelService struct {
}
func (s *PanelService) RestartPanel(delay time.Duration) error { func (s *PanelService) RestartPanel(delay time.Duration) error {
p, err := os.FindProcess(syscall.Getpid()) p, err := os.FindProcess(syscall.Getpid())

View File

@@ -15,7 +15,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"x-ui/config" "x-ui/config"
"x-ui/database" "x-ui/database"
"x-ui/logger" "x-ui/logger"
@@ -34,9 +33,9 @@ import (
type ProcessState string type ProcessState string
const ( const (
Running ProcessState = "Running" Running ProcessState = "running"
Stop ProcessState = "Stop" Stop ProcessState = "stop"
Error ProcessState = "Error" Error ProcessState = "error"
) )
type Status struct { type Status struct {
@@ -251,6 +250,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
} }
func (s *ServerService) StopXrayService() (string error) { func (s *ServerService) StopXrayService() (string error) {
err := s.xrayService.StopXray() err := s.xrayService.StopXray()
if err != nil { if err != nil {
logger.Error("stop xray failed:", err) logger.Error("stop xray failed:", err)
@@ -261,6 +261,7 @@ func (s *ServerService) StopXrayService() (string error) {
} }
func (s *ServerService) RestartXrayService() (string error) { func (s *ServerService) RestartXrayService() (string error) {
s.xrayService.StopXray() s.xrayService.StopXray()
defer func() { defer func() {
err := s.xrayService.RestartXray(true) err := s.xrayService.RestartXray(true)
@@ -364,6 +365,7 @@ func (s *ServerService) UpdateXray(version string) error {
} }
return nil return nil
} }
func (s *ServerService) GetLogs(count string, level string, syslog string) []string { func (s *ServerService) GetLogs(count string, level string, syslog string) []string {

View File

@@ -9,7 +9,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
@@ -56,15 +55,10 @@ var defaultValueMap = map[string]string{
"subEncrypt": "true", "subEncrypt": "true",
"subShowInfo": "false", "subShowInfo": "false",
"subURI": "", "subURI": "",
"subJsonPath": "/json/",
"subJsonURI": "",
"subJsonFragment": "",
"subJsonMux": "",
"subJsonRules": "",
"warp": "",
} }
type SettingService struct{} type SettingService struct {
}
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) { func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
db := database.GetDB() db := database.GetDB()
@@ -358,11 +352,17 @@ func (s *SettingService) GetSubPort() (int, error) {
} }
func (s *SettingService) GetSubPath() (string, error) { func (s *SettingService) GetSubPath() (string, error) {
return s.getString("subPath") subPath, err := s.getString("subPath")
} if err != nil {
return "", err
func (s *SettingService) GetSubJsonPath() (string, error) { }
return s.getString("subJsonPath") if !strings.HasPrefix(subPath, "/") {
subPath = "/" + subPath
}
if !strings.HasSuffix(subPath, "/") {
subPath += "/"
}
return subPath, nil
} }
func (s *SettingService) GetSubDomain() (string, error) { func (s *SettingService) GetSubDomain() (string, error) {
@@ -377,8 +377,8 @@ func (s *SettingService) GetSubKeyFile() (string, error) {
return s.getString("subKeyFile") return s.getString("subKeyFile")
} }
func (s *SettingService) GetSubUpdates() (string, error) { func (s *SettingService) GetSubUpdates() (int, error) {
return s.getString("subUpdates") return s.getInt("subUpdates")
} }
func (s *SettingService) GetSubEncrypt() (bool, error) { func (s *SettingService) GetSubEncrypt() (bool, error) {
@@ -397,30 +397,6 @@ func (s *SettingService) GetSubURI() (string, error) {
return s.getString("subURI") return s.getString("subURI")
} }
func (s *SettingService) GetSubJsonURI() (string, error) {
return s.getString("subJsonURI")
}
func (s *SettingService) GetSubJsonFragment() (string, error) {
return s.getString("subJsonFragment")
}
func (s *SettingService) GetSubJsonMux() (string, error) {
return s.getString("subJsonMux")
}
func (s *SettingService) GetSubJsonRules() (string, error) {
return s.getString("subJsonRules")
}
func (s *SettingService) GetWarp() (string, error) {
return s.getString("warp")
}
func (s *SettingService) SetWarp(data string) error {
return s.setString("warp", data)
}
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
if err := allSetting.CheckValid(); err != nil { if err := allSetting.CheckValid(); err != nil {
return err return err
@@ -462,7 +438,6 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() }, "tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
"subEnable": func() (interface{}, error) { return s.GetSubEnable() }, "subEnable": func() (interface{}, error) { return s.GetSubEnable() },
"subURI": func() (interface{}, error) { return s.GetSubURI() }, "subURI": func() (interface{}, error) { return s.GetSubURI() },
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() }, "remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
} }
@@ -476,11 +451,10 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
result[key] = value result[key] = value
} }
if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(string) == "") { if result["subEnable"].(bool) && result["subURI"].(string) == "" {
subURI := "" subURI := ""
subPort, _ := s.GetSubPort() subPort, _ := s.GetSubPort()
subPath, _ := s.GetSubPath() subPath, _ := s.GetSubPath()
subJsonPath, _ := s.GetSubJsonPath()
subDomain, _ := s.GetSubDomain() subDomain, _ := s.GetSubDomain()
subKeyFile, _ := s.GetSubKeyFile() subKeyFile, _ := s.GetSubKeyFile()
subCertFile, _ := s.GetSubCertFile() subCertFile, _ := s.GetSubCertFile()
@@ -501,12 +475,12 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
} else { } else {
subURI += fmt.Sprintf("%s:%d", subDomain, subPort) subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
} }
if result["subURI"].(string) == "" { if subPath[0] == byte('/') {
result["subURI"] = subURI + subPath subURI += subPath
} } else {
if result["subJsonURI"].(string) == "" { subURI += "/" + subPath
result["subJsonURI"] = subURI + subJsonPath
} }
result["subURI"] = subURI
} }
return result, nil return result, nil

View File

@@ -8,7 +8,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"x-ui/config" "x-ui/config"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
@@ -20,12 +19,10 @@ import (
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
) )
var ( var bot *tgbotapi.BotAPI
bot *tgbotapi.BotAPI var adminIds []int64
adminIds []int64 var isRunning bool
isRunning bool var hostname string
hostname string
)
type LoginStatus byte type LoginStatus byte
@@ -135,7 +132,7 @@ func (t *Tgbot) OnReceive() {
isAdmin := checkAdmin(tgId) isAdmin := checkAdmin(tgId)
if update.Message == nil { if update.Message == nil {
if update.CallbackQuery != nil { if update.CallbackQuery != nil {
t.asnwerCallback(update.CallbackQuery) t.asnwerCallback(update.CallbackQuery, isAdmin)
} }
} else { } else {
if update.Message.IsCommand() { if update.Message.IsCommand() {
@@ -196,7 +193,7 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
t.SendAnswer(chatId, msg, isAdmin) t.SendAnswer(chatId, msg, isAdmin)
} }
func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery) { func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bool) {
// Respond to the callback query, telling Telegram to show the user // Respond to the callback query, telling Telegram to show the user
// a message with the data received. // a message with the data received.
callback := tgbotapi.NewCallback(callbackQuery.ID, callbackQuery.Data) callback := tgbotapi.NewCallback(callbackQuery.ID, callbackQuery.Data)

Some files were not shown because too many files have changed in this diff Show More