mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-09-10 12:16:18 +00:00
Merge branch 'MHSanaei:main' into main
This commit is contained in:
commit
3c1cc2d14a
89 changed files with 2896 additions and 1709 deletions
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -77,7 +77,7 @@ jobs:
|
||||||
cd x-ui/bin
|
cd x-ui/bin
|
||||||
|
|
||||||
# Download dependencies
|
# Download dependencies
|
||||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.8/"
|
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.9/"
|
||||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||||
wget ${Xray_URL}Xray-linux-64.zip
|
wget ${Xray_URL}Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@
|
||||||
.cache
|
.cache
|
||||||
.sync*
|
.sync*
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
*.log
|
||||||
access.log
|
access.log
|
||||||
error.log
|
error.log
|
||||||
tmp
|
tmp
|
||||||
|
|
|
@ -27,7 +27,7 @@ case $1 in
|
||||||
esac
|
esac
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
cd build/bin
|
cd build/bin
|
||||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.8/Xray-linux-${ARCH}.zip"
|
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.9/Xray-linux-${ARCH}.zip"
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# 3X-UI
|
# 3X-UI
|
||||||
|
|
||||||
|
[English](/README.md) | [Chinese](/README.zh.md)
|
||||||
|
|
||||||
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
|
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
|
||||||
|
|
||||||
**An Advanced Web Panel • Built on Xray Core**
|
**An Advanced Web Panel • Built on Xray Core**
|
||||||
|
@ -26,10 +28,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/maple367/3x-ui/main/install.sh
|
||||||
|
|
||||||
## Install Custom Version
|
## Install Custom Version
|
||||||
|
|
||||||
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.2.1`:
|
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.2.6`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.1
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.6
|
||||||
```
|
```
|
||||||
|
|
||||||
## SSL Certificate
|
## SSL Certificate
|
||||||
|
|
473
README.zh.md
Normal file
473
README.zh.md
Normal file
|
@ -0,0 +1,473 @@
|
||||||
|
# 3X-UI
|
||||||
|
|
||||||
|
[English](/README.md) | [Chinese](/README.zh.md)
|
||||||
|
|
||||||
|
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
|
||||||
|
|
||||||
|
**一个更好的面板 • 基于Xray Core构建**
|
||||||
|
|
||||||
|
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||||
|
[](#)
|
||||||
|
[](#)
|
||||||
|
[](#)
|
||||||
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
|
> **Disclaimer:** 此项目仅供个人学习交流,请不要用于非法目的,请不要在生产环境中使用。
|
||||||
|
|
||||||
|
**如果此项目对你有用,请给一个**:star2:
|
||||||
|
|
||||||
|
<p align="left"><a href="#"><img width="125" src="https://github.com/MHSanaei/3x-ui/assets/115543613/7aa895dd-048a-42e7-989b-afd41a74e2e1" alt="Image"></a></p>
|
||||||
|
|
||||||
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
|
|
||||||
|
## 安装 & 升级
|
||||||
|
|
||||||
|
```
|
||||||
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安装指定版本
|
||||||
|
|
||||||
|
要安装所需的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.2.6`:
|
||||||
|
|
||||||
|
```
|
||||||
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.6
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSL 认证
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>点击查看 SSL 认证</summary>
|
||||||
|
|
||||||
|
### Cloudflare
|
||||||
|
|
||||||
|
管理脚本具有用于 Cloudflare 的内置 SSL 证书应用程序。若要使用此脚本申请证书,需要满足以下条件:
|
||||||
|
|
||||||
|
- Cloudflare 邮箱地址
|
||||||
|
- Cloudflare Global API Key
|
||||||
|
- 域名已通过 cloudflare 解析到当前服务器
|
||||||
|
|
||||||
|
**1:** 在终端中运行`x-ui`, 选择 `Cloudflare SSL Certificate`.
|
||||||
|
|
||||||
|
|
||||||
|
### Certbot
|
||||||
|
```
|
||||||
|
apt-get install certbot -y
|
||||||
|
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||||
|
certbot renew --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
***Tip:*** *管理脚本具有 Certbot 。使用 `x-ui` 命令, 选择 `SSL Certificate Management`.*
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 手动安装 & 升级
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>点击查看 手动安装 & 升级</summary>
|
||||||
|
|
||||||
|
#### 使用
|
||||||
|
|
||||||
|
1. 若要将最新版本的压缩包直接下载到服务器,请运行以下命令:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
case "${ARCH}" in
|
||||||
|
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
||||||
|
i*86 | x86) XUI_ARCH="386" ;;
|
||||||
|
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
||||||
|
armv7* | armv7) XUI_ARCH="armv7" ;;
|
||||||
|
armv6* | armv6) XUI_ARCH="armv6" ;;
|
||||||
|
armv5* | armv5) XUI_ARCH="armv5" ;;
|
||||||
|
*) XUI_ARCH="amd64" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 下载压缩包后,执行以下命令安装或升级 x-ui:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
case "${ARCH}" in
|
||||||
|
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
||||||
|
i*86 | x86) XUI_ARCH="386" ;;
|
||||||
|
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
||||||
|
armv7* | armv7) XUI_ARCH="armv7" ;;
|
||||||
|
armv6* | armv6) XUI_ARCH="armv6" ;;
|
||||||
|
armv5* | armv5) XUI_ARCH="armv5" ;;
|
||||||
|
*) XUI_ARCH="amd64" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cd /root/
|
||||||
|
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
||||||
|
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
|
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
|
||||||
|
cp x-ui/x-ui.sh /usr/bin/x-ui
|
||||||
|
cp -f x-ui/x-ui.service /etc/systemd/system/
|
||||||
|
mv x-ui/ /usr/local/
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable x-ui
|
||||||
|
systemctl restart x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 通过Docker安装
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>点击查看 通过Docker安装</summary>
|
||||||
|
|
||||||
|
#### 使用
|
||||||
|
|
||||||
|
1. 安装Docker:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash <(curl -sSL https://get.docker.com)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 克隆仓库:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/MHSanaei/3x-ui.git
|
||||||
|
cd 3x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 运行服务:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
或
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run -itd \
|
||||||
|
-e XRAY_VMESS_AEAD_FORCED=false \
|
||||||
|
-v $PWD/db/:/etc/x-ui/ \
|
||||||
|
-v $PWD/cert/:/root/cert/ \
|
||||||
|
--network=host \
|
||||||
|
--restart=unless-stopped \
|
||||||
|
--name 3x-ui \
|
||||||
|
ghcr.io/mhsanaei/3x-ui:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
更新至最新版本
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd 3x-ui
|
||||||
|
docker compose down
|
||||||
|
docker compose pull 3x-ui
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
从Docker中删除3x-ui
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker stop 3x-ui
|
||||||
|
docker rm 3x-ui
|
||||||
|
cd --
|
||||||
|
rm -r 3x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
## 建议使用的操作系统
|
||||||
|
|
||||||
|
- Ubuntu 20.04+
|
||||||
|
- Debian 11+
|
||||||
|
- CentOS 8+
|
||||||
|
- Fedora 36+
|
||||||
|
- Arch Linux
|
||||||
|
- Manjaro
|
||||||
|
- Armbian
|
||||||
|
- AlmaLinux 9+
|
||||||
|
- Rockylinux 9+
|
||||||
|
|
||||||
|
## 支持的架构和设备
|
||||||
|
<details>
|
||||||
|
<summary>点击查看 支持的架构和设备</summary>
|
||||||
|
|
||||||
|
我们的平台提供与各种架构和设备的兼容性,确保在各种计算环境中的灵活性。以下是我们支持的关键架构:
|
||||||
|
|
||||||
|
- **amd64**: 这种流行的架构是个人计算机和服务器的标准,可以无缝地适应大多数现代操作系统。
|
||||||
|
|
||||||
|
- **x86 / i386**: 这种架构在台式机和笔记本电脑中被广泛采用,得到了众多操作系统和应用程序的广泛支持,包括但不限于 Windows、macOS 和 Linux 系统。
|
||||||
|
|
||||||
|
- **armv8 / arm64 / aarch64**: 这种架构专为智能手机和平板电脑等当代移动和嵌入式设备量身定制,以 Raspberry Pi 4、Raspberry Pi 3、Raspberry Pi Zero 2/Zero 2 W、Orange Pi 3 LTS 等设备为例。
|
||||||
|
|
||||||
|
- **armv7 / arm / arm32**: 作为较旧的移动和嵌入式设备的架构,它仍然广泛用于Orange Pi Zero LTS、Orange Pi PC Plus、Raspberry Pi 2等设备。
|
||||||
|
|
||||||
|
- **armv6 / arm / arm32**: 这种架构面向非常老旧的嵌入式设备,虽然不太普遍,但仍在使用中。Raspberry Pi 1、Raspberry Pi Zero/Zero W 等设备都依赖于这种架构。
|
||||||
|
|
||||||
|
- **armv5 / arm / arm32**: 它是一种主要与早期嵌入式系统相关的旧架构,目前不太常见,但仍可能出现在早期 Raspberry Pi 版本和一些旧智能手机等传统设备中。
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Languages
|
||||||
|
|
||||||
|
- English(英语)
|
||||||
|
- Farsi(伊朗语)
|
||||||
|
- Chinese(中文)
|
||||||
|
- Russian(俄语)
|
||||||
|
- Vietnamese(越南语)
|
||||||
|
- Spanish(西班牙语)
|
||||||
|
- Indonesian (印度尼西亚语)
|
||||||
|
- Ukrainian(乌克兰语)
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 系统状态监控
|
||||||
|
- 在所有入站和客户端中搜索
|
||||||
|
- 深色/浅色主题
|
||||||
|
- 支持多用户和多协议
|
||||||
|
- 支持多种协议,包括 VMess、VLESS、Trojan、Shadowsocks、Dokodemo-door、Socks、HTTP、wireguard
|
||||||
|
- 支持 XTLS 原生协议,包括 RPRX-Direct、Vision、REALITY
|
||||||
|
- 流量统计、流量限制、过期时间限制
|
||||||
|
- 可自定义的 Xray配置模板
|
||||||
|
- 支持HTTPS访问面板(自建域名+SSL证书)
|
||||||
|
- 支持一键式SSL证书申请和自动续费
|
||||||
|
- 更多高级配置项目请参考面板
|
||||||
|
- 修复了 API 路由(用户设置将使用 API 创建)
|
||||||
|
- 支持通过面板中提供的不同项目更改配置。
|
||||||
|
- 支持从面板导出/导入数据库
|
||||||
|
|
||||||
|
|
||||||
|
## 默认设置
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>点击查看 默认设置</summary>
|
||||||
|
|
||||||
|
### 信息
|
||||||
|
|
||||||
|
- **端口:** 2053
|
||||||
|
- **用户名 & 密码:** 当您跳过设置时,此项会随机生成。
|
||||||
|
- **数据库路径:**
|
||||||
|
- /etc/x-ui/x-ui.db
|
||||||
|
- **Xray 配置路径:**
|
||||||
|
- /usr/local/x-ui/bin/config.json
|
||||||
|
- **面板链接(无SSL):**
|
||||||
|
- http://ip:2053/panel
|
||||||
|
- http://domain:2053/panel
|
||||||
|
- **面板链接(有SSL):**
|
||||||
|
- https://domain:2053/panel
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## [WARP 配置](https://gitlab.com/fscarmen/warp)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>点击查看 WARP 配置</summary>
|
||||||
|
|
||||||
|
#### 使用
|
||||||
|
|
||||||
|
如果要在 v2.1.0 之前使用 WARP 路由,请按照以下步骤操作:
|
||||||
|
|
||||||
|
**1.** 在 **SOCKS Proxy Mode** 模式中安装Wrap
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
**2.** 如果您已经安装了 warp,您可以使用以下命令卸载:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
warp u
|
||||||
|
```
|
||||||
|
|
||||||
|
**3.** 在面板中打开您需要的配置
|
||||||
|
|
||||||
|
配置:
|
||||||
|
|
||||||
|
- Block Ads
|
||||||
|
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
|
||||||
|
- Fix Google 403 error
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## IP 限制
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>点击查看 IP 限制</summary>
|
||||||
|
|
||||||
|
#### 使用
|
||||||
|
|
||||||
|
**注意:** 使用 IP 隧道时,IP 限制无法正常工作。
|
||||||
|
|
||||||
|
- 适用于最高 `v1.6.1` :
|
||||||
|
|
||||||
|
- IP 限制 已被集成在面板中。
|
||||||
|
|
||||||
|
- 适用于 `v1.7.0` 以及更新的版本:
|
||||||
|
|
||||||
|
- 要使 IP 限制正常工作,您需要按照以下步骤安装 fail2ban 及其所需的文件:
|
||||||
|
|
||||||
|
1. 使用面板内置的 `x-ui` 指令
|
||||||
|
2. 选择 `IP Limit Management`.
|
||||||
|
3. 根据您的需要选择合适的选项。
|
||||||
|
|
||||||
|
- 确保您的 Xray 配置上有 ./access.log 。在 v2.1.3 之后,我们有一个选项。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
"log": {
|
||||||
|
"access": "./access.log",
|
||||||
|
"dnsLog": false,
|
||||||
|
"loglevel": "warning"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Telegram 机器人
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>点击查看 Telegram 机器人</summary>
|
||||||
|
|
||||||
|
#### 使用
|
||||||
|
|
||||||
|
Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备份、系统状态、客户端信息等通知和功能。要使用机器人,您需要在面板中设置机器人相关参数,包括:
|
||||||
|
|
||||||
|
- 电报令牌
|
||||||
|
- 管理员聊天 ID
|
||||||
|
- 通知时间(cron 语法)
|
||||||
|
- 到期日期通知
|
||||||
|
- 流量上限通知
|
||||||
|
- 数据库备份
|
||||||
|
- CPU 负载通知
|
||||||
|
|
||||||
|
|
||||||
|
**参考:**
|
||||||
|
|
||||||
|
- `30 \* \* \* \* \*` - 在每个点的 30 秒处通知
|
||||||
|
- `0 \*/10 \* \* \* \*` - 每 10 分钟的第一秒通知
|
||||||
|
- `@hourly` - 每小时通知
|
||||||
|
- `@daily` - 每天通知 (00:00)
|
||||||
|
- `@weekly` - 每周通知
|
||||||
|
- `@every 8h` - 每8小时通知
|
||||||
|
|
||||||
|
### Telegram Bot 功能
|
||||||
|
|
||||||
|
- 定期报告
|
||||||
|
- 登录通知
|
||||||
|
- CPU 阈值通知
|
||||||
|
- 提前报告的过期时间和流量阈值
|
||||||
|
- 如果将客户的电报用户名添加到用户的配置中,则支持客户端报告菜单
|
||||||
|
- 支持使用UUID(VMESS/VLESS)或密码(TROJAN)搜索报文流量报告 - 匿名
|
||||||
|
- 基于菜单的机器人
|
||||||
|
- 通过电子邮件搜索客户端(仅限管理员)
|
||||||
|
- 检查所有入库
|
||||||
|
- 检查服务器状态
|
||||||
|
- 检查耗尽的用户
|
||||||
|
- 根据请求和定期报告接收备份
|
||||||
|
- 多语言机器人
|
||||||
|
|
||||||
|
### 注册 Telegram bot
|
||||||
|
|
||||||
|
- 与 [Botfather](https://t.me/BotFather) 对话:
|
||||||
|

|
||||||
|
|
||||||
|
- 使用 /newbot 创建新机器人:你需要提供机器人名称以及用户名,注意名称中末尾要包含“bot”
|
||||||
|

|
||||||
|
|
||||||
|
- 启动您刚刚创建的机器人。可以在此处找到机器人的链接。
|
||||||
|

|
||||||
|
|
||||||
|
- 输入您的面板并配置 Telegram 机器人设置,如下所示:
|
||||||
|

|
||||||
|
|
||||||
|
在输入字段编号 3 中输入机器人令牌。
|
||||||
|
在输入字段编号 4 中输入用户 ID。具有此 id 的 Telegram 帐户将是机器人管理员。 (您可以输入多个,只需将它们用“ ,”分开即可)
|
||||||
|
|
||||||
|
- 如何获取TG ID? 使用 [bot](https://t.me/useridinfobot), 启动机器人,它会给你 Telegram 用户 ID。
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## API 路由
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>点击查看 API 路由</summary>
|
||||||
|
|
||||||
|
#### 使用
|
||||||
|
|
||||||
|
- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录
|
||||||
|
- `/panel/api/inbounds` 以下操作的基础:
|
||||||
|
|
||||||
|
| 方法 | 路径 | 操作 |
|
||||||
|
| :----: | ---------------------------------- | ------------------------------------------- |
|
||||||
|
| `GET` | `"/list"` | 获取所有入站 |
|
||||||
|
| `GET` | `"/get/:id"` | 获取所有入站以及inbound.id |
|
||||||
|
| `GET` | `"/getClientTraffics/:email"` | 通过电子邮件获取客户端流量 |
|
||||||
|
| `GET` | `"/createbackup"` | Telegram 机器人向管理员发送备份 |
|
||||||
|
| `POST` | `"/add"` | 添加入站 |
|
||||||
|
| `POST` | `"/del/:id"` | 删除入站 |
|
||||||
|
| `POST` | `"/update/:id"` | 更新入站 |
|
||||||
|
| `POST` | `"/clientIps/:email"` | 客户端 IP 地址 |
|
||||||
|
| `POST` | `"/clearClientIps/:email"` | 清除客户端 IP 地址 |
|
||||||
|
| `POST` | `"/addClient"` | 将客户端添加到入站 |
|
||||||
|
| `POST` | `"/:id/delClient/:clientId"` | 通过 clientId\* 删除客户端 |
|
||||||
|
| `POST` | `"/updateClient/:clientId"` | 通过 clientId\* 更新客户端 |
|
||||||
|
| `POST` | `"/:id/resetClientTraffic/:email"` | 重置客户端的流量 |
|
||||||
|
| `POST` | `"/resetAllTraffics"` | 重置所有入站的流量 |
|
||||||
|
| `POST` | `"/resetAllClientTraffics/:id"` | 重置入站中所有客户端的流量 |
|
||||||
|
| `POST` | `"/delDepletedClients/:id"` | 删除入站耗尽的客户端 (-1: all) |
|
||||||
|
| `POST` | `"/onlines"` | 获取在线用户 ( 电子邮件列表 ) |
|
||||||
|
|
||||||
|
\*- `clientId` 项应该使用下列数据
|
||||||
|
|
||||||
|
- `client.id` VMESS and VLESS
|
||||||
|
- `client.password` TROJAN
|
||||||
|
- `client.email` Shadowsocks
|
||||||
|
|
||||||
|
|
||||||
|
- [API 文档](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm)
|
||||||
|
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9)
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>点击查看 环境变量</summary>
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
| 变量 | Type | 默认 |
|
||||||
|
| -------------- | :--------------------------------------------: | :------------ |
|
||||||
|
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
||||||
|
| XUI_DEBUG | `boolean` | `false` |
|
||||||
|
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
||||||
|
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
||||||
|
| XUI_LOG_FOLDER | `string` | `"/var/log"` |
|
||||||
|
|
||||||
|
例子:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 预览
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## 特别感谢
|
||||||
|
|
||||||
|
- [alireza0](https://github.com/alireza0/)
|
||||||
|
|
||||||
|
## 致谢
|
||||||
|
|
||||||
|
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
|
||||||
|
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._
|
||||||
|
|
||||||
|
## Star趋势
|
||||||
|
|
||||||
|
[](https://starchart.cc/MHSanaei/3x-ui)
|
|
@ -1 +1 @@
|
||||||
2.2.1
|
2.2.6
|
|
@ -2,6 +2,7 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"x-ui/util/json_util"
|
"x-ui/util/json_util"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
42
go.mod
42
go.mod
|
@ -12,35 +12,35 @@ require (
|
||||||
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.1
|
||||||
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.24.2
|
||||||
github.com/valyala/fasthttp v1.52.0
|
github.com/valyala/fasthttp v1.52.0
|
||||||
github.com/xtls/xray-core v1.8.8
|
github.com/xtls/xray-core v1.8.9
|
||||||
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.62.0
|
google.golang.org/grpc v1.62.1
|
||||||
gorm.io/driver/sqlite v1.5.5
|
gorm.io/driver/sqlite v1.5.5
|
||||||
gorm.io/gorm v1.25.7
|
gorm.io/gorm v1.25.7
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
github.com/bytedance/sonic v1.10.2 // indirect
|
github.com/bytedance/sonic v1.11.2 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
github.com/cloudflare/circl v1.3.7 // 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/fasthttp/router v1.4.22 // indirect
|
github.com/fasthttp/router v1.5.0 // indirect
|
||||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.17.0 // indirect
|
github.com/go-playground/validator/v10 v10.19.0 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/pprof v0.0.0-20240225044709-fd706174c886 // indirect
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
|
||||||
github.com/gorilla/context v1.1.2 // indirect
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.2.2 // indirect
|
github.com/gorilla/sessions v1.2.2 // indirect
|
||||||
|
@ -51,22 +51,22 @@ require (
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.7 // indirect
|
github.com/klauspost/compress v1.17.7 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
|
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.19 // indirect
|
github.com/mattn/go-sqlite3 v1.14.22 // 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.15.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.16.0 // 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/quic-go v0.41.0 // indirect
|
github.com/quic-go/quic-go v0.41.0 // indirect
|
||||||
github.com/refraction-networking/utls v1.6.3 // indirect
|
github.com/refraction-networking/utls v1.6.3 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
github.com/sagernet/sing v0.3.2 // indirect
|
github.com/sagernet/sing v0.3.6 // indirect
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
|
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // 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
|
||||||
|
@ -79,21 +79,21 @@ require (
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
|
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
go.uber.org/mock v0.4.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.6.0 // indirect
|
golang.org/x/arch v0.6.0 // indirect
|
||||||
golang.org/x/crypto v0.19.0 // indirect
|
golang.org/x/crypto v0.21.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||||
golang.org/x/mod v0.15.0 // indirect
|
golang.org/x/mod v0.16.0 // indirect
|
||||||
golang.org/x/net v0.21.0 // indirect
|
golang.org/x/net v0.22.0 // indirect
|
||||||
golang.org/x/sys v0.17.0 // indirect
|
golang.org/x/sys v0.18.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.18.0 // indirect
|
golang.org/x/tools v0.19.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 // indirect
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
google.golang.org/protobuf v1.33.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
|
||||||
|
|
93
go.sum
93
go.sum
|
@ -20,8 +20,8 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT
|
||||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
|
||||||
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
|
@ -41,8 +41,8 @@ github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fp
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/fasthttp/router v1.4.22 h1:qwWcYBbndVDwts4dKaz+A2ehsnbKilmiP6pUhXBfYKo=
|
github.com/fasthttp/router v1.5.0 h1:3Qbbo27HAPzwbpRzgiV5V9+2faPkPt3eNuRaDV6LYDA=
|
||||||
github.com/fasthttp/router v1.4.22/go.mod h1:KeMvHLqhlB9vyDWD5TSvTccl9qeWrjSSiTJrJALHKV0=
|
github.com/fasthttp/router v1.5.0/go.mod h1:FddcKNXFZg1imHcy+uKB0oo/o6yE9zD3wNguqlhWDak=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||||
|
@ -61,8 +61,8 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
|
@ -76,8 +76,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
|
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||||
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
@ -93,8 +93,8 @@ github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
|
@ -111,8 +111,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/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-20240225044709-fd706174c886 h1:JSJUTZTQT1Gzb2ROdAKOY3HwzBYcclS2GgumhMfHqjw=
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||||
github.com/google/pprof v0.0.0-20240225044709-fd706174c886/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/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=
|
||||||
|
@ -155,8 +155,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
|
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
|
||||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||||
|
@ -165,8 +165,8 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||||
|
@ -183,8 +183,8 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE
|
||||||
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.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||||
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
|
github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
|
||||||
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
|
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
github.com/onsi/gomega v1.30.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=
|
||||||
|
@ -221,17 +221,17 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
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.2 h1:CwWcxUBPkMvwgfe2/zUgY5oHG9qOL8Aob/evIFYK9jo=
|
github.com/sagernet/sing v0.3.6 h1:dsEdYLKBQlrxUfw1a92x0VdPvR1/BOxQ+HIMyaoEJsQ=
|
||||||
github.com/sagernet/sing v0.3.2/go.mod h1:qHySJ7u8po9DABtMYEkNBcOumx7ZZJf/fbv2sfTkNHE=
|
github.com/sagernet/sing v0.3.6/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
|
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
|
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||||
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.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
|
github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk=
|
||||||
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=
|
||||||
|
@ -270,9 +270,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
github.com/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=
|
||||||
|
@ -301,10 +301,10 @@ 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.8 h1:6ApBa5pNkPZ+I7jyJDZod3v5sadizS/BZr0pW7zcs8o=
|
github.com/xtls/xray-core v1.8.9 h1:wefcON0behu4DoQvCKJYZKsJlSvNhyq2I7vC2fxLFcY=
|
||||||
github.com/xtls/xray-core v1.8.8/go.mod h1:Zp33A8cxnhP5Kt6nguQrMgNH4A/tgq7LE8cBedeNje8=
|
github.com/xtls/xray-core v1.8.9/go.mod h1:XDE4f422qJKAU3hNDSNZyWrOHvn9kF8UHVdyOzU38rc=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/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=
|
||||||
|
@ -321,16 +321,16 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf
|
||||||
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-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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||||
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.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -340,8 +340,8 @@ 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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
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=
|
||||||
|
@ -371,9 +371,9 @@ 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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
|
golang.org/x/sys v0.18.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,8 +390,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||||
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.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||||
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.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=
|
||||||
|
@ -409,19 +409,18 @@ 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-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 h1:em/y72n4XlYRtayY/cVj6pnVzHa//BDA1BdoO+z9mdE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
|
||||||
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.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||||
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||||
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.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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
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=
|
||||||
|
|
|
@ -8,12 +8,14 @@ import (
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger *logging.Logger
|
var (
|
||||||
var logBuffer []struct {
|
logger *logging.Logger
|
||||||
|
logBuffer []struct {
|
||||||
time string
|
time string
|
||||||
level logging.Level
|
level logging.Level
|
||||||
log string
|
log string
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
InitLogger(logging.INFO)
|
InitLogger(logging.INFO)
|
||||||
|
|
26
main.go
26
main.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
@ -243,21 +244,33 @@ func migrateDb() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeSecret() {
|
func removeSecret() {
|
||||||
err := database.InitDB(config.GetDBPath())
|
userService := service.UserService{}
|
||||||
|
|
||||||
|
secretExists, err := userService.CheckSecretExistence()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println("Error checking secret existence:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userService := service.UserService{}
|
|
||||||
|
if !secretExists {
|
||||||
|
fmt.Println("No secret exists to remove.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err = userService.RemoveUserSecret()
|
err = userService.RemoveUserSecret()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println("Error removing secret:", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settingService := service.SettingService{}
|
settingService := service.SettingService{}
|
||||||
err = settingService.SetSecretStatus(false)
|
err = settingService.SetSecretStatus(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println("Error updating secret status:", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("Secret removed successfully.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -284,6 +297,7 @@ func main() {
|
||||||
var remove_secret bool
|
var remove_secret bool
|
||||||
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
|
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
|
||||||
settingCmd.BoolVar(&show, "show", false, "show current settings")
|
settingCmd.BoolVar(&show, "show", false, "show current settings")
|
||||||
|
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "remove secret")
|
||||||
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
||||||
settingCmd.StringVar(&username, "username", "", "set login username")
|
settingCmd.StringVar(&username, "username", "", "set login username")
|
||||||
settingCmd.StringVar(&password, "password", "", "set login password")
|
settingCmd.StringVar(&password, "password", "", "set login password")
|
||||||
|
@ -342,7 +356,7 @@ func main() {
|
||||||
updateTgbotEnableSts(enabletgbot)
|
updateTgbotEnableSts(enabletgbot)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Println("except 'run' or 'setting' subcommands")
|
fmt.Println("Invalid subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
runCmd.Usage()
|
runCmd.Usage()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"remarks": "",
|
||||||
"dns": {
|
"dns": {
|
||||||
"tag": "dns_out",
|
"tag": "dns_out",
|
||||||
"queryStrategy": "UseIP",
|
"queryStrategy": "UseIP",
|
||||||
|
@ -78,28 +79,9 @@
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"network": "tcp,udp",
|
"network": "tcp,udp",
|
||||||
"balancerTag": "all"
|
"outboundTag": "proxy"
|
||||||
}
|
|
||||||
],
|
|
||||||
"balancers": [
|
|
||||||
{
|
|
||||||
"tag": "all",
|
|
||||||
"selector": [
|
|
||||||
"proxy"
|
|
||||||
],
|
|
||||||
"strategy": {
|
|
||||||
"type": "leastPing"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"observatory": {
|
|
||||||
"probeInterval": "5m",
|
|
||||||
"probeURL": "https://api.github.com/_private/browser/stats",
|
|
||||||
"subjectSelector": [
|
|
||||||
"proxy"
|
|
||||||
],
|
|
||||||
"EnableConcurrency": true
|
|
||||||
},
|
|
||||||
"stats": {}
|
"stats": {}
|
||||||
}
|
}
|
31
sub/sub.go
31
sub/sub.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
|
@ -91,9 +92,21 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
SubJsonFragment = ""
|
SubJsonFragment = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SubJsonMux, err := s.settingService.GetSubJsonMux()
|
||||||
|
if err != nil {
|
||||||
|
SubJsonMux = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
SubJsonRules, err := s.settingService.GetSubJsonRules()
|
||||||
|
if err != nil {
|
||||||
|
SubJsonRules = ""
|
||||||
|
}
|
||||||
|
|
||||||
g := engine.Group("/")
|
g := engine.Group("/")
|
||||||
|
|
||||||
s.sub = NewSUBController(g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates, SubJsonFragment)
|
s.sub = NewSUBController(
|
||||||
|
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
||||||
|
SubJsonFragment, SubJsonMux, SubJsonRules)
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
|
@ -144,21 +157,19 @@ func (s *Server) Start() (err error) {
|
||||||
|
|
||||||
if certFile != "" || keyFile != "" {
|
if certFile != "" || keyFile != "" {
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
listener.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c := &tls.Config{
|
c := &tls.Config{
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
}
|
}
|
||||||
listener = network.NewAutoHttpsListener(listener)
|
listener = network.NewAutoHttpsListener(listener)
|
||||||
listener = tls.NewListener(listener, c)
|
listener = tls.NewListener(listener, c)
|
||||||
}
|
logger.Info("sub server run https on", listener.Addr())
|
||||||
|
|
||||||
if certFile != "" || keyFile != "" {
|
|
||||||
logger.Info("Sub server run https on", listener.Addr())
|
|
||||||
} else {
|
} else {
|
||||||
logger.Info("Sub server run http on", listener.Addr())
|
logger.Error("error in loading certificates: ", err)
|
||||||
|
logger.Info("sub server run http on", listener.Addr())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Info("sub server run http on", listener.Addr())
|
||||||
}
|
}
|
||||||
s.listener = listener
|
s.listener = listener
|
||||||
|
|
||||||
|
|
|
@ -25,16 +25,19 @@ func NewSUBController(
|
||||||
showInfo bool,
|
showInfo bool,
|
||||||
rModel string,
|
rModel string,
|
||||||
update string,
|
update string,
|
||||||
jsonFragment string) *SUBController {
|
jsonFragment string,
|
||||||
|
jsonMux string,
|
||||||
|
jsonRules string,
|
||||||
|
) *SUBController {
|
||||||
|
sub := NewSubService(showInfo, rModel)
|
||||||
a := &SUBController{
|
a := &SUBController{
|
||||||
subPath: subPath,
|
subPath: subPath,
|
||||||
subJsonPath: jsonPath,
|
subJsonPath: jsonPath,
|
||||||
subEncrypt: encrypt,
|
subEncrypt: encrypt,
|
||||||
updateInterval: update,
|
updateInterval: update,
|
||||||
|
|
||||||
subService: NewSubService(showInfo, rModel),
|
subService: sub,
|
||||||
subJsonService: NewSubJsonService(jsonFragment),
|
subJsonService: NewSubJsonService(jsonFragment, jsonMux, jsonRules, sub),
|
||||||
}
|
}
|
||||||
a.initRouter(g)
|
a.initRouter(g)
|
||||||
return a
|
return a
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/util/json_util"
|
"x-ui/util/json_util"
|
||||||
|
@ -17,15 +18,47 @@ import (
|
||||||
var defaultJson string
|
var defaultJson string
|
||||||
|
|
||||||
type SubJsonService struct {
|
type SubJsonService struct {
|
||||||
fragmanet string
|
configJson map[string]interface{}
|
||||||
|
defaultOutbounds []json_util.RawMessage
|
||||||
|
fragment string
|
||||||
|
mux string
|
||||||
|
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
SubService
|
SubService *SubService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSubJsonService(fragment string, mux string, rules string, subService *SubService) *SubJsonService {
|
||||||
|
var configJson map[string]interface{}
|
||||||
|
var defaultOutbounds []json_util.RawMessage
|
||||||
|
json.Unmarshal([]byte(defaultJson), &configJson)
|
||||||
|
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
|
||||||
|
for _, defaultOutbound := range outboundSlices {
|
||||||
|
jsonBytes, _ := json.Marshal(defaultOutbound)
|
||||||
|
defaultOutbounds = append(defaultOutbounds, jsonBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rules != "" {
|
||||||
|
var newRules []interface{}
|
||||||
|
routing, _ := configJson["routing"].(map[string]interface{})
|
||||||
|
defaultRules, _ := routing["rules"].([]interface{})
|
||||||
|
json.Unmarshal([]byte(rules), &newRules)
|
||||||
|
defaultRules = append(newRules, defaultRules...)
|
||||||
|
fmt.Printf("routing: %#v\n\nRules: %#v\n\n", routing, defaultRules)
|
||||||
|
routing["rules"] = defaultRules
|
||||||
|
configJson["routing"] = routing
|
||||||
|
}
|
||||||
|
|
||||||
|
if fragment != "" {
|
||||||
|
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSubJsonService(fragment string) *SubJsonService {
|
|
||||||
return &SubJsonService{
|
return &SubJsonService{
|
||||||
fragmanet: fragment,
|
configJson: configJson,
|
||||||
|
defaultOutbounds: defaultOutbounds,
|
||||||
|
fragment: fragment,
|
||||||
|
mux: mux,
|
||||||
|
SubService: subService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,19 +71,8 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
|
||||||
var header string
|
var header string
|
||||||
var traffic xray.ClientTraffic
|
var traffic xray.ClientTraffic
|
||||||
var clientTraffics []xray.ClientTraffic
|
var clientTraffics []xray.ClientTraffic
|
||||||
var configJson map[string]interface{}
|
var configArray []json_util.RawMessage
|
||||||
var defaultOutbounds []json_util.RawMessage
|
|
||||||
|
|
||||||
json.Unmarshal([]byte(defaultJson), &configJson)
|
|
||||||
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
|
|
||||||
for _, defaultOutbound := range outboundSlices {
|
|
||||||
jsonBytes, _ := json.Marshal(defaultOutbound)
|
|
||||||
defaultOutbounds = append(defaultOutbounds, jsonBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outbounds := []json_util.RawMessage{}
|
|
||||||
startIndex := 0
|
|
||||||
// Prepare Inbounds
|
// Prepare Inbounds
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.inboundService.GetClients(inbound)
|
clients, err := s.inboundService.GetClients(inbound)
|
||||||
|
@ -61,7 +83,7 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
||||||
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
|
listen, port, streamSettings, err := s.SubService.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
inbound.Listen = listen
|
inbound.Listen = listen
|
||||||
inbound.Port = port
|
inbound.Port = port
|
||||||
|
@ -69,22 +91,16 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var subClients []model.Client
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Enable && client.SubID == subId {
|
if client.Enable && client.SubID == subId {
|
||||||
subClients = append(subClients, client)
|
|
||||||
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
|
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
|
||||||
|
newConfigs := s.getConfig(inbound, client, host)
|
||||||
|
configArray = append(configArray, newConfigs...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outbound := s.getOutbound(inbound, subClients, host, startIndex)
|
if len(configArray) == 0 {
|
||||||
if outbound != nil {
|
|
||||||
outbounds = append(outbounds, outbound...)
|
|
||||||
startIndex += len(outbound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(outbounds) == 0 {
|
|
||||||
return "", "", nil
|
return "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,21 +127,15 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.fragmanet != "" {
|
|
||||||
outbounds = append(outbounds, json_util.RawMessage(s.fragmanet))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combile outbounds
|
// Combile outbounds
|
||||||
outbounds = append(outbounds, defaultOutbounds...)
|
finalJson, _ := json.MarshalIndent(configArray, "", " ")
|
||||||
configJson["outbounds"] = outbounds
|
|
||||||
finalJson, _ := json.MarshalIndent(configJson, "", " ")
|
|
||||||
|
|
||||||
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
||||||
return string(finalJson), header, nil
|
return string(finalJson), header, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Client, host string, startIndex int) []json_util.RawMessage {
|
func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client, host string) []json_util.RawMessage {
|
||||||
var newOutbounds []json_util.RawMessage
|
var newJsonArray []json_util.RawMessage
|
||||||
stream := s.streamData(inbound.StreamSettings)
|
stream := s.streamData(inbound.StreamSettings)
|
||||||
|
|
||||||
externalProxies, ok := stream["externalProxy"].([]interface{})
|
externalProxies, ok := stream["externalProxy"].([]interface{})
|
||||||
|
@ -135,13 +145,13 @@ func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Cli
|
||||||
"forceTls": "same",
|
"forceTls": "same",
|
||||||
"dest": host,
|
"dest": host,
|
||||||
"port": float64(inbound.Port),
|
"port": float64(inbound.Port),
|
||||||
|
"remark": "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(stream, "externalProxy")
|
delete(stream, "externalProxy")
|
||||||
|
|
||||||
config_index := startIndex
|
|
||||||
for _, ep := range externalProxies {
|
for _, ep := range externalProxies {
|
||||||
extPrxy := ep.(map[string]interface{})
|
extPrxy := ep.(map[string]interface{})
|
||||||
inbound.Listen = extPrxy["dest"].(string)
|
inbound.Listen = extPrxy["dest"].(string)
|
||||||
|
@ -160,21 +170,28 @@ func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Cli
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
|
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
|
||||||
inbound.StreamSettings = string(streamSettings)
|
|
||||||
|
|
||||||
for _, client := range clients {
|
var newOutbounds []json_util.RawMessage
|
||||||
inbound.Tag = fmt.Sprintf("proxy_%d", config_index)
|
|
||||||
switch inbound.Protocol {
|
switch inbound.Protocol {
|
||||||
case "vmess", "vless":
|
case "vmess", "vless":
|
||||||
newOutbounds = append(newOutbounds, s.genVnext(inbound, client))
|
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
|
||||||
case "trojan", "shadowsocks":
|
case "trojan", "shadowsocks":
|
||||||
newOutbounds = append(newOutbounds, s.genServer(inbound, client))
|
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
|
||||||
}
|
|
||||||
config_index += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newOutbounds
|
newOutbounds = append(newOutbounds, s.defaultOutbounds...)
|
||||||
|
newConfigJson := make(map[string]interface{})
|
||||||
|
for key, value := range s.configJson {
|
||||||
|
newConfigJson[key] = value
|
||||||
|
}
|
||||||
|
newConfigJson["outbounds"] = newOutbounds
|
||||||
|
newConfigJson["remarks"] = s.SubService.genRemark(inbound, client.Email, extPrxy["remark"].(string))
|
||||||
|
newConfig, _ := json.MarshalIndent(newConfigJson, "", " ")
|
||||||
|
newJsonArray = append(newJsonArray, newConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newJsonArray
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
||||||
|
@ -188,7 +205,7 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
||||||
}
|
}
|
||||||
delete(streamSettings, "sockopt")
|
delete(streamSettings, "sockopt")
|
||||||
|
|
||||||
if s.fragmanet != "" {
|
if s.fragment != "" {
|
||||||
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`)
|
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +231,7 @@ func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]inter
|
||||||
|
|
||||||
func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
|
func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
|
||||||
tlsData := make(map[string]interface{}, 1)
|
tlsData := make(map[string]interface{}, 1)
|
||||||
tlsClientSettings := tData["settings"].(map[string]interface{})
|
tlsClientSettings, _ := tData["settings"].(map[string]interface{})
|
||||||
|
|
||||||
tlsData["serverName"] = tData["serverName"]
|
tlsData["serverName"] = tData["serverName"]
|
||||||
tlsData["alpn"] = tData["alpn"]
|
tlsData["alpn"] = tData["alpn"]
|
||||||
|
@ -229,7 +246,7 @@ func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interf
|
||||||
|
|
||||||
func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
|
func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
|
||||||
rltyData := make(map[string]interface{}, 1)
|
rltyData := make(map[string]interface{}, 1)
|
||||||
rltyClientSettings := rData["settings"].(map[string]interface{})
|
rltyClientSettings, _ := rData["settings"].(map[string]interface{})
|
||||||
|
|
||||||
rltyData["show"] = false
|
rltyData["show"] = false
|
||||||
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
||||||
|
@ -253,7 +270,7 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
|
||||||
return rltyData
|
return rltyData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) json_util.RawMessage {
|
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
|
||||||
outbound := Outbound{}
|
outbound := Outbound{}
|
||||||
usersData := make([]UserVnext, 1)
|
usersData := make([]UserVnext, 1)
|
||||||
|
|
||||||
|
@ -272,8 +289,11 @@ func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) j
|
||||||
}
|
}
|
||||||
|
|
||||||
outbound.Protocol = string(inbound.Protocol)
|
outbound.Protocol = string(inbound.Protocol)
|
||||||
outbound.Tag = inbound.Tag
|
outbound.Tag = "proxy"
|
||||||
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
|
if s.mux != "" {
|
||||||
|
outbound.Mux = json_util.RawMessage(s.mux)
|
||||||
|
}
|
||||||
|
outbound.StreamSettings = streamSettings
|
||||||
outbound.Settings = OutboundSettings{
|
outbound.Settings = OutboundSettings{
|
||||||
Vnext: vnextData,
|
Vnext: vnextData,
|
||||||
}
|
}
|
||||||
|
@ -282,7 +302,7 @@ func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) j
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubJsonService) genServer(inbound *model.Inbound, client model.Client) json_util.RawMessage {
|
func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
|
||||||
outbound := Outbound{}
|
outbound := Outbound{}
|
||||||
|
|
||||||
serverData := make([]ServerSetting, 1)
|
serverData := make([]ServerSetting, 1)
|
||||||
|
@ -308,8 +328,11 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, client model.Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
outbound.Protocol = string(inbound.Protocol)
|
outbound.Protocol = string(inbound.Protocol)
|
||||||
outbound.Tag = inbound.Tag
|
outbound.Tag = "proxy"
|
||||||
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
|
if s.mux != "" {
|
||||||
|
outbound.Mux = json_util.RawMessage(s.mux)
|
||||||
|
}
|
||||||
|
outbound.StreamSettings = streamSettings
|
||||||
outbound.Settings = OutboundSettings{
|
outbound.Settings = OutboundSettings{
|
||||||
Servers: serverData,
|
Servers: serverData,
|
||||||
}
|
}
|
||||||
|
@ -322,7 +345,7 @@ type Outbound struct {
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||||
Mux map[string]interface{} `json:"mux,omitempty"`
|
Mux json_util.RawMessage `json:"mux,omitempty"`
|
||||||
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
|
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
|
||||||
Settings OutboundSettings `json:"settings,omitempty"`
|
Settings OutboundSettings `json:"settings,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,12 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
|
"x-ui/util/random"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
|
@ -212,9 +214,14 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
obj["path"] = grpc["serviceName"].(string)
|
obj["path"] = grpc["serviceName"].(string)
|
||||||
|
obj["authority"] = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
obj["type"] = "multi"
|
obj["type"] = "multi"
|
||||||
}
|
}
|
||||||
|
case "httpupgrade":
|
||||||
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||||
|
obj["path"] = httpupgrade["path"].(string)
|
||||||
|
obj["host"] = httpupgrade["host"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
@ -346,9 +353,14 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
params["authority"] = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
params["mode"] = "multi"
|
params["mode"] = "multi"
|
||||||
}
|
}
|
||||||
|
case "httpupgrade":
|
||||||
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||||
|
params["path"] = httpupgrade["path"].(string)
|
||||||
|
params["host"] = httpupgrade["host"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
@ -391,25 +403,21 @@ 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[0].(string)
|
params["sni"] = sNames[random.Num(len(sNames))].(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[0].(string)
|
params["sid"] = shortIds[random.Num(len(shortIds))].(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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
|
params["spx"] = "/" + random.Seq(15)
|
||||||
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 {
|
||||||
|
@ -562,9 +570,14 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
params["authority"] = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
params["mode"] = "multi"
|
params["mode"] = "multi"
|
||||||
}
|
}
|
||||||
|
case "httpupgrade":
|
||||||
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||||
|
params["path"] = httpupgrade["path"].(string)
|
||||||
|
params["host"] = httpupgrade["host"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
@ -603,25 +616,21 @@ 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[0].(string)
|
params["sni"] = sNames[random.Num(len(sNames))].(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[0].(string)
|
params["sid"] = shortIds[random.Num(len(shortIds))].(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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
|
params["spx"] = "/" + random.Seq(15)
|
||||||
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 {
|
||||||
|
@ -779,9 +788,14 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
params["authority"] = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
params["mode"] = "multi"
|
params["mode"] = "multi"
|
||||||
}
|
}
|
||||||
|
case "httpupgrade":
|
||||||
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||||
|
params["path"] = httpupgrade["path"].(string)
|
||||||
|
params["host"] = httpupgrade["host"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package common
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,14 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
var numSeq [10]rune
|
var (
|
||||||
var lowerSeq [26]rune
|
numSeq [10]rune
|
||||||
var upperSeq [26]rune
|
lowerSeq [26]rune
|
||||||
var numLowerSeq [36]rune
|
upperSeq [26]rune
|
||||||
var numUpperSeq [36]rune
|
numLowerSeq [36]rune
|
||||||
var allSeq [62]rune
|
numUpperSeq [36]rune
|
||||||
|
allSeq [62]rune
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
|
|
|
@ -80,6 +80,11 @@ html[data-theme='ultra-dark'] {
|
||||||
.dark .waves-header {
|
.dark .waves-header {
|
||||||
background-color: #0a2227;
|
background-color: #0a2227;
|
||||||
}
|
}
|
||||||
|
.dark .ant-calendar-year-panel-year:hover,
|
||||||
|
.dark .ant-calendar-month-panel-month:hover,
|
||||||
|
.dark .ant-calendar-decade-panel-decade:hover {
|
||||||
|
background-color: var(--dark-color-surface-600);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
|
@ -175,7 +180,7 @@ style attribute {
|
||||||
position: relative;
|
position: relative;
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
.ant-table-body {
|
.ant-table-wrapper > div > div > div > div > div > div {
|
||||||
overflow-x: auto !important;
|
overflow-x: auto !important;
|
||||||
}
|
}
|
||||||
.ant-card-hoverable {
|
.ant-card-hoverable {
|
||||||
|
@ -750,8 +755,8 @@ style attribute {
|
||||||
.dark .ant-btn-danger[disabled],
|
.dark .ant-btn-danger[disabled],
|
||||||
.dark .ant-calendar-ok-btn-disabled {
|
.dark .ant-calendar-ok-btn-disabled {
|
||||||
color: rgb(255 255 255 / 35%);
|
color: rgb(255 255 255 / 35%);
|
||||||
background-color: var(--dark-color-surface-300);
|
background-color: var(--dark-color-surface-200);
|
||||||
border-color: #42516c;
|
border-color: var(--dark-color-surface-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark
|
.dark
|
||||||
|
@ -791,9 +796,17 @@ style attribute {
|
||||||
border-color: var(--dark-color-surface-500);
|
border-color: var(--dark-color-surface-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.dark .ant-popover-inner {
|
||||||
|
background-color: var(--dark-color-surface-200);
|
||||||
|
}
|
||||||
|
.dark > .ant-popover-content > .ant-popover-arrow {
|
||||||
|
border-color: var(--dark-color-surface-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,
|
.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,
|
||||||
.dark .ant-select-dropdown-menu-item-selected,
|
.dark .ant-select-dropdown-menu-item-selected,
|
||||||
.dark .ant-select-dropdown-menu-item:hover,
|
|
||||||
.dark .ant-calendar-time-picker-select-option-selected {
|
.dark .ant-calendar-time-picker-select-option-selected {
|
||||||
background-color: var(--dark-color-surface-600);
|
background-color: var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
@ -1249,3 +1262,17 @@ b, strong {
|
||||||
.dark .ant-alert-close-icon .anticon-close:hover {
|
.dark .ant-alert-close-icon .anticon-close:hover {
|
||||||
color: rgb(255 255 255);
|
color: rgb(255 255 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-empty-small {
|
||||||
|
margin: 4px 0;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-empty-small .ant-empty-image {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-theme-switch:hover {
|
||||||
|
background-color: transparent !important;
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
|
|
@ -14,3 +14,17 @@ axios.interceptors.request.use(
|
||||||
},
|
},
|
||||||
(error) => Promise.reject(error),
|
(error) => Promise.reject(error),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
if (error.response) {
|
||||||
|
const statusCode = error.response.status;
|
||||||
|
// Check the status code
|
||||||
|
if (statusCode === 401) { // Unauthorized
|
||||||
|
return window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -51,7 +51,14 @@ const OutboundDomainStrategies = [
|
||||||
"AsIs",
|
"AsIs",
|
||||||
"UseIP",
|
"UseIP",
|
||||||
"UseIPv4",
|
"UseIPv4",
|
||||||
"UseIPv6"
|
"UseIPv6",
|
||||||
|
"UseIPv6v4",
|
||||||
|
"UseIPv4v6",
|
||||||
|
"ForceIP",
|
||||||
|
"ForceIPv6v4",
|
||||||
|
"ForceIPv6",
|
||||||
|
"ForceIPv4v6",
|
||||||
|
"ForceIPv4"
|
||||||
];
|
];
|
||||||
|
|
||||||
const WireguardDomainStrategy = [
|
const WireguardDomainStrategy = [
|
||||||
|
@ -250,24 +257,48 @@ class QuicStreamSettings extends CommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
class GrpcStreamSettings extends CommonClass {
|
class GrpcStreamSettings extends CommonClass {
|
||||||
constructor(serviceName="", multiMode=false) {
|
constructor(serviceName="", multiMode=false, authority="") {
|
||||||
super();
|
super();
|
||||||
this.serviceName = serviceName;
|
this.serviceName = serviceName;
|
||||||
this.multiMode = multiMode;
|
this.multiMode = multiMode;
|
||||||
|
this.authority = authority;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new GrpcStreamSettings(json.serviceName, json.multiMode);
|
return new GrpcStreamSettings(json.serviceName, json.multiMode,json.authority);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
serviceName: this.serviceName,
|
serviceName: this.serviceName,
|
||||||
multiMode: this.multiMode,
|
multiMode: this.multiMode,
|
||||||
|
authority: this.authority
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HttpUpgradeStreamSettings extends CommonClass {
|
||||||
|
constructor(path='/', host='') {
|
||||||
|
super();
|
||||||
|
this.path = path;
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new HttpUpgradeStreamSettings(
|
||||||
|
json.path,
|
||||||
|
json.Host,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
path: this.path,
|
||||||
|
host: this.host,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TlsStreamSettings extends CommonClass {
|
class TlsStreamSettings extends CommonClass {
|
||||||
constructor(serverName='',
|
constructor(serverName='',
|
||||||
alpn=[],
|
alpn=[],
|
||||||
|
@ -327,6 +358,34 @@ class RealityStreamSettings extends CommonClass {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
class SockoptStreamSettings extends CommonClass {
|
||||||
|
constructor(dialerProxy = "", tcpFastOpen = false, tcpKeepAliveInterval = 0, tcpNoDelay = false) {
|
||||||
|
super();
|
||||||
|
this.dialerProxy = dialerProxy;
|
||||||
|
this.tcpFastOpen = tcpFastOpen;
|
||||||
|
this.tcpKeepAliveInterval = tcpKeepAliveInterval;
|
||||||
|
this.tcpNoDelay = tcpNoDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
if (Object.keys(json).length === 0) return undefined;
|
||||||
|
return new SockoptStreamSettings(
|
||||||
|
json.dialerProxy,
|
||||||
|
json.tcpFastOpen,
|
||||||
|
json.tcpKeepAliveInterval,
|
||||||
|
json.tcpNoDelay,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
dialerProxy: this.dialerProxy,
|
||||||
|
tcpFastOpen: this.tcpFastOpen,
|
||||||
|
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
|
||||||
|
tcpNoDelay: this.tcpNoDelay,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class StreamSettings extends CommonClass {
|
class StreamSettings extends CommonClass {
|
||||||
constructor(network='tcp',
|
constructor(network='tcp',
|
||||||
|
@ -339,6 +398,8 @@ class StreamSettings extends CommonClass {
|
||||||
httpSettings=new HttpStreamSettings(),
|
httpSettings=new HttpStreamSettings(),
|
||||||
quicSettings=new QuicStreamSettings(),
|
quicSettings=new QuicStreamSettings(),
|
||||||
grpcSettings=new GrpcStreamSettings(),
|
grpcSettings=new GrpcStreamSettings(),
|
||||||
|
httpupgradeSettings=new HttpUpgradeStreamSettings(),
|
||||||
|
sockopt = undefined,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.network = network;
|
this.network = network;
|
||||||
|
@ -351,6 +412,8 @@ class StreamSettings extends CommonClass {
|
||||||
this.http = httpSettings;
|
this.http = httpSettings;
|
||||||
this.quic = quicSettings;
|
this.quic = quicSettings;
|
||||||
this.grpc = grpcSettings;
|
this.grpc = grpcSettings;
|
||||||
|
this.httpupgrade = httpupgradeSettings;
|
||||||
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isTls() {
|
get isTls() {
|
||||||
|
@ -361,6 +424,14 @@ class StreamSettings extends CommonClass {
|
||||||
return this.security === "reality";
|
return this.security === "reality";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get sockoptSwitch() {
|
||||||
|
return this.sockopt != undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
set sockoptSwitch(value) {
|
||||||
|
this.sockopt = value ? new SockoptStreamSettings() : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new StreamSettings(
|
return new StreamSettings(
|
||||||
json.network,
|
json.network,
|
||||||
|
@ -373,6 +444,8 @@ class StreamSettings extends CommonClass {
|
||||||
HttpStreamSettings.fromJson(json.httpSettings),
|
HttpStreamSettings.fromJson(json.httpSettings),
|
||||||
QuicStreamSettings.fromJson(json.quicSettings),
|
QuicStreamSettings.fromJson(json.quicSettings),
|
||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
|
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||||
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,6 +462,37 @@ class StreamSettings extends CommonClass {
|
||||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
|
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
||||||
|
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Mux extends CommonClass {
|
||||||
|
constructor(enabled = false, concurrency = 8, xudpConcurrency = 16, xudpProxyUDP443 = "reject") {
|
||||||
|
super();
|
||||||
|
this.enabled = enabled;
|
||||||
|
this.concurrency = concurrency;
|
||||||
|
this.xudpConcurrency = xudpConcurrency;
|
||||||
|
this.xudpProxyUDP443 = xudpProxyUDP443;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
if (Object.keys(json).length === 0) return undefined;
|
||||||
|
return new Mux(
|
||||||
|
json.enabled,
|
||||||
|
json.concurrency,
|
||||||
|
json.xudpConcurrency,
|
||||||
|
json.xudpProxyUDP443,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
enabled: this.enabled,
|
||||||
|
concurrency: this.concurrency,
|
||||||
|
xudpConcurrency: this.xudpConcurrency,
|
||||||
|
xudpProxyUDP443: this.xudpProxyUDP443,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -399,12 +503,16 @@ class Outbound extends CommonClass {
|
||||||
protocol=Protocols.VMess,
|
protocol=Protocols.VMess,
|
||||||
settings=null,
|
settings=null,
|
||||||
streamSettings = new StreamSettings(),
|
streamSettings = new StreamSettings(),
|
||||||
|
sendThrough,
|
||||||
|
mux = new Mux(),
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
this._protocol = protocol;
|
this._protocol = protocol;
|
||||||
this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings;
|
this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings;
|
||||||
this.stream = streamSettings;
|
this.stream = streamSettings;
|
||||||
|
this.sendThrough = sendThrough;
|
||||||
|
this.mux = mux;
|
||||||
}
|
}
|
||||||
|
|
||||||
get protocol() {
|
get protocol() {
|
||||||
|
@ -419,7 +527,7 @@ class Outbound extends CommonClass {
|
||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
|
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
|
||||||
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
|
return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade"].includes(this.stream.network);
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is used for xtls-rprx-vision
|
//this is used for xtls-rprx-vision
|
||||||
|
@ -439,6 +547,10 @@ class Outbound extends CommonClass {
|
||||||
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol);
|
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canEnableMux() {
|
||||||
|
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, Protocols.HTTP, Protocols.Socks].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
hasVnext() {
|
hasVnext() {
|
||||||
return [Protocols.VMess, Protocols.VLESS].includes(this.protocol);
|
return [Protocols.VMess, Protocols.VLESS].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
@ -469,15 +581,26 @@ class Outbound extends CommonClass {
|
||||||
json.protocol,
|
json.protocol,
|
||||||
Outbound.Settings.fromJson(json.protocol, json.settings),
|
Outbound.Settings.fromJson(json.protocol, json.settings),
|
||||||
StreamSettings.fromJson(json.streamSettings),
|
StreamSettings.fromJson(json.streamSettings),
|
||||||
|
json.sendThrough,
|
||||||
|
Mux.fromJson(json.mux),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
|
var stream;
|
||||||
|
if (this.canEnableStream()) {
|
||||||
|
stream = this.stream.toJson();
|
||||||
|
} else {
|
||||||
|
if (this.stream?.sockopt)
|
||||||
|
stream = { sockopt: this.stream.sockopt.toJson() };
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
tag: this.tag == '' ? undefined : this.tag,
|
tag: this.tag == '' ? undefined : this.tag,
|
||||||
protocol: this.protocol,
|
protocol: this.protocol,
|
||||||
settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings,
|
settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings,
|
||||||
streamSettings: this.canEnableStream() ? this.stream.toJson() : undefined,
|
streamSettings: stream,
|
||||||
|
sendThrough: this.sendThrough != "" ? this.sendThrough : undefined,
|
||||||
|
mux: this.mux?.enabled ? this.mux : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,6 +646,8 @@ class Outbound extends CommonClass {
|
||||||
json.type ? json.type : 'none');
|
json.type ? json.type : 'none');
|
||||||
} else if (network === 'grpc') {
|
} else if (network === 'grpc') {
|
||||||
stream.grpc = new GrpcStreamSettings(json.path, json.type == 'multi');
|
stream.grpc = new GrpcStreamSettings(json.path, json.type == 'multi');
|
||||||
|
} else if (network === 'httpupgrade') {
|
||||||
|
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path,json.host);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(json.tls && json.tls == 'tls'){
|
if(json.tls && json.tls == 'tls'){
|
||||||
|
@ -533,7 +658,6 @@ class Outbound extends CommonClass {
|
||||||
json.allowInsecure);
|
json.allowInsecure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, json.port, json.id), stream);
|
return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, json.port, json.id), stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,6 +688,8 @@ class Outbound extends CommonClass {
|
||||||
headerType ?? 'none');
|
headerType ?? 'none');
|
||||||
} else if (type === 'grpc') {
|
} else if (type === 'grpc') {
|
||||||
stream.grpc = new GrpcStreamSettings(url.searchParams.get('serviceName') ?? '', url.searchParams.get('mode') == 'multi');
|
stream.grpc = new GrpcStreamSettings(url.searchParams.get('serviceName') ?? '', url.searchParams.get('mode') == 'multi');
|
||||||
|
} else if (type === 'httpupgrade') {
|
||||||
|
stream.httpupgrade = new HttpUpgradeStreamSettings(path,host);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(security == 'tls'){
|
if(security == 'tls'){
|
||||||
|
@ -586,7 +712,7 @@ class Outbound extends CommonClass {
|
||||||
let data = link.split('?');
|
let data = link.split('?');
|
||||||
if(data.length != 2) return null;
|
if(data.length != 2) return null;
|
||||||
|
|
||||||
const regex = /([^@]+):\/\/([^@]+)@([^:]+):(\d+)\?(.*)$/;
|
const regex = /([^@]+):\/\/([^@]+)@(.+):(\d+)\?(.*)$/;
|
||||||
const match = link.match(regex);
|
const match = link.match(regex);
|
||||||
|
|
||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
|
|
|
@ -35,9 +35,11 @@ class AllSetting {
|
||||||
this.subUpdates = 0;
|
this.subUpdates = 0;
|
||||||
this.subEncrypt = true;
|
this.subEncrypt = true;
|
||||||
this.subShowInfo = false;
|
this.subShowInfo = false;
|
||||||
this.subURI = '';
|
this.subURI = "";
|
||||||
this.subJsonURI = '';
|
this.subJsonURI = "";
|
||||||
this.subJsonFragment = '';
|
this.subJsonFragment = "";
|
||||||
|
this.subJsonMux = "";
|
||||||
|
this.subJsonRules = "";
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
|
|
|
@ -446,16 +446,19 @@ class QuicStreamSettings extends XrayCommonClass {
|
||||||
class GrpcStreamSettings extends XrayCommonClass {
|
class GrpcStreamSettings extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
serviceName="",
|
serviceName="",
|
||||||
|
authority="",
|
||||||
multiMode=false,
|
multiMode=false,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.serviceName = serviceName;
|
this.serviceName = serviceName;
|
||||||
|
this.authority = authority;
|
||||||
this.multiMode = multiMode;
|
this.multiMode = multiMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new GrpcStreamSettings(
|
return new GrpcStreamSettings(
|
||||||
json.serviceName,
|
json.serviceName,
|
||||||
|
json.authority,
|
||||||
json.multiMode
|
json.multiMode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -463,11 +466,36 @@ class GrpcStreamSettings extends XrayCommonClass {
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
serviceName: this.serviceName,
|
serviceName: this.serviceName,
|
||||||
|
authority: this.authority,
|
||||||
multiMode: this.multiMode,
|
multiMode: this.multiMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HttpUpgradeStreamSettings extends XrayCommonClass {
|
||||||
|
constructor(acceptProxyProtocol=false, path='/', host='') {
|
||||||
|
super();
|
||||||
|
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||||
|
this.path = path;
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new HttpUpgradeStreamSettings(
|
||||||
|
json.acceptProxyProtocol,
|
||||||
|
json.path,
|
||||||
|
json.host,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
acceptProxyProtocol: this.acceptProxyProtocol,
|
||||||
|
path: this.path,
|
||||||
|
host: this.host,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TlsStreamSettings extends XrayCommonClass {
|
class TlsStreamSettings extends XrayCommonClass {
|
||||||
constructor(serverName='',
|
constructor(serverName='',
|
||||||
|
@ -833,6 +861,7 @@ class StreamSettings extends XrayCommonClass {
|
||||||
httpSettings=new HttpStreamSettings(),
|
httpSettings=new HttpStreamSettings(),
|
||||||
quicSettings=new QuicStreamSettings(),
|
quicSettings=new QuicStreamSettings(),
|
||||||
grpcSettings=new GrpcStreamSettings(),
|
grpcSettings=new GrpcStreamSettings(),
|
||||||
|
httpupgradeSettings=new HttpUpgradeStreamSettings(),
|
||||||
sockopt = undefined,
|
sockopt = undefined,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -848,6 +877,7 @@ class StreamSettings extends XrayCommonClass {
|
||||||
this.http = httpSettings;
|
this.http = httpSettings;
|
||||||
this.quic = quicSettings;
|
this.quic = quicSettings;
|
||||||
this.grpc = grpcSettings;
|
this.grpc = grpcSettings;
|
||||||
|
this.httpupgrade = httpupgradeSettings;
|
||||||
this.sockopt = sockopt;
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -910,6 +940,7 @@ class StreamSettings extends XrayCommonClass {
|
||||||
HttpStreamSettings.fromJson(json.httpSettings),
|
HttpStreamSettings.fromJson(json.httpSettings),
|
||||||
QuicStreamSettings.fromJson(json.quicSettings),
|
QuicStreamSettings.fromJson(json.quicSettings),
|
||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
|
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||||
SockoptStreamSettings.fromJson(json.sockopt),
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -929,6 +960,7 @@ class StreamSettings extends XrayCommonClass {
|
||||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
|
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
||||||
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1045,6 +1077,10 @@ class Inbound extends XrayCommonClass {
|
||||||
return this.network === "http";
|
return this.network === "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isHttpupgrade() {
|
||||||
|
return this.network === "httpupgrade";
|
||||||
|
}
|
||||||
|
|
||||||
// Shadowsocks
|
// Shadowsocks
|
||||||
get method() {
|
get method() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
|
@ -1075,6 +1111,8 @@ class Inbound extends XrayCommonClass {
|
||||||
return this.stream.ws.getHeader("Host");
|
return this.stream.ws.getHeader("Host");
|
||||||
} else if (this.isH2) {
|
} else if (this.isH2) {
|
||||||
return this.stream.http.host[0];
|
return this.stream.http.host[0];
|
||||||
|
} else if (this.isHttpupgrade) {
|
||||||
|
return this.stream.httpupgrade.host;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1086,6 +1124,8 @@ class Inbound extends XrayCommonClass {
|
||||||
return this.stream.ws.path;
|
return this.stream.ws.path;
|
||||||
} else if (this.isH2) {
|
} else if (this.isH2) {
|
||||||
return this.stream.http.path;
|
return this.stream.http.path;
|
||||||
|
} else if (this.isHttpupgrade) {
|
||||||
|
return this.stream.httpupgrade.path;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1121,7 +1161,7 @@ class Inbound extends XrayCommonClass {
|
||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
if(![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
|
if(![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
|
||||||
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.network);
|
return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade"].includes(this.network);
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is used for xtls-rprx-vision
|
//this is used for xtls-rprx-vision
|
||||||
|
@ -1204,9 +1244,14 @@ class Inbound extends XrayCommonClass {
|
||||||
obj.path = this.stream.quic.key;
|
obj.path = this.stream.quic.key;
|
||||||
} else if (network === 'grpc') {
|
} else if (network === 'grpc') {
|
||||||
obj.path = this.stream.grpc.serviceName;
|
obj.path = this.stream.grpc.serviceName;
|
||||||
|
obj.authority = this.stream.grpc.authority;
|
||||||
if (this.stream.grpc.multiMode){
|
if (this.stream.grpc.multiMode){
|
||||||
obj.type = 'multi'
|
obj.type = 'multi'
|
||||||
}
|
}
|
||||||
|
} else if (network === 'httpupgrade') {
|
||||||
|
let httpupgrade = this.stream.httpupgrade;
|
||||||
|
obj.path = httpupgrade.path;
|
||||||
|
obj.host = httpupgrade.host;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security === 'tls') {
|
if (security === 'tls') {
|
||||||
|
@ -1275,10 +1320,16 @@ class Inbound extends XrayCommonClass {
|
||||||
case "grpc":
|
case "grpc":
|
||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
params.set("authority", grpc.authority);
|
||||||
if(grpc.multiMode){
|
if(grpc.multiMode){
|
||||||
params.set("mode", "multi");
|
params.set("mode", "multi");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "httpupgrade":
|
||||||
|
const httpupgrade = this.stream.httpupgrade;
|
||||||
|
params.set("path", httpupgrade.path);
|
||||||
|
params.set("host", httpupgrade.host);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security === 'tls') {
|
if (security === 'tls') {
|
||||||
|
@ -1389,10 +1440,16 @@ class Inbound extends XrayCommonClass {
|
||||||
case "grpc":
|
case "grpc":
|
||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
params.set("authority", grpc.authority);
|
||||||
if(grpc.multiMode){
|
if(grpc.multiMode){
|
||||||
params.set("mode", "multi");
|
params.set("mode", "multi");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "httpupgrade":
|
||||||
|
const httpupgrade = this.stream.httpupgrade;
|
||||||
|
params.set("path", httpupgrade.path);
|
||||||
|
params.set("host", httpupgrade.host);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security === 'tls') {
|
if (security === 'tls') {
|
||||||
|
@ -1470,10 +1527,16 @@ class Inbound extends XrayCommonClass {
|
||||||
case "grpc":
|
case "grpc":
|
||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
params.set("authority", grpc.authority);
|
||||||
if(grpc.multiMode){
|
if(grpc.multiMode){
|
||||||
params.set("mode", "multi");
|
params.set("mode", "multi");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "httpupgrade":
|
||||||
|
const httpupgrade = this.stream.httpupgrade;
|
||||||
|
params.set("path", httpupgrade.path);
|
||||||
|
params.set("host", httpupgrade.host);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security === 'tls') {
|
if (security === 'tls') {
|
||||||
|
|
|
@ -22,91 +22,37 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/panel/api/inbounds")
|
g = g.Group("/panel/api/inbounds")
|
||||||
g.Use(a.checkLogin)
|
g.Use(a.checkLogin)
|
||||||
|
|
||||||
g.GET("/list", a.getAllInbounds)
|
|
||||||
g.GET("/get/:id", a.getSingleInbound)
|
|
||||||
g.GET("/getClientTraffics/:email", a.getClientTraffics)
|
|
||||||
g.POST("/add", a.addInbound)
|
|
||||||
g.POST("/del/:id", a.delInbound)
|
|
||||||
g.POST("/update/:id", a.updateInbound)
|
|
||||||
g.POST("/clientIps/:email", a.getClientIps)
|
|
||||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
|
||||||
g.POST("/addClient", a.addInboundClient)
|
|
||||||
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
|
||||||
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
|
||||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
|
||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
|
||||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
|
||||||
g.GET("/createbackup", a.createBackup)
|
|
||||||
g.POST("/onlines", a.onlines)
|
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
|
|
||||||
|
inboundRoutes := []struct {
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
Handler gin.HandlerFunc
|
||||||
|
}{
|
||||||
|
{"GET", "/createbackup", a.createBackup},
|
||||||
|
{"GET", "/list", a.inboundController.getInbounds},
|
||||||
|
{"GET", "/get/:id", a.inboundController.getInbound},
|
||||||
|
{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics},
|
||||||
|
{"POST", "/add", a.inboundController.addInbound},
|
||||||
|
{"POST", "/del/:id", a.inboundController.delInbound},
|
||||||
|
{"POST", "/update/:id", a.inboundController.updateInbound},
|
||||||
|
{"POST", "/clientIps/:email", a.inboundController.getClientIps},
|
||||||
|
{"POST", "/clearClientIps/:email", a.inboundController.clearClientIps},
|
||||||
|
{"POST", "/addClient", a.inboundController.addInboundClient},
|
||||||
|
{"POST", "/:id/delClient/:clientId", a.inboundController.delInboundClient},
|
||||||
|
{"POST", "/updateClient/:clientId", a.inboundController.updateInboundClient},
|
||||||
|
{"POST", "/:id/resetClientTraffic/:email", a.inboundController.resetClientTraffic},
|
||||||
|
{"POST", "/resetAllTraffics", a.inboundController.resetAllTraffics},
|
||||||
|
{"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics},
|
||||||
|
{"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients},
|
||||||
|
{"POST", "/onlines", a.inboundController.onlines},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) getAllInbounds(c *gin.Context) {
|
for _, route := range inboundRoutes {
|
||||||
a.inboundController.getInbounds(c)
|
g.Handle(route.Method, route.Path, route.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) getSingleInbound(c *gin.Context) {
|
|
||||||
a.inboundController.getInbound(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) getClientTraffics(c *gin.Context) {
|
|
||||||
a.inboundController.getClientTraffics(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) addInbound(c *gin.Context) {
|
|
||||||
a.inboundController.addInbound(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) delInbound(c *gin.Context) {
|
|
||||||
a.inboundController.delInbound(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) updateInbound(c *gin.Context) {
|
|
||||||
a.inboundController.updateInbound(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) getClientIps(c *gin.Context) {
|
|
||||||
a.inboundController.getClientIps(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) clearClientIps(c *gin.Context) {
|
|
||||||
a.inboundController.clearClientIps(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) addInboundClient(c *gin.Context) {
|
|
||||||
a.inboundController.addInboundClient(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) delInboundClient(c *gin.Context) {
|
|
||||||
a.inboundController.delInboundClient(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) updateInboundClient(c *gin.Context) {
|
|
||||||
a.inboundController.updateInboundClient(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) resetClientTraffic(c *gin.Context) {
|
|
||||||
a.inboundController.resetClientTraffic(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) resetAllTraffics(c *gin.Context) {
|
|
||||||
a.inboundController.resetAllTraffics(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
|
||||||
a.inboundController.resetAllClientTraffics(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) delDepletedClients(c *gin.Context) {
|
|
||||||
a.inboundController.delDepletedClients(c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) createBackup(c *gin.Context) {
|
func (a *APIController) createBackup(c *gin.Context) {
|
||||||
a.Tgbot.SendBackupToAdmins()
|
a.Tgbot.SendBackupToAdmins()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) onlines(c *gin.Context) {
|
|
||||||
a.inboundController.onlines(c)
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/locale"
|
"x-ui/web/locale"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
@ -9,13 +10,12 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BaseController struct {
|
type BaseController struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (a *BaseController) checkLogin(c *gin.Context) {
|
func (a *BaseController) checkLogin(c *gin.Context) {
|
||||||
if !session.IsLogin(c) {
|
if !session.IsLogin(c) {
|
||||||
if isAjax(c) {
|
if isAjax(c) {
|
||||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.loginAgain"))
|
pureJsonMsg(c, http.StatusUnauthorized, false, I18nWeb(c, "pages.login.loginAgain"))
|
||||||
} else {
|
} else {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
@ -174,7 +175,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client(s) added", nil)
|
jsonMsg(c, "Client(s) added", nil)
|
||||||
if err == nil && needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +196,7 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client deleted", nil)
|
jsonMsg(c, "Client deleted", nil)
|
||||||
if err == nil && needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,7 +219,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client updated", nil)
|
jsonMsg(c, "Client updated", nil)
|
||||||
if err == nil && needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,7 +240,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "traffic reseted", nil)
|
jsonMsg(c, "traffic reseted", nil)
|
||||||
if err == nil && needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package controller
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
@ -49,15 +50,15 @@ func (a *IndexController) login(c *gin.Context) {
|
||||||
var form LoginForm
|
var form LoginForm
|
||||||
err := c.ShouldBind(&form)
|
err := c.ShouldBind(&form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.Username == "" {
|
if form.Username == "" {
|
||||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
|
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.Password == "" {
|
if form.Password == "" {
|
||||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
|
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ func (a *IndexController) login(c *gin.Context) {
|
||||||
if user == nil {
|
if user == nil {
|
||||||
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c))
|
logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c))
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package controller
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
|
@ -48,18 +49,11 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
||||||
c.JSON(http.StatusOK, m)
|
c.JSON(http.StatusOK, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pureJsonMsg(c *gin.Context, success bool, msg string) {
|
func pureJsonMsg(c *gin.Context, statusCode int, success bool, msg string) {
|
||||||
if success {
|
c.JSON(statusCode, entity.Msg{
|
||||||
c.JSON(http.StatusOK, entity.Msg{
|
Success: success,
|
||||||
Success: true,
|
|
||||||
Msg: msg,
|
Msg: msg,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, entity.Msg{
|
|
||||||
Success: false,
|
|
||||||
Msg: msg,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func html(c *gin.Context, name string, title string, data gin.H) {
|
func html(c *gin.Context, name string, title string, data gin.H) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,6 +52,8 @@ type AllSetting struct {
|
||||||
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
|
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
|
||||||
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
|
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
|
||||||
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
|
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
|
||||||
|
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
|
||||||
|
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
||||||
Datepicker string `json:"datepicker" form:"datepicker"`
|
Datepicker string `json:"datepicker" form:"datepicker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,10 @@ import (
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var webServer WebServer
|
var (
|
||||||
var subServer SubServer
|
webServer WebServer
|
||||||
|
subServer SubServer
|
||||||
|
)
|
||||||
|
|
||||||
type WebServer interface {
|
type WebServer interface {
|
||||||
GetCron() *cron.Cron
|
GetCron() *cron.Cron
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{{define "qrcodeModal"}}
|
{{define "qrcodeModal"}}
|
||||||
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
||||||
|
:dialog-style="{ top: '20px' }"
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:class="themeSwitcher.currentTheme"
|
:class="themeSwitcher.currentTheme"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
|
|
|
@ -374,6 +374,12 @@
|
||||||
transform: translateZ(-100px);
|
transform: translateZ(-100px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.ant-menu-item .anticon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
.ant-menu-inline .ant-menu-item {
|
||||||
|
padding: 0 16px !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
|
@ -410,19 +416,19 @@
|
||||||
<a-col span="24">
|
<a-col span="24">
|
||||||
<a-form>
|
<a-form>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
<a-input autocomplete="username" v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
||||||
@keydown.enter.native="login" autofocus>
|
@keydown.enter.native="login" autofocus>
|
||||||
<a-icon slot="prefix" type="user" style="font-size: 16px;"></a-icon>
|
<a-icon slot="prefix" type="user" style="font-size: 16px;"></a-icon>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<password-input icon="lock" v-model.trim="user.password"
|
<password-input autocomplete="current-password" icon="lock" v-model.trim="user.password"
|
||||||
placeholder='{{ i18n "password" }}'
|
placeholder='{{ i18n "password" }}'
|
||||||
@keydown.enter.native="login">
|
@keydown.enter.native="login">
|
||||||
</password-input>
|
</password-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="secretEnable">
|
<a-form-item v-if="secretEnable">
|
||||||
<password-input icon="key" v-model.trim="user.loginSecret"
|
<password-input autocomplete="secret" icon="key" v-model.trim="user.loginSecret"
|
||||||
placeholder='{{ i18n "secretToken" }}'
|
placeholder='{{ i18n "secretToken" }}'
|
||||||
@keydown.enter.native="login">
|
@keydown.enter.native="login">
|
||||||
</password-input>
|
</password-input>
|
||||||
|
@ -455,17 +461,7 @@
|
||||||
</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-col>
|
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
|
||||||
</a-col>
|
|
||||||
<a-col>
|
|
||||||
<theme-switch></theme-switch>
|
<theme-switch></theme-switch>
|
||||||
<a-checkbox v-if="themeSwitcher.isDarkTheme" style="padding-left: 1rem; vertical-align: middle;"
|
|
||||||
:checked="themeSwitcher.isUltra"
|
|
||||||
@click="themeSwitcher.toggleUltra()">
|
|
||||||
Ultra
|
|
||||||
</a-checkbox>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
|
@ -24,18 +24,7 @@
|
||||||
|
|
||||||
{{define "commonSider"}}
|
{{define "commonSider"}}
|
||||||
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<theme-switch></theme-switch>
|
||||||
<a-menu-item mode="inline">
|
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
|
||||||
<theme-switch>
|
|
||||||
</theme-switch>
|
|
||||||
<a-checkbox v-if="themeSwitcher.isDarkTheme" style="padding-left: 1rem; vertical-align: middle;"
|
|
||||||
:checked="themeSwitcher.isUltra"
|
|
||||||
@click="themeSwitcher.toggleUltra()">
|
|
||||||
Ultra
|
|
||||||
</a-checkbox>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||||
{{template "menuItems" .}}
|
{{template "menuItems" .}}
|
||||||
|
@ -49,18 +38,7 @@
|
||||||
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
||||||
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
||||||
</div>
|
</div>
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<theme-switch></theme-switch>
|
||||||
<a-menu-item mode="inline">
|
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
|
||||||
<theme-switch>
|
|
||||||
</theme-switch>
|
|
||||||
<a-checkbox v-if="themeSwitcher.isDarkTheme" style="padding-left: 1rem; vertical-align: middle;"
|
|
||||||
:checked="themeSwitcher.isUltra"
|
|
||||||
@click="themeSwitcher.toggleUltra()">
|
|
||||||
Ultra
|
|
||||||
</a-checkbox>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||||
{{template "menuItems" .}}
|
{{template "menuItems" .}}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
|
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'number'">
|
<template v-else-if="type === 'number'">
|
||||||
<a-input-number :value="value" :step="step" @change="value => $emit('input', value)" :min="min" style="width: 100%;"></a-input-number>
|
<a-input-number :value="value" :step="step" @change="value => $emit('input', value)" :min="min" :max="max" style="width: 100%;"></a-input-number>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'switch'">
|
<template v-else-if="type === 'switch'">
|
||||||
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
{{define "component/setting"}}
|
{{define "component/setting"}}
|
||||||
<script>
|
<script>
|
||||||
Vue.component('setting-list-item', {
|
Vue.component('setting-list-item', {
|
||||||
props: ["type", "title", "desc", "value", "min", "step", "placeholder"],
|
props: ["type", "title", "desc", "value", "min", "max" , "step", "placeholder"],
|
||||||
template: `{{template "component/settingListItem"}}`,
|
template: `{{template "component/settingListItem"}}`,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -50,9 +50,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: function (createElement) {
|
render: function (createElement) {
|
||||||
return createElement(
|
return createElement('a-table', {
|
||||||
'a-table',
|
|
||||||
{
|
|
||||||
class: {
|
class: {
|
||||||
'ant-table-is-sorting': this.isDragging(),
|
'ant-table-is-sorting': this.isDragging(),
|
||||||
},
|
},
|
||||||
|
@ -66,9 +64,7 @@
|
||||||
drop: (e) => this.dropHandler(e),
|
drop: (e) => this.dropHandler(e),
|
||||||
},
|
},
|
||||||
scopedSlots: this.$scopedSlots,
|
scopedSlots: this.$scopedSlots,
|
||||||
},
|
}, this.$slots.default, )
|
||||||
this.$slots.default,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$memoSort = {};
|
this.$memoSort = {};
|
||||||
|
@ -91,8 +87,14 @@
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const hideDragImage = this.$el.cloneNode(true);
|
||||||
|
hideDragImage.id = "hideDragImage-hide";
|
||||||
|
hideDragImage.style.opacity = 0;
|
||||||
|
e.dataTransfer.setDragImage(hideDragImage, 0, 0);
|
||||||
},
|
},
|
||||||
dragStopHandler(e, index) {
|
dragStopHandler(e, index) {
|
||||||
|
const hideDragImage = document.getElementById('hideDragImage-hide');
|
||||||
|
if (hideDragImage) hideDragImage.remove();
|
||||||
this.resetSortableIndex(e, index);
|
this.resetSortableIndex(e, index);
|
||||||
},
|
},
|
||||||
dragOverHandler(e, index) {
|
dragOverHandler(e, index) {
|
||||||
|
@ -209,16 +211,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ant-table-is-sorting .draggable-row td {
|
.ant-table-is-sorting .draggable-row td {
|
||||||
background-color: white !important;
|
background-color: #ffffff !important;
|
||||||
}
|
}
|
||||||
.dark .ant-table-is-sorting .draggable-row td {
|
.dark .ant-table-is-sorting .draggable-row td {
|
||||||
background-color: var(--dark-color-surface-100) !important;
|
background-color: var(--dark-color-surface-100) !important;
|
||||||
}
|
}
|
||||||
|
.ant-table-is-sorting .dragging td {
|
||||||
|
background-color: rgb(232 244 242) !important;
|
||||||
|
color: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
.dark .ant-table-is-sorting .dragging td {
|
||||||
|
background-color: var(--dark-color-table-hover) !important;
|
||||||
|
color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
.ant-table-is-sorting .dragging {
|
.ant-table-is-sorting .dragging {
|
||||||
opacity: 0.5;
|
opacity: 1;
|
||||||
|
box-shadow: 1px -2px 2px #008771;
|
||||||
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
.ant-table-is-sorting .dragging .ant-table-row-index {
|
.ant-table-is-sorting .dragging .ant-table-row-index {
|
||||||
opacity: 0;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{{end}}
|
{{end}}
|
|
@ -1,8 +1,14 @@
|
||||||
{{define "component/themeSwitchTemplate"}}
|
{{define "component/themeSwitchTemplate"}}
|
||||||
<template>
|
<template>
|
||||||
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme"
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
@change="themeSwitcher.toggleTheme()">
|
<a-menu-item mode="inline" class="ant-menu-theme-switch">
|
||||||
</a-switch>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
|
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
|
||||||
|
<template v-if="themeSwitcher.isDarkTheme">
|
||||||
|
<a-checkbox style="margin-left: 1rem; vertical-align: middle;" :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()">Ultra</a-checkbox>
|
||||||
|
</template>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
<a-input v-model.trim="client.tgId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item v-if="app.ipLimitEnable">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
</template>
|
</template>
|
||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
<a-form-item v-if="client.limitIp > 0 && client.email && isEdit">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input-number v-model.number="inbound.port"></a-input-number>
|
<a-input-number v-model.number="inbound.port" :min="1" :max="65531"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}' has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
|
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}' has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
|
||||||
<a-input v-model.trim="outbound.tag" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
|
<a-input v-model.trim="outbound.tag" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.sendThrough" }}'>
|
||||||
|
<a-input v-model="outbound.sendThrough"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<!-- freedom settings-->
|
<!-- freedom settings-->
|
||||||
<template v-if="outbound.protocol === Protocols.Freedom">
|
<template v-if="outbound.protocol === Protocols.Freedom">
|
||||||
|
@ -219,16 +222,16 @@
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
: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">mKCP</a-select-option>
|
||||||
<a-select-option value="ws">WS</a-select-option>
|
<a-select-option value="ws">WebSocket</a-select-option>
|
||||||
<a-select-option value="http">H2</a-select-option>
|
<a-select-option value="http">H2</a-select-option>
|
||||||
<a-select-option value="quic">QUIC</a-select-option>
|
<a-select-option value="quic">QUIC</a-select-option>
|
||||||
<a-select-option value="grpc">gRPC</a-select-option>
|
<a-select-option value="grpc">gRPC</a-select-option>
|
||||||
|
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="outbound.stream.network === 'tcp'">
|
<template v-if="outbound.stream.network === 'tcp'">
|
||||||
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
||||||
<a-switch
|
<a-switch :checked="outbound.stream.tcp.type === 'http'"
|
||||||
:checked="outbound.stream.tcp.type === 'http'"
|
|
||||||
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
|
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -252,6 +255,7 @@
|
||||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
<a-select-option value="wechat-video">WeChat</a-select-option>
|
||||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
||||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
<a-select-option value="wireguard">WireGuard</a-select-option>
|
||||||
|
<a-select-option value="dns">DNS</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
|
@ -329,10 +333,23 @@
|
||||||
<a-form-item label='Service Name'>
|
<a-form-item label='Service Name'>
|
||||||
<a-input v-model.trim="outbound.stream.grpc.serviceName"></a-input>
|
<a-input v-model.trim="outbound.stream.grpc.serviceName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Authority">
|
||||||
|
<a-input v-model.trim="outbound.stream.grpc.authority"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label='Multi Mode'>
|
<a-form-item label='Multi Mode'>
|
||||||
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
|
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- httpupgrade -->
|
||||||
|
<template v-if="outbound.stream.network === 'httpupgrade'">
|
||||||
|
<a-form-item label='{{ i18n "host" }}'>
|
||||||
|
<a-input v-model="outbound.stream.httpupgrade.host"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
|
<a-form-item><a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
|
@ -341,7 +358,7 @@
|
||||||
<a-radio-group v-model="outbound.stream.security" button-style="solid">
|
<a-radio-group v-model="outbound.stream.security" button-style="solid">
|
||||||
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||||
<a-radio-button value="tls">TLS</a-radio-button>
|
<a-radio-button value="tls">TLS</a-radio-button>
|
||||||
<a-radio-button v-if="outbound.canEnableReality()" value="reality">REALITY</a-radio-button>
|
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="outbound.stream.isTls">
|
<template v-if="outbound.stream.isTls">
|
||||||
|
@ -389,6 +406,48 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- sockopt settings -->
|
||||||
|
<a-form-item label="Sockopts">
|
||||||
|
<a-switch v-model="outbound.stream.sockoptSwitch"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="outbound.stream.sockoptSwitch">
|
||||||
|
<a-form-item label="Dialer Proxy">
|
||||||
|
<a-select v-model="outbound.stream.sockopt.dialerProxy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="TCP Fast Open">
|
||||||
|
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Keep Alive Interval">
|
||||||
|
<a-input-number v-model="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="TCP No-Delay">
|
||||||
|
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- mux settings -->
|
||||||
|
<template v-if="outbound.canEnableMux()">
|
||||||
|
<a-form-item label="Mux">
|
||||||
|
<a-switch v-model="outbound.mux.enabled"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="outbound.mux.enabled">
|
||||||
|
<a-form-item label="Concurrency">
|
||||||
|
<a-input-number v-model="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="xudp Concurrency">
|
||||||
|
<a-input-number v-model="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="xudp UDP 443">
|
||||||
|
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="c in ['reject', 'allow', 'skip']" :value="c">[[ c ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" tab="JSON" force-render="true">
|
<a-tab-pane key="2" tab="JSON" force-render="true">
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
<a-form-item label="Service Name">
|
<a-form-item label="Service Name">
|
||||||
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Authority">
|
||||||
|
<a-input v-model.trim="inbound.stream.grpc.authority"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label="Multi Mode">
|
<a-form-item label="Multi Mode">
|
||||||
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
|
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
13
web/html/xui/form/stream/stream_httpupgrade.html
Normal file
13
web/html/xui/form/stream/stream_httpupgrade.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{{define "form/streamHTTPUpgrade"}}
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-form-item label="PROXY Protocol">
|
||||||
|
<a-switch v-model="inbound.stream.httpupgrade.acceptProxyProtocol"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
|
<a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "host" }}'>
|
||||||
|
<a-input v-model.trim="inbound.stream.httpupgrade.host"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
{{end}}
|
|
@ -23,25 +23,25 @@
|
||||||
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
|
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='MTU'>
|
<a-form-item label='MTU'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576" :max="1460"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='TTI (ms)'>
|
<a-form-item label='TTI (ms)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.tti" :min="10" :max="100"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Uplink (MB/s)'>
|
<a-form-item label='Uplink (MB/s)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.upCap" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Downlink (MB/s)'>
|
<a-form-item label='Downlink (MB/s)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.downCap" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Congestion'>
|
<a-form-item label='Congestion'>
|
||||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Read Buffer (MB)'>
|
<a-form-item label='Read Buffer (MB)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.readBuffer" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Write Buffer (MB)'>
|
<a-form-item label='Write Buffer (MB)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -2,14 +2,15 @@
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="inbound.stream.network" style="width: 50%" @change="streamNetworkChange"
|
<a-select v-model="inbound.stream.network" style="width: 75%" @change="streamNetworkChange"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
: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">mKCP</a-select-option>
|
||||||
<a-select-option value="ws">WS</a-select-option>
|
<a-select-option value="ws">WebSocket</a-select-option>
|
||||||
<a-select-option value="http">H2</a-select-option>
|
<a-select-option value="http">H2</a-select-option>
|
||||||
<a-select-option value="quic">QUIC</a-select-option>
|
<a-select-option value="quic">QUIC</a-select-option>
|
||||||
<a-select-option value="grpc">gRPC</a-select-option>
|
<a-select-option value="grpc">gRPC</a-select-option>
|
||||||
|
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
@ -43,6 +44,12 @@
|
||||||
<template v-if="inbound.stream.network === 'grpc'">
|
<template v-if="inbound.stream.network === 'grpc'">
|
||||||
{{template "form/streamGRPC"}}
|
{{template "form/streamGRPC"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- httpupgrade -->
|
||||||
|
<template v-if="inbound.stream.network === 'httpupgrade'">
|
||||||
|
{{template "form/streamHTTPUpgrade"}}
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- sockopt -->
|
<!-- sockopt -->
|
||||||
<template>
|
<template>
|
||||||
{{template "form/streamSockopt"}}
|
{{template "form/streamSockopt"}}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
:overlay-class-name="themeSwitcher.currentTheme"
|
:overlay-class-name="themeSwitcher.currentTheme"
|
||||||
ok-text='{{ i18n "reset"}}'
|
ok-text='{{ i18n "reset"}}'
|
||||||
cancel-text='{{ i18n "cancel"}}'>
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: #008771' : 'color: #008771'"></a-icon>
|
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: var(--color-primary-100)' : 'color: var(--color-primary-100)'"></a-icon>
|
||||||
<a-icon style="font-size: 24px; cursor: pointer;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
|
<a-icon style="font-size: 24px; cursor: pointer;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
||||||
<template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
|
<template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
|
||||||
</template>
|
</template>
|
||||||
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
|
<a-badge :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
|
||||||
</a-badge>
|
</a-badge>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
[[ client.email ]]
|
[[ client.email ]]
|
||||||
|
@ -86,13 +86,12 @@
|
||||||
<td width="120px" v-else-if="client.totalGB > 0">
|
<td width="120px" v-else-if="client.totalGB > 0">
|
||||||
<a-progress :stroke-color="clientStatsColor(record, client.email)"
|
<a-progress :stroke-color="clientStatsColor(record, client.email)"
|
||||||
:show-info="false"
|
:show-info="false"
|
||||||
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
:status="isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
:percent="statsProgress(record, client.email)"/>
|
:percent="statsProgress(record, client.email)"/>
|
||||||
</td>
|
</td>
|
||||||
<td width="120px" v-else class="infinite-bar">
|
<td width="120px" v-else class="infinite-bar">
|
||||||
<a-progress
|
<a-progress
|
||||||
:show-info="false"
|
:show-info="false"
|
||||||
:status="isClientOnline(client.email)? 'active' : ''"
|
|
||||||
:percent="100"></a-progress>
|
:percent="100"></a-progress>
|
||||||
</td>
|
</td>
|
||||||
<td width="60px">
|
<td width="60px">
|
||||||
|
@ -117,7 +116,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td width="120px" class="infinite-bar">
|
<td width="120px" class="infinite-bar">
|
||||||
<a-progress :show-info="false"
|
<a-progress :show-info="false"
|
||||||
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
:status="isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||||
</td>
|
</td>
|
||||||
<td width="60px">[[ client.reset + "d" ]]</td>
|
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||||
|
@ -202,14 +201,13 @@
|
||||||
</template>
|
</template>
|
||||||
<a-progress :stroke-color="clientStatsColor(record, client.email)"
|
<a-progress :stroke-color="clientStatsColor(record, client.email)"
|
||||||
:show-info="false"
|
:show-info="false"
|
||||||
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
:status="isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
:percent="statsProgress(record, client.email)"/>
|
:percent="statsProgress(record, client.email)"/>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</td>
|
</td>
|
||||||
<td width="120px" v-else class="infinite-bar">
|
<td width="120px" v-else class="infinite-bar">
|
||||||
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'"
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'"
|
||||||
:show-info="false"
|
:show-info="false"
|
||||||
:status="isClientOnline(client.email)? 'active' : ''"
|
|
||||||
:percent="100"></a-progress>
|
:percent="100"></a-progress>
|
||||||
</td>
|
</td>
|
||||||
<td width="80px">
|
<td width="80px">
|
||||||
|
@ -235,7 +233,7 @@
|
||||||
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
</template>
|
</template>
|
||||||
<a-progress :show-info="false"
|
<a-progress :show-info="false"
|
||||||
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
:status="isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade ">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "host" }}</td>
|
<td>{{ i18n "host" }}</td>
|
||||||
<td v-if="inbound.host">
|
<td v-if="inbound.host">
|
||||||
|
@ -143,7 +143,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
||||||
[[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
|
[[ getRemStats() ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -423,7 +423,11 @@
|
||||||
},
|
},
|
||||||
statsColor(stats) {
|
statsColor(stats) {
|
||||||
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
|
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
|
||||||
}
|
},
|
||||||
|
getRemStats() {
|
||||||
|
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down;
|
||||||
|
return remained>0 ? sizeFormat(remained) : '-';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{{define "inboundModal"}}
|
{{define "inboundModal"}}
|
||||||
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title"
|
||||||
|
:dialog-style="{ top: '20px' }" @ok="inModal.ok"
|
||||||
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.currentTheme"
|
:class="themeSwitcher.currentTheme"
|
||||||
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
|
|
|
@ -36,13 +36,6 @@
|
||||||
.ant-collapse {
|
.ant-collapse {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
.online-animation .ant-badge-status-dot {
|
|
||||||
animation: 1.2s ease infinite normal none running onlineAnimation;
|
|
||||||
}
|
|
||||||
@keyframes onlineAnimation {
|
|
||||||
0%, 50%, 100% { transform: scale(1); opacity: 1; }
|
|
||||||
10% { transform: scale(1.5); opacity: .2; }
|
|
||||||
}
|
|
||||||
.info-large-tag {
|
.info-large-tag {
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -537,7 +530,7 @@
|
||||||
{ title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } },
|
{ title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 100, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
|
||||||
];
|
];
|
||||||
|
|
||||||
const innerMobileColumns = [
|
const innerMobileColumns = [
|
||||||
|
@ -578,6 +571,7 @@
|
||||||
datepicker: 'gregorian',
|
datepicker: 'gregorian',
|
||||||
tgBotEnable: false,
|
tgBotEnable: false,
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
|
ipLimitEnable: false,
|
||||||
pageSize: 0,
|
pageSize: 0,
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
},
|
},
|
||||||
|
@ -625,6 +619,7 @@
|
||||||
this.pageSize = pageSize;
|
this.pageSize = pageSize;
|
||||||
this.remarkModel = remarkModel;
|
this.remarkModel = remarkModel;
|
||||||
this.datepicker = datepicker;
|
this.datepicker = datepicker;
|
||||||
|
this.ipLimitEnable = ipLimitEnable;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setInbounds(dbInbounds) {
|
setInbounds(dbInbounds) {
|
||||||
|
|
|
@ -259,17 +259,13 @@
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
|
|
||||||
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
|
||||||
:closable="true" @ok="() => versionModal.visible = false"
|
@ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
|
||||||
:class="themeSwitcher.currentTheme"
|
|
||||||
footer="">
|
|
||||||
<a-alert type="warning" style="margin-bottom: 12px; width: fit-content"
|
<a-alert type="warning" style="margin-bottom: 12px; width: fit-content"
|
||||||
message='{{ i18n "pages.index.xraySwitchClickDesk" }}'
|
message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
|
||||||
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' : 'green'"
|
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'" style="margin-right: 12px; margin-bottom: 12px"
|
||||||
style="margin-right: 10px" @click="switchV2rayVersion(version)">
|
@click="switchV2rayVersion(version)">
|
||||||
[[ version ]]
|
[[ version ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -138,7 +138,7 @@
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="0"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="1" :max="65531"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelUrlPath"}}' desc='{{ i18n "pages.settings.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelUrlPath"}}' desc='{{ i18n "pages.settings.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
||||||
|
@ -197,16 +197,16 @@
|
||||||
<a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider>
|
<a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider>
|
||||||
<a-form layout="horizontal" :colon="false" style="float: left; margin: 10px 0;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }">
|
<a-form layout="horizontal" :colon="false" style="float: left; margin: 10px 0;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
||||||
<a-input v-model="user.oldUsername"></a-input>
|
<a-input autocomplete="username" v-model="user.oldUsername"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'>
|
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'>
|
||||||
<password-input v-model="user.oldPassword"></password-input>
|
<password-input autocomplete="current-password" v-model="user.oldPassword"></password-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'>
|
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'>
|
||||||
<a-input v-model="user.newUsername"></a-input>
|
<a-input v-model="user.newUsername"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'>
|
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'>
|
||||||
<password-input v-model="user.newPassword"></password-input>
|
<password-input autocomplete="new-password" v-model="user.newPassword"></password-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
||||||
|
@ -282,12 +282,12 @@
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort" :min="1" :max="65531"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates" :min="1"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable">
|
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable">
|
||||||
|
@ -295,11 +295,62 @@
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.fragment"}}' desc='{{ i18n "pages.settings.fragmentDesc"}}' v-model="fragment"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.fragment"}}' desc='{{ i18n "pages.settings.fragmentDesc"}}' v-model="fragment"></setting-list-item>
|
||||||
<template v-if="fragment">
|
<setting-list-item type="switch" title='Mux' v-model="enableMux"></setting-list-item>
|
||||||
<setting-list-item type="text" title='length' v-model="fragmentLength" placeholder="100-200"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.directCountryConfigs"}}' desc='{{ i18n "pages.xray.directCountryConfigsDesc"}}' v-model="enableDirect"></setting-list-item>
|
||||||
<setting-list-item type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item>
|
|
||||||
</template>
|
|
||||||
</a-list>
|
</a-list>
|
||||||
|
<a-collapse v-if="fragment || enableMux || enableDirect">
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.fragment"}}' v-if="fragment">
|
||||||
|
<a-list-item style="padding: 20px">
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta title='Packets'/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-select
|
||||||
|
v-model="fragmentPackets"
|
||||||
|
style="width: 100%"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="p" :label="p" v-for="p in ['1-1', '1-3', 'tlshello']">
|
||||||
|
[[ p ]]
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
|
<setting-list-item type="text" title='Length' v-model="fragmentLength" placeholder="100-200"></setting-list-item>
|
||||||
|
<setting-list-item type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='Mux' v-if="enableMux">
|
||||||
|
<setting-list-item type="number" title='Concurrency' v-model="muxConcurrency" :min="-1" :max="1024"></setting-list-item>
|
||||||
|
<setting-list-item type="number" title='xudp Concurrency' v-model="muxXudpConcurrency" :min="-1" :max="1024"></setting-list-item>
|
||||||
|
<a-list-item style="padding: 20px">
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta title='xudp UDP 443'/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-select
|
||||||
|
v-model="muxXudpProxyUDP443"
|
||||||
|
style="width: 100%"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="p" :label="p" v-for="p in ['reject', 'allow', 'skip']">
|
||||||
|
[[ p ]]
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.directCountryConfigs"}}' v-if="enableDirect">
|
||||||
|
<a-list-item style="padding: 20px">
|
||||||
|
<a-checkbox-group
|
||||||
|
v-model="directCountries"
|
||||||
|
name="Countries"
|
||||||
|
:options="countryOptions"
|
||||||
|
/>
|
||||||
|
</a-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
@ -348,6 +399,40 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
defaultMux: {
|
||||||
|
enabled: true,
|
||||||
|
concurrency: 8,
|
||||||
|
xudpConcurrency: 16,
|
||||||
|
xudpProxyUDP443: "reject"
|
||||||
|
},
|
||||||
|
defaultRules: [
|
||||||
|
{
|
||||||
|
type: "field",
|
||||||
|
outboundTag: "direct",
|
||||||
|
domain: [
|
||||||
|
"geosite:category-ir",
|
||||||
|
"geosite:cn"
|
||||||
|
],
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "field",
|
||||||
|
outboundTag: "direct",
|
||||||
|
ip: [
|
||||||
|
"geoip:private",
|
||||||
|
"geoip:ir",
|
||||||
|
"geoip:cn"
|
||||||
|
],
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
],
|
||||||
|
countryOptions: [
|
||||||
|
{ label: 'Private IP/Domain', value: 'private' },
|
||||||
|
{ label: '🇮🇷 Iran', value: 'ir' },
|
||||||
|
{ label: '🇨🇳 China', value: 'cn' },
|
||||||
|
{ label: '🇷🇺 Russia', value: 'ru' },
|
||||||
|
{ label: '🇻🇳 Vietnam', value: 'vn' },
|
||||||
|
],
|
||||||
get remarkModel() {
|
get remarkModel() {
|
||||||
rm = this.allSetting.remarkModel;
|
rm = this.allSetting.remarkModel;
|
||||||
return rm.length>1 ? rm.substring(1).split('') : [];
|
return rm.length>1 ? rm.substring(1).split('') : [];
|
||||||
|
@ -406,7 +491,6 @@
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.user = {};
|
this.user = {};
|
||||||
window.location.replace(basePath + "logout");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async restartPanel() {
|
async restartPanel() {
|
||||||
|
@ -443,9 +527,8 @@
|
||||||
async updateSecret() {
|
async updateSecret() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/panel/setting/updateUserSecret", this.user);
|
const msg = await HttpUtil.post("/panel/setting/updateUserSecret", this.user);
|
||||||
if (msg.success) {
|
if (msg && msg.obj) {
|
||||||
this.user = msg.obj;
|
this.user = msg.obj;
|
||||||
window.location.replace(basePath + "logout");
|
|
||||||
}
|
}
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
await this.updateAllSetting();
|
await this.updateAllSetting();
|
||||||
|
@ -483,6 +566,16 @@
|
||||||
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
|
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
fragmentPackets: {
|
||||||
|
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.packets : ""; },
|
||||||
|
set: function(v) {
|
||||||
|
if (v != ""){
|
||||||
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
|
newFragment.settings.fragment.packets = v;
|
||||||
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
fragmentLength: {
|
fragmentLength: {
|
||||||
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
|
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
|
||||||
set: function(v) {
|
set: function(v) {
|
||||||
|
@ -503,6 +596,61 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
enableMux: {
|
||||||
|
get: function() { return this.allSetting?.subJsonMux != ""; },
|
||||||
|
set: function (v) {
|
||||||
|
this.allSetting.subJsonMux = v ? JSON.stringify(this.defaultMux) : "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
muxConcurrency: {
|
||||||
|
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).concurrency : -1; },
|
||||||
|
set: function(v) {
|
||||||
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
|
newMux.concurrency = v;
|
||||||
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
muxXudpConcurrency: {
|
||||||
|
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpConcurrency : -1; },
|
||||||
|
set: function(v) {
|
||||||
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
|
newMux.xudpConcurrency = v;
|
||||||
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
muxXudpProxyUDP443: {
|
||||||
|
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpProxyUDP443 : "reject"; },
|
||||||
|
set: function(v) {
|
||||||
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
|
newMux.xudpProxyUDP443 = v;
|
||||||
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enableDirect: {
|
||||||
|
get: function() { return this.allSetting?.subJsonRules != ""; },
|
||||||
|
set: function (v) {
|
||||||
|
this.allSetting.subJsonRules = v ? JSON.stringify(this.defaultRules) : "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directCountries: {
|
||||||
|
get: function() {
|
||||||
|
if (!this.enableDirect) return [];
|
||||||
|
rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
|
return Array.isArray(rules) ? rules[1].ip.map(d => d.replace("geoip:","")) : [];
|
||||||
|
},
|
||||||
|
set: function (v) {
|
||||||
|
rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
|
if (!Array.isArray(rules)) return;
|
||||||
|
rules[0].domain = [];
|
||||||
|
rules[1].ip = [];
|
||||||
|
v.forEach(d => {
|
||||||
|
category = ["cn","private"].includes(d) ? "" : "category-";
|
||||||
|
rules[0].domain.push("geosite:"+category+d);
|
||||||
|
rules[1].ip.push("geoip:"+d);
|
||||||
|
});
|
||||||
|
this.allSetting.subJsonRules = JSON.stringify(rules);
|
||||||
|
}
|
||||||
|
},
|
||||||
confAlerts: {
|
confAlerts: {
|
||||||
get: function() {
|
get: function() {
|
||||||
if (!this.allSetting) return [];
|
if (!this.allSetting) return [];
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<td>[[ warpModal.warpData.access_token ]]</td>
|
<td>[[ warpModal.warpData.access_token ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Devide ID</td>
|
<td>Device ID</td>
|
||||||
<td>[[ warpModal.warpData.device_id ]]</td>
|
<td>[[ warpModal.warpData.device_id ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="client-table-odd-row">
|
<tr class="client-table-odd-row">
|
||||||
|
@ -24,19 +24,19 @@
|
||||||
<td>[[ warpModal.warpData.private_key ]]</td>
|
<td>[[ warpModal.warpData.private_key ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<a-divider style="margin: 0;">{{ i18n "pages.settings.toasts.modifySettings" }}</a-divider>
|
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
|
||||||
<a-collapse style="margin: 10px 0;">
|
<a-collapse style="margin: 10px 0;">
|
||||||
<a-collapse-panel header='WARP/WARP+ License Key'>
|
<a-collapse-panel header='WARP/WARP+ License Key'>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="License Key">
|
<a-form-item label="Key">
|
||||||
<a-input v-model="warpPlus"></a-input>
|
<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-button @click="updateLicense(warpPlus)" :disabled="warpPlus.length<26" :loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.update" }}</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-divider style="margin: 0;">{{ i18n "pages.settings.toasts.getSettings" }}</a-divider>
|
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.accountInfo" }}</a-divider>
|
||||||
<a-button icon="sync" @click="getConfig" style="margin-bottom: 10px;" :loading="warpModal.confirmLoading">{{ i18n "info" }}</a-button>
|
<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)">
|
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig)">
|
||||||
<table style="width: 100%">
|
<table style="width: 100%">
|
||||||
<tr class="client-table-odd-row">
|
<tr class="client-table-odd-row">
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
<td>[[ warpModal.warpConfig.model ]]</td>
|
<td>[[ warpModal.warpConfig.model ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="client-table-odd-row">
|
<tr class="client-table-odd-row">
|
||||||
<td>Device Active</td>
|
<td>Device Enabled</td>
|
||||||
<td>[[ warpModal.warpConfig.enabled ]]</td>
|
<td>[[ warpModal.warpConfig.enabled ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account)">
|
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account)">
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
<td>[[ warpModal.warpConfig.account.role ]]</td>
|
<td>[[ warpModal.warpConfig.account.role ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Premium Data</td>
|
<td>WARP+ Data</td>
|
||||||
<td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
|
<td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="client-table-odd-row">
|
<tr class="client-table-odd-row">
|
||||||
|
@ -74,16 +74,15 @@
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
<a-divider style="margin: 10px 0;">WARP {{ i18n "pages.xray.rules.outbound" }}</a-divider>
|
<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} }">
|
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "status" }}'>
|
|
||||||
<template v-if="warpOutboundIndex>=0">
|
<template v-if="warpOutboundIndex>=0">
|
||||||
<a-tag color="green">{{ i18n "enabled" }}</a-tag>
|
<a-tag color="green" style="line-height: 31px;">{{ i18n "enabled" }}</a-tag>
|
||||||
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading">{{ i18n "reset" }}</a-button>
|
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading" type="danger">{{ i18n "reset" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-tag color="orange">{{ i18n "disabled" }}</a-tag>
|
<a-tag color="orange" style="line-height: 31px;">{{ i18n "disabled" }}</a-tag>
|
||||||
<a-button @click="addOutbound" :loading="warpModal.confirmLoading">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
<a-button @click="addOutbound" :loading="warpModal.confirmLoading" type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
|
@ -103,13 +103,10 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
<a-tabs class="ant-card-dark-box-nohover" default-active-key="tpl-1"
|
<a-tabs class="ant-card-dark-box-nohover" default-active-key="1"
|
||||||
@change="(activeKey) => { if(activeKey == 'tpl-advanced') this.changeCode(); }"
|
@change="(activeKey) => { this.changePage(activeKey); }"
|
||||||
:class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme">
|
||||||
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.xray.basicTemplate"}}'>
|
<a-tab-pane key="tpl-basic" tab='{{ i18n "pages.xray.basicTemplate"}}' style="padding-top: 20px;">
|
||||||
<a-space direction="horizontal" style="padding: 20px 20px">
|
|
||||||
<a-button type="danger" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
|
||||||
</a-space>
|
|
||||||
<a-collapse>
|
<a-collapse>
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
@ -180,7 +177,7 @@
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||||
<a-select-option v-for="s in access" :value="s">[[ s ]]</a-select-option>
|
<a-select-option v-for="s in access" :key="s" :value="s">[[ s ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
@ -193,7 +190,7 @@
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||||
<a-select-option v-for="s in error" :value="s">[[ s ]]</a-select-option>
|
<a-select-option v-for="s in error" :key="s" :value="s">[[ s ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
@ -284,11 +281,14 @@
|
||||||
</template>
|
</template>
|
||||||
<a-button v-else type="primary" icon="cloud" style="margin: 15px 20px;" @click="showWarp()">WARP</a-button>
|
<a-button v-else type="primary" icon="cloud" style="margin: 15px 20px;" @click="showWarp()">WARP</a-button>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
||||||
|
<a-space direction="horizontal" style="padding: 0 20px">
|
||||||
|
<a-button type="danger" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.xray.Routings"}}' style="padding-top: 20px;">
|
<a-tab-pane key="tpl-routing" tab='{{ i18n "pages.xray.Routings"}}' style="padding-top: 20px;">
|
||||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
|
||||||
message='{{ i18n "pages.xray.RoutingsDesc"}}' show-icon></a-alert>
|
|
||||||
<a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
|
<a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
|
||||||
<a-table-sortable :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
|
<a-table-sortable :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
|
@ -410,7 +410,7 @@
|
||||||
</template>
|
</template>
|
||||||
</a-table-sortable>
|
</a-table-sortable>
|
||||||
</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-outbound" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="12" :sm="12" :lg="12">
|
<a-col :xs="12" :sm="12" :lg="12">
|
||||||
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n
|
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n
|
||||||
|
@ -478,7 +478,7 @@
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</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-reverse" tab='{{ i18n "pages.xray.outbound.reverse"}}' 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-button type="primary" icon="plus" @click="addReverse()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addReverse" }}</a-button>
|
||||||
<a-table :columns="reverseColumns" bordered v-if="reverseData.length>0"
|
<a-table :columns="reverseColumns" bordered v-if="reverseData.length>0"
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
|
@ -506,9 +506,7 @@
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-5" tab='{{ i18n "pages.xray.Balancers"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-balancer" tab='{{ i18n "pages.xray.Balancers"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
|
||||||
message='{{ i18n "pages.xray.balancer.balancerDesc" }}' show-icon></a-alert>
|
|
||||||
<a-button type="primary" icon="plus" @click="addBalancer()" style="margin-bottom: 10px;">{{ i18n "pages.xray.balancer.addBalancer"}}</a-button>
|
<a-button type="primary" icon="plus" @click="addBalancer()" style="margin-bottom: 10px;">{{ i18n "pages.xray.balancer.addBalancer"}}</a-button>
|
||||||
<a-table :columns="balancerColumns" bordered v-if="balancersData.length>0"
|
<a-table :columns="balancerColumns" bordered v-if="balancersData.length>0"
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
|
@ -537,15 +535,29 @@
|
||||||
<template slot="strategy" slot-scope="text, balancer, index">
|
<template slot="strategy" slot-scope="text, balancer, index">
|
||||||
<a-tag style="margin:0;" v-if="balancer.strategy=='random'" color="purple">Random</a-tag>
|
<a-tag style="margin:0;" v-if="balancer.strategy=='random'" color="purple">Random</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="balancer.strategy=='roundRobin'" color="green">Round Robin</a-tag>
|
<a-tag style="margin:0;" v-if="balancer.strategy=='roundRobin'" color="green">Round Robin</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="balancer.strategy=='leastload'" color="green">Least Load</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="balancer.strategy=='leastping'" color="green">Least Ping</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="selector" slot-scope="text, balancer, index">
|
<template slot="selector" slot-scope="text, balancer, index">
|
||||||
<a-tag class="info-large-tag" style="margin:1;" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
|
<a-tag class="info-large-tag" style="margin:1;" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
|
<a-radio-group
|
||||||
|
v-model="obsSettings"
|
||||||
|
v-if="observatoryEnable || burstObservatoryEnable"
|
||||||
|
@change="changeObsCode"
|
||||||
|
button-style="solid"
|
||||||
|
style="margin: 10px 0;"
|
||||||
|
:size="isMobile ? 'small' : ''">
|
||||||
|
<a-radio-button value="observatory" v-if="observatoryEnable">Observatory</a-radio-button>
|
||||||
|
<a-radio-button value="burstObservatory" v-if="burstObservatoryEnable">Burst Observatory</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<textarea style="position:absolute; left: -800px;" id="obsSetting"></textarea>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-6" tab='DNS' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-dns" tab='DNS' style="padding-top: 20px;" force-render="true">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item>
|
||||||
<template v-if="enableDNS">
|
<template v-if="enableDNS">
|
||||||
|
<setting-list-item type="text" title='{{ i18n "pages.xray.dns.tag" }}' desc='{{ i18n "pages.xray.dns.tagDesc" }}' v-model="dnsTag"></setting-list-item>
|
||||||
<a-list-item>
|
<a-list-item>
|
||||||
<a-row style="padding: 20px">
|
<a-row style="padding: 20px">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
|
@ -693,6 +705,13 @@
|
||||||
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
|
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const balancerColumns = [
|
||||||
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||||
|
{ title: '{{ i18n "pages.xray.balancer.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
||||||
|
{ title: '{{ i18n "pages.xray.balancer.balancerStrategy"}}', align: 'center', width: 50, scopedSlots: { customRender: 'strategy' }},
|
||||||
|
{ title: '{{ i18n "pages.xray.balancer.balancerSelectors"}}', align: 'center', width: 100, scopedSlots: { customRender: 'selector' }},
|
||||||
|
];
|
||||||
|
|
||||||
const dnsColumns = [
|
const dnsColumns = [
|
||||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||||
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
||||||
|
@ -705,13 +724,6 @@
|
||||||
{ title: '{{ i18n "pages.xray.fakedns.poolSize"}}', dataIndex: 'poolSize', align: 'center', width: 50 },
|
{ title: '{{ i18n "pages.xray.fakedns.poolSize"}}', dataIndex: 'poolSize', align: 'center', width: 50 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const balancerColumns = [
|
|
||||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
|
||||||
{ title: '{{ i18n "pages.xray.balancer.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
|
||||||
{ title: '{{ i18n "pages.xray.balancer.balancerStrategy"}}', align: 'center', width: 50, scopedSlots: { customRender: 'strategy' }},
|
|
||||||
{ title: '{{ i18n "pages.xray.balancer.balancerSelectors"}}', align: 'center', width: 100, scopedSlots: { customRender: 'selector' }},
|
|
||||||
];
|
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
|
@ -730,6 +742,7 @@
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
advSettings: 'xraySetting',
|
advSettings: 'xraySetting',
|
||||||
|
obsSettings: '',
|
||||||
cm: null,
|
cm: null,
|
||||||
cmOptions: {
|
cmOptions: {
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
|
@ -765,8 +778,8 @@
|
||||||
},
|
},
|
||||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
||||||
logLevel: ["none" , "debug" , "info" , "warning", "error"],
|
logLevel: ["none" , "debug" , "info" , "warning", "error"],
|
||||||
access: ["none" , "./access.log" ],
|
access: [],
|
||||||
error: ["none" , "./error.log" ],
|
error: [],
|
||||||
settingsData: {
|
settingsData: {
|
||||||
protocols: {
|
protocols: {
|
||||||
bittorrent: ["bittorrent"],
|
bittorrent: ["bittorrent"],
|
||||||
|
@ -824,6 +837,22 @@
|
||||||
],
|
],
|
||||||
"queryStrategy": "UseIP"
|
"queryStrategy": "UseIP"
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
defaultObservatory: {
|
||||||
|
subjectSelector: [],
|
||||||
|
probeURL: "http://www.google.com/gen_204",
|
||||||
|
probeInterval: "10m",
|
||||||
|
enableConcurrency: true
|
||||||
|
},
|
||||||
|
defaultBurstObservatory: {
|
||||||
|
subjectSelector: [],
|
||||||
|
pingConfig: {
|
||||||
|
destination: "http://www.google.com/gen_204",
|
||||||
|
interval: "30m",
|
||||||
|
connectivity: "http://connectivitycheck.platform.hicloud.com/generate_204",
|
||||||
|
timeout: "10s",
|
||||||
|
sampling: 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -924,6 +953,10 @@
|
||||||
this.saveBtnDisable = true;
|
this.saveBtnDisable = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
changePage(pageKey) {
|
||||||
|
if(pageKey == 'tpl-advanced') this.changeCode();
|
||||||
|
if(pageKey == 'tpl-balancer') this.changeObsCode();
|
||||||
|
},
|
||||||
syncRulesWithOutbound(tag, setting) {
|
syncRulesWithOutbound(tag, setting) {
|
||||||
const newTemplateSettings = this.templateSettings;
|
const newTemplateSettings = this.templateSettings;
|
||||||
const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
|
const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
|
||||||
|
@ -1006,6 +1039,23 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
changeObsCode() {
|
||||||
|
if (this.obsSettings == ''){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(this.cm != null) {
|
||||||
|
this.cm.toTextArea();
|
||||||
|
}
|
||||||
|
textAreaObj = document.getElementById('obsSetting');
|
||||||
|
textAreaObj.value = this[this.obsSettings];
|
||||||
|
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
||||||
|
this.cm.on('change',editor => {
|
||||||
|
value = editor.getValue();
|
||||||
|
if(this.isJsonString(value)){
|
||||||
|
this[this.obsSettings] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
isJsonString(str) {
|
isJsonString(str) {
|
||||||
try {
|
try {
|
||||||
JSON.parse(str);
|
JSON.parse(str);
|
||||||
|
@ -1084,121 +1134,6 @@
|
||||||
outbounds.splice(0, 0, outbounds.splice(index, 1)[0]);
|
outbounds.splice(0, 0, outbounds.splice(index, 1)[0]);
|
||||||
this.outboundSettings = JSON.stringify(outbounds);
|
this.outboundSettings = JSON.stringify(outbounds);
|
||||||
},
|
},
|
||||||
async refreshOutboundTraffic() {
|
|
||||||
if (!this.refreshing) {
|
|
||||||
this.refreshing = true;
|
|
||||||
await this.getOutboundsTraffic();
|
|
||||||
|
|
||||||
data = []
|
|
||||||
if (this.templateSettings != null) {
|
|
||||||
this.templateSettings.outbounds.forEach((o, index) => {
|
|
||||||
data.push({'key': index, ...o});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.outboundData = data;
|
|
||||||
this.refreshing = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async resetOutboundTraffic(index) {
|
|
||||||
let tag = "-alltags-";
|
|
||||||
if (index >= 0) {
|
|
||||||
tag = this.outboundData[index].tag ? this.outboundData[index].tag : ""
|
|
||||||
}
|
|
||||||
const msg = await HttpUtil.post("/panel/xray/resetOutboundsTraffic", { tag: tag });
|
|
||||||
if (msg.success) {
|
|
||||||
await this.refreshOutboundTraffic();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addBalancer() {
|
|
||||||
balancerModal.show({
|
|
||||||
title: '{{ i18n "pages.xray.balancer.addBalancer"}}',
|
|
||||||
okText: '{{ i18n "pages.xray.balancer.addBalancer"}}',
|
|
||||||
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
|
|
||||||
balancer: {
|
|
||||||
tag: '',
|
|
||||||
strategy: 'random',
|
|
||||||
selector: []
|
|
||||||
},
|
|
||||||
confirm: (balancer) => {
|
|
||||||
balancerModal.loading();
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
if (newTemplateSettings.routing.balancers == undefined) {
|
|
||||||
newTemplateSettings.routing.balancers = [];
|
|
||||||
}
|
|
||||||
let tmpBalancer = {
|
|
||||||
'tag': balancer.tag,
|
|
||||||
'selector': balancer.selector
|
|
||||||
};
|
|
||||||
if (balancer.strategy == 'roundRobin') {
|
|
||||||
tmpBalancer.strategy = {
|
|
||||||
'type': balancer.strategy
|
|
||||||
};
|
|
||||||
}
|
|
||||||
newTemplateSettings.routing.balancers.push(tmpBalancer);
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
balancerModal.close();
|
|
||||||
},
|
|
||||||
isEdit: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
editBalancer(index) {
|
|
||||||
const oldTag = this.balancersData[index].tag;
|
|
||||||
balancerModal.show({
|
|
||||||
title: '{{ i18n "pages.xray.balancer.editBalancer"}}',
|
|
||||||
okText: '{{ i18n "sure" }}',
|
|
||||||
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
|
|
||||||
balancer: this.balancersData[index],
|
|
||||||
confirm: (balancer) => {
|
|
||||||
balancerModal.loading();
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
|
|
||||||
let tmpBalancer = {
|
|
||||||
'tag': balancer.tag,
|
|
||||||
'selector': balancer.selector
|
|
||||||
};
|
|
||||||
if (balancer.strategy == 'roundRobin') {
|
|
||||||
tmpBalancer.strategy = {
|
|
||||||
'type': balancer.strategy
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
newTemplateSettings.routing.balancers[index] = tmpBalancer;
|
|
||||||
// change edited tag if used in rule section
|
|
||||||
if (oldTag != balancer.tag) {
|
|
||||||
newTemplateSettings.routing.rules.forEach((rule) => {
|
|
||||||
if (rule.balancerTag && rule.balancerTag == oldTag) {
|
|
||||||
rule.balancerTag = balancer.tag;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
balancerModal.close();
|
|
||||||
},
|
|
||||||
isEdit: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deleteBalancer(index) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
|
|
||||||
//remove from balancers
|
|
||||||
const oldTag = this.balancersData[index].tag;
|
|
||||||
this.balancersData.splice(index, 1);
|
|
||||||
|
|
||||||
// remove from settings
|
|
||||||
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag == oldTag);
|
|
||||||
newTemplateSettings.routing.balancers.splice(realIndex, 1);
|
|
||||||
|
|
||||||
// remove related routing rules
|
|
||||||
let rules = [];
|
|
||||||
newTemplateSettings.routing.rules.forEach((r) => {
|
|
||||||
if (!r.balancerTag || r.balancerTag != oldTag) {
|
|
||||||
rules.push(r);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
newTemplateSettings.routing.rules = rules;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
addReverse(){
|
addReverse(){
|
||||||
reverseModal.show({
|
reverseModal.show({
|
||||||
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
||||||
|
@ -1284,6 +1219,167 @@
|
||||||
|
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
|
async refreshOutboundTraffic() {
|
||||||
|
if (!this.refreshing) {
|
||||||
|
this.refreshing = true;
|
||||||
|
await this.getOutboundsTraffic();
|
||||||
|
|
||||||
|
data = []
|
||||||
|
if (this.templateSettings != null) {
|
||||||
|
this.templateSettings.outbounds.forEach((o, index) => {
|
||||||
|
data.push({'key': index, ...o});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outboundData = data;
|
||||||
|
this.refreshing = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async resetOutboundTraffic(index) {
|
||||||
|
let tag = "-alltags-";
|
||||||
|
if (index >= 0) {
|
||||||
|
tag = this.outboundData[index].tag ? this.outboundData[index].tag : ""
|
||||||
|
}
|
||||||
|
const msg = await HttpUtil.post("/panel/xray/resetOutboundsTraffic", { tag: tag });
|
||||||
|
if (msg.success) {
|
||||||
|
await this.refreshOutboundTraffic();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addBalancer() {
|
||||||
|
balancerModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.balancer.addBalancer"}}',
|
||||||
|
okText: '{{ i18n "pages.xray.balancer.addBalancer"}}',
|
||||||
|
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
|
||||||
|
balancer: {
|
||||||
|
tag: '',
|
||||||
|
strategy: 'random',
|
||||||
|
selector: []
|
||||||
|
},
|
||||||
|
confirm: (balancer) => {
|
||||||
|
balancerModal.loading();
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
if (newTemplateSettings.routing.balancers == undefined) {
|
||||||
|
newTemplateSettings.routing.balancers = [];
|
||||||
|
}
|
||||||
|
let tmpBalancer = {
|
||||||
|
'tag': balancer.tag,
|
||||||
|
'selector': balancer.selector
|
||||||
|
};
|
||||||
|
if (balancer.strategy && balancer.strategy != 'random') {
|
||||||
|
tmpBalancer.strategy = {
|
||||||
|
'type': balancer.strategy
|
||||||
|
};
|
||||||
|
if (balancer.strategy == 'leastPing'){
|
||||||
|
if (!newTemplateSettings.observatory)
|
||||||
|
newTemplateSettings.observatory = this.defaultObservatory;
|
||||||
|
if (!newTemplateSettings.observatory.subjectSelector.includes(balancer.tag))
|
||||||
|
newTemplateSettings.observatory.subjectSelector.push(balancer.tag);
|
||||||
|
}
|
||||||
|
if (balancer.strategy == 'leastLoad'){
|
||||||
|
if (!newTemplateSettings.burstObservatory)
|
||||||
|
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
|
||||||
|
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(balancer.tag))
|
||||||
|
newTemplateSettings.burstObservatory.subjectSelector.push(balancer.tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newTemplateSettings.routing.balancers.push(tmpBalancer);
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
balancerModal.close();
|
||||||
|
this.changeObsCode();
|
||||||
|
},
|
||||||
|
isEdit: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editBalancer(index) {
|
||||||
|
const oldTag = this.balancersData[index].tag;
|
||||||
|
balancerModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.balancer.editBalancer"}}',
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
|
||||||
|
balancer: this.balancersData[index],
|
||||||
|
confirm: (balancer) => {
|
||||||
|
balancerModal.loading();
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
|
||||||
|
let tmpBalancer = {
|
||||||
|
'tag': balancer.tag,
|
||||||
|
'selector': balancer.selector
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove old tag
|
||||||
|
if (newTemplateSettings.observatory){
|
||||||
|
newTemplateSettings.observatory.subjectSelector = newTemplateSettings.observatory.subjectSelector.filter(s => s != oldTag);
|
||||||
|
}
|
||||||
|
if (newTemplateSettings.burstObservatory){
|
||||||
|
newTemplateSettings.burstObservatory.subjectSelector = newTemplateSettings.burstObservatory.subjectSelector.filter(s => s != oldTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (balancer.strategy && balancer.strategy != 'random') {
|
||||||
|
tmpBalancer.strategy = {
|
||||||
|
'type': balancer.strategy
|
||||||
|
};
|
||||||
|
if (balancer.strategy == 'leastPing'){
|
||||||
|
if (!newTemplateSettings.observatory)
|
||||||
|
newTemplateSettings.observatory = this.defaultObservatory;
|
||||||
|
if (!newTemplateSettings.observatory.subjectSelector.includes(balancer.tag))
|
||||||
|
newTemplateSettings.observatory.subjectSelector.push(balancer.tag);
|
||||||
|
}
|
||||||
|
if (balancer.strategy == 'leastLoad'){
|
||||||
|
if (!newTemplateSettings.burstObservatory)
|
||||||
|
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
|
||||||
|
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(balancer.tag))
|
||||||
|
newTemplateSettings.burstObservatory.subjectSelector.push(balancer.tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newTemplateSettings.routing.balancers[index] = tmpBalancer;
|
||||||
|
// change edited tag if used in rule section
|
||||||
|
if (oldTag != balancer.tag) {
|
||||||
|
newTemplateSettings.routing.rules.forEach((rule) => {
|
||||||
|
if (rule.balancerTag && rule.balancerTag == oldTag) {
|
||||||
|
rule.balancerTag = balancer.tag;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
balancerModal.close();
|
||||||
|
this.changeObsCode();
|
||||||
|
},
|
||||||
|
isEdit: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteBalancer(index) {
|
||||||
|
let newTemplateSettings = { ...this.templateSettings };
|
||||||
|
|
||||||
|
// Remove from balancers
|
||||||
|
const removedBalancer = this.balancersData.splice(index, 1)[0];
|
||||||
|
|
||||||
|
// Remove from settings
|
||||||
|
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
|
||||||
|
newTemplateSettings.routing.balancers.splice(realIndex, 1);
|
||||||
|
|
||||||
|
// Remove tag from observatory
|
||||||
|
if (newTemplateSettings.observatory){
|
||||||
|
newTemplateSettings.observatory.subjectSelector = newTemplateSettings.observatory.subjectSelector.filter(s => s != removedBalancer.tag);
|
||||||
|
}
|
||||||
|
if (newTemplateSettings.burstObservatory){
|
||||||
|
newTemplateSettings.burstObservatory.subjectSelector = newTemplateSettings.burstObservatory.subjectSelector.filter(s => s != removedBalancer.tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove related routing rules
|
||||||
|
newTemplateSettings.routing.rules.forEach((rule) => {
|
||||||
|
if (rule.balancerTag === removedBalancer.tag) {
|
||||||
|
delete rule.balancerTag;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update balancers property to an empty array if there are no more balancers
|
||||||
|
if (newTemplateSettings.routing.balancers.length === 0) {
|
||||||
|
delete newTemplateSettings.routing.balancers;
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
this.changeObsCode()
|
||||||
|
},
|
||||||
addDNSServer(){
|
addDNSServer(){
|
||||||
dnsModal.show({
|
dnsModal.show({
|
||||||
title: '{{ i18n "pages.xray.dns.add" }}',
|
title: '{{ i18n "pages.xray.dns.add" }}',
|
||||||
|
@ -1403,8 +1499,31 @@
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
templateSettings: {
|
templateSettings: {
|
||||||
get: function () { return this.xraySetting ? JSON.parse(this.xraySetting) : null; },
|
get: function () {
|
||||||
set: function (newValue) { this.xraySetting = JSON.stringify(newValue, null, 2); },
|
const parsedSettings = this.xraySetting ? JSON.parse(this.xraySetting) : null;
|
||||||
|
let accessLogPath = "./access.log";
|
||||||
|
let errorLogPath = "./error.log";
|
||||||
|
|
||||||
|
if (parsedSettings && parsedSettings.log) {
|
||||||
|
if (parsedSettings.log.access && parsedSettings.log.access !== "none") {
|
||||||
|
accessLogPath = parsedSettings.log.access;
|
||||||
|
}
|
||||||
|
if (parsedSettings.log.error && parsedSettings.log.error !== "none") {
|
||||||
|
errorLogPath = parsedSettings.log.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.access = ["none", accessLogPath];
|
||||||
|
this.error = ["none", errorLogPath];
|
||||||
|
return parsedSettings;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue && newValue.log) {
|
||||||
|
this.xraySetting = JSON.stringify(newValue, null, 2);
|
||||||
|
this.access = ["none", newValue.log.access || "./access.log"];
|
||||||
|
this.error = ["none", newValue.log.error || "./error.log"];
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
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; },
|
||||||
|
@ -1451,27 +1570,6 @@
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
balancersData: {
|
|
||||||
get: function () {
|
|
||||||
data = []
|
|
||||||
if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) {
|
|
||||||
this.templateSettings.routing.balancers.forEach((o, index) => {
|
|
||||||
let strategy = "random"
|
|
||||||
if (o.strategy && o.strategy.type == "roundRobin") {
|
|
||||||
strategy = o.strategy.type
|
|
||||||
}
|
|
||||||
|
|
||||||
data.push({
|
|
||||||
'key': index,
|
|
||||||
'tag': o.tag ? o.tag : "",
|
|
||||||
'strategy': strategy,
|
|
||||||
'selector': o.selector ? o.selector : []
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
routingRuleSettings: {
|
routingRuleSettings: {
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
|
@ -1501,6 +1599,58 @@
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
balancersData: {
|
||||||
|
get: function () {
|
||||||
|
data = []
|
||||||
|
if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) {
|
||||||
|
this.templateSettings.routing.balancers.forEach((o, index) => {
|
||||||
|
data.push({
|
||||||
|
'key': index,
|
||||||
|
'tag': o.tag ? o.tag : "",
|
||||||
|
'strategy': o.strategy?.type ?? "random",
|
||||||
|
'selector': o.selector ? o.selector : []
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
observatory: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateSettings?.observatory ? JSON.stringify(this.templateSettings.observatory, null, 2) : null;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.observatory = JSON.parse(newValue);
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
burstObservatory: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateSettings?.burstObservatory ? JSON.stringify(this.templateSettings.burstObservatory, null, 2) : null;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.burstObservatory = JSON.parse(newValue);
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
observatoryEnable: {
|
||||||
|
get: function () { return this.templateSettings != null && this.templateSettings.observatory },
|
||||||
|
set: function (v) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.observatory = v ? this.defaultObservatory : undefined;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
burstObservatoryEnable: {
|
||||||
|
get: function () { return this.templateSettings != null && this.templateSettings.burstObservatory },
|
||||||
|
set: function (v) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.burstObservatory = v ? this.defaultBurstObservatory : undefined;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
freedomStrategy: {
|
freedomStrategy: {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!this.templateSettings) return "AsIs";
|
if (!this.templateSettings) return "AsIs";
|
||||||
|
@ -2011,7 +2161,23 @@
|
||||||
},
|
},
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
newTemplateSettings = this.templateSettings;
|
newTemplateSettings = this.templateSettings;
|
||||||
newTemplateSettings.dns = newValue ? { servers: [], queryStrategy: "UseIP" } : null;
|
if (newValue) {
|
||||||
|
newTemplateSettings.dns = { servers: [], queryStrategy: "UseIP", tag: "dns_inbound" };
|
||||||
|
newTemplateSettings.fakedns = null;
|
||||||
|
} else {
|
||||||
|
delete newTemplateSettings.dns;
|
||||||
|
delete newTemplateSettings.fakedns;
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dnsTag: {
|
||||||
|
get: function () {
|
||||||
|
return this.enableDNS ? this.templateSettings.dns.tag : "";
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.dns.tag = newValue;
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2037,7 +2203,11 @@
|
||||||
get: function () { return this.templateSettings && this.templateSettings.fakedns ? this.templateSettings.fakedns : []; },
|
get: function () { return this.templateSettings && this.templateSettings.fakedns ? this.templateSettings.fakedns : []; },
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
newTemplateSettings = this.templateSettings;
|
newTemplateSettings = this.templateSettings;
|
||||||
|
if (this.enableDNS) {
|
||||||
newTemplateSettings.fakedns = newValue.length > 0 ? newValue : null;
|
newTemplateSettings.fakedns = newValue.length > 0 ? newValue : null;
|
||||||
|
} else {
|
||||||
|
delete newTemplateSettings.fakedns;
|
||||||
|
}
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
<a-select v-model="balancerModal.balancer.strategy" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="balancerModal.balancer.strategy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="random">Random</a-select-option>
|
<a-select-option value="random">Random</a-select-option>
|
||||||
<a-select-option value="roundRobin">Round Robin</a-select-option>
|
<a-select-option value="roundRobin">Round Robin</a-select-option>
|
||||||
|
<a-select-option value="leastload">Least Load</a-select-option>
|
||||||
|
<a-select-option value="leastping">Least Ping</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback
|
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback
|
||||||
|
|
|
@ -38,7 +38,6 @@
|
||||||
:options="reverseModal.inboundTags"></a-checkbox-group>
|
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
@ -114,6 +113,7 @@
|
||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||||
this.inboundTags.push(...app.inboundTags);
|
this.inboundTags.push(...app.inboundTags);
|
||||||
|
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
|
||||||
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
|
|
|
@ -195,6 +195,7 @@
|
||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||||
this.inboundTags.push(...app.inboundTags);
|
this.inboundTags.push(...app.inboundTags);
|
||||||
|
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
|
||||||
this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
|
this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
|
||||||
if(app.templateSettings.reverse){
|
if(app.templateSettings.reverse){
|
||||||
if(app.templateSettings.reverse.bridges) {
|
if(app.templateSettings.reverse.bridges) {
|
||||||
|
|
|
@ -14,23 +14,16 @@ import (
|
||||||
|
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/config"
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CheckClientIpJob struct {
|
type CheckClientIpJob struct {
|
||||||
|
lastClear int64
|
||||||
disAllowedIps []string
|
disAllowedIps []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var job *CheckClientIpJob
|
var job *CheckClientIpJob
|
||||||
var ipFiles = []string{
|
|
||||||
xray.GetIPLimitLogPath(),
|
|
||||||
xray.GetIPLimitBannedLogPath(),
|
|
||||||
xray.GetIPLimitBannedPrevLogPath(),
|
|
||||||
xray.GetAccessPersistentLogPath(),
|
|
||||||
xray.GetAccessPersistentPrevLogPath(),
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCheckClientIpJob() *CheckClientIpJob {
|
func NewCheckClientIpJob() *CheckClientIpJob {
|
||||||
job = new(CheckClientIpJob)
|
job = new(CheckClientIpJob)
|
||||||
|
@ -38,52 +31,53 @@ func NewCheckClientIpJob() *CheckClientIpJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) Run() {
|
func (j *CheckClientIpJob) Run() {
|
||||||
|
if j.lastClear == 0 {
|
||||||
// create files and dirs required for iplimit if not exists
|
j.lastClear = time.Now().Unix()
|
||||||
for i := 0; i < len(ipFiles); i++ {
|
|
||||||
err := os.MkdirAll(config.GetLogFolder(), 0770)
|
|
||||||
j.checkError(err)
|
|
||||||
file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
|
||||||
j.checkError(err)
|
|
||||||
defer file.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for limit ip
|
shouldClearAccessLog := false
|
||||||
|
f2bInstalled := j.checkFail2BanInstalled()
|
||||||
|
isAccessLogAvailable := j.checkAccessLogAvailable(f2bInstalled)
|
||||||
|
|
||||||
if j.hasLimitIp() {
|
if j.hasLimitIp() {
|
||||||
j.checkFail2BanInstalled()
|
if f2bInstalled && isAccessLogAvailable {
|
||||||
j.processLogFile()
|
shouldClearAccessLog = j.processLogFile()
|
||||||
|
} else {
|
||||||
|
if !f2bInstalled {
|
||||||
|
logger.Warning("[iplimit] fail2ban is not installed. IP limiting may not work properly.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !j.hasLimitIp() && xray.GetAccessLogPath() == "./access.log" {
|
|
||||||
go j.clearLogTime()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) clearLogTime() {
|
if shouldClearAccessLog || isAccessLogAvailable && time.Now().Unix()-j.lastClear > 3600 {
|
||||||
for {
|
|
||||||
time.Sleep(time.Hour)
|
|
||||||
j.clearAccessLog()
|
j.clearAccessLog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) clearAccessLog() {
|
func (j *CheckClientIpJob) clearAccessLog() {
|
||||||
accessLogPath := xray.GetAccessLogPath()
|
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||||
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
j.checkError(err)
|
||||||
|
|
||||||
|
// get access log path to open it
|
||||||
|
accessLogPath, err := xray.GetAccessLogPath()
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
defer logAccessP.Close()
|
|
||||||
|
|
||||||
// reopen the access log file for reading
|
// reopen the access log file for reading
|
||||||
file, err := os.Open(accessLogPath)
|
file, err := os.Open(accessLogPath)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// copy access log content to persistent file
|
// copy access log content to persistent file
|
||||||
_, err = io.Copy(logAccessP, file)
|
_, err = io.Copy(logAccessP, file)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
|
// close the file after copying content
|
||||||
|
logAccessP.Close()
|
||||||
|
file.Close()
|
||||||
|
|
||||||
// clean access log
|
// clean access log
|
||||||
err = os.Truncate(accessLogPath, 0)
|
err = os.Truncate(accessLogPath, 0)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
|
j.lastClear = time.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) hasLimitIp() bool {
|
func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||||
|
@ -115,32 +109,12 @@ func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) checkFail2BanInstalled() {
|
func (j *CheckClientIpJob) processLogFile() bool {
|
||||||
cmd := "fail2ban-client"
|
accessLogPath, err := xray.GetAccessLogPath()
|
||||||
args := []string{"-h"}
|
j.checkError(err)
|
||||||
|
|
||||||
err := exec.Command(cmd, args...).Run()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("fail2ban is not installed. IP limiting may not work properly.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *CheckClientIpJob) processLogFile() {
|
|
||||||
accessLogPath := xray.GetAccessLogPath()
|
|
||||||
|
|
||||||
if accessLogPath == "none" {
|
|
||||||
logger.Warning("Access log is set to 'none' check your Xray Configs")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if accessLogPath == "" {
|
|
||||||
logger.Warning("Access log doesn't exist in your Xray Configs")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Open(accessLogPath)
|
file, err := os.Open(accessLogPath)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
InboundClientIps := make(map[string][]string)
|
InboundClientIps := make(map[string][]string)
|
||||||
|
|
||||||
|
@ -176,6 +150,7 @@ func (j *CheckClientIpJob) processLogFile() {
|
||||||
}
|
}
|
||||||
|
|
||||||
j.checkError(scanner.Err())
|
j.checkError(scanner.Err())
|
||||||
|
file.Close()
|
||||||
|
|
||||||
shouldCleanLog := false
|
shouldCleanLog := false
|
||||||
|
|
||||||
|
@ -189,12 +164,38 @@ func (j *CheckClientIpJob) processLogFile() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// added delay before cleaning logs to reduce chance of logging IP that already has been banned
|
return shouldCleanLog
|
||||||
time.Sleep(time.Second * 2)
|
|
||||||
|
|
||||||
if shouldCleanLog {
|
|
||||||
j.clearAccessLog()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j *CheckClientIpJob) checkFail2BanInstalled() bool {
|
||||||
|
cmd := "fail2ban-client"
|
||||||
|
args := []string{"-h"}
|
||||||
|
err := exec.Command(cmd, args...).Run()
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *CheckClientIpJob) checkAccessLogAvailable(handleWarning bool) bool {
|
||||||
|
isAvailable := true
|
||||||
|
warningMsg := ""
|
||||||
|
accessLogPath, err := xray.GetAccessLogPath()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// access log is not available if it is set to 'none' or an empty string
|
||||||
|
switch accessLogPath {
|
||||||
|
case "none":
|
||||||
|
warningMsg = "Access log is set to 'none', check your Xray Configs"
|
||||||
|
isAvailable = false
|
||||||
|
case "":
|
||||||
|
warningMsg = "Access log doesn't exist in your Xray Configs"
|
||||||
|
isAvailable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if handleWarning && warningMsg != "" {
|
||||||
|
logger.Warning(warningMsg)
|
||||||
|
}
|
||||||
|
return isAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) checkError(e error) {
|
func (j *CheckClientIpJob) checkError(e error) {
|
||||||
|
@ -272,7 +273,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
||||||
j.disAllowedIps = []string{}
|
j.disAllowedIps = []string{}
|
||||||
|
|
||||||
// create iplimit log file channel
|
// create iplimit log file channel
|
||||||
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("failed to create or open ip limit log file: %s", err)
|
logger.Errorf("failed to create or open ip limit log file: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -305,9 +306,8 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
err = db.Save(inboundClientIps).Error
|
err = db.Save(inboundClientIps).Error
|
||||||
if err != nil {
|
j.checkError(err)
|
||||||
return shouldCleanLog
|
|
||||||
}
|
|
||||||
return shouldCleanLog
|
return shouldCleanLog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package job
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v3/cpu"
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
@ -28,21 +30,23 @@ func (j *ClearLogsJob) Run() {
|
||||||
for i := 0; i < len(logFiles); i++ {
|
for i := 0; i < len(logFiles); i++ {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
// copy to previous logs
|
// copy to previous logs
|
||||||
logFilePrev, err := os.OpenFile(logFilesPrev[i-1], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
logFilePrev, err := os.OpenFile(logFilesPrev[i-1], os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("clear logs job err:", err)
|
logger.Warning("clear logs job err:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logFile, err := os.ReadFile(logFiles[i])
|
logFile, err := os.Open(logFiles[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("clear logs job err:", err)
|
logger.Warning("clear logs job err:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = logFilePrev.Write(logFile)
|
_, err = io.Copy(logFilePrev, logFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("clear logs job err:", err)
|
logger.Warning("clear logs job err:", err)
|
||||||
}
|
}
|
||||||
defer logFilePrev.Close()
|
|
||||||
|
logFile.Close()
|
||||||
|
logFilePrev.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := os.Truncate(logFiles[i], 0)
|
err := os.Truncate(logFiles[i], 0)
|
||||||
|
|
|
@ -36,5 +36,4 @@ func (j *XrayTrafficJob) Run() {
|
||||||
if needRestart0 || needRestart1 {
|
if needRestart0 || needRestart1 {
|
||||||
j.xrayService.SetToNeedRestart()
|
j.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"embed"
|
"embed"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -12,9 +13,11 @@ import (
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
var i18nBundle *i18n.Bundle
|
var (
|
||||||
var LocalizerWeb *i18n.Localizer
|
i18nBundle *i18n.Bundle
|
||||||
var LocalizerBot *i18n.Localizer
|
LocalizerWeb *i18n.Localizer
|
||||||
|
LocalizerBot *i18n.Localizer
|
||||||
|
)
|
||||||
|
|
||||||
type I18nType string
|
type I18nType string
|
||||||
|
|
||||||
|
@ -79,7 +82,6 @@ func I18n(i18nType I18nType, key string, params ...string) string {
|
||||||
MessageID: key,
|
MessageID: key,
|
||||||
TemplateData: templateData,
|
TemplateData: templateData,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to localize message: %v", err)
|
logger.Errorf("Failed to localize message: %v", err)
|
||||||
return ""
|
return ""
|
||||||
|
@ -135,7 +137,6 @@ func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error {
|
||||||
_, err = i18nBundle.ParseMessageFileBytes(data, path)
|
_, err = i18nBundle.ParseMessageFileBytes(data, path)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,9 @@
|
||||||
{
|
{
|
||||||
"tag": "direct",
|
"tag": "direct",
|
||||||
"protocol": "freedom",
|
"protocol": "freedom",
|
||||||
"settings": {}
|
"settings": {
|
||||||
|
"domainStrategy": "UseIP"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "blocked",
|
"tag": "blocked",
|
||||||
|
@ -51,7 +53,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"routing": {
|
"routing": {
|
||||||
"domainStrategy": "IPIfNonMatch",
|
"domainStrategy": "AsIs",
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
@ -90,7 +91,6 @@ func (s *InboundService) getAllEmails() ([]string, error) {
|
||||||
FROM inbounds,
|
FROM inbounds,
|
||||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||||
`).Scan(&emails).Error
|
`).Scan(&emails).Error
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -573,15 +573,19 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||||
}
|
}
|
||||||
|
|
||||||
oldEmail := ""
|
oldEmail := ""
|
||||||
|
newClientId := ""
|
||||||
clientIndex := 0
|
clientIndex := 0
|
||||||
for index, oldClient := range oldClients {
|
for index, oldClient := range oldClients {
|
||||||
oldClientId := ""
|
oldClientId := ""
|
||||||
if oldInbound.Protocol == "trojan" {
|
if oldInbound.Protocol == "trojan" {
|
||||||
oldClientId = oldClient.Password
|
oldClientId = oldClient.Password
|
||||||
|
newClientId = clients[0].Password
|
||||||
} else if oldInbound.Protocol == "shadowsocks" {
|
} else if oldInbound.Protocol == "shadowsocks" {
|
||||||
oldClientId = oldClient.Email
|
oldClientId = oldClient.Email
|
||||||
|
newClientId = clients[0].Email
|
||||||
} else {
|
} else {
|
||||||
oldClientId = oldClient.ID
|
oldClientId = oldClient.ID
|
||||||
|
newClientId = clients[0].ID
|
||||||
}
|
}
|
||||||
if clientId == oldClientId {
|
if clientId == oldClientId {
|
||||||
oldEmail = oldClient.Email
|
oldEmail = oldClient.Email
|
||||||
|
@ -590,6 +594,11 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate new client ID
|
||||||
|
if newClientId == "" {
|
||||||
|
return false, common.NewError("empty client ID")
|
||||||
|
}
|
||||||
|
|
||||||
if len(clients[0].Email) > 0 && clients[0].Email != oldEmail {
|
if len(clients[0].Email) > 0 && clients[0].Email != oldEmail {
|
||||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -682,7 +691,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||||
return needRestart, tx.Save(oldInbound).Error
|
return needRestart, tx.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
||||||
var err error
|
var err error
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
|
@ -694,7 +703,7 @@ func (s *InboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*
|
||||||
tx.Commit()
|
tx.Commit()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
err = s.addInboundTraffic(tx, traffics)
|
err = s.addInboundTraffic(tx, inboundTraffics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, false
|
return err, false
|
||||||
}
|
}
|
||||||
|
@ -969,7 +978,7 @@ func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error
|
||||||
s.xrayApi.Init(p.GetAPIPort())
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
err1 := s.xrayApi.DelInbound(tag)
|
err1 := s.xrayApi.DelInbound(tag)
|
||||||
if err == nil {
|
if err1 == nil {
|
||||||
logger.Debug("Inbound disabled by api:", tag)
|
logger.Debug("Inbound disabled by api:", tag)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("Error in disabling inbound by api:", err1)
|
logger.Debug("Error in disabling inbound by api:", err1)
|
||||||
|
@ -1060,11 +1069,8 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
||||||
clientTraffic.Reset = client.Reset
|
clientTraffic.Reset = client.Reset
|
||||||
result := tx.Create(&clientTraffic)
|
result := tx.Create(&clientTraffic)
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error {
|
func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error {
|
||||||
result := tx.Model(xray.ClientTraffic{}).
|
result := tx.Model(xray.ClientTraffic{}).
|
||||||
|
@ -1074,13 +1080,11 @@ func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *mod
|
||||||
"email": client.Email,
|
"email": client.Email,
|
||||||
"total": client.TotalGB,
|
"total": client.TotalGB,
|
||||||
"expiry_time": client.ExpiryTime,
|
"expiry_time": client.ExpiryTime,
|
||||||
"reset": client.Reset})
|
"reset": client.Reset,
|
||||||
|
})
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InboundService) UpdateClientIPs(tx *gorm.DB, oldEmail string, newEmail string) error {
|
func (s *InboundService) UpdateClientIPs(tx *gorm.DB, oldEmail string, newEmail string) error {
|
||||||
return tx.Model(model.InboundClientIps{}).Where("client_email = ?", oldEmail).Update("client_email", newEmail).Error
|
return tx.Model(model.InboundClientIps{}).Where("client_email = ?", oldEmail).Update("client_email", newEmail).Error
|
||||||
|
@ -1204,11 +1208,8 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId string) err
|
||||||
}
|
}
|
||||||
inbound.Settings = string(modifiedSettings)
|
inbound.Settings = string(modifiedSettings)
|
||||||
_, err = s.UpdateInboundClient(inbound, clientId)
|
_, err = s.UpdateInboundClient(inbound, clientId)
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InboundService) checkIsEnabledByEmail(clientEmail string) (bool, error) {
|
func (s *InboundService) checkIsEnabledByEmail(clientEmail string) (bool, error) {
|
||||||
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||||
|
@ -1354,11 +1355,8 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
|
||||||
}
|
}
|
||||||
inbound.Settings = string(modifiedSettings)
|
inbound.Settings = string(modifiedSettings)
|
||||||
_, err = s.UpdateInboundClient(inbound, clientId)
|
_, err = s.UpdateInboundClient(inbound, clientId)
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) error {
|
func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) error {
|
||||||
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||||
|
@ -1414,11 +1412,8 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
|
||||||
}
|
}
|
||||||
inbound.Settings = string(modifiedSettings)
|
inbound.Settings = string(modifiedSettings)
|
||||||
_, err = s.UpdateInboundClient(inbound, clientId)
|
_, err = s.UpdateInboundClient(inbound, clientId)
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, totalGB int) error {
|
func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, totalGB int) error {
|
||||||
if totalGB < 0 {
|
if totalGB < 0 {
|
||||||
|
@ -1477,11 +1472,8 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
|
||||||
}
|
}
|
||||||
inbound.Settings = string(modifiedSettings)
|
inbound.Settings = string(modifiedSettings)
|
||||||
_, err = s.UpdateInboundClient(inbound, clientId)
|
_, err = s.UpdateInboundClient(inbound, clientId)
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
@ -1573,12 +1565,8 @@ func (s *InboundService) ResetAllClientTraffics(id int) error {
|
||||||
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InboundService) ResetAllTraffics() error {
|
func (s *InboundService) ResetAllTraffics() error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
@ -1588,12 +1576,8 @@ func (s *InboundService) ResetAllTraffics() error {
|
||||||
Updates(map[string]interface{}{"up": 0, "down": 0})
|
Updates(map[string]interface{}{"up": 0, "down": 0})
|
||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InboundService) DelDepletedClients(id int) (err error) {
|
func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
@ -1666,13 +1650,9 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Where(whereText+" and enable = ?", id, false).Delete(xray.ClientTraffic{}).Error
|
err = tx.Where(whereText+" and enable = ?", id, false).Delete(xray.ClientTraffic{}).Error
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InboundService) GetClientTrafficTgBot(tgId string) ([]*xray.ClientTraffic, error) {
|
func (s *InboundService) GetClientTrafficTgBot(tgId string) ([]*xray.ClientTraffic, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
|
|
|
@ -9,8 +9,7 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OutboundService struct {
|
type OutboundService struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -4,11 +4,11 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PanelService struct {
|
type PanelService struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PanelService) RestartPanel(delay time.Duration) error {
|
func (s *PanelService) RestartPanel(delay time.Duration) error {
|
||||||
p, err := os.FindProcess(syscall.Getpid())
|
p, err := os.FindProcess(syscall.Getpid())
|
||||||
|
|
|
@ -382,7 +382,6 @@ func (s *ServerService) UpdateXray(version string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
|
func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
@ -16,6 +17,7 @@ import (
|
||||||
"x-ui/util/random"
|
"x-ui/util/random"
|
||||||
"x-ui/util/reflect_util"
|
"x-ui/util/reflect_util"
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed config.json
|
//go:embed config.json
|
||||||
|
@ -60,12 +62,13 @@ var defaultValueMap = map[string]string{
|
||||||
"subJsonPath": "/json/",
|
"subJsonPath": "/json/",
|
||||||
"subJsonURI": "",
|
"subJsonURI": "",
|
||||||
"subJsonFragment": "",
|
"subJsonFragment": "",
|
||||||
|
"subJsonMux": "",
|
||||||
|
"subJsonRules": "",
|
||||||
"datepicker": "gregorian",
|
"datepicker": "gregorian",
|
||||||
"warp": "",
|
"warp": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetDefaultJsonConfig() (interface{}, error) {
|
func (s *SettingService) GetDefaultJsonConfig() (interface{}, error) {
|
||||||
var jsonData interface{}
|
var jsonData interface{}
|
||||||
|
@ -437,6 +440,14 @@ func (s *SettingService) GetSubJsonFragment() (string, error) {
|
||||||
return s.getString("subJsonFragment")
|
return s.getString("subJsonFragment")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubJsonMux() (string, error) {
|
||||||
|
return s.getString("subJsonMux")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubJsonRules() (string, error) {
|
||||||
|
return s.getString("subJsonRules")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetDatepicker() (string, error) {
|
func (s *SettingService) GetDatepicker() (string, error) {
|
||||||
return s.getString("datepicker")
|
return s.getString("datepicker")
|
||||||
}
|
}
|
||||||
|
@ -444,10 +455,19 @@ func (s *SettingService) GetDatepicker() (string, error) {
|
||||||
func (s *SettingService) GetWarp() (string, error) {
|
func (s *SettingService) GetWarp() (string, error) {
|
||||||
return s.getString("warp")
|
return s.getString("warp")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetWarp(data string) error {
|
func (s *SettingService) SetWarp(data string) error {
|
||||||
return s.setString("warp", data)
|
return s.setString("warp", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetIpLimitEnable() (bool, error) {
|
||||||
|
accessLogPath, err := xray.GetAccessLogPath()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return (accessLogPath != "none" && accessLogPath != ""), nil
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
@ -492,6 +512,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||||
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
|
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
|
||||||
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
||||||
"datepicker": func() (interface{}, error) { return s.GetDatepicker() },
|
"datepicker": func() (interface{}, error) { return s.GetDatepicker() },
|
||||||
|
"ipLimitEnable": func() (interface{}, error) { return s.GetIpLimitEnable() },
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(map[string]interface{})
|
result := make(map[string]interface{})
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
|
@ -26,12 +27,14 @@ import (
|
||||||
"github.com/valyala/fasthttp/fasthttpproxy"
|
"github.com/valyala/fasthttp/fasthttpproxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var bot *telego.Bot
|
var (
|
||||||
var botHandler *th.BotHandler
|
bot *telego.Bot
|
||||||
var adminIds []int64
|
botHandler *th.BotHandler
|
||||||
var isRunning bool
|
adminIds []int64
|
||||||
var hostname string
|
isRunning bool
|
||||||
var hashStorage *global.HashStorage
|
hostname string
|
||||||
|
hashStorage *global.HashStorage
|
||||||
|
)
|
||||||
|
|
||||||
type LoginStatus byte
|
type LoginStatus byte
|
||||||
|
|
||||||
|
@ -280,7 +283,6 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
|
func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
|
||||||
|
|
||||||
chatId := callbackQuery.Message.GetChat().ID
|
chatId := callbackQuery.Message.GetChat().ID
|
||||||
|
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
|
@ -1030,9 +1032,15 @@ func (t *Tgbot) getInboundUsages() string {
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic, printEnabled bool, printOnline bool, printActive bool,
|
func (t *Tgbot) clientInfoMsg(
|
||||||
printDate bool, printTraffic bool, printRefreshed bool) string {
|
traffic *xray.ClientTraffic,
|
||||||
|
printEnabled bool,
|
||||||
|
printOnline bool,
|
||||||
|
printActive bool,
|
||||||
|
printDate bool,
|
||||||
|
printTraffic bool,
|
||||||
|
printRefreshed bool,
|
||||||
|
) string {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
flag := false
|
flag := false
|
||||||
|
@ -1544,7 +1552,6 @@ func (t *Tgbot) sendBackup(chatId int64) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.Error("Error in opening db file for backup: ", err)
|
logger.Error("Error in opening db file for backup: ", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err = os.Open(xray.GetConfigPath())
|
file, err = os.Open(xray.GetConfigPath())
|
||||||
|
@ -1560,8 +1567,6 @@ func (t *Tgbot) sendBackup(chatId int64) {
|
||||||
} else {
|
} else {
|
||||||
logger.Error("Error in opening config.json file for backup: ", err)
|
logger.Error("Error in opening config.json file for backup: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.sendBanLogs(chatId, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
@ -9,8 +10,7 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserService struct {
|
type UserService struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *UserService) GetFirstUser() (*model.User, error) {
|
func (s *UserService) GetFirstUser() (*model.User, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
@ -79,6 +79,21 @@ func (s *UserService) GetUserSecret(id int) *model.User {
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserService) CheckSecretExistence() (bool, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
err := db.Model(model.User{}).
|
||||||
|
Where("login_secret IS NOT NULL").
|
||||||
|
Count(&count).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserService) UpdateFirstUser(username string, password string) error {
|
func (s *UserService) UpdateFirstUser(username string, password string) error {
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return errors.New("username can not be empty")
|
return errors.New("username can not be empty")
|
||||||
|
|
|
@ -4,16 +4,19 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
var p *xray.Process
|
var (
|
||||||
var lock sync.Mutex
|
p *xray.Process
|
||||||
var isNeedXrayRestart atomic.Bool
|
lock sync.Mutex
|
||||||
var result string
|
isNeedXrayRestart atomic.Bool
|
||||||
|
result string
|
||||||
|
)
|
||||||
|
|
||||||
type XrayService struct {
|
type XrayService struct {
|
||||||
inboundService InboundService
|
inboundService InboundService
|
||||||
|
@ -87,7 +90,6 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||||
// check users active or not
|
// check users active or not
|
||||||
clientStats := inbound.ClientStats
|
clientStats := inbound.ClientStats
|
||||||
for _, clientTraffic := range clientStats {
|
for _, clientTraffic := range clientStats {
|
||||||
|
|
||||||
indexDecrease := 0
|
indexDecrease := 0
|
||||||
for index, client := range clients {
|
for index, client := range clients {
|
||||||
c := client.(map[string]interface{})
|
c := client.(map[string]interface{})
|
||||||
|
@ -96,20 +98,15 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||||
clients = RemoveIndex(clients, index-indexDecrease)
|
clients = RemoveIndex(clients, index-indexDecrease)
|
||||||
indexDecrease++
|
indexDecrease++
|
||||||
logger.Info("Remove Inbound User ", c["email"], " due the expire or traffic limit")
|
logger.Info("Remove Inbound User ", c["email"], " due the expire or traffic limit")
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear client config for additional parameters
|
// clear client config for additional parameters
|
||||||
var final_clients []interface{}
|
var final_clients []interface{}
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
|
|
||||||
c := client.(map[string]interface{})
|
c := client.(map[string]interface{})
|
||||||
|
|
||||||
if c["enable"] != nil {
|
if c["enable"] != nil {
|
||||||
if enable, ok := c["enable"].(bool); ok && !enable {
|
if enable, ok := c["enable"].(bool); ok && !enable {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
|
|
||||||
sessions "github.com/Calidity/gin-sessions"
|
sessions "github.com/Calidity/gin-sessions"
|
||||||
|
|
|
@ -201,7 +201,7 @@
|
||||||
"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" = "Duration"
|
||||||
"days" = "Day(s)"
|
"days" = "Day(s)"
|
||||||
"renew" = "Auto Renew"
|
"renew" = "Auto Renew"
|
||||||
|
@ -327,7 +327,7 @@
|
||||||
"blockCountryConfigs" = "Block Country"
|
"blockCountryConfigs" = "Block Country"
|
||||||
"blockCountryConfigsDesc" = "These options will block traffic based on the specific requested country."
|
"blockCountryConfigsDesc" = "These options will block traffic based on the specific requested country."
|
||||||
"directCountryConfigs" = "Direct Country"
|
"directCountryConfigs" = "Direct Country"
|
||||||
"directCountryConfigsDesc" = "These options will directly forward traffic based on the specific requested country."
|
"directCountryConfigsDesc" = "A direct connection ensures that specific traffic is not routed through another server."
|
||||||
"ipv4Configs" = "IPv4 Routing"
|
"ipv4Configs" = "IPv4 Routing"
|
||||||
"ipv4ConfigsDesc" = "These options will route traffic based on a specific destination via IPv4."
|
"ipv4ConfigsDesc" = "These options will route traffic based on a specific destination via IPv4."
|
||||||
"warpConfigs" = "WARP Routing"
|
"warpConfigs" = "WARP Routing"
|
||||||
|
@ -446,6 +446,10 @@
|
||||||
"bridge" = "Bridge"
|
"bridge" = "Bridge"
|
||||||
"portal" = "Portal"
|
"portal" = "Portal"
|
||||||
"intercon" = "Interconnection"
|
"intercon" = "Interconnection"
|
||||||
|
"settings" = "Settings"
|
||||||
|
"accountInfo" = "Account Information"
|
||||||
|
"outboundStatus" = "Outbound Status"
|
||||||
|
"sendThrough" = "Send Through"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
[pages.xray.balancer]
|
||||||
"addBalancer" = "Add Balancer"
|
"addBalancer" = "Add Balancer"
|
||||||
|
@ -467,6 +471,8 @@
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "Enable DNS"
|
"enable" = "Enable DNS"
|
||||||
"enableDesc" = "Enable built-in DNS server"
|
"enableDesc" = "Enable built-in DNS server"
|
||||||
|
"tag" = "DNS Inbound Tag"
|
||||||
|
"tagDesc" = "This tag will be available as an Inbound tag in routing rules."
|
||||||
"strategy" = "Query Strategy"
|
"strategy" = "Query Strategy"
|
||||||
"strategyDesc" = "Overall strategy to resolve domain names"
|
"strategyDesc" = "Overall strategy to resolve domain names"
|
||||||
"add" = "Add Server"
|
"add" = "Add Server"
|
||||||
|
|
|
@ -52,14 +52,14 @@
|
||||||
"secretToken" = "Token Secreto"
|
"secretToken" = "Token Secreto"
|
||||||
"remained" = "Restante"
|
"remained" = "Restante"
|
||||||
"security" = "Seguridad"
|
"security" = "Seguridad"
|
||||||
"secAlertTitle" = "Alerta de seguridad"
|
"secAlertTitle" = "Alerta de Seguridad"
|
||||||
"secAlertSsl" = "Esta conexión no es segura. Evite ingresar información confidencial hasta que TLS esté activado para la protección de datos."
|
"secAlertSsl" = "Esta conexión no es segura. Por favor, evite ingresar información sensible hasta que se active TLS para la protección de datos."
|
||||||
"secAlertConf" = "Certae occasus vulnerabiles sunt impetus. Commendatur ad securitatem protocolla roboranda ne interrupta potentiale."
|
"secAlertConf" = "Ciertas configuraciones son vulnerables a ataques. Se recomienda reforzar los protocolos de seguridad para prevenir posibles violaciones."
|
||||||
"secAlertSSL" = "La panel carece de conexión segura. Por favor, instale un certificado TLS para la protección de datos."
|
"secAlertSSL" = "El panel carece de una conexión segura. Por favor, instale un certificado TLS para la protección de datos."
|
||||||
"secAlertPanelPort" = "La puerto predeterminado del panel es vulnerable. Por favor, configure un puerto aleatorio o específico."
|
"secAlertPanelPort" = "El puerto predeterminado del panel es vulnerable. Por favor, configure un puerto aleatorio o específico."
|
||||||
"secAlertPanelURI" = "La ruta URI predeterminada del panel no es segura. Por favor, configure una ruta URI compleja."
|
"secAlertPanelURI" = "La ruta URI predeterminada del panel no es segura. Por favor, configure una ruta URI compleja."
|
||||||
"secAlertSubURI" = "La ruta URI predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja."
|
"secAlertSubURI" = "La ruta URI predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja."
|
||||||
"secAlertSubJsonURI" = "La ruta URI predeterminada de la suscripción JSON no es segura. Por favor, configure una ruta URI compleja."
|
"secAlertSubJsonURI" = "La ruta URI JSON predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja."
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Estado del Sistema"
|
"dashboard" = "Estado del Sistema"
|
||||||
|
@ -147,10 +147,10 @@
|
||||||
"totalFlow" = "Flujo Total"
|
"totalFlow" = "Flujo Total"
|
||||||
"leaveBlankToNeverExpire" = "Dejar en Blanco para Nunca Expirar"
|
"leaveBlankToNeverExpire" = "Dejar en Blanco para Nunca Expirar"
|
||||||
"noRecommendKeepDefault" = "No hay requisitos especiales para mantener la configuración predeterminada"
|
"noRecommendKeepDefault" = "No hay requisitos especiales para mantener la configuración predeterminada"
|
||||||
"certificatePath" = "Ruta del Archivo"
|
"certificatePath" = "Camino"
|
||||||
"certificateContent" = "Contenido del Archivo"
|
"certificateContent" = "Contenido"
|
||||||
"publicKey" = "llave Pública"
|
"publicKey" = "Clave Pública"
|
||||||
"privatekey" = "llave Privada"
|
"privatekey" = "Clave Privada"
|
||||||
"clickOnQRcode" = "Haz clic en el Código QR para Copiar"
|
"clickOnQRcode" = "Haz clic en el Código QR para Copiar"
|
||||||
"client" = "Cliente"
|
"client" = "Cliente"
|
||||||
"export" = "Exportar Enlaces"
|
"export" = "Exportar Enlaces"
|
||||||
|
@ -201,11 +201,11 @@
|
||||||
"last" = "Último"
|
"last" = "Último"
|
||||||
"prefix" = "Prefijo"
|
"prefix" = "Prefijo"
|
||||||
"postfix" = "Sufijo"
|
"postfix" = "Sufijo"
|
||||||
"delayedStart" = "Inicio Inicial"
|
"delayedStart" = "Iniciar el primer uso"
|
||||||
"expireDays" = "Duratio"
|
"expireDays" = "Duración"
|
||||||
"days" = "día(s)"
|
"days" = "Día(s)"
|
||||||
"renew" = "Renovación automática"
|
"renew" = "Renovación automática"
|
||||||
"renewDesc" = "Auto-renovatio post tutelam receptam. (0 = disable) (unitas: dies)"
|
"renewDesc" = "Renovación automática después de la expiración. (0 = desactivar) (unidad: día)"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "Recibir"
|
"obtain" = "Recibir"
|
||||||
|
@ -310,7 +310,7 @@
|
||||||
"subURI" = "URI de proxy inverso"
|
"subURI" = "URI de proxy inverso"
|
||||||
"subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy"
|
"subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy"
|
||||||
"fragment" = "Fragmentación"
|
"fragment" = "Fragmentación"
|
||||||
"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo TLS"
|
"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo de TLS"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Xray Configuración"
|
"title" = "Xray Configuración"
|
||||||
|
@ -321,13 +321,13 @@
|
||||||
"generalConfigs" = "Configuraciones Generales"
|
"generalConfigs" = "Configuraciones Generales"
|
||||||
"generalConfigsDesc" = "Estas opciones proporcionarán ajustes generales."
|
"generalConfigsDesc" = "Estas opciones proporcionarán ajustes generales."
|
||||||
"logConfigs" = "Registro"
|
"logConfigs" = "Registro"
|
||||||
"logConfigsDesc" = "Los registros pueden afectar la eficiencia de su servidor. Se recomienda habilitarlo sabiamente solo en caso de sus necesidades."
|
"logConfigsDesc" = "Los registros pueden afectar la eficiencia de su servidor. Se recomienda habilitarlos sabiamente solo en caso de sus necesidades."
|
||||||
"blockConfigs" = "Configuraciones de Bloqueo"
|
"blockConfigs" = "Configuraciones de Bloqueo"
|
||||||
"blockConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a protocolos y sitios web específicos."
|
"blockConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a protocolos y sitios web específicos."
|
||||||
"blockCountryConfigs" = "Configuraciones de Bloqueo por País"
|
"blockCountryConfigs" = "Configuraciones de Bloqueo por País"
|
||||||
"blockCountryConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a dominios de países específicos."
|
"blockCountryConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a dominios de países específicos."
|
||||||
"directCountryConfigs" = "Configuraciones de Conexión Directa por País"
|
"directCountryConfigs" = "Configuraciones de Conexión Directa por País"
|
||||||
"directCountryConfigsDesc" = "Estas opciones conectarán a los usuarios directamente a dominios de países específicos."
|
"directCountryConfigsDesc" = "Una conexión directa asegura que el tráfico específico no se enrutará a través de otro servidor."
|
||||||
"ipv4Configs" = "Configuraciones IPv4"
|
"ipv4Configs" = "Configuraciones IPv4"
|
||||||
"ipv4ConfigsDesc" = "Estas opciones solo enrutarán a los dominios objetivo a través de IPv4."
|
"ipv4ConfigsDesc" = "Estas opciones solo enrutarán a los dominios objetivo a través de IPv4."
|
||||||
"warpConfigs" = "Configuraciones de WARP"
|
"warpConfigs" = "Configuraciones de WARP"
|
||||||
|
@ -346,7 +346,7 @@
|
||||||
"AdsDesc" = "Cambia la plantilla de configuración para bloquear anuncios."
|
"AdsDesc" = "Cambia la plantilla de configuración para bloquear anuncios."
|
||||||
"Family" = "Bloquee malware y contenido para adultos"
|
"Family" = "Bloquee malware y contenido para adultos"
|
||||||
"FamilyDesc" = "Resolutores de DNS de Cloudflare para bloquear malware y contenido para adultos para protección familiar."
|
"FamilyDesc" = "Resolutores de DNS de Cloudflare para bloquear malware y contenido para adultos para protección familiar."
|
||||||
"Security" = "Bloquee sitios web de malware, phishing y criptomineros"
|
"Security" = "Escudo de Seguridad"
|
||||||
"SecurityDesc" = "Cambiar la plantilla de configuración para la protección de seguridad."
|
"SecurityDesc" = "Cambiar la plantilla de configuración para la protección de seguridad."
|
||||||
"Speedtest" = "Bloquear Sitios Web de Pruebas de Velocidad"
|
"Speedtest" = "Bloquear Sitios Web de Pruebas de Velocidad"
|
||||||
"SpeedtestDesc" = "Cambia la plantilla de configuración para evitar la conexión a sitios web de pruebas de velocidad."
|
"SpeedtestDesc" = "Cambia la plantilla de configuración para evitar la conexión a sitios web de pruebas de velocidad."
|
||||||
|
@ -387,13 +387,13 @@
|
||||||
"NetflixIPv4" = "Usar IPv4 para Netflix"
|
"NetflixIPv4" = "Usar IPv4 para Netflix"
|
||||||
"NetflixIPv4Desc" = "Agregar enrutamiento para que Netflix se conecte con IPv4."
|
"NetflixIPv4Desc" = "Agregar enrutamiento para que Netflix se conecte con IPv4."
|
||||||
"GoogleWARP" = "Google"
|
"GoogleWARP" = "Google"
|
||||||
"GoogleWARPDesc" = "Enruta el tráfico a Apple a través de WARP."
|
"GoogleWARPDesc" = "Agrega enrutamiento para Google a través de WARP."
|
||||||
"OpenAIWARP" = "OpenAI (ChatGPT)"
|
"OpenAIWARP" = "ChatGPT"
|
||||||
"OpenAIWARPDesc" = "Enruta el tráfico a OpenAI (ChatGPT) a través de WARP."
|
"OpenAIWARPDesc" = "Enruta el tráfico a ChatGPT a través de WARP."
|
||||||
"NetflixWARP" = "Netflix"
|
"NetflixWARP" = "Netflix"
|
||||||
"NetflixWARPDesc" = "Enruta el tráfico a Netflix a través de WARP."
|
"NetflixWARPDesc" = "Enruta el tráfico a Netflix a través de WARP."
|
||||||
"MetaWARP" = "Meta"
|
"MetaWARP" = "Meta"
|
||||||
"MetaWARPDesc" = "Enruta el tráfico a Meta (Instagram, Facebook, WhatsApp, Threads,...) a través de WARP."
|
"MetaWARPDesc" = "Enruta el tráfico a Meta (Instagram, Facebook, WhatsApp, Threads, etc.) a través de WARP."
|
||||||
"AppleWARP" = "Apple"
|
"AppleWARP" = "Apple"
|
||||||
"AppleWARPDesc" = "Enruta el tráfico a Apple a través de WARP."
|
"AppleWARPDesc" = "Enruta el tráfico a Apple a través de WARP."
|
||||||
"RedditWARP" = "Reddit"
|
"RedditWARP" = "Reddit"
|
||||||
|
@ -414,22 +414,22 @@
|
||||||
"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse."
|
"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse."
|
||||||
"accessLog" = "Registro de acceso"
|
"accessLog" = "Registro de acceso"
|
||||||
"accessLogDesc" = "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros de acceso"
|
"accessLogDesc" = "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros de acceso"
|
||||||
"errorLog" = "Registro de errores"
|
"errorLog" = "Registro de Errores"
|
||||||
"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'ninguno' deshabilitó los registros de errores"
|
"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'none' desactiva los registros de errores."
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Primero"
|
"first" = "Primero"
|
||||||
"last" = "Último"
|
"last" = "Último"
|
||||||
"up" = "arriba"
|
"up" = "Arriba"
|
||||||
"down" = "abajo"
|
"down" = "Abajo"
|
||||||
"source" = "Fuente"
|
"source" = "Fuente"
|
||||||
"dest" = "Destino"
|
"dest" = "Destino"
|
||||||
"inbound" = "Entrante"
|
"inbound" = "Entrante"
|
||||||
"outbound" = "saliente"
|
"outbound" = "Saliente"
|
||||||
"balancer" = "Balancín"
|
"balancer" = "Equilibrador"
|
||||||
"info" = "Información"
|
"info" = "Información"
|
||||||
"add" = "Agregar regla"
|
"add" = "Agregar Regla"
|
||||||
"edit" = "Editar regla"
|
"edit" = "Editar Regla"
|
||||||
"useComma" = "Elementos separados por comas"
|
"useComma" = "Elementos separados por comas"
|
||||||
|
|
||||||
[pages.xray.outbound]
|
[pages.xray.outbound]
|
||||||
|
@ -446,6 +446,10 @@
|
||||||
"bridge" = "puente"
|
"bridge" = "puente"
|
||||||
"portal" = "portal"
|
"portal" = "portal"
|
||||||
"intercon" = "Interconexión"
|
"intercon" = "Interconexión"
|
||||||
|
"settings" = "Configuración"
|
||||||
|
"accountInfo" = "Información de la cuenta"
|
||||||
|
"outboundStatus" = "Estado de salida"
|
||||||
|
"sendThrough" = "Enviar a través de"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
[pages.xray.balancer]
|
||||||
"addBalancer" = "Agregar equilibrador"
|
"addBalancer" = "Agregar equilibrador"
|
||||||
|
@ -466,16 +470,16 @@
|
||||||
|
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "Habilitar DNS"
|
"enable" = "Habilitar DNS"
|
||||||
"enableDesc" = "Habilitar servidor DNS integrado"
|
"enableDesc" = "Habilitar servidor DNS incorporado"
|
||||||
"strategy" = "Estrategia de consulta"
|
"strategy" = "Estrategia de Consulta"
|
||||||
"strategyDesc" = "Estrategia general para resolver nombres de dominio"
|
"strategyDesc" = "Estrategia general para resolver nombres de dominio"
|
||||||
"add" = "Agregar servidor"
|
"add" = "Agregar Servidor"
|
||||||
"edit" = "Editar servidor"
|
"edit" = "Editar Servidor"
|
||||||
"domains" = "Dominios"
|
"domains" = "Dominios"
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
[pages.xray.fakedns]
|
||||||
"add" = "Agregar DNS falso"
|
"add" = "Agregar DNS Falso"
|
||||||
"edit" = "Editar DNS falso"
|
"edit" = "Editar DNS Falso"
|
||||||
"ipPool" = "Subred del grupo de IP"
|
"ipPool" = "Subred del grupo de IP"
|
||||||
"poolSize" = "Tamaño del grupo"
|
"poolSize" = "Tamaño del grupo"
|
||||||
|
|
||||||
|
|
|
@ -327,7 +327,7 @@
|
||||||
"blockCountryConfigs" = "مسدودسازی کشور"
|
"blockCountryConfigs" = "مسدودسازی کشور"
|
||||||
"blockCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص مسدود میکند"
|
"blockCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص مسدود میکند"
|
||||||
"directCountryConfigs" = "اتصال مستقیم کشور"
|
"directCountryConfigs" = "اتصال مستقیم کشور"
|
||||||
"directCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص بصورت مستقیم ارسال میکند"
|
"directCountryConfigsDesc" = "اتصال مستقیم اطمینان حاصل میکند که ترافیک خاص از طریق یک سرور دیگر هدایت نمیشود."
|
||||||
"ipv4Configs" = "IPv4 مسیریابی"
|
"ipv4Configs" = "IPv4 مسیریابی"
|
||||||
"ipv4ConfigsDesc" = "این گزینهها ترافیک را از طریق آیپینسخه4 به مقصد هدایت میکند"
|
"ipv4ConfigsDesc" = "این گزینهها ترافیک را از طریق آیپینسخه4 به مقصد هدایت میکند"
|
||||||
"warpConfigs" = "WARP مسیریابی"
|
"warpConfigs" = "WARP مسیریابی"
|
||||||
|
@ -446,6 +446,10 @@
|
||||||
"bridge" = "پل"
|
"bridge" = "پل"
|
||||||
"portal" = "پورتال"
|
"portal" = "پورتال"
|
||||||
"intercon" = "اتصال میانی"
|
"intercon" = "اتصال میانی"
|
||||||
|
"settings" = "تنظیمات"
|
||||||
|
"accountInfo" = "اطلاعات حساب"
|
||||||
|
"outboundStatus" = "وضعیت خروجی"
|
||||||
|
"sendThrough" = "ارسال با"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
[pages.xray.balancer]
|
||||||
"addBalancer" = "افزودن بالانسر"
|
"addBalancer" = "افزودن بالانسر"
|
||||||
|
@ -467,6 +471,8 @@
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "فعال کردن حل دامنه"
|
"enable" = "فعال کردن حل دامنه"
|
||||||
"enableDesc" = "سرور حل دامنه داخلی را فعال کنید"
|
"enableDesc" = "سرور حل دامنه داخلی را فعال کنید"
|
||||||
|
"tag" = "برچسب"
|
||||||
|
"tagDesc" = "این برچسب در قوانین مسیریابی به عنوان یک برچسب ورودی قابل استفاده خواهد بود"
|
||||||
"strategy" = "استراتژی پرسوجو"
|
"strategy" = "استراتژی پرسوجو"
|
||||||
"strategyDesc" = "استراتژی کلی برای حل نام دامنه"
|
"strategyDesc" = "استراتژی کلی برای حل نام دامنه"
|
||||||
"add" = "افزودن سرور"
|
"add" = "افزودن سرور"
|
||||||
|
|
|
@ -327,7 +327,7 @@
|
||||||
"blockCountryConfigs" = "Blokir Negara"
|
"blockCountryConfigs" = "Blokir Negara"
|
||||||
"blockCountryConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan negara yang diminta."
|
"blockCountryConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan negara yang diminta."
|
||||||
"directCountryConfigs" = "Langsung ke Negara"
|
"directCountryConfigs" = "Langsung ke Negara"
|
||||||
"directCountryConfigsDesc" = "Opsi ini akan langsung meneruskan lalu lintas berdasarkan negara yang diminta."
|
"directCountryConfigsDesc" = "Koneksi langsung memastikan bahwa lalu lintas tertentu tidak diarahkan melalui server lain."
|
||||||
"ipv4Configs" = "Pengalihan IPv4"
|
"ipv4Configs" = "Pengalihan IPv4"
|
||||||
"ipv4ConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui IPv4."
|
"ipv4ConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui IPv4."
|
||||||
"warpConfigs" = "Pengalihan WARP"
|
"warpConfigs" = "Pengalihan WARP"
|
||||||
|
@ -446,6 +446,10 @@
|
||||||
"bridge" = "Jembatan"
|
"bridge" = "Jembatan"
|
||||||
"portal" = "Portal"
|
"portal" = "Portal"
|
||||||
"intercon" = "Interkoneksi"
|
"intercon" = "Interkoneksi"
|
||||||
|
"settings" = "Pengaturan"
|
||||||
|
"accountInfo" = "Informasi Akun"
|
||||||
|
"outboundStatus" = "Status Keluar"
|
||||||
|
"sendThrough" = "Kirim Melalui"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
[pages.xray.balancer]
|
||||||
"addBalancer" = "Tambahkan Penyeimbang"
|
"addBalancer" = "Tambahkan Penyeimbang"
|
||||||
|
@ -467,6 +471,8 @@
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "Aktifkan DNS"
|
"enable" = "Aktifkan DNS"
|
||||||
"enableDesc" = "Aktifkan server DNS bawaan"
|
"enableDesc" = "Aktifkan server DNS bawaan"
|
||||||
|
"tag" = "Tanda DNS Masuk"
|
||||||
|
"tagDesc" = "Tanda ini akan tersedia sebagai tanda masuk dalam aturan penataan."
|
||||||
"strategy" = "Strategi Kueri"
|
"strategy" = "Strategi Kueri"
|
||||||
"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain"
|
"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain"
|
||||||
"add" = "Tambahkan Server"
|
"add" = "Tambahkan Server"
|
||||||
|
|
|
@ -327,7 +327,7 @@
|
||||||
"blockCountryConfigs" = "Конфигурации блокировки страны"
|
"blockCountryConfigs" = "Конфигурации блокировки страны"
|
||||||
"blockCountryConfigsDesc" = "Эти параметры не позволят пользователям подключаться к доменам определенной страны"
|
"blockCountryConfigsDesc" = "Эти параметры не позволят пользователям подключаться к доменам определенной страны"
|
||||||
"directCountryConfigs" = "Настройки прямого подключения для страны"
|
"directCountryConfigs" = "Настройки прямого подключения для страны"
|
||||||
"directCountryConfigsDesc" = "Эти параметры позволят пользователям подключаться напрямую к доменам определенной страны"
|
"directCountryConfigsDesc" = "Прямое подключение обеспечивает, что конкретный трафик не направляется через другой сервер."
|
||||||
"ipv4Configs" = "Настройки IPv4"
|
"ipv4Configs" = "Настройки IPv4"
|
||||||
"ipv4ConfigsDesc" = "Эти параметры позволят пользователям маршрутизироваться к целевым доменам только через IPv4"
|
"ipv4ConfigsDesc" = "Эти параметры позволят пользователям маршрутизироваться к целевым доменам только через IPv4"
|
||||||
"warpConfigs" = "Настройки WARP"
|
"warpConfigs" = "Настройки WARP"
|
||||||
|
@ -446,6 +446,10 @@
|
||||||
"bridge" = "Мост"
|
"bridge" = "Мост"
|
||||||
"portal" = "Портал"
|
"portal" = "Портал"
|
||||||
"intercon" = "Соединение"
|
"intercon" = "Соединение"
|
||||||
|
"settings" = "Настройки"
|
||||||
|
"accountInfo" = "Информация Об Учетной Записи"
|
||||||
|
"outboundStatus" = "Исходящий статус"
|
||||||
|
"sendThrough" = "Отправить через"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
[pages.xray.balancer]
|
||||||
"addBalancer" = "Добавить балансир"
|
"addBalancer" = "Добавить балансир"
|
||||||
|
@ -467,6 +471,8 @@
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "Включить DNS"
|
"enable" = "Включить DNS"
|
||||||
"enableDesc" = "Включить встроенный DNS-сервер"
|
"enableDesc" = "Включить встроенный DNS-сервер"
|
||||||
|
"tag" = "Входящий тег DNS"
|
||||||
|
"tagDesc" = "Этот тег будет доступен как входящий тег в правилах маршрутизации."
|
||||||
"strategy" = "Стратегия запроса"
|
"strategy" = "Стратегия запроса"
|
||||||
"strategyDesc" = "Общая стратегия разрешения доменных имен"
|
"strategyDesc" = "Общая стратегия разрешения доменных имен"
|
||||||
"add" = "Добавить сервер"
|
"add" = "Добавить сервер"
|
||||||
|
|
|
@ -327,7 +327,7 @@
|
||||||
"blockCountryConfigs" = "Заблокувати країну"
|
"blockCountryConfigs" = "Заблокувати країну"
|
||||||
"blockCountryConfigsDesc" = "Ці параметри блокуватимуть трафік на основі конкретної запитуваної країни."
|
"blockCountryConfigsDesc" = "Ці параметри блокуватимуть трафік на основі конкретної запитуваної країни."
|
||||||
"directCountryConfigs" = "Пряма країна"
|
"directCountryConfigs" = "Пряма країна"
|
||||||
"directCountryConfigsDesc" = "Ці параметри безпосередньо перенаправлятимуть трафік на основі конкретної запитуваної країни."
|
"directCountryConfigsDesc" = "Пряме підключення забезпечує, що конкретний трафік не маршрутизується через інший сервер."
|
||||||
"ipv4Configs" = "Маршрутизація IPv4"
|
"ipv4Configs" = "Маршрутизація IPv4"
|
||||||
"ipv4ConfigsDesc" = "Ці параметри спрямовуватимуть трафік на основі певного призначення через IPv4."
|
"ipv4ConfigsDesc" = "Ці параметри спрямовуватимуть трафік на основі певного призначення через IPv4."
|
||||||
"warpConfigs" = "WARP маршрутизація"
|
"warpConfigs" = "WARP маршрутизація"
|
||||||
|
@ -446,6 +446,10 @@
|
||||||
"bridge" = "Міст"
|
"bridge" = "Міст"
|
||||||
"portal" = "Портал"
|
"portal" = "Портал"
|
||||||
"intercon" = "Взаємозв'язок"
|
"intercon" = "Взаємозв'язок"
|
||||||
|
"settings" = "Налаштування"
|
||||||
|
"accountInfo" = "Інформація про обліковий запис"
|
||||||
|
"outboundStatus" = "Статус виходу"
|
||||||
|
"sendThrough" = "Надіслати через"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
[pages.xray.balancer]
|
||||||
"addBalancer" = "Додати балансир"
|
"addBalancer" = "Додати балансир"
|
||||||
|
@ -467,6 +471,8 @@
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "Увімкнути DNS"
|
"enable" = "Увімкнути DNS"
|
||||||
"enableDesc" = "Увімкнути вбудований DNS-сервер"
|
"enableDesc" = "Увімкнути вбудований DNS-сервер"
|
||||||
|
"tag" = "Мітка вхідного DNS"
|
||||||
|
"tagDesc" = "Ця мітка буде доступна як вхідна мітка в правилах маршрутизації."
|
||||||
"strategy" = "Стратегія запиту"
|
"strategy" = "Стратегія запиту"
|
||||||
"strategyDesc" = "Загальна стратегія вирішення доменних імен"
|
"strategyDesc" = "Загальна стратегія вирішення доменних імен"
|
||||||
"add" = "Додати сервер"
|
"add" = "Додати сервер"
|
||||||
|
|
|
@ -327,7 +327,7 @@
|
||||||
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
|
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
|
||||||
"blockCountryConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các tên miền quốc gia cụ thể."
|
"blockCountryConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các tên miền quốc gia cụ thể."
|
||||||
"directCountryConfigs" = "Cấu hình Kết nối Trực tiếp Quốc gia"
|
"directCountryConfigs" = "Cấu hình Kết nối Trực tiếp Quốc gia"
|
||||||
"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" = "Một kết nối trực tiếp đảm bảo rằng lưu lượng cụ thể không được định tuyến qua một máy chủ khác."
|
||||||
"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"
|
"warpConfigs" = "Cấu hình WARP"
|
||||||
|
@ -446,6 +446,10 @@
|
||||||
"bridge" = "Cầu"
|
"bridge" = "Cầu"
|
||||||
"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"
|
||||||
|
"sendThrough" = "Gửi qua"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
[pages.xray.balancer]
|
||||||
"addBalancer" = "Thêm cân bằng"
|
"addBalancer" = "Thêm cân bằng"
|
||||||
|
@ -467,6 +471,8 @@
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "Kích hoạt DNS"
|
"enable" = "Kích hoạt DNS"
|
||||||
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
|
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
|
||||||
|
"tag" = "Thẻ gửi đến DNS"
|
||||||
|
"tagDesc" = "Thẻ này sẽ có sẵn dưới dạng thẻ Gửi đến trong quy tắc định tuyến."
|
||||||
"strategy" = "Chiến lược truy vấn"
|
"strategy" = "Chiến lược truy vấn"
|
||||||
"strategyDesc" = "Chiến lược tổng thể để phân giải tên miền"
|
"strategyDesc" = "Chiến lược tổng thể để phân giải tên miền"
|
||||||
"add" = "Thêm máy chủ"
|
"add" = "Thêm máy chủ"
|
||||||
|
|
|
@ -30,8 +30,8 @@
|
||||||
"sure" = "确定"
|
"sure" = "确定"
|
||||||
"encryption" = "加密"
|
"encryption" = "加密"
|
||||||
"transmission" = "传输"
|
"transmission" = "传输"
|
||||||
"host" = "Host"
|
"host" = "主机"
|
||||||
"path" = "Path"
|
"path" = "路径"
|
||||||
"camouflage" = "伪装"
|
"camouflage" = "伪装"
|
||||||
"status" = "状态"
|
"status" = "状态"
|
||||||
"enabled" = "开启"
|
"enabled" = "开启"
|
||||||
|
@ -48,15 +48,15 @@
|
||||||
"getVersion" = "获取版本"
|
"getVersion" = "获取版本"
|
||||||
"install" = "安装"
|
"install" = "安装"
|
||||||
"clients" = "客户端"
|
"clients" = "客户端"
|
||||||
"usage" = "用法"
|
"usage" = "使用情况"
|
||||||
"secretToken" = "安全密钥"
|
"secretToken" = "安全密钥"
|
||||||
"remained" = "剩余"
|
"remained" = "剩余"
|
||||||
"security" = "安全"
|
"security" = "安全"
|
||||||
"secAlertTitle" = "安全警报"
|
"secAlertTitle" = "安全警报"
|
||||||
"secAlertSsl" = "此连接不安全;在激活 TLS 进行数据保护之前,请勿输入敏感信息"
|
"secAlertSsl" = "此连接不安全。在激活 TLS 进行数据保护之前,请勿输入敏感信息。"
|
||||||
"secAlertConf" = "某些设置容易受到攻击。建议加强安全协议以防止潜在的违规行为。"
|
"secAlertConf" = "某些设置易受攻击。建议加强安全协议以防止潜在漏洞。"
|
||||||
"secAlertSSL" = "面板缺乏安全连接。请安装 TLS 证书以保护数据。"
|
"secAlertSSL" = "面板缺少安全连接。请安装 TLS 证书以保护数据安全。"
|
||||||
"secAlertPanelPort" = "面板默认端口存在漏洞。请配置随机或特定端口。"
|
"secAlertPanelPort" = "面板默认端口存在安全风险。请配置随机端口或特定端口。"
|
||||||
"secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
"secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
||||||
"secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
"secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
||||||
"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
||||||
|
@ -84,33 +84,33 @@
|
||||||
[pages.index]
|
[pages.index]
|
||||||
"title" = "系统状态"
|
"title" = "系统状态"
|
||||||
"memory" = "内存"
|
"memory" = "内存"
|
||||||
"hard" = "硬盘"
|
"hard" = "磁盘"
|
||||||
"xrayStatus" = "Xray"
|
"xrayStatus" = "Xray"
|
||||||
"stopXray" = "停止"
|
"stopXray" = "停止"
|
||||||
"restartXray" = "重启"
|
"restartXray" = "重启"
|
||||||
"xraySwitch" = "版本"
|
"xraySwitch" = "版本"
|
||||||
"xraySwitchClick" = "点击你想切换的版本"
|
"xraySwitchClick" = "选择你要切换到的版本"
|
||||||
"xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容"
|
"xraySwitchClickDesk" = "请谨慎选择,因为较旧版本可能与当前配置不兼容"
|
||||||
"operationHours" = "系统正常运行时间"
|
"operationHours" = "系统正常运行时间"
|
||||||
"systemLoad" = "系统负载"
|
"systemLoad" = "系统负载"
|
||||||
"systemLoadDesc" = "过去 1、5 和 15 分钟的系统平均负载"
|
"systemLoadDesc" = "过去 1、5 和 15 分钟的系统平均负载"
|
||||||
"connectionTcpCountDesc" = "所有网卡的总 TCP 连接数。"
|
"connectionTcpCountDesc" = "系统中所有 TCP 连接数"
|
||||||
"connectionUdpCountDesc" = "所有网卡的总 UDP 连接数。"
|
"connectionUdpCountDesc" = "系统中所有 UDP 连接数"
|
||||||
"connectionCount" = "连接数"
|
"connectionCount" = "连接数"
|
||||||
"upSpeed" = "所有网卡的总上传速度"
|
"upSpeed" = "总上传速度"
|
||||||
"downSpeed" = "所有网卡的总下载速度"
|
"downSpeed" = "总下载速度"
|
||||||
"totalSent" = "系统启动以来所有网卡的总上传流量"
|
"totalSent" = "系统启动以来发送的总数据量"
|
||||||
"totalReceive" = "系统启动以来所有网卡的总下载流量"
|
"totalReceive" = "系统启动以来接收的总数据量"
|
||||||
"xraySwitchVersionDialog" = "切换 xray 版本"
|
"xraySwitchVersionDialog" = "切换 Xray 版本"
|
||||||
"xraySwitchVersionDialogDesc" = "是否切换 xray 版本至"
|
"xraySwitchVersionDialogDesc" = "是否切换 Xray 版本至"
|
||||||
"dontRefresh" = "安装中,请不要刷新此页面"
|
"dontRefresh" = "安装中,请勿刷新此页面"
|
||||||
"logs" = "日志"
|
"logs" = "日志"
|
||||||
"config" = "配置"
|
"config" = "配置"
|
||||||
"backup" = "备份还原"
|
"backup" = "备份和恢复"
|
||||||
"backupTitle" = "备份和恢复数据库"
|
"backupTitle" = "备份和恢复数据库"
|
||||||
"backupDescription" = "请记住在导入新数据库之前进行备份。"
|
"backupDescription" = "恢复数据库之前建议进行备份"
|
||||||
"exportDatabase" = "下载数据库"
|
"exportDatabase" = "备份"
|
||||||
"importDatabase" = "上传数据库"
|
"importDatabase" = "恢复"
|
||||||
|
|
||||||
[pages.inbounds]
|
[pages.inbounds]
|
||||||
"title" = "入站列表"
|
"title" = "入站列表"
|
||||||
|
@ -133,20 +133,20 @@
|
||||||
"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" = "留空表示监听所有 IP"
|
||||||
"meansNoLimit" = " = 无限制(单位:GB)"
|
"meansNoLimit" = " = 无限制(单位:GB)"
|
||||||
"totalFlow" = "总流量"
|
"totalFlow" = "总流量"
|
||||||
"leaveBlankToNeverExpire" = "留空则永不到期"
|
"leaveBlankToNeverExpire" = "留空表示永不过期"
|
||||||
"noRecommendKeepDefault" = "没有特殊需求保持默认即可"
|
"noRecommendKeepDefault" = "建议保留默认值"
|
||||||
"certificatePath" = "文件路径"
|
"certificatePath" = "文件路径"
|
||||||
"certificateContent" = "文件内容"
|
"certificateContent" = "文件内容"
|
||||||
"publicKey" = "公钥"
|
"publicKey" = "公钥"
|
||||||
|
@ -156,71 +156,71 @@
|
||||||
"export" = "导出链接"
|
"export" = "导出链接"
|
||||||
"clone" = "克隆"
|
"clone" = "克隆"
|
||||||
"cloneInbound" = "克隆"
|
"cloneInbound" = "克隆"
|
||||||
"cloneInboundContent" = "此入站的所有项目除 Port、Listening IP、Clients 将应用于克隆"
|
"cloneInboundContent" = "此入站规则除端口(Port)、监听 IP(Listening IP)和客户端(Clients)以外的所有配置都将应用于克隆"
|
||||||
"cloneInboundOk" = "从创建克隆"
|
"cloneInboundOk" = "创建克隆"
|
||||||
"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" = "电子邮件必须完全唯一"
|
||||||
"IPLimit" = "IP 限制"
|
"IPLimit" = "IP 限制"
|
||||||
"IPLimitDesc" = "如果超过输入的计数则禁用入站(0 表示禁用限制 ip)"
|
"IPLimitDesc" = "如果数量超过设置值,则禁用入站流量。(0 = 禁用)"
|
||||||
"IPLimitlog" = "IP 日志"
|
"IPLimitlog" = "IP 日志"
|
||||||
"IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
|
"IPLimitlogDesc" = "IP 历史日志(要启用被禁用的入站流量,请清除日志)"
|
||||||
"IPLimitlogclear" = "清除日志"
|
"IPLimitlogclear" = "清除日志"
|
||||||
"setDefaultCert" = "从面板设置证书"
|
"setDefaultCert" = "从面板设置证书"
|
||||||
"xtlsDesc" = "Xray 核心需要 1.7.5"
|
"xtlsDesc" = "Xray 核心需要 1.7.5"
|
||||||
"realityDesc" = "Xray 核心需要 1.8.0 及以上版本"
|
"realityDesc" = "Xray 核心需要 1.8.0 及以上版本"
|
||||||
"telegramDesc" = "仅使用聊天 ID(可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)"
|
"telegramDesc" = "请输入电报 (Telegram) 或聊天 ID,无需添加 '@' 符号。(可通过 @userinfobot 获取,或在机器人中使用 '/id' 命令)"
|
||||||
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称"
|
"subscriptionDesc" = "要找到你的订阅 URL,请导航到“详细信息”。此外,你可以为多个客户端使用相同的名称。"
|
||||||
"info" = "信息"
|
"info" = "信息"
|
||||||
"same" = "相同"
|
"same" = "相同"
|
||||||
"inboundData" = "入站数据"
|
"inboundData" = "入站数据"
|
||||||
"exportInbound" = "出口 入境"
|
"exportInbound" = "导出入站规则"
|
||||||
"import"="导入"
|
"import"="导入"
|
||||||
"importInbound" = "导入入站"
|
"importInbound" = "导入入站规则"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "添加客户端"
|
"add" = "添加客户端"
|
||||||
"edit" = "编辑客户端"
|
"edit" = "编辑客户端"
|
||||||
"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" = "获取"
|
||||||
|
|
||||||
[pages.inbounds.stream.general]
|
[pages.inbounds.stream.general]
|
||||||
"request" = "要求"
|
"request" = "请求"
|
||||||
"response" = "回复"
|
"response" = "响应"
|
||||||
"name" = "姓名"
|
"name" = "名称"
|
||||||
"value" = "价值"
|
"value" = "值"
|
||||||
|
|
||||||
[pages.inbounds.stream.tcp]
|
[pages.inbounds.stream.tcp]
|
||||||
"version" = "版本"
|
"version" = "版本"
|
||||||
"method" = "方法"
|
"method" = "方法"
|
||||||
"path" = "小路"
|
"path" = "路径"
|
||||||
"status" = "地位"
|
"status" = "状态"
|
||||||
"statusDescription" = "状态说明"
|
"statusDescription" = "状态说明"
|
||||||
"requestHeader" = "请求头"
|
"requestHeader" = "请求头"
|
||||||
"responseHeader" = "响应头"
|
"responseHeader" = "响应头"
|
||||||
|
@ -229,16 +229,16 @@
|
||||||
"encryption" = "加密"
|
"encryption" = "加密"
|
||||||
|
|
||||||
[pages.settings]
|
[pages.settings]
|
||||||
"title" = "设置"
|
"title" = "面板设置"
|
||||||
"save" = "保存配置"
|
"save" = "保存"
|
||||||
"infoDesc" = "此处的所有更改都需要保存并重启面板才能生效"
|
"infoDesc" = "此处的所有更改都需要保存并重启面板才能生效"
|
||||||
"restartPanel" = "重启面板"
|
"restartPanel" = "重启面板"
|
||||||
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
"restartPanelDesc" = "确定要重启面板吗?若重启后无法访问面板,请前往服务器查看面板日志信息"
|
||||||
"actions" = "动作"
|
"actions" = "操作"
|
||||||
"resetDefaultConfig" = "重置为默认配置"
|
"resetDefaultConfig" = "重置为默认配置"
|
||||||
"panelSettings" = "面板配置"
|
"panelSettings" = "常规"
|
||||||
"securitySettings" = "安全设定"
|
"securitySettings" = "安全设定"
|
||||||
"TGBotSettings" = "TG提醒相关设置"
|
"TGBotSettings" = "Telegram 机器人配置"
|
||||||
"panelListeningIP" = "面板监听 IP"
|
"panelListeningIP" = "面板监听 IP"
|
||||||
"panelListeningIPDesc" = "默认留空监听所有 IP"
|
"panelListeningIPDesc" = "默认留空监听所有 IP"
|
||||||
"panelListeningDomain" = "面板监听域名"
|
"panelListeningDomain" = "面板监听域名"
|
||||||
|
@ -262,171 +262,171 @@
|
||||||
"currentPassword" = "原密码"
|
"currentPassword" = "原密码"
|
||||||
"newUsername" = "新用户名"
|
"newUsername" = "新用户名"
|
||||||
"newPassword" = "新密码"
|
"newPassword" = "新密码"
|
||||||
"telegramBotEnable" = "启用电报机器人"
|
"telegramBotEnable" = "启用 Telegram 机器人"
|
||||||
"telegramBotEnableDesc" = "重启面板生效"
|
"telegramBotEnableDesc" = "启用 Telegram 机器人功能"
|
||||||
"telegramToken" = "电报机器人TOKEN"
|
"telegramToken" = "Telegram 机器人令牌(token)"
|
||||||
"telegramTokenDesc" = "重启面板生效"
|
"telegramTokenDesc" = "从 '@BotFather' 获取的 Telegram 机器人令牌"
|
||||||
"telegramProxy" = "Socks5 代理"
|
"telegramProxy" = "SOCKS5 Proxy"
|
||||||
"telegramProxyDesc" = "如果您需要 Socks5 代理来连接 Telegram。 根据指南调整其设置。"
|
"telegramProxyDesc" = "启用 SOCKS5 代理连接到 Telegram(根据指南调整设置)"
|
||||||
"telegramChatId" = "以逗号分隔的多个 chatID 重启面板生效"
|
"telegramChatId" = "管理员聊天 ID"
|
||||||
"telegramChatIdDesc" = "多个聊天 ID 用逗号分隔。使用 @userinfobot 或在机器人中使用'/id'命令获取您的聊天 ID。"
|
"telegramChatIdDesc" = "Telegram 管理员聊天 ID (多个以逗号分隔)(可通过 @userinfobot 获取,或在机器人中使用 '/id' 命令获取)"
|
||||||
"telegramNotifyTime" = "电报机器人通知时间"
|
"telegramNotifyTime" = "通知时间"
|
||||||
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
"telegramNotifyTimeDesc" = "设置周期性的 Telegram 机器人通知时间(使用 crontab 时间格式)"
|
||||||
"tgNotifyBackup" = "数据库备份"
|
"tgNotifyBackup" = "数据库备份"
|
||||||
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知"
|
"tgNotifyBackupDesc" = "发送带有报告的数据库备份文件"
|
||||||
"tgNotifyLogin" = "登录通知"
|
"tgNotifyLogin" = "登录通知"
|
||||||
"tgNotifyLoginDesc" = "当有人试图登录您的面板时显示用户名、IP 地址和时间"
|
"tgNotifyLoginDesc" = "当有人试图登录你的面板时显示用户名、IP 地址和时间"
|
||||||
"sessionMaxAge" = "会话最大年龄"
|
"sessionMaxAge" = "会话时长"
|
||||||
"sessionMaxAgeDesc" = "您可以保持登录状态的时间(单位:分钟)"
|
"sessionMaxAgeDesc" = "保持登录状态的时长(单位:分钟)"
|
||||||
"expireTimeDiff" = "耗尽时间阈值"
|
"expireTimeDiff" = "到期通知阈值"
|
||||||
"expireTimeDiffDesc" = "到期前检测耗尽(单位:天)"
|
"expireTimeDiffDesc" = "达到此阈值时,将收到有关到期时间的通知(单位:天)"
|
||||||
"trafficDiff" = "耗尽流量阈值"
|
"trafficDiff" = "流量耗尽阈值"
|
||||||
"trafficDiffDesc" = "完成流量前检测耗尽(单位:GB)"
|
"trafficDiffDesc" = "达到此阈值时,将收到有关流量耗尽的通知(单位:GB)"
|
||||||
"tgNotifyCpu" = "CPU 百分比警报阈值"
|
"tgNotifyCpu" = "CPU 负载通知阈值"
|
||||||
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
|
"tgNotifyCpuDesc" = "CPU 负载超过此阈值时,将收到通知(单位:%)"
|
||||||
"timeZone" = "时区"
|
"timeZone" = "时区"
|
||||||
"timeZoneDesc" = "定时任务按照该时区的时间运行"
|
"timeZoneDesc" = "定时任务将按照该时区的时间运行"
|
||||||
"subSettings" = "订阅"
|
"subSettings" = "订阅设置"
|
||||||
"subEnable" = "启用服务"
|
"subEnable" = "启用订阅服务"
|
||||||
"subEnableDesc" = "具有单独配置的订阅功能"
|
"subEnableDesc" = "启用订阅服务功能"
|
||||||
"subListen" = "监听 IP"
|
"subListen" = "监听 IP"
|
||||||
"subListenDesc" = "留空默认监听所有IP"
|
"subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP)"
|
||||||
"subPort" = "订阅端口"
|
"subPort" = "监听端口"
|
||||||
"subPortDesc" = "服务订阅服务的端口号必须在服务器中未使用"
|
"subPortDesc" = "订阅服务监听的端口号(必须是未使用的端口)"
|
||||||
"subCertPath" = "订阅证书公钥文件路径"
|
"subCertPath" = "公钥路径"
|
||||||
"subCertPathDesc" = "填写以'/'开头的绝对路径"
|
"subCertPathDesc" = "订阅服务使用的公钥文件路径(以 '/' 开头)"
|
||||||
"subKeyPath" = "订阅证书私钥文件路径"
|
"subKeyPath" = "私钥路径"
|
||||||
"subKeyPathDesc" = "填写以'/'开头的绝对路径"
|
"subKeyPathDesc" = "订阅服务使用的私钥文件路径(以 '/' 开头)"
|
||||||
"subPath" = "订阅 URL 根路径"
|
"subPath" = "URI 路径"
|
||||||
"subPathDesc" = "必须以'/'开始并以'/'结束"
|
"subPathDesc" = "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)"
|
||||||
"subDomain" = "监听域名"
|
"subDomain" = "监听域名"
|
||||||
"subDomainDesc" = "留空默认监控所有域名和IP"
|
"subDomainDesc" = "订阅服务监听的域名(留空表示监听所有域名和 IP)"
|
||||||
"subUpdates" = "订阅更新间隔"
|
"subUpdates" = "更新间隔"
|
||||||
"subUpdatesDesc" = "客户端应用程序更新订阅的间隔时间"
|
"subUpdatesDesc" = "客户端应用中订阅 URL 的更新间隔(单位:小时)"
|
||||||
"subEncrypt" = "加密配置"
|
"subEncrypt" = "编码"
|
||||||
"subEncryptDesc" = "在订阅中加密返回的配置"
|
"subEncryptDesc" = "订阅服务返回的内容将采用 Base64 编码"
|
||||||
"subShowInfo" = "显示使用信息"
|
"subShowInfo" = "显示使用信息"
|
||||||
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
"subShowInfoDesc" = "客户端应用中将显示剩余流量和日期信息"
|
||||||
"subURI" = "反向代理 URI"
|
"subURI" = "反向代理 URI"
|
||||||
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
"subURIDesc" = "用于代理后面的订阅 URL 的 URI 路径"
|
||||||
"fragment" = "碎片"
|
"fragment" = "分片"
|
||||||
"fragmentDesc" = "启用 TLS hello 数据包分段"
|
"fragmentDesc" = "启用 TLS hello 数据包分片"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Xray 设置"
|
"title" = "Xray 配置"
|
||||||
"save" = "保存设置"
|
"save" = "保存"
|
||||||
"restart" = "重新启动 Xray"
|
"restart" = "重新启动 Xray"
|
||||||
"basicTemplate" = "基本模板"
|
"basicTemplate" = "基础配置"
|
||||||
"advancedTemplate" = "高级模板部件"
|
"advancedTemplate" = "高级配置"
|
||||||
"generalConfigs" = "通用配置"
|
"generalConfigs" = "常规配置"
|
||||||
"generalConfigsDesc" = "这些选项将提供一般调整"
|
"generalConfigsDesc" = "这些选项将决定常规配置"
|
||||||
"logConfigs" = "日志"
|
"logConfigs" = "日志"
|
||||||
"logConfigsDesc" = "日志可能会影响您服务器的效率。建议仅在您需要时明智地启用它"
|
"logConfigsDesc" = "日志可能会影响服务器的性能,建议仅在需要时启用"
|
||||||
"blockConfigs" = "阻塞配置"
|
"blockConfigs" = "防护屏蔽"
|
||||||
"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站"
|
"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站"
|
||||||
"blockCountryConfigs" = "阻止国家配置"
|
"blockCountryConfigs" = "屏蔽国家/地区"
|
||||||
"blockCountryConfigsDesc" = "这些选项将阻止用户连接到特定国家/地区的域。"
|
"blockCountryConfigsDesc" = "这些选项将阻止用户连接到特定国家/地区"
|
||||||
"directCountryConfigs" = "直接国家配置"
|
"directCountryConfigs" = "直连国家/地区"
|
||||||
"directCountryConfigsDesc" = "这些选项会将用户直接连接到特定国家/地区的域。"
|
"directCountryConfigsDesc" = "直接连接可确保特定流量不会通过其他服务器路由"
|
||||||
"ipv4Configs" = "IPv4 配置"
|
"ipv4Configs" = "IPv4 路由"
|
||||||
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域"
|
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域"
|
||||||
"warpConfigs" = "WARP 配置"
|
"warpConfigs" = "WARP 路由"
|
||||||
"warpConfigsDesc" = "注意:在使用这些选项之前,请按照面板 GitHub 上的步骤在您的服务器上以 socks5 代理模式安装 WARP。 WARP 将通过 Cloudflare 服务器将流量路由到网站。"
|
"warpConfigsDesc" = "注意:在使用这些选项之前,请按照面板 GitHub 上的步骤在你的服务器上以 socks5 代理模式安装 WARP。WARP 将通过 Cloudflare 服务器将流量路由到网站。"
|
||||||
"Template" = "Xray 配置模板"
|
"Template" = "高级 Xray 配置模板"
|
||||||
"TemplateDesc" = "以该模型为基础生成最终的Xray配置文件,重新启动面板生成效率"
|
"TemplateDesc" = "最终的 Xray 配置文件将基于此模板生成"
|
||||||
"FreedomStrategy" = "配置自由协议的策略"
|
"FreedomStrategy" = "Freedom 协议策略"
|
||||||
"FreedomStrategyDesc" = "在自由协议中设置网络输出策略"
|
"FreedomStrategyDesc" = "设置 Freedom 协议中网络的输出策略"
|
||||||
"RoutingStrategy" = "配置路由域策略"
|
"RoutingStrategy" = "配置路由域策略"
|
||||||
"RoutingStrategyDesc" = "设置 DNS 解析的整体路由策略"
|
"RoutingStrategyDesc" = "设置 DNS 解析的整体路由策略"
|
||||||
"Torrent" = "禁止使用 bittorrent"
|
"Torrent" = "屏蔽 BitTorrent 协议"
|
||||||
"TorrentDesc" = "更改配置模板避免用户使用bittorrent"
|
"TorrentDesc" = "禁止使用 BitTorrent"
|
||||||
"PrivateIp" = "禁止私人 IP 范围连接"
|
"PrivateIp" = "屏蔽私有 IP"
|
||||||
"PrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围"
|
"PrivateIpDesc" = "阻止连接到私有 IP"
|
||||||
"Ads" = "屏蔽广告"
|
"Ads" = "屏蔽广告"
|
||||||
"AdsDesc" = "修改配置模板屏蔽广告"
|
"AdsDesc" = "屏蔽广告网站"
|
||||||
"Family" = "阻止恶意软件和成人内容"
|
"Family" = "家庭保护"
|
||||||
"FamilyDesc" = "Cloudflare DNS 解析器可阻止恶意软件和成人内容以保护家庭."
|
"FamilyDesc" = "屏蔽成人内容和恶意网站"
|
||||||
"Security" = "阻止恶意软件、网络钓鱼和加密货币挖矿网站"
|
"Security" = "安全防护"
|
||||||
"SecurityDesc" = "更改安全防护配置模板."
|
"SecurityDesc" = "屏蔽恶意软件、网络钓鱼和挖矿网站"
|
||||||
"Speedtest" = "阻止测速网站"
|
"Speedtest" = "屏蔽测速网站"
|
||||||
"SpeedtestDesc" = "更改配置模板以避免连接到速度测试网站。 重新启动面板以应用更改。"
|
"SpeedtestDesc" = "阻止连接到测速网站"
|
||||||
"IRIp" = "禁止伊朗 IP 范围连接"
|
"IRIp" = "屏蔽连接到伊朗 IP"
|
||||||
"IRIpDesc" = "修改配置模板避免连接伊朗IP段"
|
"IRIpDesc" = "阻止建立到伊朗 IP 范围的连接"
|
||||||
"IRDomain" = "禁止伊朗域连接"
|
"IRDomain" = "屏蔽连接到伊朗域名"
|
||||||
"IRDomainDesc" = "更改配置模板避免连接伊朗域名"
|
"IRDomainDesc" = "阻止建立到伊朗域名的连接"
|
||||||
"ChinaIp" = "禁止中国 IP 范围连接"
|
"ChinaIp" = "屏蔽连接到中国 IP"
|
||||||
"ChinaIpDesc" = "修改配置模板避免连接中国IP段"
|
"ChinaIpDesc" = "阻止建立到中国 IP 范围的连接"
|
||||||
"ChinaDomain" = "禁止中国域名连接"
|
"ChinaDomain" = "屏蔽连接到中国域名"
|
||||||
"ChinaDomainDesc" = "更改配置模板避免连接中国域"
|
"ChinaDomainDesc" = "阻止建立到中国域名的连接"
|
||||||
"RussiaIp" = "禁止俄罗斯 IP 范围连接"
|
"RussiaIp" = "屏蔽连接到俄罗斯 IP"
|
||||||
"RussiaIpDesc" = "修改配置模板避免连接俄罗斯IP范围"
|
"RussiaIpDesc" = "阻止建立到俄罗斯 IP 范围的连接"
|
||||||
"RussiaDomain" = "禁止俄罗斯域连接"
|
"RussiaDomain" = "屏蔽连接到俄罗斯域名"
|
||||||
"RussiaDomainDesc" = "更改配置模板避免连接俄罗斯域"
|
"RussiaDomainDesc" = "阻止建立到俄罗斯域名的连接"
|
||||||
"VNIp" = "禁用与越南 IP 的连接"
|
"VNIp" = "屏蔽连接到越南 IP"
|
||||||
"VNIpDesc" = "更改配置模板以避免连接到越南 IP 范围"
|
"VNIpDesc" = "阻止建立到越南 IP 范围的连接"
|
||||||
"VNDomain" = "禁用与越南域的连接"
|
"VNDomain" = "屏蔽连接到越南域名"
|
||||||
"VNDomainDesc" = "更改配置模板以避免连接到越南域"
|
"VNDomainDesc" = "阻止建立到越南域名的连接"
|
||||||
"DirectIRIp" = "直接连接到伊朗 IP 范围"
|
"DirectIRIp" = "直连伊朗 IP"
|
||||||
"DirectIRIpDesc" = "更改直接连接到伊朗 IP 范围的配置模板"
|
"DirectIRIpDesc" = "直接建立到伊朗 IP 范围的连接"
|
||||||
"DirectIRDomain" = "直接连接到伊朗域"
|
"DirectIRDomain" = "直连伊朗域名"
|
||||||
"DirectIRDomainDesc" = "更改直接连接到伊朗域的配置模板"
|
"DirectIRDomainDesc" = "直接建立到伊朗域名的连接"
|
||||||
"DirectChinaIp" = "直连中国IP范围"
|
"DirectChinaIp" = "直连中国 IP"
|
||||||
"DirectChinaIpDesc" = "更改直连中国 IP 范围的配置模板"
|
"DirectChinaIpDesc" = "直接建立到中国 IP 范围的连接"
|
||||||
"DirectChinaDomain" = "直连中国域名"
|
"DirectChinaDomain" = "直连中国域名"
|
||||||
"DirectChinaDomainDesc" = "修改中国域名直连配置模板"
|
"DirectChinaDomainDesc" = "直接建立到中国域名的连接"
|
||||||
"DirectRussiaIp" = "直接连接到俄罗斯 IP 范围"
|
"DirectRussiaIp" = "直连俄罗斯 IP"
|
||||||
"DirectRussiaIpDesc" = "更改直接连接到俄罗斯 IP 范围的配置模板"
|
"DirectRussiaIpDesc" = "直接建立到俄罗斯 IP 范围的连接"
|
||||||
"DirectRussiaDomain" = "直接连接到俄罗斯域"
|
"DirectRussiaDomain" = "直连俄罗斯域名"
|
||||||
"DirectRussiaDomainDesc" = "更改直接连接到俄罗斯域的配置模板"
|
"DirectRussiaDomainDesc" = "直接建立到俄罗斯域名的连接"
|
||||||
"DirectVNIp" = "直接连接越南IP"
|
"DirectVNIp" = "直连越南 IP"
|
||||||
"DirectVNIpDesc" = "更改直接连接到越南 IP 范围的配置模板"
|
"DirectVNIpDesc" = "直接建立到越南 IP 范围的连接"
|
||||||
"DirectVNDomain" = "直接连接至越南域名"
|
"DirectVNDomain" = "直连越南域名"
|
||||||
"DirectVNDomainDesc" = "更改直连越南域的配置模板。"
|
"DirectVNDomainDesc" = "直接建立到越南域名的连接"
|
||||||
"GoogleIPv4" = "为谷歌使用 IPv4"
|
"GoogleIPv4" = "Google"
|
||||||
"GoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
|
"GoogleIPv4Desc" = "通过 IPv4 将流量路由到谷歌"
|
||||||
"NetflixIPv4" = "为 Netflix 使用 IPv4"
|
"NetflixIPv4" = "Netflix"
|
||||||
"NetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
|
"NetflixIPv4Desc" = "通过 IPv4 将流量路由到 Netflix"
|
||||||
"GoogleWARP" = "Google"
|
"GoogleWARP" = "Google"
|
||||||
"GoogleWARPDesc" = "通过 WARP 将流量路由到 Google。"
|
"GoogleWARPDesc" = "通过 WARP 将流量路由到 Google"
|
||||||
"OpenAIWARP" = "OpenAI (ChatGPT)"
|
"OpenAIWARP" = "OpenAI (ChatGPT)"
|
||||||
"OpenAIWARPDesc" = "通过 WARP 将流量路由到 OpenAI (ChatGPT)。"
|
"OpenAIWARPDesc" = "通过 WARP 将流量路由到 OpenAI (ChatGPT)"
|
||||||
"NetflixWARP" = "Netflix"
|
"NetflixWARP" = "Netflix"
|
||||||
"NetflixWARPDesc" = "通过 WARP 将流量路由到 Netflix。"
|
"NetflixWARPDesc" = "通过 WARP 将流量路由到 Netflix"
|
||||||
"MetaWARP"="Meta"
|
"MetaWARP"="Meta"
|
||||||
"MetaWARPDesc" = "通过 WARP 将流量路由到 Meta(Instagram、Facebook、WhatsApp、Threads...)"
|
"MetaWARPDesc" = "通过 WARP 将流量路由到 Meta(Instagram、Facebook、WhatsApp、Threads...)"
|
||||||
"AppleWARP" = "Apple"
|
"AppleWARP" = "Apple"
|
||||||
"AppleWARPDesc" = "通过 WARP 将流量路由到 Apple。"
|
"AppleWARPDesc" = "通过 WARP 将流量路由到 Apple"
|
||||||
"RedditWARP" = "Reddit"
|
"RedditWARP" = "Reddit"
|
||||||
"RedditWARPDesc" = "通过 WARP 将流量路由到 Reddit。"
|
"RedditWARPDesc" = "通过 WARP 将流量路由到 Reddit"
|
||||||
"SpotifyWARP" = "Spotify"
|
"SpotifyWARP" = "Spotify"
|
||||||
"SpotifyWARPDesc" = "通过 WARP 将流量路由到 Spotify。"
|
"SpotifyWARPDesc" = "通过 WARP 将流量路由到 Spotify"
|
||||||
"IRWARP" = "将伊朗域名路由到 WARP"
|
"IRWARP" = "伊朗域名"
|
||||||
"IRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效"
|
"IRWARPDesc" = "通过 WARP 将流量路由到伊朗域名"
|
||||||
"Inbounds" = "入站"
|
"Inbounds" = "入站规则"
|
||||||
"InboundsDesc" = "更改配置模板接受特殊客户端"
|
"InboundsDesc" = "接受来自特定客户端的流量"
|
||||||
"Outbounds" = "出站"
|
"Outbounds" = "出站规则"
|
||||||
"Balancers" = "平衡器"
|
"Balancers" = "负载均衡"
|
||||||
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式"
|
"OutboundsDesc" = "设置出站流量传出方式"
|
||||||
"Routings" = "路由规则"
|
"Routings" = "路由规则"
|
||||||
"RoutingsDesc" = "每条规则的优先级都很重要"
|
"RoutingsDesc" = "每条规则的优先级都很重要"
|
||||||
"completeTemplate" = "全部"
|
"completeTemplate" = "全部"
|
||||||
"logLevel" = "日志级别"
|
"logLevel" = "日志级别"
|
||||||
"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。"
|
"logLevelDesc" = "错误日志的日志级别,用于指示需要记录的信息"
|
||||||
"accessLog" = "访问日志"
|
"accessLog" = "访问日志"
|
||||||
"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志"
|
"accessLogDesc" = "访问日志的文件路径。特殊值 'none' 禁用访问日志"
|
||||||
"errorLog" = "错误日志"
|
"errorLog" = "错误日志"
|
||||||
"errorLogDesc" = "错误日志的文件路径。 特殊值“none”禁用错误日志"
|
"errorLogDesc" = "错误日志的文件路径。特殊值 'none' 禁用错误日志"
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "第一个"
|
"first" = "置顶"
|
||||||
"last" = "最后"
|
"last" = "置底"
|
||||||
"up" = "向上"
|
"up" = "向上"
|
||||||
"down" = "向下"
|
"down" = "向下"
|
||||||
"source" = "来源"
|
"source" = "来源"
|
||||||
"dest" = "目的地"
|
"dest" = "目的地址"
|
||||||
"inbound" = "入站"
|
"inbound" = "入站"
|
||||||
"outbound" = "出站"
|
"outbound" = "出站"
|
||||||
"balancer" = "平衡器"
|
"balancer" = "负载均衡"
|
||||||
"info" = "信息"
|
"info" = "信息"
|
||||||
"add" = "添加规则"
|
"add" = "添加规则"
|
||||||
"edit" = "编辑规则"
|
"edit" = "编辑规则"
|
||||||
|
@ -438,35 +438,41 @@
|
||||||
"editOutbound" = "编辑出站"
|
"editOutbound" = "编辑出站"
|
||||||
"editReverse" = "编辑反向"
|
"editReverse" = "编辑反向"
|
||||||
"tag" = "标签"
|
"tag" = "标签"
|
||||||
"tagDesc" = "唯一标记"
|
"tagDesc" = "唯一标签"
|
||||||
"address" = "地址"
|
"address" = "地址"
|
||||||
"reverse" = "反转"
|
"reverse" = "反向"
|
||||||
"domain" = "域名"
|
"domain" = "域名"
|
||||||
"type" = "类型"
|
"type" = "类型"
|
||||||
"bridge" = "桥"
|
"bridge" = "Bridge"
|
||||||
"portal" = "门户"
|
"portal" = "Portal"
|
||||||
"intercon" = "互连"
|
"intercon" = "互连"
|
||||||
|
"settings" = "设置"
|
||||||
|
"accountInfo" = "帐户信息"
|
||||||
|
"outboundStatus" = "出站状态"
|
||||||
|
"sendThrough" = "发送通过"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
[pages.xray.balancer]
|
||||||
"addBalancer" = "添加平衡器"
|
"addBalancer" = "添加负载均衡"
|
||||||
"editBalancer" = "编辑平衡器"
|
"editBalancer" = "编辑负载均衡"
|
||||||
"balancerStrategy" = "战略"
|
"balancerStrategy" = "策略"
|
||||||
"balancerSelectors" = "选择器"
|
"balancerSelectors" = "选择器"
|
||||||
"tag" = "标签"
|
"tag" = "标签"
|
||||||
"tagDesc" = "唯一标记"
|
"tagDesc" = "唯一标签"
|
||||||
"balancerDesc" = "不能同时使用balancerTag和outboundTag。 如果同时使用,则只有outboundTag起作用。"
|
"balancerDesc" = "无法同时使用 balancerTag 和 outboundTag。如果同时使用,则只有 outboundTag 会生效。"
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "密钥"
|
"secretKey" = "密钥"
|
||||||
"publicKey" = "公钥"
|
"publicKey" = "公钥"
|
||||||
"allowedIPs" = "允许的 IP"
|
"allowedIPs" = "允许的 IP"
|
||||||
"endpoint" = "终点"
|
"endpoint" = "端点"
|
||||||
"psk" = "共享密钥"
|
"psk" = "共享密钥"
|
||||||
"domainStrategy" = "域策略"
|
"domainStrategy" = "域策略"
|
||||||
|
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "启用 DNS"
|
"enable" = "启用 DNS"
|
||||||
"enableDesc" = "启用内置 DNS 服务器"
|
"enableDesc" = "启用内置 DNS 服务器"
|
||||||
|
"tag" = "DNS 入站标签"
|
||||||
|
"tagDesc" = "此标签将在路由规则中可用作入站标签"
|
||||||
"strategy" = "查询策略"
|
"strategy" = "查询策略"
|
||||||
"strategyDesc" = "解析域名的总体策略"
|
"strategyDesc" = "解析域名的总体策略"
|
||||||
"add" = "添加服务器"
|
"add" = "添加服务器"
|
||||||
|
@ -481,16 +487,16 @@
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "管理员"
|
"admin" = "管理员"
|
||||||
"secret" = "密钥"
|
"secret" = "安全令牌"
|
||||||
"loginSecurity" = "登录安全"
|
"loginSecurity" = "登录安全"
|
||||||
"loginSecurityDesc" = "在用户登录页面中切换附加步骤"
|
"loginSecurityDesc" = "添加额外的身份验证以提高安全性"
|
||||||
"secretToken" = "密钥"
|
"secretToken" = "安全令牌"
|
||||||
"secretTokenDesc" = "复制此密钥并将其保存在安全的地方;没有这个你将无法登录。这也无法从 x-ui 命令工具中恢复"
|
"secretTokenDesc" = "请将此令牌存储在安全的地方。此令牌用于登录,丢失无法恢复。"
|
||||||
|
|
||||||
[pages.settings.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySettings" = "修改设置"
|
"modifySettings" = "修改设置"
|
||||||
"getSettings" = "获取设置"
|
"getSettings" = "获取设置"
|
||||||
"modifyUser" = "修改用户"
|
"modifyUser" = "修改管理员"
|
||||||
"originalUserPassIncorrect" = "原用户名或原密码错误"
|
"originalUserPassIncorrect" = "原用户名或原密码错误"
|
||||||
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
||||||
|
|
||||||
|
@ -512,7 +518,7 @@
|
||||||
"inbounds" = "入站连接"
|
"inbounds" = "入站连接"
|
||||||
"clients" = "客户端"
|
"clients" = "客户端"
|
||||||
"offline" = "🔴 离线"
|
"offline" = "🔴 离线"
|
||||||
"online" = "🟢 在线的"
|
"online" = "🟢 在线"
|
||||||
|
|
||||||
[tgbot.commands]
|
[tgbot.commands]
|
||||||
"unknown" = "❗ 未知命令"
|
"unknown" = "❗ 未知命令"
|
||||||
|
@ -585,7 +591,7 @@
|
||||||
"getInbounds" = "获取入站信息"
|
"getInbounds" = "获取入站信息"
|
||||||
"depleteSoon" = "即将耗尽"
|
"depleteSoon" = "即将耗尽"
|
||||||
"clientUsage" = "获取使用情况"
|
"clientUsage" = "获取使用情况"
|
||||||
"onlines" = "在线客户"
|
"onlines" = "在线客户端"
|
||||||
"commands" = "命令"
|
"commands" = "命令"
|
||||||
"refresh" = "🔄 刷新"
|
"refresh" = "🔄 刷新"
|
||||||
"clearIPs" = "❌ 清除 IP"
|
"clearIPs" = "❌ 清除 IP"
|
||||||
|
@ -601,11 +607,11 @@
|
||||||
"custom" = "🔢 风俗"
|
"custom" = "🔢 风俗"
|
||||||
"confirmNumber" = "✅ 确认: {{ .Num }}"
|
"confirmNumber" = "✅ 确认: {{ .Num }}"
|
||||||
"confirmNumberAdd" = "✅ 确认添加:{{ .Num }}"
|
"confirmNumberAdd" = "✅ 确认添加:{{ .Num }}"
|
||||||
"limitTraffic" = "🚧 交通限制"
|
"limitTraffic" = "🚧 流量限制"
|
||||||
"getBanLogs" = "禁止日志"
|
"getBanLogs" = "禁止日志"
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ 成功的!"
|
"successfulOperation" = "✅ 成功!"
|
||||||
"errorOperation" = "❗ 操作错误。"
|
"errorOperation" = "❗ 操作错误。"
|
||||||
"getInboundsFailed" = "❌ 获取入站信息失败。"
|
"getInboundsFailed" = "❌ 获取入站信息失败。"
|
||||||
"canceled" = "❌ {{ .Email }}:操作已取消。"
|
"canceled" = "❌ {{ .Email }}:操作已取消。"
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
|
|
6
x-ui.sh
6
x-ui.sh
|
@ -968,9 +968,15 @@ create_iplimit_jails() {
|
||||||
# Uncomment 'allowipv6 = auto' in fail2ban.conf
|
# Uncomment 'allowipv6 = auto' in fail2ban.conf
|
||||||
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
|
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
|
||||||
|
|
||||||
|
#On Debian 12+ fail2ban's default backend should be changed to systemd
|
||||||
|
if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
|
||||||
|
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
|
||||||
|
fi
|
||||||
|
|
||||||
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
|
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
|
||||||
[3x-ipl]
|
[3x-ipl]
|
||||||
enabled=true
|
enabled=true
|
||||||
|
backend=auto
|
||||||
filter=3x-ipl
|
filter=3x-ipl
|
||||||
action=3x-ipl
|
action=3x-ipl
|
||||||
logpath=${iplimit_log_path}
|
logpath=${iplimit_log_path}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
|
|
||||||
|
@ -162,8 +163,8 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
||||||
if x.grpcClient == nil {
|
if x.grpcClient == nil {
|
||||||
return nil, nil, common.NewError("xray api is not initialized")
|
return nil, nil, common.NewError("xray api is not initialized")
|
||||||
}
|
}
|
||||||
var trafficRegex = regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
trafficRegex := regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
||||||
var ClientTrafficRegex = regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
ClientTrafficRegex := regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
||||||
|
|
||||||
client := *x.StatsServiceClient
|
client := *x.StatsServiceClient
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package xray
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"x-ui/util/json_util"
|
"x-ui/util/json_util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ type Config struct {
|
||||||
Reverse json_util.RawMessage `json:"reverse"`
|
Reverse json_util.RawMessage `json:"reverse"`
|
||||||
FakeDNS json_util.RawMessage `json:"fakedns"`
|
FakeDNS json_util.RawMessage `json:"fakedns"`
|
||||||
Observatory json_util.RawMessage `json:"observatory"`
|
Observatory json_util.RawMessage `json:"observatory"`
|
||||||
|
BurstObservatory json_util.RawMessage `json:"burstObservatory"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Equals(other *Config) bool {
|
func (c *Config) Equals(other *Config) bool {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package xray
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"x-ui/util/json_util"
|
"x-ui/util/json_util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package xray
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -57,28 +57,28 @@ func GetAccessPersistentPrevLogPath() string {
|
||||||
return config.GetLogFolder() + "/3xipl-ap.prev.log"
|
return config.GetLogFolder() + "/3xipl-ap.prev.log"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAccessLogPath() string {
|
func GetAccessLogPath() (string, error) {
|
||||||
config, err := os.ReadFile(GetConfigPath())
|
config, err := os.ReadFile(GetConfigPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warningf("Something went wrong: %s", err)
|
logger.Warningf("Something went wrong: %s", err)
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonConfig := map[string]interface{}{}
|
jsonConfig := map[string]interface{}{}
|
||||||
err = json.Unmarshal([]byte(config), &jsonConfig)
|
err = json.Unmarshal([]byte(config), &jsonConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warningf("Something went wrong: %s", err)
|
logger.Warningf("Something went wrong: %s", err)
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonConfig["log"] != nil {
|
if jsonConfig["log"] != nil {
|
||||||
jsonLog := jsonConfig["log"].(map[string]interface{})
|
jsonLog := jsonConfig["log"].(map[string]interface{})
|
||||||
if jsonLog["access"] != nil {
|
if jsonLog["access"] != nil {
|
||||||
|
|
||||||
accessLogPath := jsonLog["access"].(string)
|
accessLogPath := jsonLog["access"].(string)
|
||||||
|
return accessLogPath, nil
|
||||||
return accessLogPath
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopProcess(p *Process) {
|
func stopProcess(p *Process) {
|
||||||
|
@ -202,6 +202,12 @@ func (p *process) Start() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewErrorf("Failed to generate xray configuration file: %v", err)
|
return common.NewErrorf("Failed to generate xray configuration file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(config.GetLogFolder(), 0o770)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("Something went wrong: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
configPath := GetConfigPath()
|
configPath := GetConfigPath()
|
||||||
err = os.WriteFile(configPath, data, fs.ModePerm)
|
err = os.WriteFile(configPath, data, fs.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in a new issue