Compare commits

..

1 Commits
1.7.1 ... 1.6.2

Author SHA1 Message Date
Alireza Ahmadi
905ffda848 v1.6.2 2023-12-11 14:06:59 +01:00
66 changed files with 3338 additions and 3903 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 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,81 +6,127 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build: linuxamd64build:
strategy: name: build x-ui amd64 version
matrix:
platform: [amd64, arm64, arm]
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repository - uses: actions/checkout@v4
uses: actions/checkout@v4.1.1 - name: Set up Go
uses: actions/setup-go@v5
- name: Setup Go
uses: actions/setup-go@v5.0.0
with: with:
go-version: '1.21' go-version: '1.21'
- name: build linux amd64 version
- name: Install dependencies for arm64 and arm
if: matrix.platform == 'arm64' || matrix.platform == 'arm'
run: | run: |
sudo apt-get update CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
sudo apt install gcc-aarch64-linux-gnu
if [ "${{ matrix.platform }}" == "arm" ]; then
sudo apt install gcc-arm-linux-gnueabihf
fi
- name: Build x-ui
run: |
export CGO_ENABLED=1
export GOOS=linux
export GOARCH=${{ matrix.platform }}
if [ "${{ matrix.platform }}" == "arm64" ]; then
export CC=aarch64-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "arm" ]; then
export CC=arm-linux-gnueabihf-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.7/" 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
else
wget ${Xray_URL}Xray-linux-arm32-v7a.zip
unzip Xray-linux-arm32-v7a.zip
rm -f Xray-linux-arm32-v7a.zip
fi
rm -f geoip.dat geosite.dat geoip_IR.dat geosite_IR.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
uses: svenstaro/upload-release-action@2.7.0 uses: svenstaro/upload-release-action@2.7.0
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
overwrite: true

View File

@@ -1,25 +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"
armv8 | arm64 | aarch64) FNAME="arm64";
ARCH="arm64-v8a" else
FNAME="arm64" ARCH="64";
;; FNAME="amd64";
armv7 | arm | arm32) fi
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.7/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}"

230
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,30 +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:
<img width="125" alt="image" **If you think this project is helpful to you, you may wish to give a** :star2:
src="https://github.com/alireza0/x-ui/assets/115543613/dd4f10dd-8bb0-40cf-846f-1fe1de7a6275">
- 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: |
@@ -43,24 +45,18 @@ 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.6.4`: 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.6.4 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
**Step 1:** First download the latest compressed package from https://github.com/alireza0/x-ui/releases, generally choose Architecture `amd64` > If your server cpu architecture is not `amd64` replace another architecture
**Step 2:** Then upload the compressed package to the server's `/root/` directory and login to the server with user `root`
> If your server CPU architecture is not `amd64` replace it with the appropriate architecture
```sh ```sh
ARCH=$(uname -m) ARCH=$(uname -m)
@@ -77,22 +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:** Install X-UI 2. Install X-UI
```shell ```shell
mkdir x-ui && cd x-ui mkdir x-ui && cd x-ui
@@ -111,8 +100,6 @@ docker run -itd \
docker build -t x-ui . docker build -t x-ui .
``` ```
</details>
## Languages ## Languages
- English - English
@@ -123,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
@@ -146,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
@@ -212,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
@@ -242,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
@@ -287,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.7.1 1.6.2

54
go.mod
View File

@@ -3,31 +3,33 @@ module x-ui
go 1.21.4 go 1.21.4
require ( require (
github.com/Workiva/go-datastructures v1.1.1
github.com/gin-contrib/sessions v0.0.4 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.1.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.1 github.com/shirou/gopsutil/v3 v3.23.11
github.com/xtls/xray-core v1.8.7 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.61.1 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.7 gorm.io/gorm v1.25.5
) )
require ( require (
github.com/andybalholm/brotli v1.0.6 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/bytedance/sonic v1.9.1 // indirect github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/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.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/gin-contrib/gzip v0.0.6 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.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
@@ -37,7 +39,7 @@ require (
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/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-20231229205709-960ae82b1e42 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/sessions v1.2.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect
@@ -45,7 +47,7 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.17.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
@@ -53,15 +55,15 @@ require (
github.com/mattn/go-sqlite3 v1.14.17 // 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.13.2 // 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-20221212215047-62379fc7944b // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/quic-go/quic-go v0.40.1 // indirect github.com/quic-go/quic-go v0.40.0 // indirect
github.com/refraction-networking/utls v1.6.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.0 // 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.12 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect
@@ -73,20 +75,20 @@ require (
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.3 // 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.3.0 // indirect golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.17.0 // indirect golang.org/x/crypto v0.15.0 // indirect
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.16.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.16.1 // 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-20240102182953-50ed04b92917 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/protobuf v1.32.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

128
go.sum
View File

@@ -10,6 +10,8 @@ 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/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 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=
@@ -27,8 +29,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/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/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=
@@ -44,6 +46,8 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD
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.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gaukas/godicttls v0.0.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=
@@ -114,8 +118,8 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/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-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/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=
@@ -143,8 +147,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
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/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.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 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.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@@ -187,10 +191,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
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.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU=
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.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=
@@ -199,8 +203,9 @@ github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTm
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.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= 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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -218,10 +223,10 @@ github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q= github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c= github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
github.com/refraction-networking/utls v1.6.0 h1:X5vQMqVx7dY7ehxxqkFER/W6DSjy8TMqSItXm8hRDYQ= github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o=
github.com/refraction-networking/utls v1.6.0/go.mod h1:kHJ6R9DFFA0WsRgBM35iiDku4O7AqPR6y79iuzW7b10= 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=
@@ -230,15 +235,15 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 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/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.0 h1:PIDVFZHnQAAYRL1UYqNM+0k5s8f/tb1lUW6UDcQiOc8= github.com/sagernet/sing v0.2.17 h1:vMPKb3MV0Aa5ws4dCJkRI8XEjrsUcDn810czd0FwmzI=
github.com/sagernet/sing v0.3.0/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g= 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.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI= github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ=
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU= 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=
@@ -282,10 +287,12 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 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/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 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/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
@@ -305,18 +312,19 @@ 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.7 h1:lb8O1l3/eAg3YAXA6tLm5M6N7BsX8wxW9sJLjU3dHkA= github.com/xtls/xray-core v1.8.6 h1:tr3nk/fZnFfCsmgZv7B3RC72N5qUC88oMGVLlybDey8=
github.com/xtls/xray-core v1.8.7/go.mod h1:9rFpflfQbgFeH1VKJw7yUmEy7myOyDCgNXXl0bmmyOo= github.com/xtls/xray-core v1.8.6/go.mod h1:hj2EB8rtcLdlTC8zxiWm5xL+C0k2Aie9Pk0mXtDEP6U=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.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.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
@@ -324,16 +332,18 @@ golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+
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.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 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-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= 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/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=
@@ -345,9 +355,11 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
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.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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=
@@ -357,8 +369,10 @@ 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=
@@ -368,6 +382,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
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-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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -378,8 +393,8 @@ 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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.16.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/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=
@@ -390,20 +405,25 @@ 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-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.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 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=
@@ -416,19 +436,19 @@ 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-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= 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.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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/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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -445,10 +465,10 @@ gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.7/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=

View File

@@ -23,15 +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
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;; arch="amd64"
armv7* | armv7 | arm | arm32 ) echo 'arm' ;; elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;; arch="arm64"
esac elif [[ $arch == "s390x" ]]; then
} arch="s390x"
echo "arch: $(arch)" else
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)
@@ -112,18 +122,18 @@ 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
@@ -133,10 +143,10 @@ install_x-ui() {
rm /usr/local/x-ui/ -rf rm /usr/local/x-ui/ -rf
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 bin/xray-linux-$(arch) 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
@@ -153,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_base install_base
install_x-ui $1 install_x-ui $1

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

@@ -10,7 +10,6 @@ import (
"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"
@@ -383,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 {
@@ -405,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 {
@@ -560,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 {

View File

@@ -41,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

@@ -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;
} }
@@ -1049,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;
}

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,27 +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"
]; ]
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 {
@@ -418,7 +408,7 @@ class Outbound extends CommonClass {
} }
canEnableTls() { canEnableTls() {
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false; if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network); return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
} }
@@ -486,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':
@@ -503,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;
@@ -515,7 +505,7 @@ 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',
@@ -580,7 +570,7 @@ 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);
} }
let data = link.split('?'); let data = link.split('?');
@@ -635,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;
} }
} }
@@ -651,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;
} }
} }
@@ -851,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,
); );
} }
@@ -875,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,
); );
} }
@@ -905,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(",") : 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

@@ -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 = {
@@ -230,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),
@@ -408,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;
@@ -460,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;
@@ -814,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='',
@@ -1140,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);
@@ -1158,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) {
@@ -1320,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);
@@ -1334,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);
@@ -1347,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:
@@ -1392,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)){
@@ -1415,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) => {
@@ -1425,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 '';
} }
} }
@@ -1483,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;
} }
} }
@@ -1497,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;
} }
} }
@@ -2088,75 +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());
}
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.0/24'], keepAlive=0) {
super();
this.privateKey = privateKey
this.publicKey = publicKey;
if (!this.publicKey){
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
}
this.psk = psk;
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() {
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

@@ -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,25 +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")
println(license)
resp, err = a.XraySettingService.SetWarpLicence(license)
}
jsonObj(c, resp, err)
}

View File

@@ -6,16 +6,12 @@
<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>Subscription</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>
</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>
@@ -36,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 () {

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,8 +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>
<h1 class="title" style="margin-bottom: 5px; font-size: 24px;">X-UI</h1> <h1 class="title">{{ i18n "pages.login.title" }}</h1>
<h2 class="title" style="text-align: center;">{{ i18n "pages.login.title" }}</h2>
</a-col> </a-col>
</a-row> </a-row>
<a-row type="flex" justify="center"> <a-row type="flex" justify="center">
@@ -117,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>
@@ -211,4 +276,4 @@
}, },
}); });
</script> </script>
{{end}} {{end}}

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

@@ -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"}}
@@ -115,4 +138,4 @@
<template v-if="inbound.canSniffing()"> <template v-if="inbound.canSniffing()">
{{template "form/sniffing"}} {{template "form/sniffing"}}
</template> </template>
{{end}} {{end}}

View File

@@ -2,395 +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>
</td>
</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> <a-select-option value="http">HTTP2</a-select-option>
</a-form-item> <a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option>
</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 <td>http {{ i18n "camouflage" }}</td>
:checked="outbound.stream.tcp.type === 'http'" <td>
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'"> <a-form-item>
</a-switch> <a-switch
</a-form-item> :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> <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 label='{{ i18n "password" }}'> <a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
<a-input v-model="outbound.stream.kcp.seed"></a-input> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='MTU'> </td>
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number> </tr>
</a-form-item> <tr>
<a-form-item label='TTI (ms)'> <td>{{ i18n "password" }}</td>
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number> <td>
</a-form-item> <a-form-item>
<a-form-item label='Uplink (MB/s)'> <a-input v-model="outbound.stream.kcp.seed" style="width: 250px;"></a-input>
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number> </a-form-item>
</a-form-item> </td>
<a-form-item label='Downlink (MB/s)'> </tr>
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number> <tr>
</a-form-item> <td>mtu</td>
<a-form-item label='Congestion'> <td>
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch> <a-form-item>
</a-form-item> <a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
<a-form-item label='Read Buffer (MB)'> </a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number> </td>
</a-form-item> </tr>
<a-form-item label='Write Buffer (MB)'> <tr>
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number> <td>tti (ms)</td>
</a-form-item> <td>
<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-form-item><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='Multi Mode'> <a-form-item>
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch> <a-input v-model.trim="outbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>MultiMode</td>
<td>
<a-form-item>
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
</a-form-item>
</td>
</tr>
</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>
</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">
@@ -401,4 +541,4 @@
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea> <textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
{{end}} {{end}}

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,28 +32,55 @@
</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>
{{end}} {{end}}

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:8} }" :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%" 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%" 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,10 +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="Multi Mode"> <td>
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch> <a-form-item>
</a-form-item> <a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
</a-form-item>
</td>
</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,47 +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> <a-select-option value="utp">utp (BT download)</a-select-option>
</a-form-item> <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>
<template slot="label"> <a-select-option value="wireguard">wireguard (wireguard packages)</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.kcp.seed = 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.kcp.seed"></a-input> <a-input v-model="inbound.stream.kcp.seed" style="width: 250px;"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='MTU'> </td>
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number> </tr>
</a-form-item> <tr>
<a-form-item label='TTI (ms)'> <td>mtu</td>
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number> <td>
</a-form-item> <a-form-item>
<a-form-item label='Uplink (MB/s)'> <a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number> </a-form-item>
</a-form-item> </td>
<a-form-item label='Downlink (MB/s)'> </tr>
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number> <tr>
</a-form-item> <td>tti (ms)</td>
<a-form-item label='Congestion'> <td>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch> <a-form-item>
</a-form-item> <a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
<a-form-item label='Read Buffer (MB)'> </a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number> </td>
</a-form-item> </tr>
<a-form-item label='Write Buffer (MB)'> <tr>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number> <td>uplink capacity (MB/S)</td>
</a-form-item> <td>
<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,13 +1,13 @@
{{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> </a-select>
@@ -47,4 +47,4 @@
<template> <template>
{{template "form/streamSockopt"}} {{template "form/streamSockopt"}}
</template> </template>
{{end}} {{end}}

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,15 +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 "pages.inbounds.stream.tcp.requestHeader" }}'> <br>
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button> <a-form-item style="width: 100%;">
</a-form-item> <span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
<a-form-item :wrapper-col="{span:24}"> <a-button size="small" style="margin-left: 10px" @click="inbound.stream.ws.addHeader()">+</a-button>
<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>
@@ -20,4 +21,4 @@
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
</a-form> </a-form>
{{end}} {{end}}

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" :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" :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

@@ -7,20 +7,15 @@
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>
@@ -28,19 +23,12 @@
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2"> <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>
@@ -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"><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>
@@ -175,8 +154,8 @@
<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>
@@ -188,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>
@@ -260,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>
@@ -349,11 +263,7 @@
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);
@@ -414,4 +324,4 @@
}); });
</script> </script>
{{end}} {{end}}

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">
<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>
@@ -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"> <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">
@@ -529,9 +517,9 @@
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 50, scopedSlots: { customRender: 'actions' } }, { title: '{{ i18n "pages.inbounds.operate" }}', width: 50, scopedSlots: { customRender: 'actions' } },
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 20, scopedSlots: { customRender: 'enable' } }, { title: '{{ i18n "pages.inbounds.enable" }}', width: 20, scopedSlots: { customRender: 'enable' } },
{ title: '{{ i18n "online" }}', width: 20, scopedSlots: { customRender: 'online' } }, { title: '{{ i18n "online" }}', width: 20, scopedSlots: { customRender: 'online' } },
{ title: '{{ i18n "pages.inbounds.client" }}', width: 70, scopedSlots: { customRender: 'client' } }, { 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.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, align: 'center', scopedSlots: { customRender: 'expiryTime' } }, { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
]; ];
const innerMobileColumns = [ const innerMobileColumns = [
@@ -735,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;
@@ -769,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;
@@ -1044,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) {
@@ -1108,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),
}) })
@@ -1196,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" }}',
@@ -1224,24 +1189,7 @@
}, },
}); });
}, },
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>
@@ -83,112 +83,108 @@
<transition name="list" appear> <transition name="list" appear>
<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.index.machineInfo" }}:</strong> X-UI: <a href="https://github.com/alireza0/x-ui/releases" target="_blank"><a-tag color="blue">{{ .cur_ver }}</a-tag></a>
<a-tooltip> Xray:
<template slot="title"> <a-tooltip title='{{ i18n "pages.index.xraySwitch" }}'>
{{ i18n "pages.index.hostname" }} <a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
</template>
<a-tag color="blue" style="margin-right: 3px;">[[ status.hostInfo.hostname ]]</a-tag>
</a-tooltip> </a-tooltip>
<template v-if="status.hostInfo.ipv4">
<a-tooltip>
<template slot="title">
IPv4:<br>[[ status.hostInfo.ipv4 ]]<br>IPv6:<br>[[ status.hostInfo.ipv6 ]]
</template>
<a-tag color="blue" style="margin-right: 3px;">IPv4/v6</a-tag>
</a-tooltip>
<a href="https://github.com/alireza0/x-ui/releases" target="_blank"><a-tag color="purple">X-UI {{ .cur_ver }}</a-tag></a>
</template>
</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-tooltip title='{{ i18n "pages.index.xraySwitch" }}'>
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
</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 "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>
<template slot="title">
{{ i18n "pages.index.systemLoadDesc" }}
</template>
<a-tag color="blue">[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]</a-tag>
</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> {{ i18n "usage"}}:
<a-tooltip> Memory: [[ sizeFormat(status.appStats.mem) ]] -
<a-tag color="blue" style="margin-right: 3px;">RAM [[ sizeFormat(status.appStats.mem) ]]</a-tag> Threads: [[ status.appStats.threads ]]
</a-tooltip> </a-tooltip>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-card hoverable>
Host: [[ status.hostInfo.hostname ]] -
<template v-if="status.hostInfo.ipv4">IPv4:
<a-tooltip> <a-tooltip>
<a-tag color="blue">Threads [[ status.appStats.threads ]]</a-tag> <template slot="title">
[[ status.hostInfo.ipv4 ]]
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</template>
<template v-if="status.hostInfo.ipv6">IPv6:
<a-tooltip>
<template slot="title">
[[ status.hostInfo.ipv6 ]]
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</template>
</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>
<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> [[ status.tcpCount ]] <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> [[ status.udpCount ]] <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</a-col> </a-col>
</a-row> </a-row>
@@ -199,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>UL:</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>DL:</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>
@@ -223,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>
@@ -251,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)">
@@ -302,7 +300,7 @@
</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"
@@ -330,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);
@@ -400,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 = '#3dbd7d'; this.xray.color = "blue";
break; break;
case State.Stop: case State.Stop:
this.xray.color = "orange"; this.xray.color = "orange";
@@ -435,8 +433,7 @@
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;
} }
@@ -135,22 +134,42 @@
</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"}}'>
@@ -323,4 +342,4 @@
}); });
</script> </script>
</body> </body>
</html> </html>

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) {
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}}

View File

@@ -3,11 +3,10 @@
{{template "head" .}} {{template "head" .}}
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .cur_ver }}"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
<script src="{{ .base_path }}assets/base64/base64.min.js"></script> <script src="{{ .base_path }}assets/js/model/outbound.js"></script>
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/codemirror/codemirror.js"></script> <script src="{{ .base_path }}assets/codemirror/codemirror.js"></script>
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script> <script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script> <script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
@@ -30,14 +29,9 @@
margin: 0; margin: 0;
padding: 12px .5rem; padding: 12px .5rem;
} }
.ant-table-thead > tr > th,
.ant-table-tbody > tr > td {
padding: 10px 0px;
}
} }
.ant-tabs-bar { .ant-tabs-bar {
font-weight: bold;
margin: 0; margin: 0;
} }
@@ -86,7 +80,7 @@
<template slot="content"> <template slot="content">
<p style="max-width: 400px" v-for="line in restartResult.split('\n')">[[ line ]]</p> <p style="max-width: 400px" v-for="line in restartResult.split('\n')">[[ line ]]</p>
</template> </template>
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle" theme="filled"></a-icon>
</a-popover> </a-popover>
</a-space> </a-space>
</a-col> </a-col>
@@ -104,7 +98,7 @@
</a-col> </a-col>
</a-row> </a-row>
</a-card> </a-card>
<a-tabs default-active-key="1" @change="(activeKey) => { if(activeKey == 'tpl-advanced') this.changeCode(); }"> <a-tabs default-active-key="1" @change="(activeKey) => { if(activeKey == 'tpl-4') this.changeCode(); }">
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.xray.basicTemplate"}}' style="padding-top: 20px;"> <a-tab-pane key="tpl-1" tab='{{ i18n "pages.xray.basicTemplate"}}' style="padding-top: 20px;">
<a-collapse> <a-collapse>
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'> <a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
@@ -128,7 +122,7 @@
<a-select <a-select
v-model="freedomStrategy" v-model="freedomStrategy"
style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme"> style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</template> </template>
</a-col> </a-col>
@@ -152,49 +146,6 @@
</a-row> </a-row>
</a-list-item> </a-list-item>
</a-collapse-panel> </a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.logConfigs" }}'>
<a-row :xs="24" :sm="24" :lg="12">
<a-alert type="warning" style="text-align: center;">
<template slot="message">
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
{{ i18n "pages.xray.logConfigsDesc" }}
</template>
</a-alert>
</a-row>
<a-list-item>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta title='Level'/>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select
v-model="logLevel"
style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="level in ['none', 'debug', 'info', 'warning', 'error']" :value="level">[[ level ]]</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
</a-list-item>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta title='Access Logs' />
</a-col>
<a-col :lg="24" :xl="12">
<a-input v-model="logAccess"></a-input>
</a-col>
</a-row>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta title='Error Logs' />
</a-col>
<a-col :lg="24" :xl="12">
<a-input v-model="logError"></a-input>
</a-col>
</a-row>
</a-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'> <a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
<a-row :xs="24" :sm="24" :lg="12"> <a-row :xs="24" :sm="24" :lg="12">
<a-alert type="warning" style="text-align: center;"> <a-alert type="warning" style="text-align: center;">
@@ -253,24 +204,6 @@
<setting-list-item type="switch" title='{{ i18n "pages.xray.GoogleIPv4"}}' desc='{{ i18n "pages.xray.GoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.xray.GoogleIPv4"}}' desc='{{ i18n "pages.xray.GoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixIPv4"}}' desc='{{ i18n "pages.xray.NetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixIPv4"}}' desc='{{ i18n "pages.xray.NetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
</a-collapse-panel> </a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.warpConfigs"}}'>
<a-row :xs="24" :sm="24" :lg="12">
<a-alert type="warning" style="text-align: center;">
<template slot="message">
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
{{ i18n "pages.xray.warpConfigsDesc" }}
</template>
</a-alert>
</a-row>
<template v-if="WarpExist">
<setting-list-item type="switch" title='{{ i18n "pages.xray.GoogleWARP"}}' desc='{{ i18n "pages.xray.GoogleWARPDesc"}}' v-model="GoogleWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.OpenAIWARP"}}' desc='{{ i18n "pages.xray.OpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixWARP"}}' desc='{{ i18n "pages.xray.NetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.SpotifyWARP"}}' desc='{{ i18n "pages.xray.SpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.MetaWARP"}}' desc='{{ i18n "pages.xray.MetaWARPDesc"}}' v-model="MetaWARPSettings"></setting-list-item>
</template>
<a-button v-else style="margin: 10px 0;" @click="showWarp">WARP {{ i18n "pages.xray.rules.outbound" }}</a-button>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.resetDefaultConfig"}}'> <a-collapse-panel header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
<a-space direction="horizontal" style="padding: 0 20px"> <a-space direction="horizontal" style="padding: 0 20px">
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button> <a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
@@ -387,78 +320,79 @@
</a-table> </a-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true"> <a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button> <a-button type="primary" icon="plus" @click="addOutbound()">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button> <a-button type="primary" icon="plus" @click="addReverse()">{{ i18n "pages.xray.outbound.addReverse" }}</a-button>
<a-table :columns="outboundColumns" bordered <a-row>
:row-key="r => r.key" <a-col :sm="24" :md="12">
:data-source="outboundData" <p style="margin: 10px;">{{ i18n "pages.xray.Outbounds"}}</p>
:scroll="isMobile ? {} : { x: 200 }" <a-table :columns="outboundColumns" bordered
:pagination="false" :row-key="r => r.key"
:indent-size="0" :data-source="outboundData"
:style="isMobile ? 'padding: 5px 5px' : 'margin-right: 1px;'"> :scroll="isMobile ? {} : { x: 200 }"
<template slot="action" slot-scope="text, outbound, index"> :pagination="false"
[[ index+1 ]] :indent-size="0"
<a-dropdown :trigger="['click']"> :style="isMobile ? 'padding: 5px 5px' : 'margin-right: 1px;'">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon> <template slot="action" slot-scope="text, outbound, index">
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme"> [[ index+1 ]]
<a-menu-item v-if="index>0" @click="setFirstOutbound(index)"> <a-dropdown :trigger="['click']">
<a-icon type="vertical-align-top"></a-icon> <a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
{{ i18n "pages.xray.rules.first"}} <a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
</a-menu-item> <a-menu-item @click="editOutbound(index)">
<a-menu-item @click="editOutbound(index)"> <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 @click="deleteOutbound(index)">
<a-menu-item @click="deleteOutbound(index)"> <span style="color: #FF4D4F">
<span style="color: #FF4D4F"> <a-icon type="delete"></a-icon> {{ i18n "delete"}}
<a-icon type="delete"></a-icon> {{ i18n "delete"}} </span>
</span> </a-menu-item>
</a-menu-item> </a-menu>
</a-menu> </a-dropdown>
</a-dropdown> </template>
</template> <template slot="address" slot-scope="text, outbound, index">
<template slot="address" slot-scope="text, outbound, index"> <p style="margin: 0 5px;" v-for="addr in findOutboundAddress(outbound)">[[ addr ]]</p>
<p style="margin: 0 5px;" v-for="addr in findOutboundAddress(outbound)">[[ addr ]]</p> </template>
</template> <template slot="protocol" slot-scope="text, outbound, index">
<template slot="protocol" slot-scope="text, outbound, index"> <a-tag style="margin:0;" color="purple">[[ outbound.protocol ]]</a-tag>
<a-tag style="margin:0;" color="purple">[[ outbound.protocol ]]</a-tag> <template v-if="[Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
<template v-if="[Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)"> <a-tag style="margin:0;" color="blue">[[ outbound.streamSettings.network ]]</a-tag>
<a-tag style="margin:0;" color="blue">[[ outbound.streamSettings.network ]]</a-tag> <a-tag style="margin:0;" v-if="outbound.streamSettings.security=='tls'" color="green">tls</a-tag>
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='tls'" color="green">tls</a-tag> <a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag> </template>
</template> </template>
</template> </a-table>
</a-table> </a-col>
<a-col :sm="24" :md="12" v-if="reverseData.length>0">
<p style="margin: 10px;">{{ i18n "pages.xray.outbound.reverse"}}</p>
<a-table :columns="reverseColumns" bordered
:row-key="r => r.key"
:data-source="reverseData"
:scroll="isMobile ? {} : { x: 200 }"
:pagination="false"
:indent-size="0"
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
<template slot="action" slot-scope="text, reverse, index">
[[ index+1 ]]
<a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
<a-menu-item @click="editReverse(index)">
<a-icon type="edit"></a-icon>
{{ i18n "edit" }}
</a-menu-item>
<a-menu-item @click="deleteReverse(index)">
<span style="color: #FF4D4F">
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</a-table>
</a-col>
</a-row>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true"> <a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
<a-button type="primary" icon="plus" @click="addReverse()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addReverse" }}</a-button>
<a-table :columns="reverseColumns" bordered
:row-key="r => r.key"
:data-source="reverseData"
:scroll="isMobile ? {} : { x: 200 }"
:pagination="false"
:indent-size="0"
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
<template slot="action" slot-scope="text, reverse, index">
[[ index+1 ]]
<a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
<a-menu-item @click="editReverse(index)">
<a-icon type="edit"></a-icon>
{{ i18n "edit" }}
</a-menu-item>
<a-menu-item @click="deleteReverse(index)">
<span style="color: #FF4D4F">
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
<a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta> <a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''"> <a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
<a-radio-button value="xraySetting">{{ i18n "pages.xray.completeTemplate"}}</a-radio-button> <a-radio-button value="xraySetting">{{ i18n "pages.xray.completeTemplate"}}</a-radio-button>
@@ -480,13 +414,12 @@
{{template "ruleModal"}} {{template "ruleModal"}}
{{template "outModal"}} {{template "outModal"}}
{{template "reverseModal"}} {{template "reverseModal"}}
{{template "warpModal"}}
<script> <script>
const rulesColumns = [ const rulesColumns = [
{ title: "#", align: 'center', width: 15, scopedSlots: { customRender: 'action' } }, { title: "#", align: 'center', width: 15, scopedSlots: { customRender: 'action' } },
{ title: '{{ i18n "pages.xray.rules.source"}}', children: [ { title: '{{ i18n "pages.xray.rules.source"}}', children: [
{ title: 'IP', dataIndex: "source", align: 'center', width: 20, ellipsis: true }, { title: 'IP', dataIndex: "source", align: 'center', width: 20, ellipsis: true },
{ title: 'Port', dataIndex: 'sourcePort', align: 'center', width: 10, ellipsis: true } ]}, { title: 'port', dataIndex: 'sourcePort', align: 'center', width: 10, ellipsis: true } ]},
{ title: '{{ i18n "pages.inbounds.network"}}', children: [ { title: '{{ i18n "pages.inbounds.network"}}', children: [
{ title: 'L4', dataIndex: 'network', align: 'center', width: 10 }, { title: 'L4', dataIndex: 'network', align: 'center', width: 10 },
{ title: 'Protocol', dataIndex: 'protocol', align: 'center', width: 10, ellipsis: true }, { title: 'Protocol', dataIndex: 'protocol', align: 'center', width: 10, ellipsis: true },
@@ -497,7 +430,7 @@
{ title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]}, { title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
{ title: '{{ i18n "pages.xray.rules.inbound"}}', children: [ { title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
{ title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 20, ellipsis: true }, { title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 20, ellipsis: true },
{ title: 'Client Email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]}, { title: 'User email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]},
{ title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 20 }, { title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 20 },
]; ];
@@ -579,7 +512,7 @@
ips: { ips: {
local: ["geoip:private"], local: ["geoip:private"],
cn: ["geoip:cn"], cn: ["geoip:cn"],
ir: ["ext:geoip_IR.dat:ir"], ir: ["ext:geoip_IR.dat:ir","ext:geoip_IR.dat:arvancloud","ext:geoip_IR.dat:derakcloud","ext:geoip_IR.dat:iranserver"],
ru: ["geoip:ru"], ru: ["geoip:ru"],
}, },
domains: { domains: {
@@ -587,11 +520,8 @@
"geosite:category-ads-all", "geosite:category-ads-all",
"ext:geosite_IR.dat:category-ads-all" "ext:geosite_IR.dat:category-ads-all"
], ],
openai: ["geosite:openai"],
google: ["geosite:google"], google: ["geosite:google"],
spotify: ["geosite:spotify"],
netflix: ["geosite:netflix"], netflix: ["geosite:netflix"],
meta: ["geosite:meta"],
cn: [ cn: [
"geosite:cn", "geosite:cn",
"regexp:.*\\.cn$" "regexp:.*\\.cn$"
@@ -603,17 +533,17 @@
ir: [ ir: [
"regexp:.*\\.ir$", "regexp:.*\\.ir$",
"regexp:.*\\.xn--mgba3a4f16a$", // .ایران "regexp:.*\\.xn--mgba3a4f16a$", // .ایران
"ext:geosite_IR.dat:ir" "ext:geosite_IR.dat:ir" // have rules to bypass all .ir domains.
] ]
}, },
familyProtectDNS: { familyProtectDNS: {
"servers": [ "servers": [
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/ "1.1.1.3",
"1.0.0.3", "1.0.0.3",
"2606:4700:4700::1113", "94.140.14.15",
"2606:4700:4700::1003" "94.140.15.16"
], ],
"queryStrategy": "UseIP" "queryStrategy": "UseIPv4"
}, },
} }
}, },
@@ -772,8 +702,6 @@
break; break;
case Protocols.DNS: case Protocols.DNS:
return [o.settings.address + ':' + o.settings.port]; return [o.settings.address + ':' + o.settings.port];
case Protocols.Wireguard:
return o.settings.peers.map(peer => peer.endpoint);
default: default:
return null; return null;
} }
@@ -814,11 +742,6 @@
outbounds.splice(index,1); outbounds.splice(index,1);
this.outboundSettings = JSON.stringify(outbounds); this.outboundSettings = JSON.stringify(outbounds);
}, },
setFirstOutbound(index){
outbounds = this.templateSettings.outbounds;
outbounds.splice(0, 0, outbounds.splice(index, 1)[0]);
this.outboundSettings = JSON.stringify(outbounds);
},
addReverse(){ addReverse(){
reverseModal.show({ reverseModal.show({
title: '{{ i18n "pages.xray.outbound.addReverse"}}', title: '{{ i18n "pages.xray.outbound.addReverse"}}',
@@ -854,27 +777,17 @@
confirm: (reverse, rules) => { confirm: (reverse, rules) => {
reverseModal.loading(); reverseModal.loading();
if(reverse.tag.length > 0){ if(reverse.tag.length > 0){
oldData = this.reverseData[index]; oldtag = this.reverseData[index].tag;
this.deleteReverse(index);
newTemplateSettings = this.templateSettings; newTemplateSettings = this.templateSettings;
oldReverseIndex = newTemplateSettings.reverse[oldData.type+'s'].findIndex(rs => rs.tag == oldData.tag); if(newTemplateSettings.reverse == undefined) newTemplateSettings.reverse = {};
oldRuleIndex0 = oldRules.length>0 ? newTemplateSettings.routing.rules.findIndex(r => JSON.stringify(r) == JSON.stringify(oldRules[0])) : -1; if(newTemplateSettings.reverse[reverse.type+'s'] == undefined) newTemplateSettings.reverse[reverse.type+'s'] = [];
oldRuleIndex1 = oldRules.length==2 ? newTemplateSettings.routing.rules.findIndex(r => JSON.stringify(r) == JSON.stringify(oldRules[1])) : -1; newTemplateSettings.reverse[reverse.type+'s'].push({ tag: reverse.tag, domain: reverse.domain });
if(oldData.type == reverse.type){
newTemplateSettings.reverse[oldData.type + 's'][oldReverseIndex] = { tag: reverse.tag, domain: reverse.domain };
} else {
newTemplateSettings.reverse[oldData.type+'s'].splice(oldReverseIndex,1);
// delete empty object
if(newTemplateSettings.reverse[oldData.type+'s'].length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type+'s');
// add other type of reverse if it is not exist
if(!newTemplateSettings.reverse[reverse.type+'s']) newTemplateSettings.reverse[reverse.type+'s'] = [];
newTemplateSettings.reverse[reverse.type+'s'].push({ tag: reverse.tag, domain: reverse.domain });
}
this.templateSettings = newTemplateSettings; this.templateSettings = newTemplateSettings;
// Adjust Rules // Adjust Rules
newRules = this.templateSettings.routing.rules; newRules = this.templateSettings.routing.rules.filter(r => r.outboundTag != oldtag && (r.inboundTag && !r.inboundTag.includes(oldtag)));
oldRuleIndex0 != -1 ? newRules[oldRuleIndex0] = rules[0] : newRules.push(rules[0]); newRules.push(...rules)
oldRuleIndex1 != -1 ? newRules[oldRuleIndex1] = rules[1] : newRules.push(rules[1]);
this.routingRuleSettings = JSON.stringify(newRules); this.routingRuleSettings = JSON.stringify(newRules);
} }
reverseModal.close(); reverseModal.close();
@@ -889,17 +802,10 @@
realIndex = reverseTypeObj.findIndex(r => r.tag==oldData.tag && r.domain==oldData.domain); realIndex = reverseTypeObj.findIndex(r => r.tag==oldData.tag && r.domain==oldData.domain);
newTemplateSettings.reverse[oldData.type+'s'].splice(realIndex,1); newTemplateSettings.reverse[oldData.type+'s'].splice(realIndex,1);
// delete empty objects
if(reverseTypeObj.length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type+'s'); if(reverseTypeObj.length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type+'s');
if(Object.keys(newTemplateSettings.reverse).length === 0) Reflect.deleteProperty(newTemplateSettings, 'reverse'); if(Object.keys(newTemplateSettings.reverse).length === 0) Reflect.deleteProperty(newTemplateSettings, 'reverse');
// delete related routing rules newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag && (r.inboundTag && !r.inboundTag.includes(oldData.tag)));
newRules = newTemplateSettings.routing.rules;
if(oldData.type == "bridge"){
newRules = newTemplateSettings.routing.rules.filter(r => !( r.inboundTag && r.inboundTag.length == 1 && r.inboundTag[0] == oldData.tag));
} else if(oldData.type == "portal"){
newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag);
}
newTemplateSettings.routing.rules = newRules; newTemplateSettings.routing.rules = newRules;
this.templateSettings = newTemplateSettings; this.templateSettings = newTemplateSettings;
@@ -944,9 +850,6 @@
rules = this.templateSettings.routing.rules; rules = this.templateSettings.routing.rules;
rules.splice(index,1); rules.splice(index,1);
this.routingRuleSettings = JSON.stringify(rules); this.routingRuleSettings = JSON.stringify(rules);
},
showWarp(){
warpModal.show();
} }
}, },
async mounted() { async mounted() {
@@ -965,38 +868,6 @@
get: function () { return this.xraySetting ? JSON.parse(this.xraySetting) : null; }, get: function () { return this.xraySetting ? JSON.parse(this.xraySetting) : null; },
set: function (newValue) { this.xraySetting = JSON.stringify(newValue, null, 2); }, set: function (newValue) { this.xraySetting = JSON.stringify(newValue, null, 2); },
}, },
logSettings: {
get: function () { return this.templateSettings ? this.templateSettings.log : {}; },
set: function (newValue) {
newTemplateSettings = this.templateSettings;
newTemplateSettings.log = newValue;
this.templateSettings = newTemplateSettings;
},
},
logLevel: {
get: function () { return this.logSettings?.loglevel?? 'none'; },
set: function (newValue) {
newLogSettings = this.logSettings;
newLogSettings.loglevel = newValue;
this.logSettings = newLogSettings;
},
},
logAccess: {
get: function () { return this.logSettings?.access?? ''; },
set: function (newValue) {
newLogSettings = this.logSettings;
newValue == "" ? delete newLogSettings.access : newLogSettings.access = newValue;
this.logSettings = newLogSettings;
},
},
logError: {
get: function () { return this.logSettings?.error?? ''; },
set: function (newValue) {
newLogSettings = this.logSettings;
newValue == "" ? delete newLogSettings.error : newLogSettings.error = newValue;
this.logSettings = newLogSettings;
},
},
inboundSettings: { inboundSettings: {
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; }, get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
set: function (newValue) { set: function (newValue) {
@@ -1154,14 +1025,6 @@
this.syncRulesWithOutbound("IPv4", this.ipv4Settings); this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
} }
}, },
warpDomains: {
get: function () {
return this.templateRuleGetter({ outboundTag: "warp", property: "domain" });
},
set: function (newValue) {
this.templateRuleSetter({ outboundTag: "warp", property: "domain", data: newValue });
}
},
torrentSettings: { torrentSettings: {
get: function () { get: function () {
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols); return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
@@ -1381,73 +1244,8 @@
} }
} }
}, },
WarpExist: {
get: function() {
return this.templateSettings ? this.templateSettings.outbounds.findIndex((o) => o.tag == "warp")>=0 : false;
},
},
GoogleWARPSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.google, this.warpDomains);
},
set: function (newValue) {
if (newValue) {
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.google];
} else {
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.google.includes(data));
}
},
},
OpenAIWARPSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.openai, this.warpDomains);
},
set: function (newValue) {
if (newValue) {
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.openai];
} else {
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.openai.includes(data));
}
},
},
NetflixWARPSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.netflix, this.warpDomains);
},
set: function (newValue) {
if (newValue) {
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.netflix];
} else {
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.netflix.includes(data));
}
},
},
MetaWARPSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.meta, this.warpDomains);
},
set: function (newValue) {
if (newValue) {
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.meta];
} else {
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.meta.includes(data));
}
},
},
SpotifyWARPSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.spotify, this.warpDomains);
},
set: function (newValue) {
if (newValue) {
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.spotify];
} else {
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.spotify.includes(data));
}
},
},
}, },
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@@ -2,41 +2,77 @@
<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> </table>
</a-form> </a-form>

View File

@@ -2,111 +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>
</td>
</tr>
</table> </table>
</a-form> </a-form>
</a-modal> </a-modal>

View File

@@ -474,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 {

View File

@@ -33,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 {

View File

@@ -55,7 +55,6 @@ var defaultValueMap = map[string]string{
"subEncrypt": "true", "subEncrypt": "true",
"subShowInfo": "false", "subShowInfo": "false",
"subURI": "", "subURI": "",
"warp": "",
} }
type SettingService struct { type SettingService struct {
@@ -398,13 +397,6 @@ func (s *SettingService) GetSubURI() (string, error) {
return s.getString("subURI") return s.getString("subURI")
} }
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

View File

@@ -1,13 +1,8 @@
package service package service
import ( import (
"bytes"
_ "embed" _ "embed"
"encoding/json" "encoding/json"
"fmt"
"net/http"
"os"
"time"
"x-ui/util/common" "x-ui/util/common"
"x-ui/xray" "x-ui/xray"
) )
@@ -31,142 +26,3 @@ func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
} }
return nil return nil
} }
func (s *XraySettingService) GetWarpData() (string, error) {
warp, err := s.SettingService.GetWarp()
if err != nil {
return "", err
}
return warp, nil
}
func (s *XraySettingService) GetWarpConfig() (string, error) {
var warpData map[string]string
warp, err := s.SettingService.GetWarp()
if err != nil {
return "", err
}
err = json.Unmarshal([]byte(warp), &warpData)
if err != nil {
return "", err
}
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"])
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
buffer := bytes.NewBuffer(make([]byte, 8192))
buffer.Reset()
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return "", err
}
return buffer.String(), nil
}
func (s *XraySettingService) RegWarp(secretKey string, publicKey string) (string, error) {
tos := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
hostName, _ := os.Hostname()
data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName)
url := "https://api.cloudflareclient.com/v0a2158/reg"
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
if err != nil {
return "", err
}
req.Header.Add("CF-Client-Version", "a-7.21-0721")
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
buffer := bytes.NewBuffer(make([]byte, 8192))
buffer.Reset()
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return "", err
}
var rspData map[string]interface{}
err = json.Unmarshal(buffer.Bytes(), &rspData)
if err != nil {
return "", err
}
deviceId := rspData["id"].(string)
token := rspData["token"].(string)
license, ok := rspData["account"].(map[string]interface{})["license"].(string)
if !ok {
fmt.Println("Error accessing license value.")
return "", err
}
warpData := fmt.Sprintf("{\n \"access_token\": \"%s\",\n \"device_id\": \"%s\",", token, deviceId)
warpData += fmt.Sprintf("\n \"license_key\": \"%s\",\n \"private_key\": \"%s\"\n}", license, secretKey)
s.SettingService.SetWarp(warpData)
result := fmt.Sprintf("{\n \"data\": %s,\n \"config\": %s\n}", warpData, buffer.String())
return result, nil
}
func (s *XraySettingService) SetWarpLicence(license string) (string, error) {
var warpData map[string]string
warp, err := s.SettingService.GetWarp()
if err != nil {
return "", err
}
err = json.Unmarshal([]byte(warp), &warpData)
if err != nil {
return "", err
}
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s/account", warpData["device_id"])
data := fmt.Sprintf(`{"license": "%s"}`, license)
req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(data)))
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
buffer := bytes.NewBuffer(make([]byte, 8192))
buffer.Reset()
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return "", err
}
warpData["license_key"] = license
newWarpData, err := json.MarshalIndent(warpData, "", " ")
if err != nil {
return "", err
}
s.SettingService.SetWarp(string(newWarpData))
println(string(newWarpData))
return string(newWarpData), nil
}

View File

@@ -1,6 +1,6 @@
"username" = "Username" "username" = "Username"
"password" = "Password" "password" = "Password"
"login" = "Log In" "login" = "Login"
"confirm" = "Confirm" "confirm" = "Confirm"
"cancel" = "Cancel" "cancel" = "Cancel"
"close" = "Close" "close" = "Close"
@@ -8,7 +8,7 @@
"copied" = "Copied" "copied" = "Copied"
"download" = "Download" "download" = "Download"
"remark" = "Remark" "remark" = "Remark"
"enable" = "Enabled" "enable" = "Enable"
"protocol" = "Protocol" "protocol" = "Protocol"
"search" = "Search" "search" = "Search"
"filter" = "Filter" "filter" = "Filter"
@@ -26,13 +26,13 @@
"edit" = "Edit" "edit" = "Edit"
"delete" = "Delete" "delete" = "Delete"
"reset" = "Reset" "reset" = "Reset"
"copySuccess" = "Copied Successful" "copySuccess" = "Copied successfully"
"sure" = "Sure" "sure" = "Sure"
"encryption" = "Encryption" "encryption" = "Encryption"
"transmission" = "Transmission" "transmission" = "Transmission"
"host" = "Host" "host" = "Host"
"path" = "Path" "path" = "Path"
"camouflage" = "Obfuscation" "camouflage" = "Camouflage"
"status" = "Status" "status" = "Status"
"enabled" = "Enabled" "enabled" = "Enabled"
"disabled" = "Disabled" "disabled" = "Disabled"
@@ -40,108 +40,104 @@
"depletingSoon" = "Depleting" "depletingSoon" = "Depleting"
"offline" = "Offline" "offline" = "Offline"
"online" = "Online" "online" = "Online"
"domainName" = "Domain Name" "domainName" = "Domain name"
"monitor" = "Listen IP" "monitor" = "Listen IP"
"certificate" = "Certificate" "certificate" = "Certificate"
"fail" = " Failed" "fail" = " Fail"
"success" = " Successful" "success" = " Success"
"getVersion" = "Get Version" "getVersion" = "Get version"
"install" = "Install" "install" = "Install"
"clients" = "Clients" "clients" = "Clients"
"usage" = "Usage" "usage" = "Usage"
"remained" = "Remaining" "remained" = "Remained"
"secAlertTitle" = "Security Alert" "secAlertTitle" = "Security Alert"
"secAlertSsl" = "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection." "secAlertSsl" = "This connection is not secure; Please refrain from entering sensitive information until TLS is activated for data protection"
"security" = "Security" "security" = "Security"
[menu] [menu]
"dashboard" = "Overview" "dashboard" = "System Status"
"inbounds" = "Inbounds" "inbounds" = "Inbounds"
"settings" = "Panel Settings" "settings" = "Panel Settings"
"xray" = "Xray Configs" "xray" = "Xray Settings"
"logout" = "Log Out" "logout" = "Logout"
"link" = "Manage" "link" = "Other"
[pages.login] [pages.login]
"title" = "Welcome" "title" = "Login"
"loginAgain" = "Your session has expired, please log in again" "loginAgain" = "The login time limit has expired, please log in again"
[pages.login.toasts] [pages.login.toasts]
"invalidFormData" = "The input data format is invalid" "invalidFormData" = "Input data format is invalid."
"emptyUsername" = "Username is required" "emptyUsername" = "Please enter username."
"emptyPassword" = "Password is required" "emptyPassword" = "Please enter password."
"wrongUsernameOrPassword" = "The username or password is incorrect" "wrongUsernameOrPassword" = "Invalid username or password."
"successLogin" = "Login" "successLogin" = "Login"
[pages.index] [pages.index]
"title" = "Overview" "title" = "System Status"
"memory" = "RAM" "memory" = "Memory"
"hard" = "Disk" "hard" = "Hard Disk"
"machineInfo" = "Machine" "xrayStatus" = "Xray Status"
"hostname" = "Hostname"
"xrayStatus" = "Xray"
"stopXray" = "Stop" "stopXray" = "Stop"
"restartXray" = "Restart" "restartXray" = "Restart"
"xraySwitch" = "Change Xray Version" "xraySwitch" = "Switch Version"
"xraySwitchClick" = "Choose the version you want to switch." "xraySwitchClick" = "Choose the version you want to switch to."
"xraySwitchClickDesk" = "Choose carefully, as older versions may not be compatible with the current configurations." "xraySwitchClickDesk" = "Choose wisely, as older versions may not be compatible with current configurations."
"operationHours" = "Uptime" "operationHours" = "Operation Hours"
"operationHoursDesc" = "Uptime since OS startup" "operationHoursDesc" = "System uptime: time since startup."
"xrayoperationHoursDesc" = "Uptime since Xray last restart"
"systemLoad" = "System Load" "systemLoad" = "System Load"
"systemLoadDesc" = "Average load for the past 1, 5, and 15 minutes" "connectionTcpCountDesc" = "Total TCP connections across all network cards."
"connectionTcpCountDesc" = "Total TCP connections" "connectionUdpCountDesc" = "Total UDP connections across all network cards."
"connectionUdpCountDesc" = "Total UDP connections" "upSpeed" = "Total upload speed for all network cards."
"upSpeed" = "Overall upload speed" "downSpeed" = "Total download speed for all network cards."
"downSpeed" = "Overall download speed" "totalSent" = "Total upload traffic of all network cards since system startup."
"totalSent" = "Total data sent since OS startup" "totalReceive" = "Total download data across all network cards since system startup."
"totalReceive" = "Total data received since OS startup" "xraySwitchVersionDialog" = "Switch Xray Version"
"xraySwitchVersionDialog" = "Change Xray Version" "xraySwitchVersionDialogDesc" = "Are you sure you want to switch the Xray version to"
"xraySwitchVersionDialogDesc" = "Are you sure you want to change the Xray version to"
"dontRefresh" = "Installation is in progress, please do not refresh this page." "dontRefresh" = "Installation is in progress, please do not refresh this page."
"logs" = "Logs" "logs" = "Logs"
"config" = "Config" "config" = "Config"
"backup" = "Backup & Restore" "backup" = "Backup & Restore"
"backupTitle" = "Database Backup & Restore" "backupTitle" = "Backup & Restore Database"
"backupDescription" = "It is recommended to make a backup before restoring a database." "backupDescription" = "Remember to backup before importing a new database."
"exportDatabase" = "Get Backup" "exportDatabase" = "Download Database"
"importDatabase" = "Restore" "importDatabase" = "Upload Database"
[pages.inbounds] [pages.inbounds]
"title" = "Inbounds" "title" = "Inbounds"
"totalDownUp" = "Total Sent/Received" "totalDownUp" = "Total Uploads/Downloads"
"totalUsage" = "Total Usage" "totalUsage" = "Total Usage"
"inboundCount" = "Total Inbounds" "inboundCount" = "Number of Inbounds"
"operate" = "Menu" "operate" = "Menu"
"enable" = "Enabled" "enable" = "Enable"
"remark" = "Remark" "remark" = "Remark"
"protocol" = "Protocol" "protocol" = "Protocol"
"port" = "Port" "port" = "Port"
"traffic" = "Traffic" "traffic" = "Traffic"
"details" = "Details" "details" = "Details"
"transportConfig" = "Transport Config" "transportConfig" = "Transport Config"
"expireDate" = "Expiration" "expireDate" = "Expire Date"
"resetTraffic" = "Reset Traffic" "resetTraffic" = "Reset Traffic"
"addInbound" = "Add Inbound" "addInbound" = "Add Inbound"
"generalActions" = "General Actions" "generalActions" = "General Actions"
"create" = "Create" "create" = "Create"
"update" = "Update" "update" = "Update"
"modifyInbound" = "Edit Inbound" "modifyInbound" = "Modify Inbound"
"deleteInbound" = "Delete Inbound" "deleteInbound" = "Delete Inbound"
"deleteInboundContent" = "Are you sure you want to delete inbound?" "deleteInboundContent" = "Are you sure you want to delete inbound?"
"deleteClient" = "Delete Client" "deleteClient" = "Delete Client"
"deleteClientContent" = "Are you sure you want to delete client?" "deleteClientContent" = "Are you sure you want to delete client?"
"resetTrafficContent" = "Are you sure you want to reset traffic?" "resetTrafficContent" = "Are you sure you want to reset traffic?"
"copyLink" = "Copy URL" "copyLink" = "Copy Link"
"address" = "Address" "address" = "Address"
"network" = "Network" "network" = "Network"
"destinationPort" = "Destination Port" "destinationPort" = "Destination Port"
"targetAddress" = "Target Address" "targetAddress" = "Target Address"
"monitorDesc" = "Leave blank to listen on all IPs" "monitorDesc" = "Leave blank by default"
"meansNoLimit" = "Zero means unlimited. (Unit: GB)" "meansNoLimit" = "Means No Limit"
"totalFlow" = "Total Traffic" "totalFlow" = "Total Flow"
"leaveBlankToNeverExpire" = "Leave blank to never expire" "leaveBlankToNeverExpire" = "Leave blank to never expire"
"noRecommendKeepDefault" = "It is recommended to keep the default" "noRecommendKeepDefault" = "No special requirements to keep the default"
"certificatePath" = "File Path" "certificatePath" = "File Path"
"certificateContent" = "File Content" "certificateContent" = "File Content"
"publicKeyPath" = "Public Key Path" "publicKeyPath" = "Public Key Path"
@@ -150,40 +146,40 @@
"keyContent" = "Private Key Content" "keyContent" = "Private Key Content"
"clickOnQRcode" = "Click on QR Code to Copy" "clickOnQRcode" = "Click on QR Code to Copy"
"client" = "Client" "client" = "Client"
"export" = "Export All URLs" "export" = "Export Links"
"clone" = "Clone" "clone" = "Clone"
"cloneInbound" = "Clone" "cloneInbound" = "Clone"
"cloneInboundContent" = "All settings for this inbound, except Port, Listen IP, and Clients, will be applied to the clone." "cloneInboundContent" = "All settings of this inbound, except for Port, Listening IP, and Clients, will be applied to the clone."
"cloneInboundOk" = "Clone" "cloneInboundOk" = "Clone"
"resetAllTraffic" = "Reset All Inbounds Traffic" "resetAllTraffic" = "Reset All Inbounds Traffic"
"resetAllTrafficTitle" = "Reset All Inbounds Traffic" "resetAllTrafficTitle" = "Reset all inbounds traffic"
"resetAllTrafficContent" = "Are you sure you want to reset the traffic of all inbounds?" "resetAllTrafficContent" = "Are you sure you want to reset all inbounds traffic?"
"resetInboundClientTraffics" = "Reset Clients Traffic" "resetInboundClientTraffics" = "Reset Clients Traffic"
"resetInboundClientTrafficTitle" = "Reset Clients Traffic" "resetInboundClientTrafficTitle" = "Reset all clients traffic"
"resetInboundClientTrafficContent" = "Are you sure you want to reset the traffic of this inbound's clients?" "resetInboundClientTrafficContent" = "Are you sure you want to reset all traffic for this inbound's clients?"
"resetAllClientTraffics" = "Reset All Clients Traffic" "resetAllClientTraffics" = "Reset All Clients Traffic"
"resetAllClientTrafficTitle" = "Reset All Clients Traffic" "resetAllClientTrafficTitle" = "Reset all clients traffic"
"resetAllClientTrafficContent" = "Are you sure you want to reset the traffic of all clients?" "resetAllClientTrafficContent" = "Are you sure you want to reset all traffics for all clients?"
"delDepletedClients" = "Delete Depleted Clients" "delDepletedClients" = "Delete Depleted Clients"
"delDepletedClientsTitle" = "Delete Depleted Clients" "delDepletedClientsTitle" = "Delete depleted clients"
"delDepletedClientsContent" = "Are you sure you want to delete all the depleted clients?" "delDepletedClientsContent" = "Are you sure you want to delete all depleted clients?"
"email" = "Email" "email" = "Email"
"emailDesc" = "Please provide a unique email address" "emailDesc" = "Please provide a unique email address."
"setDefaultCert" = "Set Cert from Panel" "setDefaultCert" = "Set cert from panel"
"telegramDesc" = "Please provide Chat ID(s) without using the '@' symbol. (Get it here @userinfobot) or (Use '/id' command in the bot)" "telegramDesc" = "Use Telegram ID without @ or chat IDs ( you can get it here @userinfobot or use '/id' command in bot )"
"subscriptionDesc" = "To find your subscription URL, navigate to the 'More Information'. Additionally, you can use the same name for several clients." "subscriptionDesc" = "You can find your sub link on Details, also you can use the same name for several configurations"
"info" = "Info" "info" = "Info"
"same" = "Same" "same" = "Same"
"inboundData" = "Inbound's Data" "inboundData" = "Inbound's data"
"exportInbound" = "Export Inbound" "copyToClipboard" = "Copy to clipboard"
"import" = "Import" "import" = "Import"
"importInbound" = "Import an Inbound" "importInbound" = "Import an inbound"
[pages.client] [pages.client]
"add" = "Add Client" "add" = "Add Client"
"edit" = "Edit Client" "edit" = "Edit Client"
"submitAdd" = "Add Client" "submitAdd" = "Add Client"
"submitEdit" = "Save Changes" "submitEdit" = "Save changes"
"clientCount" = "Number of Clients" "clientCount" = "Number of Clients"
"bulk" = "Add Bulk" "bulk" = "Add Bulk"
"method" = "Method" "method" = "Method"
@@ -191,192 +187,177 @@
"last" = "Last" "last" = "Last"
"prefix" = "Prefix" "prefix" = "Prefix"
"postfix" = "Postfix" "postfix" = "Postfix"
"delayedStart" = "Start on Initial Use" "delayedStart" = "Start after first use"
"expireDays" = "Duration" "expireDays" = "Expire days"
"days" = "Day(s)" "days" = "day(s)"
"renew" = "Auto Renew" "renew" = "Auto renew"
"renewDesc" = "Auto-renewal after expiration. (0 = disable)(Unit: day)" "renewDesc" = "Auto renew days after expiration. 0 = disable"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "Obtain" "obtain" = "Obtain"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"request" = "Request" "requestHeader" = "Request header"
"response" = "Response"
"name" = "Name" "name" = "Name"
"value" = "Value" "value" = "Value"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"version" = "Version" "requestVersion" = "Request version"
"method" = "Method" "requestMethod" = "Request method"
"path" = "Path" "requestPath" = "Request path"
"status" = "Status" "responseVersion" = "Response version"
"statusDescription" = "Status Description" "responseStatus" = "Response status"
"requestHeader" = "Request Header" "responseStatusDescription" = "Response status description"
"responseHeader" = "Response Header" "responseHeader" = "Response header"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
"encryption" = "Encryption" "encryption" = "Encryption"
[pages.settings] [pages.settings]
"title" = "Panel Settings" "title" = "Settings"
"save" = "Save" "save" = "Save"
"infoDesc" = "Every change made here needs to be saved. Please restart the panel for the changes to take effect." "infoDesc" = "Every change made here needs to be saved. Please restart the panel for the changes to take effect."
"restartPanel" = "Restart Panel" "restartPanel" = "Restart Panel "
"restartPanelDesc" = "Are you sure you want to restart the panel? If you are unable to access the panel after restarting, please check the logs in the terminal script" "restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please view the panel log information on the server."
"resetDefaultConfig" = "Reset to Default" "resetDefaultConfig" = "Reset to default config"
"panelConfig" = "General" "panelConfig" = "Panel Configurations"
"userSettings" = "Authentication" "userSettings" = "User Settings"
"TGBotSettings" = "Telegram Bot" "TGBotSettings" = "Telegram Bot Settings"
"panelListeningIP" = "Listen IP" "panelListeningIP" = "Panel Listening IP"
"panelListeningIPDesc" = "The IP address for the web panel. (Leave blank to listen on all IPs)" "panelListeningIPDesc" = "Leave blank by default to monitor all IPs."
"panelListeningDomain" = "Listen Domain" "panelListeningDomain" = "Panel Listening Domain"
"panelListeningDomainDesc" = "The domain name for the web panel. (Leave blank to listen on all domains and IPs)" "panelListeningDomainDesc" = "Leave blank by default to monitor all domains and IPs"
"panelPort" = "Listen Port" "panelPort" = "Panel Port"
"panelPortDesc" = "The port number for the web panel. (Must be an unused port)" "panelPortDesc" = "Port number for serving the panel."
"publicKeyPath" = "Public Key Path" "publicKeyPath" = "Panel Certificate Public Key File Path"
"publicKeyPathDesc" = "The public key file path for the web panel. (Begins with /)" "publicKeyPathDesc" = "Fill in an absolute path starting with '/'"
"privateKeyPath" = "Private Key Path" "privateKeyPath" = "Panel Certificate Private Key File Path"
"privateKeyPathDesc" = "The private key file path for the web panel. (Begins with /)" "privateKeyPathDesc" = "Fill in an absolute path starting with '/'"
"panelUrlPath" = "URI Path" "panelUrlPath" = "Panel URL Root Path"
"panelUrlPathDesc" = "The URI path for the web panel. (Begins with / and concludes with /)" "panelUrlPathDesc" = "Must start with '/' and end with '/'"
"pageSize" = "Pagination Size" "pageSize" = "Pagination size"
"pageSizeDesc" = "The page size for the inbounds table. (0 = disable)" "pageSizeDesc" = "Define page size for inbounds table. Set 0 to disable"
"remarkModel" = "Remark Model & Separation Character" "remarkModel" = "Remark Model and Seperation charachter"
"sampleRemark" = "Sample Remark" "sampleRemark" = "Sample remark"
"oldUsername" = "Current Username" "oldUsername" = "Current Username"
"currentPassword" = "Current Password" "currentPassword" = "Current Password"
"newUsername" = "New Username" "newUsername" = "New Username"
"newPassword" = "New Password" "newPassword" = "New Password"
"telegramBotEnable" = "Enable Telegram Bot" "telegramBotEnable" = "Enable Telegram bot"
"telegramBotEnableDesc" = "Enables the Telegram bot." "telegramBotEnableDesc" = "Your telegram bot will interact with the panel"
"telegramToken" = "Telegram Token" "telegramToken" = "Telegram Token"
"telegramTokenDesc" = "The Telegram token. (Get it here @BotFather)" "telegramTokenDesc" = "The Token you have got from @BotFather"
"telegramChatId" = "Admin Chat ID" "telegramChatId" = "Telegram Admin ChatIDs"
"telegramChatIdDesc" = "The Telegram Admin Chat ID(s). (Comma-separated)(Get it here @userinfobot) or (Use '/id' command in the bot)" "telegramChatIdDesc" = "Multiple Chat IDs separated by comma. use @userinfobot or use '/id' command in bot to get your Chat IDs."
"telegramNotifyTime" = "Notification Time" "telegramNotifyTime" = "Telegram bot notification time"
"telegramNotifyTimeDesc" = "The Telegram bot notification time set for periodic reports. (Use the crontab time format)" "telegramNotifyTimeDesc" = "Use Crontab timing format."
"tgNotifyBackup" = "Database Backup" "tgNotifyBackup" = "Database Backup"
"tgNotifyBackupDesc" = "Get a database backup file with a report." "tgNotifyBackupDesc" = "Send database backup file with report notification"
"tgNotifyLogin" = "Login Notification" "tgNotifyLogin" = "Login Notification"
"tgNotifyLoginDesc" = "Get notified about the username, IP, and time whenever someone attempts to log into your web panel." "tgNotifyLoginDesc" = "Displays the username, IP address, and time when someone tries to log into your panel."
"sessionMaxAge" = "Session Duration" "sessionMaxAge" = "Session maximum age"
"sessionMaxAgeDesc" = "The duration for which you can stay logged in. (Unit: minute)" "sessionMaxAgeDesc" = "The time that you can stay login (unit: minute)"
"expireTimeDiff" = "Expiration Time Notification" "expireTimeDiff" = "Expiration threshold for notification"
"expireTimeDiffDesc" = "Get notified when the remaining time reaches the set threshold. (Unit: day)" "expireTimeDiffDesc" = "Get notified about account expiration before the threshold (unit: day)"
"trafficDiff" = "Traffic Limit Notification" "trafficDiff" = "Traffic threshold for notification"
"trafficDiffDesc" = "Get notified when remaining traffic reaches the set threshold. (Unit: GB)" "trafficDiffDesc" = "Get notified about traffic exhaustion before reaching the threshold (unit: GB)"
"tgNotifyCpu" = "CPU Load Notification" "tgNotifyCpu" = "CPU percentage alert threshold"
"tgNotifyCpuDesc" = "Get notified if CPU load exceeds the set threshold. (Unit: %)" "tgNotifyCpuDesc" = "Receive notification if CPU usage exceeds this threshold (unit: %)"
"timeZone" = "Time Zone" "timeZone" = "Time Zone"
"timeZoneDesc" = "Scheduled tasks will run based on this time zone." "timeZoneDesc" = "Scheduled tasks run according to the time in this time zone."
"subSettings" = "Subscription" "subSettings" = "Subscription"
"subEnable" = "Enable Subscription Service" "subEnable" = "Enable service"
"subEnableDesc" = "Enables the subscription service." "subEnableDesc" = "Subscription feature with separate configuration"
"subListen" = "Listen IP" "subListen" = "Listening IP"
"subListenDesc" = "The IP address for the subscription service. (Leave blank to listen on all IPs)" "subListenDesc" = "Leave blank by default to monitor all IPs"
"subPort" = "Listen Port" "subPort" = "Subscription Port"
"subPortDesc" = "The port number for the subscription service. (Must be an unused port)" "subPortDesc" = "Port number for serving the subscription service must be unused in server"
"subCertPath" = "Public Key Path" "subCertPath" = "Subscription Certificate Public Key File Path"
"subCertPathDesc" = "The public key file path for the subscription service. (Begins with /)" "subCertPathDesc" = "Fill in an absolute path starting with '/'"
"subKeyPath" = "Private Key Path" "subKeyPath" = "Subscription Certificate Private Key File Path"
"subKeyPathDesc" = "The private key file path for the subscription service. (Begins with /)" "subKeyPathDesc" = "Fill in an absolute path starting with '/'"
"subPath" = "URI Path" "subPath" = "Subscription URL Root Path"
"subPathDesc" = "The URI path for the subscription service. (Begins with / and concludes with /)" "subPathDesc" = "Must start with '/' and end with '/'"
"subDomain" = "Listen Domain" "subDomain" = "Listening Domain"
"subDomainDesc" = "The domain name for the subscription service. (Leave blank to listen on all domains and IPs)" "subDomainDesc" = "Leave blank by default to monitor all domains and IPs"
"subUpdates" = "Update Intervals" "subUpdates" = "Subscription update intervals"
"subUpdatesDesc" = "The update intervals of the subscription URL in the client apps. (Unit: hour)" "subUpdatesDesc" = "Interval hours between updates in client application"
"subEncrypt" = "Encode" "subEncrypt" = "Encrypt configs"
"subEncryptDesc" = "The returned content of subscription service will be Base64 encoded." "subEncryptDesc" = "Encrypt the returned configs in subscription"
"subShowInfo" = "Show Usage Info" "subShowInfo" = "Show usage info"
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps." "subShowInfoDesc" = "Show remained traffic and date after config name"
"subURI" = "Reverse Proxy URI" "subURI" = "Reverse Proxy URI"
"subURIDesc" = "The subscription service will use the URI that has been set up behind reverse proxies." "subURIDesc" = "Change base URI of subscription URL for using on behind of proxies"
[pages.settings.toasts] [pages.settings.toasts]
"modifySettings" = "Modify Settings" "modifySettings" = "Modify Settings "
"getSettings" = "Get Settings" "getSettings" = "Get Settings "
"modifyUser" = "Modify Admin" "modifyUser" = "Modify User "
"originalUserPassIncorrect" = "The current username or password is incorrect" "originalUserPassIncorrect" = "Incorrect original username or password"
"userPassMustBeNotEmpty" = "The new username or password is required" "userPassMustBeNotEmpty" = "New username and new password cannot be empty"
[pages.xray] [pages.xray]
"title" = "Xray Configs" "title" = "Xray Settings"
"save" = "Save" "save" = "Save Settings"
"restart" = "Restart Xray" "restart" = "Restart Xray"
"basicTemplate" = "Basics" "basicTemplate" = "Basic Template"
"advancedTemplate" = "Advanced" "advancedTemplate" = "Advanced Template"
"generalConfigs" = "General Strategy" "generalConfigs" = "General Configs"
"generalConfigsDesc" = "These options will determine general strategy adjustments." "generalConfigsDesc" = "These options will provide general adjustments."
"logConfigs" = "Log" "blockConfigs" = "Blocking Configs"
"logConfigsDesc" = "Logs may affect your server's efficiency. It is recommended to enable it wisely only in case of your needs" "blockConfigsDesc" = "These options will prevent users from connecting to specific protocols and websites."
"blockConfigs" = "Protection Shield" "blockCountryConfigs" = "Block Country Configs"
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites." "blockCountryConfigsDesc" = "These options will prevent users from connecting to specific country domains."
"blockCountryConfigs" = "Block Country" "directCountryConfigs" = "Direct Country Configs"
"blockCountryConfigsDesc" = "These options will block traffic based on the specific requested country." "directCountryConfigsDesc" = "These options will connect users directly to specific country domains."
"directCountryConfigs" = "Direct Country" "ipv4Configs" = "IPv4 Configs"
"directCountryConfigsDesc" = "These options will directly forward traffic based on the specific requested country." "ipv4ConfigsDesc" = "These options will route to target domains only via IPv4."
"ipv4Configs" = "IPv4 Routing" "Template" = "Xray Configuration Template"
"ipv4ConfigsDesc" = "These options will route traffic based on specific requested destination via system's IPv4." "TemplateDesc" = "Generate the final Xray configuration file based on this template."
"warpConfigs" = "WARP Config" "FreedomStrategy" = "Configure Strategy for Freedom Protocol"
"warpConfigsDesc" = "These options will route traffic based on specific requested destination via WARP." "FreedomStrategyDesc" = "Set the output strategy of the network in the Freedom Protocol."
"Template" = "Advanced Xray Configuration Template" "RoutingStrategy" = "Configure Domains Routing Strategy"
"TemplateDesc" = "The final Xray config file will be generated based on this template." "RoutingStrategyDesc" = "Set the overall routing strategy for DNS resolving."
"FreedomStrategy" = "Freedom Protocol Strategy" "Torrent" = "Ban BitTorrent Usage"
"FreedomStrategyDesc" = "Set the output strategy for the network in the Freedom Protocol." "TorrentDesc" = "Change the configuration template to avoid using BitTorrent by users."
"RoutingStrategy" = "Overall Routing Strategy" "PrivateIp" = "Ban Private IP Ranges to Connect"
"RoutingStrategyDesc" = "Set the overall traffic routing strategy for resolving all requests." "PrivateIpDesc" = "Change the configuration template to avoid connecting to private IP ranges."
"Torrent" = "Block BitTorrent Protocol"
"TorrentDesc" = "Blocks BitTorrent protocol."
"PrivateIp" = "Block Connection to Private IPs"
"PrivateIpDesc" = "Blocks establishing connections to private IP ranges."
"Ads" = "Block Ads" "Ads" = "Block Ads"
"AdsDesc" = "Blocks advertising websites." "AdsDesc" = "Change the configuration template to block ads"
"Family" = "Family Protection" "Family" = "Enable Family-Friendly Configuration"
"FamilyDesc" = "Blocks adult content, and malware websites." "FamilyDesc" = "Avoid connecting to unsafe websites for family protection."
"IRIp" = "Block Connection to Iran IPs" "IRIp" = "Disable connection to Iran IP ranges"
"IRIpDesc" = "Blocks establishing connections to Iran IP ranges." "IRIpDesc" = "Change the configuration template to avoid connecting to Iran IP ranges."
"IRDomain" = "Block Connection to Iran Domains" "IRDomain" = "Disable connection to Iran domains"
"IRDomainDesc" = "Blocks establishing connections to Iran domains." "IRDomainDesc" = "Change the configuration template to avoid connecting to Iran domains."
"ChinaIp" = "Block Connection to China IPs" "ChinaIp" = "Disable connection to China IP ranges"
"ChinaIpDesc" = "Blocks establishing connections to China IP ranges." "ChinaIpDesc" = "Change the configuration template to avoid connecting to China IP ranges."
"ChinaDomain" = "Block Connection to China Domains" "ChinaDomain" = "Disable connection to China domains"
"ChinaDomainDesc" = "Blocks establishing connections to China domains." "ChinaDomainDesc" = "Change the configuration template to avoid connecting to China domains."
"RussiaIp" = "Block Connection to Russia IPs" "RussiaIp" = "Disable connection to Russia IP ranges"
"RussiaIpDesc" = "Blocks establishing connections to Russia IP ranges." "RussiaIpDesc" = "Change the configuration template to avoid connecting to Russia IP ranges."
"RussiaDomain" = "Block Connection to Russia Domains" "RussiaDomain" = "Disable connection to Russia domains"
"RussiaDomainDesc" = "Blocks establishing connections to Russia domains." "RussiaDomainDesc" = "Change the configuration template to avoid connecting to Russia domains."
"DirectIRIp" = "Direct Connection to Iran IPs" "DirectIRIp" = "Direct connection to Iran IP ranges"
"DirectIRIpDesc" = "Directly establishes connections to Iran IP ranges." "DirectIRIpDesc" = "Change the configuration template for direct connecting to Iran IP ranges."
"DirectIRDomain" = "Direct Connection to Iran Domains" "DirectIRDomain" = "Direct connection to Iran domains"
"DirectIRDomainDesc" = "Directly establishes connections to Iran domains." "DirectIRDomainDesc" = "Change the configuration template for direct connecting to Iran domains."
"DirectChinaIp" = "Direct Connection to China IPs" "DirectChinaIp" = "Direct connection to China IP ranges"
"DirectChinaIpDesc" = "Directly establishes connections to China IP ranges." "DirectChinaIpDesc" = "Change the configuration template for direct connecting to China IP ranges."
"DirectChinaDomain" = "Direct Connection to China Domains" "DirectChinaDomain" = "Direct connection to China domains"
"DirectChinaDomainDesc" = "Directly establishes connections to China domains." "DirectChinaDomainDesc" = "Change the configuration template for direct connecting to China domains."
"DirectRussiaIp" = "Direct Connection to Russia IPs" "DirectRussiaIp" = "Direct connection to Russia IP ranges"
"DirectRussiaIpDesc" = "Directly establishes connections to Russia IP ranges." "DirectRussiaIpDesc" = "Change the configuration template for direct connecting to Russia IP ranges."
"DirectRussiaDomain" = "Direct Connection to Russia Domains" "DirectRussiaDomain" = "Direct connection to Russia domains"
"DirectRussiaDomainDesc" = "Directly establishes connections to Russia domains." "DirectRussiaDomainDesc" = "Change the configuration template for direct connecting to Russia domains."
"GoogleIPv4" = "Google" "GoogleIPv4" = "Use IPv4 for Google"
"GoogleIPv4Desc" = "Routes traffic to Google via IPv4." "GoogleIPv4Desc" = "Add routing for Google to connect with IPv4."
"NetflixIPv4" = "Netflix" "NetflixIPv4" = "Use IPv4 for Netflix"
"NetflixIPv4Desc" = "Routes traffic to Netflix via IPv4." "NetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4."
"GoogleWARP" = "Google"
"GoogleWARPDesc" = "Routes traffic to Google via WARP."
"OpenAIWARP" = "ChatGPT"
"OpenAIWARPDesc" = "Routes traffic to ChatGPT via WARP."
"NetflixWARP" = "Netflix"
"NetflixWARPDesc" = "Routes traffic to Netflix via WARP."
"MetaWARP" = "Meta"
"MetaWARPDesc" = "Routes traffic to Meta (Instagram, Facebook, WhatsApp, Threads,...) via WARP."
"SpotifyWARP" = "Spotify"
"SpotifyWARPDesc" = "Routes traffic to Spotify via WARP."
"completeTemplate" = "All" "completeTemplate" = "All"
"Inbounds" = "Inbounds" "Inbounds" = "Inbounds"
"Outbounds" = "Outbounds" "Outbounds" = "Outbounds"
"Routings" = "Routing Rules" "Routings" = "Routing rules"
"RoutingsDesc" = "The priority of each rule is important!" "RoutingsDesc" = "The priority of each rule is important!"
[pages.xray.rules] [pages.xray.rules]
@@ -391,15 +372,15 @@
"info" = "Info" "info" = "Info"
"add" = "Add Rule" "add" = "Add Rule"
"edit" = "Edit Rule" "edit" = "Edit Rule"
"useComma" = "Comma-separated items" "useComma" = "Comma separated items"
[pages.xray.outbound] [pages.xray.outbound]
"addOutbound" = "Add Outbound" "addOutbound" = "Add outbound"
"addReverse" = "Add Reverse" "addReverse" = "Add reverse"
"editOutbound" = "Edit Outbound" "editOutbound" = "Edit outbound"
"editReverse" = "Edit Reverse" "editReverse" = "Edit reverse"
"tag" = "Tag" "tag" = "Tag"
"tagDesc" = "Unique Tag" "tagDesc" = "Unique tag"
"address" = "Address" "address" = "Address"
"reverse" = "Reverse" "reverse" = "Reverse"
"domain" = "Domain" "domain" = "Domain"
@@ -407,17 +388,6 @@
"bridge" = "Bridge" "bridge" = "Bridge"
"portal" = "Portal" "portal" = "Portal"
"intercon" = "Interconnection" "intercon" = "Interconnection"
"settings" = "Settings"
"accountInfo" = "Account Information"
"outboundStatus" = "Outbound Status"
[pages.xray.wireguard]
"secretKey" = "Secret Key"
"publicKey" = "Public Key"
"allowedIPs" = "Allowed IPs"
"endpoint" = "Endpoint"
"psk" = "PreShared Key"
"domainStrategy" = "Domain Strategy"
[tgbot] [tgbot]
"noResult" = "❗ No result!" "noResult" = "❗ No result!"
@@ -431,41 +401,41 @@
"clients" = "Clients" "clients" = "Clients"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗Unknown command" "unknown" = "❗ Unknown command"
"pleaseChoose" = "👇 Please choose:\r\n" "pleaseChoose" = "👇 Please choose:\r\n"
"help" = "🤖 Welcome to this bot! It's designed to provide you with specific data from the web panel and allows you to make modifications as needed.\r\n\r\n" "help" = "🤖 Welcome to this bot! It's designed to offer you specific data from the server, and it allows you to make modifications as needed.\r\n\r\n"
"start" = "👋 Hello <i>{{ .Firstname }}</i>.\r\n" "start" = "👋 Hello <i>{{ .Firstname }}</i>.\r\n"
"welcome" = "🤖 Welcome to <b>{{ .Hostname }}</b> management bot.\r\n" "welcome" = "🤖 Welcome to <b>{{ .Hostname }}</b> management bot.\r\n"
"status" = "✅ Bot is OK!" "status" = "✅ Bot is ok!"
"usage" = "❗ Please provide a text to search!" "usage" = "❗ Please provide a text to search!"
"getID" = "🆔 Your ID: <code>{{ .ID }}</code>" "getID" = "🆔 Your ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Search for a client email:\r\n<code>/Usage [Email]</code>\r\n\r\nSearch for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>" "helpAdminCommands" = "Search for a client email:\r\n<code>/usage [Email]</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>"
"helpClientCommands" = "To search for statistics, simply use the following command:\r\n\r\n<code>/Usage [UUID|Password]</code>\r\n\r\nUse UUID for VMess/VLESS and Password for Trojan/Shadowsocks." "helpClientCommands" = "To search for statistics, just use the following command:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\nUse UUID for vmess/vless and Password for Trojan."
[tgbot.messages] [tgbot.messages]
"cpuThreshold" = "🔴 CPU load {{ .Percent }}% Exceeds the threshold of {{ .Threshold }}%" "cpuThreshold" = "🔴 The CPU usage {{ .Percent }}% is more than threshold {{ .Threshold }}%"
"loginSuccess" = "✅ Logged in to the web panel successfully.\r\n" "loginSuccess" = "✅ Successfully logged-in to the panel.\r\n"
"loginFailed" = "❗Log in to the web panel failed.\r\n" "loginFailed" = "❗ Login to the panel failed.\r\n"
"report" = "🕰 Scheduled reports: {{ .RunTime }}\r\n" "report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
"datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n" "datetime" = "⏰ Date-Time: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n" "hostname" = "💻 Hostname: {{ .Hostname }}\r\n"
"version" = "🚀 X-UI: {{ .Version }}\r\n" "version" = "🚀 X-UI Version: {{ .Version }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n"
"serverUpTime" = "⏳ Uptime: {{ .UpTime }} {{ .Unit }}\r\n" "serverUpTime" = "⏳ Server Uptime: {{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 System load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" "serverLoad" = "📈 Server Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" "serverMemory" = "📋 Server Memory: {{ .Current }}/{{ .Total }}\r\n"
"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" "tcpCount" = "🔹 TcpCount: {{ .Count }}\r\n"
"udpCount" = "🔸 UDP: {{ .Count }}\r\n" "udpCount" = "🔸 UdpCount: {{ .Count }}\r\n"
"traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Status: {{ .State }}\r\n" "xrayStatus" = " Xray Status: {{ .State }}\r\n"
"username" = "👤 Username: {{ .Username }}\r\n" "username" = "👤 Username: {{ .Username }}\r\n"
"time" = "⏰ Time: {{ .Time }}\r\n" "time" = "⏰ Time: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Port: {{ .Port }}\r\n" "port" = "🔌 Port: {{ .Port }}\r\n"
"expire" = "📅 Expiry date: {{ .DateTime }}\r\n \r\n" "expire" = "📅 Expire Date: {{ .DateTime }}\r\n \r\n"
"expireIn" = "📅 Expire in: {{ .Time }}\r\n \r\n" "expireIn" = "📅 Expire In: {{ .Time }}\r\n \r\n"
"active" = "💡 Active: {{ .Enable }}\r\n" "active" = "💡 Active: {{ .Enable }}\r\n"
"online" = "🌐 Connection status: {{ .Status }}\r\n" "online" = "🌐 Connection status: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n"
@@ -473,24 +443,24 @@
"download" = "🔽 Download↓: {{ .Download }}\r\n" "download" = "🔽 Download↓: {{ .Download }}\r\n"
"total" = "🔄 Total: {{ .UpDown }} / {{ .Total }}\r\n" "total" = "🔄 Total: {{ .UpDown }} / {{ .Total }}\r\n"
"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n" "exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n"
"exhaustedCount" = "🚨 Exhausted {{ .Type }} Count:\r\n" "exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n"
"onlinesCount" = "🌐 Online clients: {{ .Count }}\r\n" "onlinesCount" = "🌐 Online clients count: {{ .Count }}\r\n"
"disabled" = "🛑 Disabled: {{ .Disabled }}\r\n" "disabled" = "🛑 Disabled: {{ .Disabled }}\r\n"
"depleteSoon" = "🔜 Deplete soon: {{ .Deplete }}\r\n \r\n" "depleteSoon" = "🔜 Deplete soon: {{ .Deplete }}\r\n \r\n"
"backupTime" = "🗄 Backup time: {{ .Time }}\r\n" "backupTime" = "🗄 Backup Time: {{ .Time }}\r\n"
"yes" = "✅ Yes" "yes" = "✅ Yes"
"no" = "❌ No" "no" = "❌ No"
[tgbot.buttons] [tgbot.buttons]
"dbBackup" = "Get Backup" "dbBackup" = "Get DB Backup"
"serverUsage" = "System Usage" "serverUsage" = "Server Usage"
"getInbounds" = "Get Inbounds" "getInbounds" = "Get Inbounds"
"depleteSoon" = "Deplete Soon" "depleteSoon" = "Deplete soon"
"clientUsage" = "Get Usage" "clientUsage" = "Get Usage"
"onlines" = "Online Clients" "onlines" = "Online Clients"
"commands" = "Commands" "commands" = "Commands"
[tgbot.answers] [tgbot.answers]
"getInboundsFailed" = "❌ Failed to get inbounds" "getInboundsFailed" = "❌ Failed to get inbounds"
"askToAddUser" = "Your configuration is not found!\r\nPlease set a Telegram username, and then ask the service admin to add it to the configuration(s)." "askToAddUser" = "Your configuration is not found!\r\nYou should configure your telegram username and ask your Admin to add it to your configuration."
"askToAddUserName" = "Your configuration is not found!\r\nPlease ask the service admin to add your Telegram username to the configuration(s).\r\n\r\nYour Username: <b>@{{ .TgUserName }}</b>" "askToAddUserName" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram username in your configuration(s).\r\n\r\nYour username: <b>@{{ .TgUserName }}</b>"

View File

@@ -1,5 +1,5 @@
"username" = "نامکاربری" "username" = "نام کاربری"
"password" = "رمزعبور" "password" = "رمز عبور"
"login" = "ورود" "login" = "ورود"
"confirm" = "تایید" "confirm" = "تایید"
"cancel" = "انصراف" "cancel" = "انصراف"
@@ -12,7 +12,7 @@
"protocol" = "پروتکل" "protocol" = "پروتکل"
"search" = "جستجو" "search" = "جستجو"
"filter" = "فیلتر" "filter" = "فیلتر"
"loading" = "...در حال بارگذاری" "loading" = "در حال بروزرسانی..."
"second" = "ثانیه" "second" = "ثانیه"
"minute" = "دقیقه" "minute" = "دقیقه"
"hour" = "ساعت" "hour" = "ساعت"
@@ -26,157 +26,153 @@
"edit" = "ویرایش" "edit" = "ویرایش"
"delete" = "حذف" "delete" = "حذف"
"reset" = "ریست" "reset" = "ریست"
"copySuccess" = "باموفقیت کپیشد" "copySuccess" = "با موفقیت کپی شد"
"sure" = "مطمئن" "sure" = "مطمئن"
"encryption" = "رمزگذاری" "encryption" = "رمزگذاری"
"transmission" = "راهاتصال" "transmission" = "راه اتصال"
"host" = "آدرس" "host" = "آدرس"
"path" = "مسیر" "path" = "مسیر"
"camouflage" = "مبهم‌سازی" "camouflage" = "استتار"
"status" = "وضعیت" "status" = "وضعیت"
"enabled" = "فعال" "enabled" = "فعال"
"disabled" = "غیرفعال" "disabled" = "غیرفعال"
"depleted" = "منقضی" "depleted" = "منقضی"
"depletingSoon" = "درحالانقضا" "depletingSoon" = "در حال انقضا"
"offline" = "آفلاین" "offline" = "آفلاین"
"online" = "آنلاین" "online" = "آنلاین"
"domainName" = "آدرس دامنه" "domainName" = "آدرس دامنه"
"monitor" = درس آی‌پی" "monitor" = ی پی اتصال"
"certificate" = "گواهی" "certificate" = "گواهی دیجیتال"
"fail" = "ناموفق" "fail" = "خطا"
"success" = " موفق" "success" = " موفق"
"getVersion" = "دریافت نسخه" "getVersion" = "دریافت ورژن"
"install" = "نصب" "install" = "نصب"
"clients" = "کاربران" "clients" = "کاربران"
"usage" = "استفاده" "usage" = "استفاده"
"remained" = "باقیمانده" "remained" = "باقیمانده"
"secAlertTitle" = "هشدارامنیتی" "secAlertTitle" = "هشدار امنیتی"
"secAlertSsl" = "ایناتصالامن نیست. لطفا تازمانیکه تی‌ال‌اس برای محافظت از دادهها فعال نشدهاست، از وارد کردن اطلاعات حساس خودداری کنید" "secAlertSsl" = "این اتصال امن نیست؛ لطفا تا زمانی که تی‌ال‌اس برای حفاظت از داده ها فعال نشده است از وارد کردن اطلاعات حساس خودداری کنید"
"security" = "امنیت" "security" = "امنیت"
[menu] [menu]
"dashboard" = "نمای کلی" "dashboard" = "وضعیت سیستم"
"inbounds" = "ورودی‌ها" "inbounds" = "سرویس ها"
"settings" = "تنظیمات پنل" "settings" = "تنظیمات پنل"
"xray" = "پیکربندی ایکس‌ری" "xray" = "الگوی ایکس‌ری"
"logout" = "خروج" "logout" = "خروج"
"link" = "مدیریت" "link" = "دیگر"
[pages.login] [pages.login]
"title" = "خوش‌آمدید" "title" = "ورود به سیستم"
"loginAgain" = "مدت زمان استفاده بهاتمام رسیده، لطفا دوباره وارد شوید" "loginAgain" = "مدت زمان استفاده به اتمام رسیده ، لطفا دوباره وارد شوید"
[pages.login.toasts] [pages.login.toasts]
"invalidFormData" = "اطلاعات به‌درستی وارد نشدهاست" "invalidFormData" = "اطلاعات وارد شده به صورت درست وارد نشده است"
"emptyUsername" = "لطفا یک نامکاربری وارد کنید‌" "emptyUsername" = "نام کاربری خالی میباشد"
"emptyPassword" = "لطفا یک رمزعبور وارد کنید" "emptyPassword" = "رمز عبور خالی میباشد"
"wrongUsernameOrPassword" = "نامکاربری یا رمزعبوراشتباه‌است" "wrongUsernameOrPassword" = "نام کاربری و رمز عبور اشتباه میباشد"
"successLogin" = "ورود" "successLogin" = "خوش آمدید"
[pages.index] [pages.index]
"title" = "نمای کلی" "title" = "وضعیت سیستم"
"memory" = "RAM" "memory" = "حافظه رم"
"hard" = "Disk" "hard" = "حافظه دیسک"
"machineInfo" = "ماشین" "xrayStatus" = "وضعیت Xray"
"hostname" = "نام میزبان"
"xrayStatus" = "‌ایکس‌ری"
"stopXray" = "توقف" "stopXray" = "توقف"
"restartXray" = "ریستارت" "restartXray" = "شروع مجدد"
"xraySwitch" = "تغییر نسخه ایکس‌ری" "xraySwitch" = "تغییر ورژن"
"xraySwitchClick" = سخه‌ مورد نظر را انتخاب کنید" "xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
"xraySwitchClickDesk" = "لطفا بادقت انتخاب کنید. درصورت انتخاب نسخه قدیمی‌تر، امکان ناهماهنگی با پیکربندی‌های فعلی وجود دارد" "xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد "
"operationHours" = "مدت‌کارکرد" "operationHours" = "مدت فعالیت"
"operationHoursDesc" = "مدت کارکرد سیستم‌عامل پس‌از شروع به‌کار" "operationHoursDesc" = "مدت فعالیت سیستم بعد از روشن شدن"
"xrayoperationHoursDesc" = "مدت کارکرد ایکس‌ری پس‌از آخرین ریستارت" "systemLoad" = "بار روی سیستم"
"systemLoad" = "بارسیستم" "connectionTcpCountDesc" = "مجموع اتصالات TCP در تمام کارت های شبکه"
"systemLoadDesc" = "میانگین بار در 1، 5 و 15 دقیقه گذشته" "connectionUdpCountDesc" = "مجموع اتصالات UDP در تمام کارت های شبکه"
"connectionTcpCountDesc" = "TCP کل اتصالات" "upSpeed" = "سرعت آپلود در حال حاضر سیستم"
"connectionUdpCountDesc" = "UDP کل اتصالات" "downSpeed" = "سرعت دانلود در حال حاضر سیستم"
"upSpeed" = "سرعت کلی آپلود" "totalSent" = "جمع کل ترافیک آپلود مصرفی"
"downSpeed" = "‌سرعت کلی دانلود" "totalReceive" = "جمع کل ترافیک دانلود مصرفی"
"totalSent" = "کل ترافیک ارسالی پس‌از شروع به‌کار سیستم‌عامل" "xraySwitchVersionDialog" = "تغییر ورژن Xray"
"totalReceive" = "کل ترافیک دریافتی پس‌از شروع به‌کار سیستم‌عامل" "xraySwitchVersionDialogDesc" = "آیا از تغییر ورژن مطمئن هستین"
"xraySwitchVersionDialog" = "تغییرنسخه‌ایکس‌ری" "dontRefresh" = "در حال نصب ، لطفا رفرش نکنید "
"xraySwitchVersionDialogDesc" = "آیا از تغییر نسخه‌ مطمئن هستید؟" "logs" = "گزارش ها"
"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید" "config" = "تنظیمات"
"logs" = "گزارش‌ها" "backup" = "پشتیبان گیری"
"config" = "کانفیگ" "backupTitle" = "پشتیبان گیری دیتابیس"
"backup" = "پشتیبان‌گیری و بازیابی" "backupDescription" = "به یاد داشته باشید که قبل از وارد کردن یک دیتابیس جدید، نسخه پشتیبان تهیه کنید"
"backupTitle" = "پشتیبان‌گیری و بازیابی دیتابیس" "exportDatabase" = "دانلود دیتابیس"
"backupDescription" = "توصیه‌می‌شود قبل‌از بازیابی دیتابیس، یک نسخه پشتیبان تهیه ‌کنید" "importDatabase" = "آپلود دیتابیس"
"exportDatabase" = "پشتیبان‌گیری"
"importDatabase" = "بازیابی"
[pages.inbounds] [pages.inbounds]
"title" = "کاربران" "title" = "کاربران"
"totalDownUp" = "دریافت/ارسال کل" "totalDownUp" = "جمع آپلود/دانلود"
"totalUsage" = "‌‌‌مصرف کل" "totalUsage" = "جمع کل"
"inboundCount" = "کل ورودی‌ها" "inboundCount" = "تعداد سرویس ها"
"operate" = "منو" "operate" = "عملیات"
"enable" = "فعال" "enable" = "فعال"
"remark" = "نام" "remark" = "نام"
"protocol" = "پروتکل" "protocol" = "پروتکل"
"port" = "پورت" "port" = "پورت"
"traffic" = "ترافیک" "traffic" = "ترافیک"
"details" = "جزئیات" "details" = "توضیحات"
"transportConfig" = "نحوه اتصال" "transportConfig" = "نحوه اتصال"
"expireDate" = "تاریخ انقضا" "expireDate" = "تاریخ انقضا"
"resetTraffic" = "ریست ترافیک" "resetTraffic" = "ریست ترافیک"
"addInbound" = "افزودن ورودی" "addInbound" = "اضافه کردن سرویس"
"generalActions" = "عملیات کلی" "generalActions" = "عملیات کلی"
"create" = "افزودن" "create" = "اضافه کردن"
"update" = "ویرایش" "update" = "ویرایش"
"modifyInbound" = "ویرایش ورودی" "modifyInbound" = "ویرایش سرویس"
"deleteInbound" = "حذف ورودی" "deleteInbound" = "حذف سرویس"
"deleteInboundContent" = "آیا مطمئن به حذف ورودی هستید؟" "deleteInboundContent" = "آیا مطمئن به حذف سرویس هستید ؟"
"deleteClient" = "حذف کاربر" "deleteClient" = "حذف کاربر"
"deleteClientContent" = "آیا مطمئن به حذف کاربر هستید؟" "deleteClientContent" = "آیا مطمئن به حذف کاربر هستید ؟"
"resetTrafficContent" = "آیا مطمئن به ریست ترافیک هستید؟" "resetTrafficContent" = "آیا مطمئن به ریست ترافیک هستید ؟"
"copyLink" = "کپی لینک" "copyLink" = "کپی لینک"
"address" = "آدرس" "address" = "آدرس"
"network" = "شبکه" "network" = "شبکه"
"destinationPort" = "پورت مقصد" "destinationPort" = "پورت مقصد"
"targetAddress" = "آدرس مقصد" "targetAddress" = "آدرس مقصد"
"monitorDesc" = رای گوش‌دادن به‌تمام آی‌پی‌ها خالیبگذارید" "monitorDesc" = ه طور پیش فرض خالی بگذارید"
"meansNoLimit" = "صفر یعنی نامحدود. واحد: گیگابایت" "meansNoLimit" = "یعنی بدون محدودیت"
"totalFlow" = "ترافیک کل" "totalFlow" = "کل ترافیک"
"leaveBlankToNeverExpire" = "برای منقضینشدن خالی‌بگذارید" "leaveBlankToNeverExpire" = "خالی بگذارید تا هرگز منقضی نشود"
"noRecommendKeepDefault" = "توصیهمیشود به‌طور پیشفرض حفظشود" "noRecommendKeepDefault" = "توصیه می شود به عنوان پیش فرض حفظ شود"
"certificatePath" = "مسیر فایل" "certificatePath" = "مسیر فایل"
"certificateContent" = "محتوای فایل" "certificateContent" = "محتوای فایل"
"publicKeyPath" = "مسیر کلید عمومی" "publicKeyPath" = "مسیر کلید عمومی"
"publicKeyContent" = "محتوای کلید عمومی" "publicKeyContent" = "محتوای کلید عمومی"
"keyPath" = "مسیر کلید خصوصی" "keyPath" = "مسیر کلید خصوصی"
"keyContent" = "محتوای کلید خصوصی" "keyContent" = "محتوای کلید خصوصی"
"clickOnQRcode" = "برای کپی لینک بر روی کدتصویری کلیک کنید" "clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید"
"client" = "کاربر" "client" = "کاربر"
"export" = "استخراج لینک‌ها" "export" = "استخراج لینک‌ها"
"clone" = "شبیهسازی" "clone" = "شبیه سازی"
"cloneInbound" = "شبیه‌سازی ورودی" "cloneInbound" = "شبیه‌سازی سرویس"
"cloneInboundContent" = "همه موارد این ورودی بجز پورت، آی‌پی و کاربر‌ها شبیهسازی خواهند شد" "cloneInboundContent" = "همه موارد این ورودی بجز پورت ، ای پی و کلاینت ها شبیه سازی خواهند شد"
"resetAllTraffic" = "ریست ترافیک کل ورودی‌ها" "resetAllTraffic" = "ریست ترافیک کل سرویس ها"
"resetAllTrafficTitle" = "ریست ترافیک کل ورودی‌ها" "resetAllTrafficTitle" = "ریست ترافیک کل سرویس ها"
"resetAllTrafficContent" = "آیا مطمئن به ریست ترافیک تمام ورودی‌ها هستید؟" "resetAllTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک سرویس ها را ریست کنید؟"
"resetInboundClientTraffics" = "ریست ترافیک کاربران" "resetInboundClientTraffics" = "ریست ترافیک کاربران"
"resetInboundClientTrafficTitle" = "ریست ترافیک کاربران" "resetInboundClientTrafficTitle" = "ریست ترافیک کل کاربران"
"resetInboundClientTrafficContent" = "آیا مطمئن به ریست ترافیک تمام کاربران این‌ ورودی هستید؟" "resetInboundClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران این سرویس را ریست کنید؟"
"resetAllClientTraffics" = "ریست ترافیک کل کاربران" "resetAllClientTraffics" = "ریست ترافیک کاربران"
"resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران" "resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران"
"resetAllClientTrafficContent" = "آیا مطمئن به ریست ترافیک تمام کاربران هستید؟" "resetAllClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران را ریست کنید؟"
"delDepletedClients" = "حذف کاربران منقضی" "delDepletedClients" = "حذف کاربران منقضی"
"delDepletedClientsTitle" = "حذف کاربران منقضی" "delDepletedClientsTitle" = "حذف کاربران منقضی"
"delDepletedClientsContent" = "آیا مطمئن به حذف تمام کاربران منقضیشده ‌هستید؟" "delDepletedClientsContent" = "آیا مطمئن هستید مه میخواهید تمامی کاربران منقضی شده را حذف کنید؟"
"email" = "ایمیل" "email" = "ایمیل"
"emailDesc" = "باید یک ایمیل یکتا باشد" "emailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
"setDefaultCert" = "استفاده از گواهی پنل" "setDefaultCert" = "استفاده از گواهی پنل"
"telegramDesc" = "دریافت کنید‌ '/id'یا دستور @userinfobot آی‌دی‌(های) چت مدیر را بدون '@' واردکنید. از" "telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot یا در ربات دستور '/id' را وارد کنید)"
"subscriptionDesc" = "لینک سابسکربپشن خودرا در 'اطلاعات بیشتر' پیدا کنید، همچنین میتوانید از همین نام برای چندین کاربر استفادهکنید" "subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید"
"info" = "اطلاعات" "info" = "اطلاعات"
"same" = "همسان" "same" = "همسان"
"inboundData" = "داده‌های ورودی" "inboundData" = "داده‌های سرویس"
"exportInbound" = "استخراج ورودی" "copyToClipboard" = "کپی در حافظه"
"import" = "افزودن" "import" = ارد کردن"
"importInbound" = "افزودن یک ورودی" "importInbound" = ارد کردن یک سرویس"
[pages.client] [pages.client]
"add" = "کاربر جدید" "add" = "کاربر جدید"
@@ -184,199 +180,184 @@
"submitAdd" = "اضافه کردن" "submitAdd" = "اضافه کردن"
"submitEdit" = "ذخیره تغییرات" "submitEdit" = "ذخیره تغییرات"
"clientCount" = "تعداد کاربران" "clientCount" = "تعداد کاربران"
"bulk" = "انبوهسازی" "bulk" = "انبوه سازی"
"method" = "روش" "method" = "روش"
"first" = "از" "first" = "از"
"last" = "تا" "last" = "تا"
"prefix" = "پیشوند" "prefix" = "پیشوند"
"postfix" = "پسوند" "postfix" = "پسوند"
"delayedStart" = "شروع‌پس‌ازاولیناستفاده" "delayedStart" = "شروع بعد از اولین استفاده"
"expireDays" = "مدت زمان" "expireDays" = "روزهای اعتبار"
"days" = "(روز)" "days" = "(روز)"
"renew" = "تمدید خودکار" "renew" = "تمدید خودکار"
"renewDesc" = "تمدید خودکار پساز انقضا. 0 = غیرفعال - واحد: روز" "renewDesc" = "روزهای تمدید خودکار پس از انقضا. 0 = غیرفعال"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "فراهم‌سازی" "obtain" = "Obtain"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"request" = "درخواست" "requestHeader" = "درخواست سربرگ"
"response" = "پاسخ"
"name" = "نام" "name" = "نام"
"value" = "مقدار" "value" = "مقدار"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"version" = "نسخه" "requestVersion" = "ورژن درخواست"
"method" = "متد" "requestMethod" = "متد درخواست"
"path" = "مسیر" "requestPath" = "مسیر درخواست"
"status" = ضعیت" "responseVersion" = رژن پاسخ"
"statusDescription" = "توضیحات وضعیت" "responseStatus" = "وضعیت پاسخ"
"requestHeader" = "سربرگ درخواست" "responseStatusDescription" = "توضیحات وضعیت پاسخ"
"responseHeader" = "سربرگ پاسخ" "responseHeader" = "سربرگ پاسخ"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
"encryption" = "رمزنگاری" "encryption" = "رمزنگاری"
[pages.settings] [pages.settings]
"title" = "تنظیمات پنل" "title" = "تنظیمات"
"save" = "ذخیره" "save" = "ذخیره"
"infoDesc" = "برای اعمال تغییرات در این بخش باید پس از ذخیره کردن، پنل را ریستارت کنید" "infoDesc" = "برای اعمال تغییرات در این بخش باید پس از ذخیره کردن، پنل را ریستارت کنید"
"restartPanel" = "ریستارت پنل" "restartPanel" = "ریستارت پنل"
"restartPanelDesc" = "آیا مطمئن به ریستارت پنل هستید؟ اگر پس‌از ریستارت نتوانستید به پنل دسترسی پیدا کنید، گزارش‌های موجود در اسکریپت پنل را بررسی کنید" "restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
"resetDefaultConfig" = "برگشت به پیشفرض" "resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
"panelConfig" = "عمومی" "panelConfig" = "تنظیمات پنل"
"userSettings" = "احرازهویت" "userSettings" = "تنظیمات مدیر"
"TGBotSettings" = "ربات تلگرام" "TGBotSettings" = "تنظیمات ربات تلگرام"
"panelListeningIP" = "آدرس آیپی" "panelListeningIP" = "محدودیت آی پی پنل"
"panelListeningIPDesc" = "آدرس آی‌پی برای وب پنل. برای گوش‌دادن به‌تمام آی‌پیها خالیبگذارید" "panelListeningIPDesc" = "برای استفاده از تمام آی‌پیها به طور پیش فرض خالی بگذارید"
"panelListeningDomain" = "نام دامنه" "panelListeningDomain" = "محدودیت دامین پنل"
"panelListeningDomainDesc" = "آدرس دامنه برای وب پنل. برای گوش‌دادن به‌تمام دامنه‌ها و آی‌پی‌ها خالیبگذارید" "panelListeningDomainDesc" = "برای استفاده از تمام دامنه‌ها و آی‌پی‌ها به طور پیش فرض خالی بگذارید"
"panelPort" = "شماره پورت" "panelPort" = "پورت پنل"
"panelPortDesc" = "شماره پورت برای وب پنل. باید پورت استفاده نشده‌باشد" "panelPortDesc" = "پورت مورد استفاده برای نمایش این پنل"
"publicKeyPath" = "مسیر کلید عمومی" "publicKeyPath" = "مسیر فایل گواهی کلید عمومی پنل"
"publicKeyPathDesc" = "مسیر فایل کلیدعمومی برای وب پنل. با '/' شروعمیشود" "publicKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود "
"privateKeyPath" = "مسیر کلید خصوصی" "privateKeyPath" = "مسیر فایل گواهی کلید خصوصی پنل"
"privateKeyPathDesc" = "مسیر فایل کلیدخصوصی برای وب پنل. با '/' شروعمیشود" "privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود "
"panelUrlPath" = "URI مسیر" "panelUrlPath" = "آدرس روت پنل"
"panelUrlPathDesc" = "مسیر لینک وب پنل. با '/' شروع و با '/' خاتمه‌ می‌یابد" "panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود"
"pageSize" = "اندازه صفحه بندی جدول" "pageSize" = "اندازه صفحه بندی جدول"
"pageSizeDesc" = "اندازه صفحه برای جدول ورودی‌ها. 0 = غیرفعال" "pageSizeDesc" = "اندازه صفحه را برای جدول سرویس ها تعریف کنید. 0: غیرفعال"
"remarkModel" = "نامکانفیگ و جداکننده" "remarkModel" = "نام کانفیگ و جداکننده"
"sampleRemark" = "نمونهنام" "sampleRemark" = "نمونه نام"
"oldUsername" = "نامکاربری فعلی" "oldUsername" = "نام کاربری فعلی"
"currentPassword" = "رمزعبور فعلی" "currentPassword" = "رمز عبور فعلی"
"newUsername" = "نامکاربری جدید" "newUsername" = "نام کاربری جدید"
"newPassword" = "رمزعبور جدید" "newPassword" = "رمز عبور جدید"
"telegramBotEnable" = "فعالسازی ربات تلگرام" "telegramBotEnable" = "فعالسازی ربات تلگرام"
"telegramBotEnableDesc" = "ربات تلگرام را فعال می‌کند" "telegramBotEnableDesc" = "از طریق بات تلگرام به امکانات ابن پنل متصل شوید"
"telegramToken" = "توکن تلگرام" "telegramToken" = "توکن تلگرام"
"telegramTokenDesc" = "دریافت کنید @botfather توکن تلگرام، از" "telegramTokenDesc" = "توکن را باید از مدیر بات های تلگرام دریافت کنید @botfather"
"telegramChatId" = "آیدی چت مدیر" "telegramChatId" = "آی دی تلگرام مدیریت"
"telegramChatIdDesc" = "دریافت کنید '/id'یا دستور @userinfobot آی‌دی(های) چت مدیر، از" "telegramChatIdDesc" = "از @userinfobot یا دستور '/id' در ربات برای دریافت شناسه های چت خود استفاده کنید. با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. "
"telegramNotifyTime" = "زمان اطلاع‌رسانی" "telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
"telegramNotifyTimeDesc" = "زمان‌اطلاع‌رسانی ربات تلگرام برای ارسال گزارش‌های دوره‌ای. از فرمت زمانی کرون‌تاب استفادهکنید" "telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید "
"tgNotifyBackup" = "پشتیبانگیری دیتابیس" "tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
"tgNotifyBackupDesc" = "فایل پشتیبان دیتابیس را بههمراه گزارش دریافت می‌کنید‌" "tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
"tgNotifyLogin" = "اطلاع‌رسانی ورود" "tgNotifyLogin" = "اعلان ورود"
"tgNotifyLoginDesc" = "هر زمان کسی سعی به ورود به وب پنل شما را داشت. درباره نام‌کاربری، آی‌پی و زمان، مطلع می‌شوید" "tgNotifyLoginDesc" = "نام کاربری، آدرس ای پی، و زمان وقتی که فردی سعی می‌کند به پنل شما وارد شود نمایش میدهد"
"sessionMaxAge" = "مدت جلسه" "sessionMaxAge" = "بیشینه زمان جلسه وب"
"sessionMaxAgeDesc" = "بیشینه مدت زمانیکه میتوانید لاگین بمانید. واحد: دقیقه" "sessionMaxAgeDesc" = "بیشینه زمانی که میتوانید لاگین بمانید (واحد: دقیقه)"
"expireTimeDiff" = "اطلاع‌رسانی زمان‌انقضا" "expireTimeDiff" = "آستانه زمان باقی مانده"
"expireTimeDiffDesc" = "وقتی زمان باقی‌مانده به‌آستانه تعیین‌شده رسید، مطلع می‌شوید. واحد: روز" "expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)"
"trafficDiff" = "اطلاع‌رسانی ترافیک باقیمانده" "trafficDiff" = "آستانه ترافیک باقی مانده"
"trafficDiffDesc" = "وقتی‌ ترافیک باقی‌مانده به‌آستانه تعیین‌شده رسید، مطلع می‌شوید. واحد: گیگابایت" "trafficDiffDesc" = "فاصله زمانی هشدار تا رسیدن به اتمام ترافیک (واحد: گیگابایت)"
"tgNotifyCpu" = "اطلاع‌رسانی بار پردازنده" "tgNotifyCpu" = "آستانه هشدار درصد پردازنده"
"tgNotifyCpuDesc" = "اگر بار پردازنده از آستانه تعیین‌شده فراتر رفت، مطلع می‌شوید. واحد: درصد" "tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
"timeZone" = "منطقه زمانی" "timeZone" = "منظقه زمانی"
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقهزمانی اجرا میشود" "timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
"subSettings" = "سابسکریپشن" "subSettings" = "سابسکریپشن"
"subEnable" = "فعال‌سازی سرویس سابسکریپشن" "subEnable" = "فعال کردن سرویس"
"subEnableDesc" = " سرویس سابسکریپشن را فعال می‌کند" "subEnableDesc" = "ویژگی سابسکریپشن با پیکربندی جداگانه"
"subListen" = "آدرس آی‌پی" "subListen" = "محدودیت آی‌پی"
"subListenDesc" = "آدرس آی‌پی برای سابسکریپشن. برای گوش‌دادن به‌تمام آی‌پیها خالیبگذارید" "subListenDesc" = "برای استفاده از همه آی‌پی ها به طور پیش فرض خالی بگذارید"
"subPort" = "شماره پورت" "subPort" = "پورت سرویس"
"subPortDesc" = "شماره پورت برای سابسکریپشن. باید پورت استفاده نشده‌باشد" "subPortDesc" = "شماره پورت برای ارائه خدمات سابسکریپشن"
"subCertPath" = "مسیر کلید عمومی" "subCertPath" = "مسیر فایل کلید عمومی گواهی سابسکریپشن"
"subCertPathDesc" = "مسیر فایل کلیدعمومی برای سابیکریپشن. با '/' شروعمیشود" "subCertPathDesc" = "یک مسیر مطلق که با '/' شروع می شود را پر کنید."
"subKeyPath" = "مسیر کلید خصوصی" "subKeyPath" = "مسیر فایل کلید خصوصی گواهی سابسکریپشن"
"subKeyPathDesc" = "مسیر فایل کلیدخصوصی برای سابسکریپشن. با '/' شروعمیشود" "subKeyPathDesc" = "یک مسیر مطلق که با '/' شروع می شود را پر کنید."
"subPath" = "URI مسیر" "subPath" = "مسیر ریشه سابسکریپشن"
"subPathDesc" = "مسیر لینک سابسکریپشن. با '/' شروع و با '/' خاتمه می‌یابد" "subPathDesc" = "باید با '/' شروع شود و با '/' ختم شود."
"subDomain" = "نام دامنه" "subDomain" = "دامنه مخصوص سابسکریپشن"
"subDomainDesc" = "آدرس دامنه برای سابسکریپشن. برای گوش‌دادن به‌تمام دامنهها و آی‌پیها خالیبگذارید" "subDomainDesc" = "برای نظارت بر همه دامنه ها و آی‌پی ها به طور پیش فرض خالی بگذارید"
"subUpdates" = "فاصله بروزرسانی" "subUpdates" = "فاصله به روز رسانی های سابسکریپشن"
"subUpdatesDesc" = "فاصله مابین بروزرسانی لینک سابسکریپشن در برنامه‌های کاربری. واحد: ساعت" "subUpdatesDesc" = "ساعت های فاصله بین به روز رسانی در برنامه کاربر"
"subEncrypt" = "کدگذاری" "subEncrypt" = "رمزگذاری کانفیگ ها"
"subEncryptDesc" = " محتوای برگشتی سابسکریپشن برپایه بیس64 کدگذاری خواهدشد" "subEncryptDesc" = "رمزگذاری کانفیگ های بازگشتی سابسکریپشن"
"subShowInfo" = "نمایش اطلاعات مصرف" "subShowInfo" = "نمایش اطلاعات مصرف"
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامه‌های کاربری نمایش میدهد" "subShowInfoDesc" = "ترافیک و زمان باقیمانده را در هر کانفیگ نمایش میدهد"
"subURI" = "پراکسی معکوس URI" "subURI" = "آدرس پایه پروکسی معکوس"
"subURIDesc" = "سابسکریپشن از لینکی که در پشت پراکسیهای معکوس تنظیم شده‌، استفاده خواهدکرد" "subURIDesc" = "آدرس پایه سابسکریپشن را برای استفاده در پشت پراکسی ها تغییر میدهد"
[pages.settings.toasts] [pages.settings.toasts]
"modifySettings" = "ویرایش تنظیمات" "modifySettings" = "ویرایش تنظیمات"
"getSettings" = "دریافت تنظیمات" "getSettings" = "دریافت تنظیمات"
"modifyUser" = "ویرایش مدیر" "modifyUser" = "ویرایش کاربر"
"originalUserPassIncorrect" = "نامکاربری یا رمزعبور فعلی اشتباه‌است" "originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد "
"userPassMustBeNotEmpty" = "نامکاربری یا رمزعبور جدید خالی‌است" "userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد "
[pages.xray] [pages.xray]
"title" = "پیکربندی ایکس‌ری" "title" = "تنظیمات Xray"
"save" = "ذخیره" "save" = "ذخیره تنظیمات"
"restart" = "ریستارت ایکس‌ری" "restart" = "ریستارت ایکس‌ری"
"basicTemplate" = "پایه" "basicTemplate" = "بخش الگو پایه"
"advancedTemplate" = "پیشرفته" "advancedTemplate" = "بخش الگو پیشرفته"
"generalConfigs" = "استراتژی‌ کلی" "generalConfigs" = "تنظیمات عمومی"
"generalConfigsDesc" = "این گزینه‌ها استراتژی کلی ترافیک را تعیین می‌کنند" "generalConfigsDesc" = "این تنظیمات میتواند ترافیک کلی سرویس را متاثر کند"
"logConfigs" = "لاگ" "blockConfigs" = "مسدود سازی"
"logConfigsDesc" = "لاگ ها ممکن است بر کارایی سرور شما تأثیر بگذارند. توصیه می شود فقط در صورت نیاز آن را آگاهانه فعال کنید" "blockConfigsDesc" = "این گزینه ها از اتصال کاربران به پروتکل ها و وب سایت های خاص جلوگیری می کند"
"blockConfigs" = "سپر محافظ" "blockCountryConfigs" = "تنظیمات برای مسدودسازی کشورها"
"blockConfigsDesc" = "این گزینهها ترافیک را بر اساس پروتکل‌های درخواستی خاص، و وب سایت‌ها مسدود میکند" "blockCountryConfigsDesc" = "این گزینه اتصال کاربران به دامنه های کشوری خاص را مسدود می کند"
"blockCountryConfigs" = "مسدودسازی کشور" "directCountryConfigs" = "تنظیمات برای اتصال مستقیم کشورها"
"blockCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص مسدود میکند" "directCountryConfigsDesc" = "این گزینه کاربران را به دامنه های کشوری خاص را به طور مستقیم، متصل می کند"
"directCountryConfigs" = "اتصال مستقیم کشور" "ipv4Configs" = "تنظیمات برای IPv4"
"directCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص بصورت مستقیم ارسال می‌کند" "ipv4ConfigsDesc" = "این گزینه فقط از طریق آیپی ورژن ۴ به دامنه های هدف هدایت می شود"
"ipv4Configs" = "IPv4 مسیریابی" "Template" = "تنظیمات الگو ایکس ری"
"ipv4ConfigsDesc" = "این گزینه‌ها ترافیک را از طریق آی‌پی نسخه4 ماشین، به مقصد هدایت می‌کند" "TemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید!"
"warpConfigs" = "WARP تنظمیات" "FreedomStrategy" = "روش استفاده از شبکه خروجی مستقیم"
"warpConfigsDesc" = "این گزینه‌ها ترافیک‌ را از طریق وارپ کلادفلر به مقصد هدایت می‌کند" "FreedomStrategyDesc" = "تعیین روش استفاده از خروجی برای پرتکل مستقیم"
"Template" = "پیکربندی پیشرفته الگو ایکس‌ری" "RoutingStrategy" = "پیکربندی استراتژی حل دامنه در مسیریابی"
"TemplateDesc" = "فایل پیکربندی نهایی ایکس‌ری بر اساس این الگو ایجاد می‌شود" "RoutingStrategyDesc" = "تعیین استراتژی مسیریابی کلی برای پیدا کردن دامنه"
"FreedomStrategy" = "Freedom استراتژی پروتکل" "Torrent" = "فیلتر کردن بیت تورنت"
"FreedomStrategyDesc" = "تعیین می‌کند Freedom استراتژی خروجی شبکه را برای پروتکل" "TorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد"
"RoutingStrategy" = "استراتژی کلی مسیریابی" "PrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی"
"RoutingStrategyDesc" = "استراتژی کلی مسیریابی برای حل تمام درخواست‌ها را تعیین می‌کند" "PrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد"
"Torrent" = "مسدودسازی پروتکل بیت‌تورنت" "Ads" = "مسدود کردن تبلیغات"
"TorrentDesc" = "پروتکل بیت تورنت را مسدود می‌کند" "AdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد"
"PrivateIp" = "مسدودسازی اتصال آی‌پی‌های خصوصی" "Family" = "فعال کردن حالت خانواده"
"PrivateIpDesc" = "اتصال به آی‌پی‌های رنج خصوصی را مسدود می‌کند" "FamilyDesc" = "برای جلوگیری از ارتباط با وبسایت های ناامن"
"Ads" = "مسدودسازی تبلیغات" "IRIp" = "جلوگیری از اتصال آیپی های ایران"
"AdsDesc" = "وب‌سایت‌های تبلیغاتی را مسدود می‌کند" "IRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد"
"Family" = "محافظ خانواده" "IRDomain" = "جلوگیری از اتصال دامنه های ایران"
"FamilyDesc" = "محتوای مخصوص بزرگسالان، و وبسایت‌های ناامن را مسدود می‌کند" "IRDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد"
"IRIp" = "مسدودسازی اتصال به آیپیهای ایران" "ChinaIp" = "جلوگیری از اتصال آیپی های چین"
"IRIpDesc" = "اتصال به آیپیهای کشور ایران را مسدود می‌کند" "ChinaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های چین تغییر میدهد"
"IRDomain" = "مسدودسازی اتصال به دامنههای ایران" "ChinaDomain" = "جلوگیری از اتصال دامنه های چین"
"IRDomainDesc" = "اتصال به دامنههای کشور ایران را مسدود می‌کند" "ChinaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های چین تغییر میدهد"
"ChinaIp" = "مسدودسازی اتصال به آی‌‌پیهای چین" "RussiaIp" = "جلوگیری از اتصال آیپی های روسیه"
"ChinaIpDesc" = "اتصال به آیپیهای کشور چین را مسدود می‌کند" "RussiaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های روسیه تغییر میدهد"
"ChinaDomain" = "مسدودسازی اتصال به دامنههای چین" "RussiaDomain" = "جلوگیری از اتصال دامنه های روسیه"
"ChinaDomainDesc" = "اتصال به دامنههای کشور چین را مسدود می‌کند" "RussiaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های روسیه تغییر میدهد"
"RussiaIp" = "مسدودسازی اتصال به آیپیهای روسیه" "DirectIRIp" = "ارتباط مستقیم به آیپی های ایران"
"RussiaIpDesc" = "اتصال به آیپیهای کشور روسیه را مسدود می‌کند" "DirectIRIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های ایران تغییر میدهد"
"RussiaDomain" = "مسدودسازی اتصال به دامنههای روسیه" "DirectIRDomain" = "ارتباط مستقیم به دامنه های ایران"
"RussiaDomainDesc" = "اتصال به دامنههای کشور روسیه را مسدود می‌کند" "DirectIRDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های ایران تغییر میدهد"
"DirectIRIp" = "اتصال مستقیم آیپیهای ایران" "DirectChinaIp" = "ارتباط مستقیم به آیپی های چین"
"DirectIRIpDesc" = "اتصال مستقیم به آیپیهای کشور ایران" "DirectChinaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های چین تغییر میدهد"
"DirectIRDomain" = "اتصال مستقیم دامنههای ایران" "DirectChinaDomain" = "ارتباط مستقیم به دامنه های چین"
"DirectIRDomainDesc" = "اتصال مستقیم به دامنههای کشور ایران" "DirectChinaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های چین تغییر میدهد"
"DirectChinaIp" = "اتصال مستقیم آیپیهای چین" "DirectRussiaIp" = "ارتباط مستقیم به آیپی های روسیه"
"DirectChinaIpDesc" = "اتصال مستقیم به آیپیهای کشور چین" "DirectRussiaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های روسیه تغییر میدهد"
"DirectChinaDomain" = "ارتباط مستقیم دامنههای چین" "DirectRussiaDomain" = "ارتباط مستقیم به دامنه های روسیه"
"DirectChinaDomainDesc" = "اتصال مستقیم به دامنههای کشور چین" "DirectRussiaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های روسیه تغییر میدهد"
"DirectRussiaIp" = "ارتباط مستقیم آی‌پی‌های روسیه" "GoogleIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به گوگل"
"DirectRussiaIpDesc" = "اتصال مستقیم به آیپی‌های کشور روسیه" "GoogleIPv4Desc" = "مسیردهی جدید برای اتصال به گوگل با آیپی ورژن 4 اضافه میکند"
"DirectRussiaDomain" = "ارتباط مستقیم دامنه های روسیه" "NetflixIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به نتفلیکس"
"DirectRussiaDomainDesc" = "اتصال مستقیم به دامنه‌های کشور روسیه" "NetflixIPv4Desc" = "مسیردهی جدید برای اتصال به نتفلیکس با آیپی ورژن 4 اضافه میکند"
"GoogleIPv4" = "گوگل"
"GoogleIPv4Desc" = "ترافیک را از طریق آی‌پی نسخه4، به گوگل هدایت می‌کند"
"NetflixIPv4" = "نتفلیکس"
"NetflixIPv4Desc" = "ترافیک را از طریق آی‌پی نسخه4، به نتفلیکس هدایت می‌کند"
"completeTemplate" = "کامل" "completeTemplate" = "کامل"
"GoogleWARP" = "گوگل"
"GoogleWARPDesc" = "ترافیک را از طریق وارپ به گوگل هدایت می‌کند"
"OpenAIWARP" = "چت جی‌پی‌تی"
"OpenAIWARPDesc" = "ترافیک را از طریق وارپ به چت جی‌پی‌تی هدایت می‌کند"
"NetflixWARP" = "نتفلیکس"
"NetflixWARPDesc" = "ترافیک را از طریق وارپ به نتفلیکس هدایت می‌کند"
"MetaWARP" = "متا"
"MetaWARPDesc" = "ترافیک را از طریق وارپ به متا (اینستاگرام، فیس بوک، واتساپ، تردز و...) هدایت می کند."
"SpotifyWARP" = "اسپاتیفای"
"SpotifyWARPDesc" = " ترافیک را از طریق وارپ به اسپاتیفای هدایت می‌کند"
"Inbounds" = "ورودی‌ها" "Inbounds" = "ورودی‌ها"
"Outbounds" = "خروجی‌ها" "Outbounds" = "خروجی‌ها"
"Routings" = "قوانین مسیریابی" "Routings" = "قوانین مسیریابی"
"RoutingsDesc" = "اولویت هر قانون مهم است" "RoutingsDesc" = "اولویت هر قانون مهم است!"
[pages.xray.rules] [pages.xray.rules]
"first" = "اولین" "first" = "اولین"
@@ -390,7 +371,7 @@
"info" = "اطلاعات" "info" = "اطلاعات"
"add" = "افزودن قانون" "add" = "افزودن قانون"
"edit" = "ویرایش قانون" "edit" = "ویرایش قانون"
"useComma" = "موارد جداشده با کاما" "useComma" = "موارد جدا شده با کاما"
[pages.xray.outbound] [pages.xray.outbound]
"addOutbound" = "افزودن خروجی" "addOutbound" = "افزودن خروجی"
@@ -404,92 +385,81 @@
"domain" = "دامنه" "domain" = "دامنه"
"type" = "نوع" "type" = "نوع"
"bridge" = "پل" "bridge" = "پل"
"portal" = ورتال" "portal" = "پرتال"
"intercon" = "اتصال میانی" "intercon" = "اتصال میانی"
"settings" = "تنظیمات"
"accountInfo" = "اطلاعات حساب"
"outboundStatus" = "وضعیت خروجی"
[pages.xray.wireguard]
"secretKey" = "کلید شخصی"
"publicKey" = "کلید عمومی"
"allowedIPs" = "آی‌پی‌های مجاز"
"endpoint" = "نقطه پایانی"
"psk" = "کلید مشترک"
"domainStrategy" = "استراتژی حل دامنه"
[tgbot] [tgbot]
"noResult" = "❗نتیجه‌ای یافت نشد" "noResult" = "❗ نتیجه‌ای یافت نشد!"
"wentWrong" = "❌ مشکلی رخ دادهاست" "wentWrong" = "❌ مشکلی رخ داده است!"
"noInbounds" = " هیچ ورودی یافت نشد" "noInbounds" = " هیچ ورودی یافت نشد!"
"unlimited" = "♾ نامحدود" "unlimited" = "♾ نامحدود"
"day" = "روز" "day" = "روز"
"days" = "روزها" "days" = "روزها"
"unknown" = "نامشخص" "unknown" = "نامشخص"
"inbounds" = "ورودی‌ها" "inbounds" = "ورودی‌ها"
"clients" = اربران" "clients" = لاینت‌ها"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ دستور ناشناخته" "unknown" = "❗ دستور ناشناخته"
"pleaseChoose" = "👇 لطفاًانتخاب کنید:\r\n" "pleaseChoose" = "👇 لطفاً انتخاب کنید:\r\n"
"help" = "🤖 به این ربات خوشآمدید! این ربات برای ارائه داده‌های خاص از وب پنل طراحی شدهاست و بهشما امکان تغییرات لازم را می‌دهد\r\n\r\n" "help" = "🤖 به این ربات خوش آمدید! این ربات برای ارائه داده‌های خاص از سرور طراحی شده است و به شما امکان تغییرات لازم را می‌دهد.\r\n\r\n"
"start" = "👋 سلام <i>{{ .Firstname }}</i>.\r\n" "start" = "👋 سلام <i>{{ .Firstname }}</i>.\r\n"
"welcome" = "🤖 بهربات مدیریت <b>{{ .Hostname }}</b> خوشآمدید\r\n" "welcome" = "🤖 به ربات مدیریت <b>{{ .Hostname }}</b> خوش آمدید.\r\n"
"status" = "✅ رباتدرحالتعادیاست" "status" = "✅ ربات در حالت عادی است!"
"usage" = "❗ لطفا یک متن برای جستجو واردکنید" "usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!"
"getID" = "🆔 شناسهشما: <code>{{ .ID }}</code>" "getID" = "🆔 شناسه شما: <code>{{ .ID }}</code>"
"helpAdminCommands" = "برای جستجوی ایمیل کاربر:\r\n<code>/usage [ایمیل]</code>\r\n \r\nبرای جستجوی ورودی‌ها (با آمار کاربر):\r\n<code>/inbound [توضیح]</code>" "helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n \r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n<code>/inbound [توضیح]</code>"
"helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفادهکنید:\r\n \r\n<code>/usage [UUID|رمز عبور]</code>\r\n \r\nاز رمزعبور استفاده کنید Trojan/Shadowsocks و برای UUID از VMess/VLESS برای" "helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفاده کنید:\r\n \r\n<code>/usage [UUID|رمز عبور]</code>\r\n \r\nاز UUID برای vmess/vless و از رمز عبور برای Trojan استفاده کنید."
[tgbot.messages] [tgbot.messages]
"cpuThreshold" = "🔴 بار ‌پردازنده {{ .Percent }}% بیشتر از آستانه است {{ .Threshold }}%" "cpuThreshold" = "🔴 میزان استفاده از CPU {{ .Percent }}% بیشتر از آستانه {{ .Threshold }}% است."
"loginSuccess" = "✅ باموفقیت به پنل واردشدید \r\n" "loginSuccess" = "✅ با موفقیت به پنل وارد شدید.\r\n"
"loginFailed" = "❗️ ورود به پنل ناموفقبود \r\n" "loginFailed" = "❗️ ورود به پنل ناموفق بود.\r\n"
"report" = "🕰 گزارشاتزمان‌بندیشده: {{ .RunTime }}\r\n" "report" = "🕰 گزارشات زمان‌بندی شده: {{ .RunTime }}\r\n"
"datetime" = "⏰ تاریخ‌وزمان: {{ .DateTime }}\r\n" "datetime" = "⏰ تاریخ-زمان: {{ .DateTime }}\r\n"
"hostname" = "💻 ناممیزبان: {{ .Hostname }}\r\n" "hostname" = "💻 نام میزبان: {{ .Hostname }}\r\n"
"version" = "🚀 X-UI: {{ .Version }}\r\n" "version" = "🚀 نسخه X-UI: {{ .Version }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 آدرس IP: {{ .IP }}\r\n"
"serverUpTime" = "⏳ مدت‌کارکرد: {{ .UpTime }} {{ .Unit }}\r\n" "serverUpTime" = "⏳ زمان کارکرد سرور: {{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 بارسیستم: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" "serverLoad" = "📈 بار سرور: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" "serverMemory" = "📋 حافظه سرور: {{ .Current }}/{{ .Total }}\r\n"
"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" "tcpCount" = "🔹 تعداد ترافیک TCP: {{ .Count }}\r\n"
"udpCount" = "🔸 UDP: {{ .Count }}\r\n" "udpCount" = "🔸 تعداد ترافیک UDP: {{ .Count }}\r\n"
"traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " وضعیت: {{ .State }}\r\n" "xrayStatus" = " وضعیت Xray: {{ .State }}\r\n"
"username" = "👤 نامکاربری: {{ .Username }}\r\n" "username" = "👤 نام کاربری: {{ .Username }}\r\n"
"time" = "⏰ زمان: {{ .Time }}\r\n" "time" = "⏰ زمان: {{ .Time }}\r\n"
"inbound" = "📍 نام‌ورودی: {{ .Remark }}\r\n" "inbound" = "📍 ورودی: {{ .Remark }}\r\n"
"port" = "🔌 پورت: {{ .Port }}\r\n" "port" = "🔌 پورت: {{ .Port }}\r\n"
"expire" = "📅 تاریخانقضا: {{ .DateTime }}\r\n \r\n" "expire" = "📅 تاریخ انقضا: {{ .DateTime }}\r\n \r\n"
"expireIn" = "📅 انقضا در: {{ .Time }}\r\n \r\n" "expireIn" = "📅 باقیمانده از انقضا: {{ .Time }}\r\n \r\n"
"active" = "💡 فعال: {{ .Enable }}\r\n" "active" = "💡 فعال: {{ .Enable }}\r\n"
"online" = "🌐 وضعیتاتصال: {{ .Status }}\r\n" "online" = "🌐 وضعیت اتصال: {{ .Status }}\r\n"
"email" = "📧 ایمیل: {{ .Email }}\r\n" "email" = "📧 ایمیل: {{ .Email }}\r\n"
"upload" = "🔼 آپلود↑: {{ .Upload }}\r\n" "upload" = "🔼 آپلود↑: {{ .Upload }}\r\n"
"download" = "🔽 دانلود↓: {{ .Download }}\r\n" "download" = "🔽 دانلود↓: {{ .Download }}\r\n"
"total" = "🔄 کل: {{ .UpDown }} / {{ .Total }}\r\n" "total" = "🔄 کل: {{ .UpDown }} / {{ .Total }}\r\n"
"exhaustedMsg" = "🚨 {{ .Type }} بهاتمامرسیدهاست:\r\n" "exhaustedMsg" = "🚨 {{ .Type }} به اتمام رسیده است:\r\n"
"exhaustedCount" = "🚨 تعداد {{ .Type }} بهاتمام رسیده‌است:\r\n" "exhaustedCount" = "🚨 تعداد {{ .Type }} به اتمام رسیده:\r\n"
"onlinesCount" = "🌐 کاربرانآنلاین: {{ .Count }}\r\n" "onlinesCount" = "🌐 تعداد کاربران آنلاین: {{ .Count }}\r\n"
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n" "disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
"depleteSoon" = "🔜 بهزودی بهپایان خواهدرسید: {{ .Deplete }}\r\n \r\n" "depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n \r\n"
"backupTime" = "🗄 زمانپشتیبان‌گیری: {{ .Time }}\r\n" "backupTime" = "🗄 زمان پشتیبان‌گیری: {{ .Time }}\r\n"
"yes" = "✅ بله" "yes" = "✅ بله"
"no" = "❌ خیر" "no" = "❌ خیر"
[tgbot.buttons] [tgbot.buttons]
"dbBackup" = "دریافت فایل پشتیبان" "dbBackup" = "دریافت پشتیبان پایگاه داده"
"serverUsage" = "استفاده از سیستم" "serverUsage" = "استفاده از سرور"
"getInbounds" = "دریافت ورودی‌ها" "getInbounds" = "دریافت ورودی‌ها"
"depleteSoon" = "بهزودی بهپایان خواهدرسید" "depleteSoon" = "به زودی به پایان خواهد رسید"
"clientUsage" = "دریافت آمار کاربر" "clientUsage" = "دریافت آمار کاربر"
"onlines" = "کاربران آنلاین" "onlines" = "کاربران آنلاین"
"commands" = "دستورات" "commands" = "دستورات"
[tgbot.answers] [tgbot.answers]
"getInboundsFailed" = "❌ دریافت ورودی‌ها باخطا مواجه شد" "getInboundsFailed" = "❌ دریافت ورودی‌ها با خطا مواجه شد."
"askToAddUser" = "پیکربندی شما پیدا نشد!\r\nشما باید نامکاربری تلگرام خود را تنظیم کنید و از مدیر سرویس خود بخواهید که آن را به پیکربندی(های) شما اضافه کند" "askToAddUser" = "پیکربندی شما یافت نشد!\r\nشما باید نام کاربری تلگرام خود را پیکربندی کنید و از مدیر خود درخواست اضافه کردن آن به پیکربندی خود بکنید."
"askToAddUserName" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر سرویس خود بخواهید اطلاعات تلگرام شما را به پیکربندی(های) شما اضافه کند \r\n\r\nنامکاربری شما: @{{ .TgUserName }}" "askToAddUserName" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود درخواست استفاده از نام کاربری تلگرام خود در پیکربندی (ها) خود را بکنید.\r\n\r\nنام کاربری شما: <b>@{{ .TgUserName }}</b>"

View File

@@ -32,7 +32,7 @@
"transmission" = "Протокол передачи" "transmission" = "Протокол передачи"
"host" = "Хост" "host" = "Хост"
"path" = "Путь" "path" = "Путь"
"camouflage" = "Затемнение" "camouflage" = "Маскировка"
"status" = "Статус" "status" = "Статус"
"enabled" = "Включено" "enabled" = "Включено"
"disabled" = "Отключено" "disabled" = "Отключено"
@@ -60,10 +60,10 @@
"settings" = "Настройки" "settings" = "Настройки"
"xray" = "Xray Настройки" "xray" = "Xray Настройки"
"logout" = "Выйти" "logout" = "Выйти"
"link" = "Менеджмент" "link" = "Другое"
[pages.login] [pages.login]
"title" = "Добро пожаловать" "title" = "Войти"
"loginAgain" = "Время сессии истекло. Пожалуйста, войдите в систему снова" "loginAgain" = "Время сессии истекло. Пожалуйста, войдите в систему снова"
[pages.login.toasts] [pages.login.toasts]
@@ -77,19 +77,15 @@
"title" = "Статус системы" "title" = "Статус системы"
"memory" = "ОЗУ" "memory" = "ОЗУ"
"hard" = "Место на диске" "hard" = "Место на диске"
"machineInfo" = "Машина" "xrayStatus" = "Статус Xray"
"hostname" = "Имя хоста"
"xrayStatus" = "Xray"
"stopXray" = "Остановка" "stopXray" = "Остановка"
"restartXray" = "Перезапуск" "restartXray" = "Перезапуск Xray"
"xraySwitch" = "Сменить версию" "xraySwitch" = "Сменить версию"
"xraySwitchClick" = "Выберите желаемую версию" "xraySwitchClick" = "Выберите желаемую версию"
"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями" "xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями"
"operationHours" = "Время работы" "operationHours" = "Время работы"
"operationHoursDesc" = "Время безотказной работы с момента запуска ОС" "operationHoursDesc" = "Время работы системы: время с момента запуска."
"xrayoperationHoursDesc" = "Время работы с момента последней перезагрузки Xray"
"systemLoad" = "Системная нагрузка" "systemLoad" = "Системная нагрузка"
"systemLoadDesc" = "Средняя нагрузка за последние 1, 5 и 15 минут"
"connectionTcpCountDesc" = "Всего подключений TCP по всем сетевым картам." "connectionTcpCountDesc" = "Всего подключений TCP по всем сетевым картам."
"connectionUdpCountDesc" = "Общее количество подключений UDP по всем сетевым картам." "connectionUdpCountDesc" = "Общее количество подключений UDP по всем сетевым картам."
"upSpeed" = "Общая скорость отдачи" "upSpeed" = "Общая скорость отдачи"
@@ -104,8 +100,8 @@
"backup" = "Бекап и восстановление" "backup" = "Бекап и восстановление"
"backupTitle" = "База данных бекапа и восстановления" "backupTitle" = "База данных бекапа и восстановления"
"backupDescription" = "Не забудьте сделать резервную копию перед импортом новой базы данных" "backupDescription" = "Не забудьте сделать резервную копию перед импортом новой базы данных"
"exportDatabase" = "Резерв" "exportDatabase" = "Экспорт базы данных"
"importDatabase" = "Восстановить" "importDatabase" = "Импорт базы данных"
[pages.inbounds] [pages.inbounds]
"title" = "Подключения" "title" = "Подключения"
@@ -138,7 +134,7 @@
"destinationPort" = "Порт назначения" "destinationPort" = "Порт назначения"
"targetAddress" = "Целевой адрес" "targetAddress" = "Целевой адрес"
"monitorDesc" = "Оставьте пустым по умолчанию" "monitorDesc" = "Оставьте пустым по умолчанию"
"meansNoLimit" = "Ноль означает неограниченно. (значение: ГБ)" "meansNoLimit" = "Значит без ограничений"
"totalFlow" = "Общий расход" "totalFlow" = "Общий расход"
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы сделать бессрочно" "leaveBlankToNeverExpire" = "Оставьте пустым, чтобы сделать бессрочно"
"noRecommendKeepDefault" = "Нет особых требований для сохранения настроек по умолчанию" "noRecommendKeepDefault" = "Нет особых требований для сохранения настроек по умолчанию"
@@ -171,11 +167,11 @@
"emailDesc" = "Пожалуйста, укажите уникальный Email" "emailDesc" = "Пожалуйста, укажите уникальный Email"
"setDefaultCert" = "Установить сертификат с панели" "setDefaultCert" = "Установить сертификат с панели"
"telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)" "telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)"
"subscriptionDesc" = "вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких клиенты" "subscriptionDesc" = "вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких конфигов"
"info" = "Информация" "info" = "Информация"
"same" = "Тот же" "same" = "Тот же"
"inboundData" = "Входящие данные" "inboundData" = "Входящие данные"
"exportInbound" = "Экспорт входящих" "copyToClipboard" = "Копировать в буфер обмена"
"import" = "Импортировать" "import" = "Импортировать"
"importInbound" = "Импортировать входящее сообщение" "importInbound" = "Импортировать входящее сообщение"
@@ -192,27 +188,26 @@
"prefix" = "Префикс" "prefix" = "Префикс"
"postfix" = "Постфикс" "postfix" = "Постфикс"
"delayedStart" = "Начать со времени первого подключения" "delayedStart" = "Начать со времени первого подключения"
"expireDays" = "Длительность" "expireDays" = "Срок действия"
"days" = "дней" "days" = "дней"
"renew" = "Автопродление" "renew" = "Автопродление"
"renewDesc" = "Автопродление после истечения срока действия. (0 = отключить)(единица: день) " "renewDesc" = "Автоматическое продление через несколько дней после истечения срока действия. 0 = отключить"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "Получить" "obtain" = "Получить"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"request" = "Запрос" "requestHeader" = "Заголовок запроса"
"response" = "Ответ"
"name" = "Имя" "name" = "Имя"
"value" = "Ценить" "value" = "Значение"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"version" = "Версия" "requestVersion" = "Версия запроса"
"method" = "Метод" "requestMethod" = "Метод запроса"
"path" = уть" "requestPath" = еть запроса"
"status" = "Положение дел" "responseVersion" = "Версия ответа"
"statusDescription" = "Описание статуса" "responseStatus" = "Статус ответа"
"requestHeader" = "Заголовок запроса" "responseStatusDescription" = "Описание статуса ответа"
"responseHeader" = "Заголовок ответа" "responseHeader" = "Заголовок ответа"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
@@ -309,8 +304,6 @@
"advancedTemplate" = "Расширенные шаблоны" "advancedTemplate" = "Расширенные шаблоны"
"generalConfigs" = "Основные настройки" "generalConfigs" = "Основные настройки"
"generalConfigsDesc" = "Общие настройки" "generalConfigsDesc" = "Общие настройки"
"logConfigs" = "Журнал"
"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их с умом только в случае ваших нужд!"
"blockConfigs" = "Блокирующие конфигурации" "blockConfigs" = "Блокирующие конфигурации"
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам." "blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам."
"blockCountryConfigs" = "Конфигурация блокировки стран" "blockCountryConfigs" = "Конфигурация блокировки стран"
@@ -319,8 +312,6 @@
"directCountryConfigsDesc" = "Эти параметры будут подключать пользователей напрямую к доменам определенной страны." "directCountryConfigsDesc" = "Эти параметры будут подключать пользователей напрямую к доменам определенной страны."
"ipv4Configs" = "Настройки IPv4" "ipv4Configs" = "Настройки IPv4"
"ipv4ConfigsDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4" "ipv4ConfigsDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4"
"warpConfigs" = "Настройки WARP"
"warpConfigsDesc" = "WARP будет направлять трафик на веб-сайты через серверы Cloudflare"
"Template" = "Шаблон конфигурации Xray" "Template" = "Шаблон конфигурации Xray"
"TemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона." "TemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона."
"FreedomStrategy" = "Настроить стратегию протокола Freedom" "FreedomStrategy" = "Настроить стратегию протокола Freedom"
@@ -363,16 +354,6 @@
"GoogleIPv4Desc" = "Применить маршрутизацию Google для подключения к IPv4." "GoogleIPv4Desc" = "Применить маршрутизацию Google для подключения к IPv4."
"NetflixIPv4" = "Использовать IPv4 для Netflix" "NetflixIPv4" = "Использовать IPv4 для Netflix"
"NetflixIPv4Desc" = "Применить маршрутизацию Netflix для подключения к IPv4." "NetflixIPv4Desc" = "Применить маршрутизацию Netflix для подключения к IPv4."
"GoogleWARP" = "Маршрутизация Google через WARP"
"GoogleWARPDesc" = "Добавить маршрутизацию для Google через WARP"
"OpenAIWARP" = "Маршрутизация OpenAI (ChatGPT) через WARP"
"OpenAIWARPDesc" = "Добавить маршрутизацию для OpenAI (ChatGPT) через WARP"
"NetflixWARP" = "Маршрутизация Netflix через WARP"
"NetflixWARPDesc" = "Добавить маршрутизацию для Netflix через WARP"
"MetaWARP" = "Мета"
"MetaWARPDesc" = "Направляет трафик в Meta (Instagram, Facebook, WhatsApp, Threads...) через WARP."
"SpotifyWARP" = "Маршрутизация Spotify через WARP"
"SpotifyWARPDesc" = "Добавить маршрутизацию для Spotify через WARP"
"completeTemplate" = "Все" "completeTemplate" = "Все"
"Inbounds" = "Входящие" "Inbounds" = "Входящие"
"Outbounds" = "Исходящие" "Outbounds" = "Исходящие"
@@ -387,7 +368,7 @@
"source" = "Источник" "source" = "Источник"
"dest" = "Пункт назначения" "dest" = "Пункт назначения"
"inbound" = "Входящий" "inbound" = "Входящий"
"outbound" = "Исходящий" "outboun" = "Исходящий"
"info" = "Информация" "info" = "Информация"
"add" = "Добавить правило" "add" = "Добавить правило"
"edit" = "Редактировать правило" "edit" = "Редактировать правило"
@@ -407,17 +388,6 @@
"bridge" = "Мост" "bridge" = "Мост"
"portal" = "Портал" "portal" = "Портал"
"intercon" = "Соединение" "intercon" = "Соединение"
"settings" = "Настройки"
"accountInfo" = "Информация Об Учетной Записи"
"outboundStatus" = "Исходящий статус"
[pages.xray.wireguard]
"secretKey" = "Секретный ключ"
"publicKey" = "Открытый ключ"
"allowedIPs" = "Разрешенные IP-адреса"
"endpoint" = "Конечная точка"
"psk" = "Общий ключ"
"domainStrategy" = "Стратегия домена"
[tgbot] [tgbot]
"noResult" = "❗ Нет результатов!" "noResult" = "❗ Нет результатов!"

View File

@@ -32,7 +32,7 @@
"transmission" = "Truyền tải" "transmission" = "Truyền tải"
"host" = "Máy chủ" "host" = "Máy chủ"
"path" = "Đường dẫn" "path" = "Đường dẫn"
"camouflage" = "Sự làm xáo trộn" "camouflage" = "Ngụy trang"
"status" = "Trạng thái" "status" = "Trạng thái"
"enabled" = "Đã kích hoạt" "enabled" = "Đã kích hoạt"
"disabled" = "Đã tắt" "disabled" = "Đã tắt"
@@ -60,10 +60,10 @@
"settings" = "Cài đặt bảng điều khiển" "settings" = "Cài đặt bảng điều khiển"
"xray" = "Cài đặt Xray" "xray" = "Cài đặt Xray"
"logout" = "Đăng xuất" "logout" = "Đăng xuất"
"link" = "Sự quản lý" "link" = "Khác"
[pages.login] [pages.login]
"title" = "Chào mừng" "title" = "Đăng nhập"
"loginAgain" = "Thời hạn đăng nhập đã hết, Vui lòng đăng nhập lại." "loginAgain" = "Thời hạn đăng nhập đã hết, Vui lòng đăng nhập lại."
[pages.login.toasts] [pages.login.toasts]
@@ -77,19 +77,15 @@
"title" = "Trạng thái hệ thống" "title" = "Trạng thái hệ thống"
"memory" = "Bộ nhớ" "memory" = "Bộ nhớ"
"hard" = "Ổ cứng" "hard" = "Ổ cứng"
"machineInfo" = "Máy" "xrayStatus" = "Trạng thái của Xray"
"hostname" = "Tên máy chủ" "stopXray" = "Dừng Xray"
"xrayStatus" = "Xray" "restartXray" = "Khởi động lại Xray"
"stopXray" = "Dừng"
"restartXray" = "Khởi động lại"
"xraySwitch" = "Chuyển đổi phiên bản" "xraySwitch" = "Chuyển đổi phiên bản"
"xraySwitchClick" = "Chọn phiên bản mà bạn muốn chuyển đổi sang." "xraySwitchClick" = "Chọn phiên bản mà bạn muốn chuyển đổi sang."
"xraySwitchClickDesk" = "Hãy lựa chọn thận trọng, vì các phiên bản cũ có thể không tương thích với các cấu hình hiện tại, của Bạn" "xraySwitchClickDesk" = "Hãy lựa chọn thận trọng, vì các phiên bản cũ có thể không tương thích với các cấu hình hiện tại, của Bạn"
"operationHours" = "Thời gian hoạt động" "operationHours" = "Thời gian hoạt động"
"operationHoursDesc" = "Thời gian hoạt động kể từ khi khởi động hệ điều hành" "operationHoursDesc" = "Thời gian hoạt động của hệ thống: thời gian kể từ khi khởi động."
"xrayoperationHoursDesc" = "Thời gian hoạt động kể từ lần khởi động Xray cuối cùng"
"systemLoad" = "Tải hệ thống" "systemLoad" = "Tải hệ thống"
"systemLoadDesc" = "Tải trung bình trong 1, 5 và 15 phút qua"
"connectionTcpCountDesc" = "Tổng số kết nối TCP trên tất cả các card mạng." "connectionTcpCountDesc" = "Tổng số kết nối TCP trên tất cả các card mạng."
"connectionUdpCountDesc" = "Tổng số kết nối UDP trên tất cả các card mạng." "connectionUdpCountDesc" = "Tổng số kết nối UDP trên tất cả các card mạng."
"upSpeed" = "Tổng tốc độ tải lên cho tất cả các thẻ mạng." "upSpeed" = "Tổng tốc độ tải lên cho tất cả các thẻ mạng."
@@ -104,8 +100,8 @@
"backup" = "Phục hồi dữ liệu đã lưu" "backup" = "Phục hồi dữ liệu đã lưu"
"backupTitle" = "Sao lưu và Khôi phục cơ sở dữ liệu" "backupTitle" = "Sao lưu và Khôi phục cơ sở dữ liệu"
"backupDescription" = "Bạn hãy nhớ sao lưu trước khi nhập cơ sở dữ liệu mới." "backupDescription" = "Bạn hãy nhớ sao lưu trước khi nhập cơ sở dữ liệu mới."
"exportDatabase" = "Sao lưu" "exportDatabase" = "Tải xuống cơ sở dữ liệu"
"importDatabase" = "Khôi phục" "importDatabase" = "Tải lên cơ sở dữ liệu"
[pages.inbounds] [pages.inbounds]
"title" = "Điểm vào (Inbounds)" "title" = "Điểm vào (Inbounds)"
@@ -138,7 +134,7 @@
"destinationPort" = "Cổng đích" "destinationPort" = "Cổng đích"
"targetAddress" = "Địa chỉ mục tiêu" "targetAddress" = "Địa chỉ mục tiêu"
"monitorDesc" = "Mặc định để trống" "monitorDesc" = "Mặc định để trống"
"meansNoLimit" = "Số không có nghĩa là không giới hạn. (đơn vị: GB)" "meansNoLimit" = "Nghĩa là không giới hạn"
"totalFlow" = "Tổng lưu lượng" "totalFlow" = "Tổng lưu lượng"
"leaveBlankToNeverExpire" = "Để trống để không bao giờ hết hạn" "leaveBlankToNeverExpire" = "Để trống để không bao giờ hết hạn"
"noRecommendKeepDefault" = "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định" "noRecommendKeepDefault" = "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định"
@@ -171,11 +167,11 @@
"emailDesc" = "vui lòng cung cấp một địa chỉ email duy nhất." "emailDesc" = "vui lòng cung cấp một địa chỉ email duy nhất."
"setDefaultCert" = "Đặt chứng chỉ từ bảng điều khiển" "setDefaultCert" = "Đặt chứng chỉ từ bảng điều khiển"
"telegramDesc" = "Sử dụng ID Telegram không có @ hoặc ID trò chuyện (bạn có thể lấy nó tại đây @userinfobot hoặc sử dụng lệnh '/id' trong bot)" "telegramDesc" = "Sử dụng ID Telegram không có @ hoặc ID trò chuyện (bạn có thể lấy nó tại đây @userinfobot hoặc sử dụng lệnh '/id' trong bot)"
"subscriptionDesc" = "Bạn có thể tìm thấy liên kết phụ của mình trên Chi tiết, bạn cũng có thể sử dụng cùng tên cho một số khách hàng" "subscriptionDesc" = "Bạn có thể tìm thấy liên kết phụ của mình trên Chi tiết, bạn cũng có thể sử dụng cùng tên cho một số cấu hình"
"info" = "Thông tin" "info" = "Thông tin"
"same" = "Như nhau" "same" = "Như nhau"
"inboundData" = "Dữ liệu gửi đến" "inboundData" = "Dữ liệu gửi đến"
"exportInbound" = "Xuất nhập khẩu" "copyToClipboard" = "Sao chép vào bảng nhớ tạm"
"import" = "Nhập" "import" = "Nhập"
"importInbound" = "Nhập hàng gửi về" "importInbound" = "Nhập hàng gửi về"
@@ -192,27 +188,26 @@
"prefix" = "Tiền Tố (Được ưu đãi)" "prefix" = "Tiền Tố (Được ưu đãi)"
"postfix" = "Hậu tố" "postfix" = "Hậu tố"
"delayedStart" = "Bắt đầu sau lần sử dụng đầu tiên" "delayedStart" = "Bắt đầu sau lần sử dụng đầu tiên"
"expireDays" = "Khoảng thời gian" "expireDays" = "Số ngày hết hạn"
"days" = "Ngày(s)" "days" = "Ngày(s)"
"renew" = "Tự động gia hạn" "renew" = "Tự động gia hạn"
"renewDesc" = "Tự động gia hạn sau khi hết hạn. (0 = tắt)(đơn vị: ngày)" "renewDesc" = "Tự động gia hạn ngày sau khi hết hạn. 0 = tắt"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "Nhận được" "obtain" = "Nhận được"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"request" = "Lời yêu cầu" "requestHeader" = "Tiêu đề yêu cầu"
"response" = "Phản ứng"
"name" = "Tên" "name" = "Tên"
"value" = "Giá trị" "value" = "Giá trị"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"version" = "Phiên bản" "requestVersion" = "Phiên bản yêu cầu"
"method" = "Phương pháp" "requestMethod" = "Phương thức yêu cầu"
"path" = "Con đường" "requestPath" = "Đường dẫn yêu cầu"
"status" = "Trạng thái" "responseVersion" = "Phiên bản phản hồi"
"statusDescription" = "Tình trạng Mô tả" "responseStatus" = "Trạng thái phản hồi"
"requestHeader" = "Tiêu đề yêu cầu" "responseStatusDescription" = "Mô tả trạng thái phản hồi"
"responseHeader" = "Tiêu đề phản hồi" "responseHeader" = "Tiêu đề phản hồi"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
@@ -309,8 +304,6 @@
"advancedTemplate" = "Mẫu nâng cao" "advancedTemplate" = "Mẫu nâng cao"
"generalConfigs" = "Cấu hình Chung" "generalConfigs" = "Cấu hình Chung"
"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát." "generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát."
"logConfigs" = "Nhật ký"
"logConfigsDesc" = "Nhật ký có thể ảnh hưởng đến hiệu suất máy chủ của bạn. Bạn chỉ nên kích hoạt nó một cách khôn ngoan trong trường hợp bạn cần"
"blockConfigs" = "Cấu hình Chặn" "blockConfigs" = "Cấu hình Chặn"
"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể." "blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể."
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia" "blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
@@ -319,8 +312,6 @@
"directCountryConfigsDesc" = "Những tùy chọn này sẽ kết nối người dùng trực tiếp đến các tên miền quốc gia cụ thể." "directCountryConfigsDesc" = "Những tùy chọn này sẽ kết nối người dùng trực tiếp đến các tên miền quốc gia cụ thể."
"ipv4Configs" = "Cấu hình IPv4" "ipv4Configs" = "Cấu hình IPv4"
"ipv4ConfigsDesc" = "Những tùy chọn này sẽ chỉ định kết nối đến các tên miền mục tiêu qua IPv4." "ipv4ConfigsDesc" = "Những tùy chọn này sẽ chỉ định kết nối đến các tên miền mục tiêu qua IPv4."
"warpConfigs" = "Cấu hình WARP"
"warpConfigsDesc" = "WARP sẽ định tuyến lưu lượng đến các trang web qua máy chủ Cloudflare."
"Template" = "Mẫu cấu hình Xray" "Template" = "Mẫu cấu hình Xray"
"TemplateDesc" = "Tạo tệp cấu hình Xray cuối cùng dựa trên mẫu này." "TemplateDesc" = "Tạo tệp cấu hình Xray cuối cùng dựa trên mẫu này."
"FreedomStrategy" = "Cấu hình chiến lược cho giao thức tự do" "FreedomStrategy" = "Cấu hình chiến lược cho giao thức tự do"
@@ -363,16 +354,6 @@
"GoogleIPv4Desc" = "Thêm định tuyến để Google kết nối với IPv4." "GoogleIPv4Desc" = "Thêm định tuyến để Google kết nối với IPv4."
"NetflixIPv4" = "Sử dụng IPv4 cho Netflix" "NetflixIPv4" = "Sử dụng IPv4 cho Netflix"
"NetflixIPv4Desc" = "Thêm định tuyến cho Netflix để kết nối với IPv4." "NetflixIPv4Desc" = "Thêm định tuyến cho Netflix để kết nối với IPv4."
"GoogleWARP" = "Định tuyến Google qua WARP."
"GoogleWARPDesc" = "Thêm định tuyến cho Google qua WARP."
"OpenAIWARP" = "Định tuyến OpenAI (ChatGPT) qua WARP."
"OpenAIWARPDesc" = "Thêm định tuyến cho OpenAI (ChatGPT) qua WARP."
"NetflixWARP" = "Định tuyến Netflix qua WARP."
"NetflixWARPDesc" = "Thêm định tuyến cho Netflix qua WARP."
"MetaWARP" = "Meta"
"MetaWARPDesc" = "Định tuyến lưu lượng truy cập tới Meta (Instagram, Facebook, WhatsApp, Threads,...) thông qua WARP."
"SpotifyWARP" = "Định tuyến Spotify qua WARP."
"SpotifyWARPDesc" = "Thêm định tuyến cho Spotify qua WARP."
"completeTemplate" = "Tất cả" "completeTemplate" = "Tất cả"
"Inbounds" = "Đầu vào" "Inbounds" = "Đầu vào"
"Outbounds" = "Đầu ra" "Outbounds" = "Đầu ra"
@@ -407,17 +388,6 @@
"bridge" = "Liên kết" "bridge" = "Liên kết"
"portal" = "Cổng thông tin" "portal" = "Cổng thông tin"
"intercon" = "Kết nối" "intercon" = "Kết nối"
"settings" = "cài đặt"
"accountInfo" = "Thông tin tài khoản"
"outboundStatus" = "Trạng thái đầu ra"
[pages.xray.wireguard]
"secretKey" = "Chìa khoá bí mật"
"publicKey" = "Khóa công khai"
"allowedIPs" = "IP được phép"
"endpoint" = "Điểm cuối"
"psk" = "Khóa chia sẻ"
"domainStrategy" = "Chiến lược tên miền"
[tgbot] [tgbot]
"noResult" = "❗ Không có kết quả!" "noResult" = "❗ Không có kết quả!"
@@ -466,7 +436,7 @@
"port" = "🔌 Cổng: {{ .Port }}\r\n" "port" = "🔌 Cổng: {{ .Port }}\r\n"
"expire" = "📅 Hạn sử dụng: {{ .DateTime }}\r\n \r\n" "expire" = "📅 Hạn sử dụng: {{ .DateTime }}\r\n \r\n"
"expireIn" = "📅 Hết hạn vào: {{ .Time }}\r\n \r\n" "expireIn" = "📅 Hết hạn vào: {{ .Time }}\r\n \r\n"
"active" = "💡 Có hiu lc {{ .Enable }}\r\n" "active" = "💡 Có hiệu lực {{ .Enable }}\r\n"
"online" = "🌐 Tình trạng kết nối: {{ .Status }}\r\n" "online" = "🌐 Tình trạng kết nối: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n"
"upload" = "🔼 Tải lên↑: {{ .Upload }}\r\n" "upload" = "🔼 Tải lên↑: {{ .Upload }}\r\n"

View File

@@ -32,7 +32,7 @@
"transmission" = "传输" "transmission" = "传输"
"host" = "主持人" "host" = "主持人"
"path" = "小路" "path" = "小路"
"camouflage" = "混淆" "camouflage" = "伪装"
"status" = "状态" "status" = "状态"
"enabled" = "开启" "enabled" = "开启"
"disabled" = "关闭" "disabled" = "关闭"
@@ -60,10 +60,10 @@
"settings" = "面板设置" "settings" = "面板设置"
"xray" = "Xray 设置" "xray" = "Xray 设置"
"logout" = "退出登录" "logout" = "退出登录"
"link" = "管理" "link" = "其他"
[pages.login] [pages.login]
"title" = "欢迎" "title" = "登录"
"loginAgain" = "登录时效已过,请重新登录" "loginAgain" = "登录时效已过,请重新登录"
[pages.login.toasts] [pages.login.toasts]
@@ -77,19 +77,15 @@
"title" = "系统状态" "title" = "系统状态"
"memory" = "内存" "memory" = "内存"
"hard" = "硬盘" "hard" = "硬盘"
"machineInfo" = "机器" "xrayStatus" = "Xray 状态"
"hostname" = "主机名"
"xrayStatus" = "Xray"
"stopXray" = "停止" "stopXray" = "停止"
"restartXray" = "重启" "restartXray" = "重启"
"xraySwitch" = "切换版本" "xraySwitch" = "切换版本"
"xraySwitchClick" = "点击你想切换的版本" "xraySwitchClick" = "点击你想切换的版本"
"xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容" "xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容"
"operationHours" = "运行时间" "operationHours" = "运行时间"
"operationHoursDesc" = "自操作系统启动以来的正常运行时间" "operationHoursDesc" = "系统启动以来的运行时间"
"xrayoperationHoursDesc" = "自 Xray 上次重启以来的正常运行时间"
"systemLoad" = "系统负载" "systemLoad" = "系统负载"
"systemLoadDesc" = "过去 1 分钟、5 分钟和 15 分钟的平均负载"
"connectionTcpCountDesc" = "所有网卡的总 TCP 连接数。" "connectionTcpCountDesc" = "所有网卡的总 TCP 连接数。"
"connectionUdpCountDesc" = "所有网卡的总 UDP 连接数。" "connectionUdpCountDesc" = "所有网卡的总 UDP 连接数。"
"upSpeed" = "所有网卡的总上传速度" "upSpeed" = "所有网卡的总上传速度"
@@ -104,8 +100,8 @@
"backup" = "备份" "backup" = "备份"
"backupTitle" = "备份数据库" "backupTitle" = "备份数据库"
"backupDescription" = "请记住在导入新数据库之前进行备份" "backupDescription" = "请记住在导入新数据库之前进行备份"
"exportDatabase" = "备份" "exportDatabase" = "下载数据库"
"importDatabase" = "恢复" "importDatabase" = "上传数据库"
[pages.inbounds] [pages.inbounds]
"title" = "入站列表" "title" = "入站列表"
@@ -138,7 +134,7 @@
"destinationPort" = "目标端口" "destinationPort" = "目标端口"
"targetAddress" = "目标地址" "targetAddress" = "目标地址"
"monitorDesc" = "默认留空即可" "monitorDesc" = "默认留空即可"
"meansNoLimit" = "零意味着无限。(单位GB)" "meansNoLimit" = "表示不限制"
"totalFlow" = "总流量" "totalFlow" = "总流量"
"leaveBlankToNeverExpire" = "留空则永不到期" "leaveBlankToNeverExpire" = "留空则永不到期"
"noRecommendKeepDefault" = "没有特殊需求保持默认即可" "noRecommendKeepDefault" = "没有特殊需求保持默认即可"
@@ -171,11 +167,11 @@
"emailDesc" = "电子邮件必须完全唯" "emailDesc" = "电子邮件必须完全唯"
"setDefaultCert" = "从面板设置证书" "setDefaultCert" = "从面板设置证书"
"telegramDesc" = "使用 Telegram ID不包含 @ 符号或聊天 ID可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)" "telegramDesc" = "使用 Telegram ID不包含 @ 符号或聊天 ID可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)"
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,可以多个客户端使用相同的名称" "subscriptionDesc" = "您可以在详细信息上找到您的子链接,可以多个配置使用相同的名称"
"info" = "信息" "info" = "信息"
"same" = "相同" "same" = "相同"
"inboundData" = "入站数据" "inboundData" = "入站数据"
"exportInbound" = "出口 入境" "copyToClipboard" = "复制到剪贴板"
"import"="导入" "import"="导入"
"importInbound" = "导入入站" "importInbound" = "导入入站"
@@ -192,27 +188,26 @@
"prefix" = "前缀" "prefix" = "前缀"
"postfix" = "后缀" "postfix" = "后缀"
"delayedStart" = "首次使用后开始" "delayedStart" = "首次使用后开始"
"expireDays" = "期间" "expireDays" = "过期天数"
"days" = "天" "days" = "天"
"renew" = "自动续订" "renew" = "自动续订"
"renewDesc" = "期后自动续订。(0 = 禁用)(单元: 天)" "renewDesc" = "期后自动续订。0 = 禁用"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "获取" "obtain" = "获取"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"request" = "要求" "requestHeader" = "请求头"
"response" = "回复" "name" = "名称"
"name" = "姓名" "value" = ""
"value" = "价值"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"version" = "版本" "requestVersion" = "请求版本"
"method" = "方法" "requestMethod" = "请求方法"
"path" = "小路" "requestPath" = "请求路径"
"status" = "地位" "responseVersion" = "响应版本"
"statusDescription" = "状态说明" "responseStatus" = "响应状态"
"requestHeader" = "请求头" "responseStatusDescription" = "响应状态说明"
"responseHeader" = "响应头" "responseHeader" = "响应头"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
@@ -309,8 +304,6 @@
"advancedTemplate" = "高级模板部件" "advancedTemplate" = "高级模板部件"
"generalConfigs" = "通用配置" "generalConfigs" = "通用配置"
"generalConfigsDesc" = "这些选项将提供一般调整" "generalConfigsDesc" = "这些选项将提供一般调整"
"logConfigs"="日志"
"logConfigsDesc" = "日志可能会影响您服务器的效率。建议仅在您需要时明智地启用它"
"blockConfigs" = "阻塞配置" "blockConfigs" = "阻塞配置"
"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站" "blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站"
"blockCountryConfigs" = "阻止国家配置" "blockCountryConfigs" = "阻止国家配置"
@@ -319,8 +312,6 @@
"directCountryConfigsDesc" = "这些选项会将用户直接连接到特定国家/地区的域。" "directCountryConfigsDesc" = "这些选项会将用户直接连接到特定国家/地区的域。"
"ipv4Configs" = "IPv4 配置" "ipv4Configs" = "IPv4 配置"
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域" "ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域"
"warpConfigs" = "WARP 配置"
"warpConfigsDesc" = "WARP 将通过 Cloudflare 服务器将流量路由到网站。"
"Template" = "Xray 配置模板" "Template" = "Xray 配置模板"
"TemplateDesc" = "以该模型为基础生成最终的Xray配置文件重新启动面板生成效率" "TemplateDesc" = "以该模型为基础生成最终的Xray配置文件重新启动面板生成效率"
"FreedomStrategy" = "配置自由协议的策略" "FreedomStrategy" = "配置自由协议的策略"
@@ -363,16 +354,6 @@
"GoogleIPv4Desc" = "添加谷歌连接IPv4的路由" "GoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
"NetflixIPv4" = "为 Netflix 使用 IPv4" "NetflixIPv4" = "为 Netflix 使用 IPv4"
"NetflixIPv4Desc" = "添加Netflix连接IPv4的路由" "NetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
"GoogleWARP" = "将谷歌路由到 WARP"
"GoogleWARPDesc" = "为谷歌添加路由到WARP"
"OpenAIWARP" = "将 OpenAI (ChatGPT) 路由到 WARP"
"OpenAIWARPDesc" = "将OpenAIChatGPT路由添加到WARP"
"NetflixWARP" = "将 Netflix 路由到 WARP"
"NetflixWARPDesc" = "为Netflix添加路由到WARP"
"MetaWARP"="元"
"MetaWARPDesc" = "通过 WARP 将流量路由到 MetaInstagram、Facebook、WhatsApp、Threads..."
"SpotifyWARP" = "将 Spotify 路由到 WARP"
"SpotifyWARPDesc" = "为Spotify添加路由到WARP"
"completeTemplate" = "全部" "completeTemplate" = "全部"
"Inbounds" = "界内" "Inbounds" = "界内"
"Outbounds" = "出站" "Outbounds" = "出站"
@@ -380,7 +361,7 @@
"RoutingsDesc" = "每条规则的优先级都很重要" "RoutingsDesc" = "每条规则的优先级都很重要"
[pages.xray.rules] [pages.xray.rules]
"first" = "第一个" "firsto" = "第一个"
"last" = "最后" "last" = "最后"
"up" = "向上" "up" = "向上"
"down" = "向下" "down" = "向下"
@@ -401,23 +382,12 @@
"tag" = "标签" "tag" = "标签"
"tagDesc" = "独特的标签" "tagDesc" = "独特的标签"
"address" = "地址" "address" = "地址"
"reverse" = "反转" "rreverse" = "反转"
"domain" = "域名" "domain" = "域名"
"type" = "类型" "type" = "类型"
"bridge" = "桥" "bridge" = "桥"
"portal" = "门户" "portal" = "门户"
"intercon" = "互连" "intercon" = "互连"
"settings" = "设置"
"accountInfo" = "帐户信息"
"outboundStatus" = "出站状态"
[pages.xray.wireguard]
"secretKey" = "密钥"
"publicKey" = "公钥"
"allowedIPs" = "允许的 IP"
"endpoint" = "终点"
"psk" = "共享密钥"
"domainStrategy" = "域策略"
[tgbot] [tgbot]
"noResult" = "❗ 没有结果!" "noResult" = "❗ 没有结果!"

149
x-ui.sh
View File

@@ -115,32 +115,8 @@ update() {
fi fi
} }
custom_version() {
echo "Enter the panel version (like 1.6.0):"
read panel_version
if [ -z "$panel_version" ]; then
echo "Panel version cannot be empty. Exiting."
exit 1
fi
download_link="https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh"
# Use the entered panel version in the download link
install_command="bash <(curl -Ls $download_link) $panel_version"
echo "Downloading and installing panel version $panel_version..."
eval $install_command
}
# Function to handle the deletion of the script file
delete_script() {
rm "$0" # Remove the script file itself
exit 1
}
uninstall() { uninstall() {
confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" " n" confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" "n"
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
show_menu show_menu
@@ -154,14 +130,14 @@ uninstall() {
systemctl reset-failed systemctl reset-failed
rm /etc/x-ui/ -rf rm /etc/x-ui/ -rf
rm /usr/local/x-ui/ -rf rm /usr/local/x-ui/ -rf
echo -e "\nUninstalled Successfully."
echo "" echo ""
echo -e "If you need to install this panel again, you can use below command:" echo -e "Uninstalled SuccessfullyIf you want to remove this scriptthen after exiting the script run ${green}rm /usr/bin/x-ui -f${plain} to delete it."
echo -e "${green}bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh)${plain}"
echo "" echo ""
# Trap the SIGTERM signal
trap delete_script SIGTERM if [[ $# == 0 ]]; then
delete_script before_show_menu
fi
} }
reset_user() { reset_user() {
@@ -307,6 +283,12 @@ show_log() {
fi fi
} }
migrate_v2_ui() {
/usr/local/x-ui/x-ui v2-ui
before_show_menu
}
install_bbr() { install_bbr() {
# temporary workaround for installing bbr # temporary workaround for installing bbr
bash <(curl -L -s https://raw.githubusercontent.com/teddysun/across/master/bbr.sh) bash <(curl -L -s https://raw.githubusercontent.com/teddysun/across/master/bbr.sh)
@@ -380,7 +362,7 @@ show_status() {
check_status check_status
case $? in case $? in
0) 0)
echo -e "Panel state: ${green}Running${plain}" echo -e "Panel state: ${green}Runing${plain}"
show_enable_status show_enable_status
;; ;;
1) 1)
@@ -415,7 +397,7 @@ check_xray_status() {
show_xray_status() { show_xray_status() {
check_xray_status check_xray_status
if [[ $? == 0 ]]; then if [[ $? == 0 ]]; then
echo -e "xray state: ${green}Running${plain}" echo -e "xray state: ${green}Runing${plain}"
else else
echo -e "xray state: ${red}Not Running${plain}" echo -e "xray state: ${red}Not Running${plain}"
fi fi
@@ -671,18 +653,18 @@ show_usage() {
echo "X-UI Control Menu Usage" echo "X-UI Control Menu Usage"
echo "------------------------------------------" echo "------------------------------------------"
echo "SUBCOMMANDS:" echo "SUBCOMMANDS:"
echo "x-ui - Admin Management Script" echo "x-ui - Admin management script"
echo "x-ui start - Start" echo "x-ui start - Start X-UI"
echo "x-ui stop - Stop" echo "x-ui stop - Stop X-UI"
echo "x-ui restart - Restart" echo "x-ui restart - Restart X-UI"
echo "x-ui status - Current Status" echo "x-ui status - Current X-UI status"
echo "x-ui enable - Enable Autostart on OS Startup" echo "x-ui enable - Enable X-UI on system startup"
echo "x-ui disable - Disable Autostart on OS Startup" echo "x-ui disable - Disable X-UI on system startup"
echo "x-ui log - Check Logs" echo "x-ui log - Check X-UI logs"
echo "x-ui update - Update" echo "x-ui update - Update X-UI"
echo "x-ui install - Install" echo "x-ui install - Install X-UI"
echo "x-ui uninstall - Uninstall" echo "x-ui uninstall - Uninstall X-UI"
echo "x-ui help - Control Menu Usage" echo "x-ui help - Control menu usage"
echo "------------------------------------------" echo "------------------------------------------"
} }
@@ -692,33 +674,32 @@ show_menu() {
———————————————— ————————————————
${green}0.${plain} Exit ${green}0.${plain} Exit
———————————————— ————————————————
${green}1.${plain} Install ${green}1.${plain} Install X-UI
${green}2.${plain} Update ${green}2.${plain} Update X-UI
${green}3.${plain} Custom Version ${green}3.${plain} Uninstall X-UI
${green}4.${plain} Uninstall
———————————————— ————————————————
${green}5.${plain} Reset Username and Password ${green}4.${plain} Reset Username and Password
${green}6.${plain} Reset Panel Settings ${green}5.${plain} Reset Panel Settings
${green}7.${plain} Set Panel Port ${green}6.${plain} Set Panel Port
${green}8.${plain} View Panel Settings ${green}7.${plain} View Current Panel Settings
———————————————— ————————————————
${green}9.${plain} Start ${green}8.${plain} Start X-UI
${green}10.${plain} Stop ${green}9.${plain} Stop X-UI
${green}11.${plain} Restart ${green}10.${plain} Restart X-UI
${green}12.${plain} Check State ${green}11.${plain} Check X-UI State
${green}13.${plain} Check Logs ${green}12.${plain} Check X-UI Logs
———————————————— ————————————————
${green}14.${plain} Enable Autostart ${green}13.${plain} Set X-UI Autostart
${green}15.${plain} Disable Autostart ${green}14.${plain} Cancel X-UI Autostart
———————————————— ————————————————
${green}16.${plain} A Key Installation BBR (latest kernel) ${green}15.${plain} A Key Installation BBR (latest kernel)
${green}17.${plain} SSL Certificate Management ${green}16.${plain} SSL Certificate Management
${green}18.${plain} Cloudflare SSL Certificate ${green}17.${plain} Cloudflare SSL Certificate
${green}19.${plain} Update Geo Files ${green}18.${plain} Update Geo files
———————————————— ————————————————
" "
show_status show_status
echo && read -p "Please enter your selection [0-19]: " num echo && read -p "Please enter your selection [0-18]: " num
case "${num}" in case "${num}" in
0) 0)
@@ -731,58 +712,55 @@ show_menu() {
check_install && update check_install && update
;; ;;
3) 3)
check_install && custom_version
;;
4)
check_install && uninstall check_install && uninstall
;; ;;
5) 4)
check_install && reset_user check_install && reset_user
;; ;;
6) 5)
check_install && reset_config check_install && reset_config
;; ;;
7) 6)
check_install && set_port check_install && set_port
;; ;;
8) 7)
check_install && check_config check_install && check_config
;; ;;
9) 8)
check_install && start check_install && start
;; ;;
10) 9)
check_install && stop check_install && stop
;; ;;
11) 10)
check_install && restart check_install && restart
;; ;;
12) 11)
check_install && status check_install && status
;; ;;
13) 12)
check_install && show_log check_install && show_log
;; ;;
14) 13)
check_install && enable check_install && enable
;; ;;
15) 14)
check_install && disable check_install && disable
;; ;;
16) 15)
install_bbr install_bbr
;; ;;
17) 16)
ssl_cert_issue_main ssl_cert_issue_main
;; ;;
18) 17)
ssl_cert_issue_CF ssl_cert_issue_CF
;; ;;
19) 18)
update_geo update_geo
;; ;;
*) *)
LOGE "Please enter the correct number [0-19]" LOGE "Please enter the correct number [0-18]"
;; ;;
esac esac
} }
@@ -810,6 +788,9 @@ if [[ $# > 0 ]]; then
"log") "log")
check_install 0 && show_log 0 check_install 0 && show_log 0
;; ;;
"v2-ui")
check_install 0 && migrate_v2_ui 0
;;
"update") "update")
check_install 0 && update 0 check_install 0 && update 0
;; ;;

View File

@@ -1,7 +1,6 @@
package xray package xray
import ( import (
"regexp"
"strings" "strings"
"x-ui/logger" "x-ui/logger"
) )
@@ -15,20 +14,21 @@ type LogWriter struct {
} }
func (lw *LogWriter) Write(m []byte) (n int, err error) { func (lw *LogWriter) Write(m []byte) (n int, err error) {
regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[([^\]]+)\] (.+)$`)
// Convert the data to a string // Convert the data to a string
message := strings.TrimSpace(string(m)) message := strings.TrimSpace(string(m))
messages := strings.Split(message, "\n") messages := strings.Split(message, "\n")
lw.lastLine = messages[len(messages)-1] lw.lastLine = messages[len(messages)-1]
for _, msg := range messages { for _, msg := range messages {
matches := regex.FindStringSubmatch(msg) // Remove timestamp
messageBody := strings.TrimSpace(strings.SplitN(msg, " ", 3)[2])
if len(matches) > 3 { // Find level in []
level := matches[2] startIndex := strings.Index(messageBody, "[")
msgBody := matches[3] endIndex := strings.Index(messageBody, "]")
if startIndex != -1 && endIndex != -1 {
level := strings.TrimSpace(messageBody[startIndex+1 : endIndex])
msgBody := "XRAY: " + strings.TrimSpace(messageBody[endIndex+1:])
// Map the level to the appropriate logger function // Map the level to the appropriate logger function
switch level { switch level {