mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-21 10:05:49 +00:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97489e743a | ||
|
|
c74efa1d43 | ||
|
|
18af7047f8 | ||
|
|
e7ae846823 | ||
|
|
b042f01e58 | ||
|
|
e43601ac08 | ||
|
|
bc29d7a252 | ||
|
|
dd21bb2db7 | ||
|
|
4d07b99fe7 | ||
|
|
8b5fe0b018 | ||
|
|
fc23af5db6 | ||
|
|
10f54cd937 | ||
|
|
b21758e6a6 | ||
|
|
903db95783 | ||
|
|
6fdc07a2d0 | ||
|
|
c9f245cb25 | ||
|
|
5dc95f8556 | ||
|
|
d9d8545cca | ||
|
|
4593147b65 | ||
|
|
020d1adc55 | ||
|
|
389b5408b1 | ||
|
|
037dbb5670 | ||
|
|
6af0d55ca9 | ||
|
|
0917eb2742 | ||
|
|
f8be7a7649 | ||
|
|
5b87b12535 | ||
|
|
8908e8b16a | ||
|
|
4f7af1e57a | ||
|
|
3af55cc5b4 | ||
|
|
ac5d8af4f9 | ||
|
|
01cd7539f9 | ||
|
|
102864525c | ||
|
|
e6254e23f2 | ||
|
|
5f3c2f781e | ||
|
|
6c73791cab | ||
|
|
f76e3ff94b | ||
|
|
7f0fc1b8ef | ||
|
|
d18eb7e4e4 | ||
|
|
d3377cd45e | ||
|
|
64a5a9f1bc | ||
|
|
32afd7200a | ||
|
|
ffa531f9ca | ||
|
|
05deb89451 | ||
|
|
6f807823b9 | ||
|
|
2f594ca7c9 | ||
|
|
ecae6e5ead | ||
|
|
34ab6ed7ee | ||
|
|
5ba9d6e118 | ||
|
|
c47a67975f | ||
|
|
6563d23f38 | ||
|
|
3a46c3302b | ||
|
|
1c97593360 | ||
|
|
95416cd553 | ||
|
|
d6cd0611d4 | ||
|
|
aad2cd8739 | ||
|
|
4cc0149317 | ||
|
|
836ee29b78 | ||
|
|
806b57f959 | ||
|
|
e827c1477c | ||
|
|
b827a4680d | ||
|
|
3fd124f76d | ||
|
|
3e8863f6ce | ||
|
|
9b026572cf | ||
|
|
6b34a3ae13 | ||
|
|
8b64180136 | ||
|
|
688ae4da10 | ||
|
|
0785da7223 | ||
|
|
2f1aad3e63 | ||
|
|
754b591e4f | ||
|
|
2b9d2d044c | ||
|
|
511eef54bb | ||
|
|
e705ae8e48 |
12
.github/workflows/docker.yml
vendored
12
.github/workflows/docker.yml
vendored
@@ -11,15 +11,15 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the code
|
- name: Check out the code
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.0.0
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@v3.0.0
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -27,12 +27,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5.5.1
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v5.1.0
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
|||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -20,10 +20,10 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5.0.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.22'
|
||||||
|
|
||||||
@@ -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.7/"
|
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
|
||||||
@@ -117,7 +117,7 @@ jobs:
|
|||||||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||||
|
|
||||||
- name: Upload files to GH release
|
- name: Upload files to GH release
|
||||||
uses: svenstaro/upload-release-action@2.9.0
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
|
|||||||
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.7/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}"
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -1,5 +1,9 @@
|
|||||||
# 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>
|
||||||
|
|
||||||
**An Advanced Web Panel • Built on Xray Core**
|
**An Advanced Web Panel • Built on Xray Core**
|
||||||
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||||
@@ -12,8 +16,7 @@
|
|||||||
|
|
||||||
**If this project is helpful to you, you may wish to give it a**:star2:
|
**If this project is helpful to you, you may wish to give it a**:star2:
|
||||||
|
|
||||||
<a href="#">
|
<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>
|
||||||
<img width="125" alt="image" src="https://github.com/MHSanaei/3x-ui/assets/115543613/7aa895dd-048a-42e7-989b-afd41a74e2e1.jpg"></a>
|
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
|
|
||||||
@@ -25,10 +28,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||||||
|
|
||||||
## 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.0`:
|
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.0
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.6
|
||||||
```
|
```
|
||||||
|
|
||||||
## SSL Certificate
|
## SSL Certificate
|
||||||
@@ -212,6 +215,7 @@ Our platform offers compatibility with a diverse range of architectures and devi
|
|||||||
- Vietnamese
|
- Vietnamese
|
||||||
- Spanish
|
- Spanish
|
||||||
- Indonesian
|
- Indonesian
|
||||||
|
- Ukrainian
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|||||||
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
|
||||||
|
- **用户名 & 密码:** It will be generated randomly if you skip modifying.
|
||||||
|
- **数据库路径:**
|
||||||
|
- /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` with `POST` user data: `{username: '', password: ''}` for login
|
||||||
|
- `/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.0
|
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"
|
||||||
)
|
)
|
||||||
|
|||||||
53
go.mod
53
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.7
|
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-20231229205709-960ae82b1e42 // 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
|
||||||
@@ -49,25 +49,24 @@ require (
|
|||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.6 // indirect
|
github.com/klauspost/compress v1.17.7 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 // 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.13.2 // 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/qtls-go1-20 v0.4.1 // indirect
|
github.com/quic-go/quic-go v0.41.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.40.1 // indirect
|
github.com/refraction-networking/utls v1.6.3 // indirect
|
||||||
github.com/refraction-networking/utls v1.6.0 // 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.0 // 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
|
||||||
@@ -80,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-20240103183307-be819d1f06fc // indirect
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||||
golang.org/x/mod v0.14.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.16.1 // 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-20240123012728-ef4313101c80 // 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
|
||||||
|
|||||||
127
go.sum
127
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=
|
||||||
@@ -88,13 +88,13 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
|||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
||||||
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-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8=
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||||
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/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=
|
||||||
@@ -138,11 +138,11 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
|||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||||
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
@@ -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,12 +165,12 @@ 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.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||||
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -183,10 +183,10 @@ 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.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||||
github.com/onsi/gomega v1.29.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=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
@@ -208,12 +208,10 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
|||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
|
||||||
github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
|
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
|
||||||
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
|
||||||
github.com/refraction-networking/utls v1.6.0 h1:X5vQMqVx7dY7ehxxqkFER/W6DSjy8TMqSItXm8hRDYQ=
|
|
||||||
github.com/refraction-networking/utls v1.6.0/go.mod h1:kHJ6R9DFFA0WsRgBM35iiDku4O7AqPR6y79iuzW7b10=
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
@@ -223,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.0 h1:PIDVFZHnQAAYRL1UYqNM+0k5s8f/tb1lUW6UDcQiOc8=
|
github.com/sagernet/sing v0.3.6 h1:dsEdYLKBQlrxUfw1a92x0VdPvR1/BOxQ+HIMyaoEJsQ=
|
||||||
github.com/sagernet/sing v0.3.0/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
|
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=
|
||||||
@@ -272,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=
|
||||||
@@ -303,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.7 h1:lb8O1l3/eAg3YAXA6tLm5M6N7BsX8wxW9sJLjU3dHkA=
|
github.com/xtls/xray-core v1.8.9 h1:wefcON0behu4DoQvCKJYZKsJlSvNhyq2I7vC2fxLFcY=
|
||||||
github.com/xtls/xray-core v1.8.7/go.mod h1:9rFpflfQbgFeH1VKJw7yUmEy7myOyDCgNXXl0bmmyOo=
|
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=
|
||||||
@@ -323,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-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||||
golang.org/x/mod v0.14.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=
|
||||||
@@ -342,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=
|
||||||
@@ -373,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=
|
||||||
@@ -392,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.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
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=
|
||||||
@@ -411,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-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
|
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-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
|
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()
|
||||||
|
|||||||
BIN
media/3X-UI.png
Normal file
BIN
media/3X-UI.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 198 KiB |
@@ -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": {}
|
||||||
}
|
}
|
||||||
33
sub/sub.go
33
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,15 +92,27 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start() (err error) {
|
func (s *Server) Start() (err error) {
|
||||||
//This is an anonymous function, no function name
|
// This is an anonymous function, no function name
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Stop()
|
s.Stop()
|
||||||
@@ -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
|
||||||
@@ -50,7 +53,6 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) subs(c *gin.Context) {
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
println(c.Request.Header["User-Agent"][0])
|
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
subs, header, err := a.subService.GetSubs(subId, host)
|
subs, header, err := a.subService.GetSubs(subId, host)
|
||||||
@@ -76,7 +78,6 @@ func (a *SUBController) subs(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) subJsons(c *gin.Context) {
|
func (a *SUBController) subJsons(c *gin.Context) {
|
||||||
println(c.Request.Header["User-Agent"][0])
|
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
|
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
|
||||||
|
|||||||
@@ -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) *SubJsonService {
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
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,8 +205,8 @@ 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}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove proxy protocol
|
// remove proxy protocol
|
||||||
@@ -214,11 +231,11 @@ 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"]
|
||||||
if allowInsecure, ok := tlsClientSettings["allowInsecure"].(string); ok {
|
if allowInsecure, ok := tlsClientSettings["allowInsecure"].(bool); ok {
|
||||||
tlsData["allowInsecure"] = allowInsecure
|
tlsData["allowInsecure"] = allowInsecure
|
||||||
}
|
}
|
||||||
if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
|
if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
|
||||||
@@ -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++ {
|
||||||
|
|||||||
@@ -538,7 +538,7 @@
|
|||||||
|
|
||||||
var on = function(emitter, type, f) {
|
var on = function(emitter, type, f) {
|
||||||
if (emitter.addEventListener) {
|
if (emitter.addEventListener) {
|
||||||
emitter.addEventListener(type, f, { passive: true });
|
emitter.addEventListener(type, f, { passive: false });
|
||||||
} else if (emitter.attachEvent) {
|
} else if (emitter.attachEvent) {
|
||||||
emitter.attachEvent("on" + type, f);
|
emitter.attachEvent("on" + type, f);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -45,12 +45,12 @@ THE SOFTWARE.
|
|||||||
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
|
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
|
||||||
.cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; }
|
.cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; }
|
||||||
|
|
||||||
.dark .cm-s-xq.CodeMirror { background-color: #000000; border-color: #25272a; color: rgb(255 255 255 / 85%); }
|
.dark .cm-s-xq.CodeMirror { background-color: var(--dark-color-surface-200); border-color: var(--dark-color-surface-300); color: rgb(255 255 255 / 65%); }
|
||||||
.dark .cm-s-xq.CodeMirror:hover { background-color: rgb(0 50 42 / 20%); border-color: #008771; transition: all .3s; }
|
.dark .cm-s-xq.CodeMirror:hover { background-color: rgb(0 50 42 / 30%); border-color: #008771; transition: all .3s; }
|
||||||
.dark .cm-s-xq div.CodeMirror-selected { background: rgba(0, 0, 0, 0.5); }
|
.dark .cm-s-xq div.CodeMirror-selected { background: var(--dark-color-codemirror-line-selection); }
|
||||||
.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: rgba(39, 0, 122, 0.99); }
|
.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: var(--dark-color-codemirror-line-selection); }
|
||||||
.dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 0, 122, 0.99); }
|
.dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: var(--dark-color-codemirror-line-selection); }
|
||||||
.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid #2c3950; }
|
.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid var(--dark-color-surface-300); }
|
||||||
.dark .cm-s-xq .CodeMirror-guttermarker { color: #FFBD40; }
|
.dark .cm-s-xq .CodeMirror-guttermarker { color: #FFBD40; }
|
||||||
.dark .cm-s-xq .CodeMirror-guttermarker-subtle { color: rgb(255 255 255 / 70%); }
|
.dark .cm-s-xq .CodeMirror-guttermarker-subtle { color: rgb(255 255 255 / 70%); }
|
||||||
.dark .cm-s-xq .CodeMirror-linenumber { color: rgb(255 255 255 / 50%); }
|
.dark .cm-s-xq .CodeMirror-linenumber { color: rgb(255 255 255 / 50%); }
|
||||||
@@ -80,7 +80,7 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
.Line-Hover{transition: all .2s;}
|
.Line-Hover{transition: all .2s;}
|
||||||
.Line-Hover:hover{ background-color: rgba(0, 102, 85, 0.05) !important; }
|
.Line-Hover:hover{ background-color: rgba(0, 102, 85, 0.05) !important; }
|
||||||
.dark .Line-Hover:hover{ background-color: rgb(0 0 0 / 20%) !important; }
|
.dark .Line-Hover:hover{ background-color: var(--dark-color-codemirror-line-hover) !important; }
|
||||||
|
|
||||||
.CodeMirror-foldmarker { color: #fc8800; text-shadow: #ffd8aa 1px 1px 2px, #ffd8aa -1px -1px 2px, #ffd8aa 1px -1px 2px, #ffd8aa -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
.CodeMirror-foldmarker { color: #fc8800; text-shadow: #ffd8aa 1px 1px 2px, #ffd8aa -1px -1px 2px, #ffd8aa 1px -1px 2px, #ffd8aa -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
||||||
.dark .CodeMirror-foldmarker { color: #ffffff; text-shadow: #bbb 1px 1px 2px, #bbb -1px -1px 2px, #bbb 1px -1px 2px, #bbb -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
.dark .CodeMirror-foldmarker { color: #ffffff; text-shadow: #bbb 1px 1px 2px, #bbb -1px -1px 2px, #bbb 1px -1px 2px, #bbb -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
||||||
|
|||||||
@@ -1,4 +1,44 @@
|
|||||||
:root {
|
:root {
|
||||||
|
--color-primary-100: #008771;
|
||||||
|
--dark-color-background: #0a1222;
|
||||||
|
--dark-color-surface-100: #151f31;
|
||||||
|
--dark-color-surface-200: #222d42;
|
||||||
|
--dark-color-surface-300: #2c3950;
|
||||||
|
--dark-color-surface-400: rgba(65, 85, 119, 0.5); /* line */
|
||||||
|
--dark-color-surface-500: #2c3950; /* popover & switch btn */
|
||||||
|
--dark-color-surface-600: #313f5a; /* dropmenu hover */
|
||||||
|
--dark-color-surface-700: #111929; /* modals */
|
||||||
|
--dark-color-table-hover: rgba(44, 57, 80, 0.2);
|
||||||
|
--dark-color-text-primary: rgba(255, 255, 255, 0.75);
|
||||||
|
--dark-color-stroke: #2c3950;
|
||||||
|
--dark-color-btn-danger: #cd3838;
|
||||||
|
--dark-color-btn-danger-border: transparent;
|
||||||
|
--dark-color-btn-danger-hover: #e94b4b;
|
||||||
|
--dark-color-tag-bg: rgba(255, 255, 255, 0.05);
|
||||||
|
--dark-color-tag-border:rgba(255, 255, 255, 0.15);
|
||||||
|
--dark-color-tag-color:rgba(255, 255, 255, 0.75);
|
||||||
|
--dark-color-tag-green-bg: #112421;
|
||||||
|
--dark-color-tag-green-border: #195141;
|
||||||
|
--dark-color-tag-green-color: #3ad3ba;
|
||||||
|
--dark-color-tag-purple-bg: #201425;
|
||||||
|
--dark-color-tag-purple-border: #5a2969;
|
||||||
|
--dark-color-tag-purple-color: #d988cd;
|
||||||
|
--dark-color-tag-red-bg: #291515;
|
||||||
|
--dark-color-tag-red-border: #5c2626;
|
||||||
|
--dark-color-tag-red-color: #e04141;
|
||||||
|
--dark-color-tag-orange-bg: #312313;
|
||||||
|
--dark-color-tag-orange-border: #593914;
|
||||||
|
--dark-color-tag-orange-color: #ffa031;
|
||||||
|
--dark-color-tag-blue-bg: #111a2c;
|
||||||
|
--dark-color-tag-blue-border: #1348ab;
|
||||||
|
--dark-color-tag-blue-color: #529fff;
|
||||||
|
--dark-color-codemirror-line-hover: rgba(0, 135, 113, 0.2);
|
||||||
|
--dark-color-codemirror-line-selection: rgba(0, 135, 113, 0.3);
|
||||||
|
--dark-color-login-background: var(--dark-color-background);
|
||||||
|
--dark-color-login-wave: var(--dark-color-surface-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='ultra-dark'] {
|
||||||
--dark-color-background: #21242a;
|
--dark-color-background: #21242a;
|
||||||
--dark-color-surface-100: #0c0e12;
|
--dark-color-surface-100: #0c0e12;
|
||||||
--dark-color-surface-200: #222327;
|
--dark-color-surface-200: #222327;
|
||||||
@@ -6,11 +46,45 @@
|
|||||||
--dark-color-surface-400: rgba(255, 255, 255, 0.1);
|
--dark-color-surface-400: rgba(255, 255, 255, 0.1);
|
||||||
--dark-color-surface-500: #3b404b;
|
--dark-color-surface-500: #3b404b;
|
||||||
--dark-color-surface-600: #505663;
|
--dark-color-surface-600: #505663;
|
||||||
|
--dark-color-surface-700: #101113;
|
||||||
|
--dark-color-table-hover: rgba(89, 89, 89, 0.15);
|
||||||
--dark-color-text-primary: rgb(255 255 255 / 85%);
|
--dark-color-text-primary: rgb(255 255 255 / 85%);
|
||||||
--dark-color-stroke: #202025;
|
--dark-color-stroke: #202025;
|
||||||
--dark-color-btn-danger: #cd3838;
|
--dark-color-tag-green-bg: #112421;
|
||||||
--dark-color-btn-danger-border: transparent;
|
--dark-color-tag-green-border: #1d5f4d;
|
||||||
--dark-color-btn-danger-hover: #e94b4b;
|
--dark-color-tag-green-color: #59cbac;
|
||||||
|
--dark-color-tag-purple-bg: #241121;
|
||||||
|
--dark-color-tag-purple-border: #5a2969;
|
||||||
|
--dark-color-tag-purple-color: #d686ca;
|
||||||
|
--dark-color-tag-red-bg: #2a1215;
|
||||||
|
--dark-color-tag-red-border: #58181c;
|
||||||
|
--dark-color-tag-red-color: #e84749;
|
||||||
|
--dark-color-tag-orange-bg: #2b1d11;
|
||||||
|
--dark-color-tag-orange-border: #593815;
|
||||||
|
--dark-color-tag-orange-color: #e89a3c;
|
||||||
|
--dark-color-tag-blue-bg: #111a2c;
|
||||||
|
--dark-color-tag-blue-border: #0f367e;
|
||||||
|
--dark-color-tag-blue-color: #3c89e8;
|
||||||
|
--dark-color-codemirror-line-hover: rgba(85, 85, 85, 0.3);
|
||||||
|
--dark-color-codemirror-line-selection: rgba(85, 85, 85, 0.4);
|
||||||
|
--dark-color-login-background: #0a2227;
|
||||||
|
--dark-color-login-wave: #0f2d32;
|
||||||
|
.ant-dropdown-menu-dark {
|
||||||
|
background-color: var(--dark-color-surface-500);
|
||||||
|
}
|
||||||
|
.dark .ant-dropdown-menu-submenu-title:hover,
|
||||||
|
.dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),
|
||||||
|
.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) {
|
||||||
|
background-color: var(--dark-color-surface-300);
|
||||||
|
}
|
||||||
|
.dark .waves-header {
|
||||||
|
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,
|
||||||
@@ -31,7 +105,7 @@ body {
|
|||||||
font-feature-settings: "tnum";
|
font-feature-settings: "tnum";
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
--antd-wave-shadow-color: #008771;
|
--antd-wave-shadow-color: var(--color-primary-100);
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
text-size-adjust: 100%;
|
text-size-adjust: 100%;
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
@@ -41,7 +115,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
background-color: #cfe8e4;
|
background-color: #cfe8e4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,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 {
|
||||||
@@ -149,7 +223,7 @@ style attribute {
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
.ant-modal-body {
|
.ant-modal-body {
|
||||||
padding: 10px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
.ant-form-item-label {
|
.ant-form-item-label {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@@ -233,7 +307,7 @@ style attribute {
|
|||||||
.ant-menu-submenu-active,
|
.ant-menu-submenu-active,
|
||||||
.ant-menu-submenu-title:hover,
|
.ant-menu-submenu-title:hover,
|
||||||
.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open {
|
.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
background-color: rgb(232 244 242);
|
background-color: rgb(232 244 242);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
@@ -495,14 +569,14 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.normal-icon:hover {
|
.normal-icon:hover {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DARK THEME */
|
/* DARK THEME */
|
||||||
|
|
||||||
.dark ::selection {
|
.dark ::selection {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .normal-icon:hover {
|
.dark .normal-icon:hover {
|
||||||
@@ -523,6 +597,7 @@ style attribute {
|
|||||||
|
|
||||||
.dark .ant-card-hoverable:hover,
|
.dark .ant-card-hoverable:hover,
|
||||||
.dark .ant-space-item > .ant-tabs:hover {
|
.dark .ant-space-item > .ant-tabs:hover {
|
||||||
|
/* box-shadow: 0 1px 10px -1px rgb(154 175 238 / 80%); */
|
||||||
box-shadow: 0 2px 8px transparent;
|
box-shadow: 0 2px 8px transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,7 +687,7 @@ style attribute {
|
|||||||
.dark .ant-calendar-year-panel-year,
|
.dark .ant-calendar-year-panel-year,
|
||||||
.dark .ant-calendar-month-panel-month,
|
.dark .ant-calendar-month-panel-month,
|
||||||
.dark .ant-calendar-decade-panel-decade {
|
.dark .ant-calendar-decade-panel-decade {
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-list-item-meta-description {
|
.dark .ant-list-item-meta-description {
|
||||||
@@ -643,7 +718,7 @@ style attribute {
|
|||||||
.dark .ant-calendar-time-picker-inner {
|
.dark .ant-calendar-time-picker-inner {
|
||||||
background-color: var(--dark-color-surface-200);
|
background-color: var(--dark-color-surface-200);
|
||||||
border-color: var(--dark-color-surface-300);
|
border-color: var(--dark-color-surface-300);
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-select-selection:hover,
|
.dark .ant-select-selection:hover,
|
||||||
@@ -653,35 +728,35 @@ style attribute {
|
|||||||
.dark .ant-input:hover,
|
.dark .ant-input:hover,
|
||||||
.dark .ant-input:focus {
|
.dark .ant-input:focus {
|
||||||
background-color: rgba(0, 135, 113, 0.3);
|
background-color: rgba(0, 135, 113, 0.3);
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) {
|
.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) {
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: var(--dark-color-text-primary);
|
||||||
background-color: rgb(10 117 87 / 30%);
|
background-color: rgb(10 117 87 / 30%);
|
||||||
border: 1px solid #008771;
|
border: 1px solid var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-radio-button-wrapper,
|
.dark .ant-radio-button-wrapper,
|
||||||
.dark .ant-radio-button-wrapper:before {
|
.dark .ant-radio-button-wrapper:before {
|
||||||
color: rgb(255 255 255 / 65%);
|
color: var(--dark-color-text-primary);
|
||||||
background-color: rgba(0, 135, 113, 0.3);
|
background-color: rgba(0, 135, 113, 0.3);
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),
|
.dark .ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),
|
||||||
.dark .ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger) {
|
.dark .ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger) {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: rgb(10 117 87 / 50%);
|
background-color: rgb(10 117 87 / 50%);
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-btn-primary[disabled],
|
.dark .ant-btn-primary[disabled],
|
||||||
.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
|
||||||
@@ -689,7 +764,7 @@ style attribute {
|
|||||||
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
|
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
|
||||||
> td,
|
> td,
|
||||||
.dark .client-table-odd-row {
|
.dark .client-table-odd-row {
|
||||||
background-color: rgb(89 89 89 / 15%);
|
background-color: var(--dark-color-table-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-row-expand-icon {
|
.dark .ant-table-row-expand-icon {
|
||||||
@@ -699,9 +774,9 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-row-expand-icon:hover {
|
.dark .ant-table-row-expand-icon:hover {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
background-color: #fff0;
|
background-color: #fff0;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-switch:not(.ant-switch-checked),
|
.dark .ant-switch:not(.ant-switch-checked),
|
||||||
@@ -713,7 +788,6 @@ style attribute {
|
|||||||
stroke: var(--dark-color-stroke) !important;
|
stroke: var(--dark-color-stroke) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-dropdown-menu-dark,
|
|
||||||
.dark .ant-popover-inner {
|
.dark .ant-popover-inner {
|
||||||
background-color: var(--dark-color-surface-500);
|
background-color: var(--dark-color-surface-500);
|
||||||
}
|
}
|
||||||
@@ -722,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);
|
||||||
}
|
}
|
||||||
@@ -738,46 +820,46 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag {
|
.dark .ant-tag {
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: var(--dark-color-tag-color);
|
||||||
background-color: rgba(255, 255, 255, 0.08);
|
background-color: var(--dark-color-tag-bg);
|
||||||
border-color: rgba(255, 255, 255, 0.15);
|
border-color: var(--dark-color-tag-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-blue {
|
.dark .ant-tag-blue {
|
||||||
background-color: #111a2c;
|
background-color: var(--dark-color-tag-blue-bg);
|
||||||
border-color: #0f367e;
|
border-color: var(--dark-color-tag-blue-border);
|
||||||
color: #3c89e8;
|
color: var(--dark-color-tag-blue-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-red,
|
.dark .ant-tag-red,
|
||||||
.dark .ant-alert-error {
|
.dark .ant-alert-error {
|
||||||
background-color: #2a1215;
|
background-color: var(--dark-color-tag-red-bg);
|
||||||
border-color: #58181c;
|
border-color: var(--dark-color-tag-red-border);
|
||||||
color: #e84749;
|
color: var(--dark-color-tag-red-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-orange,
|
.dark .ant-tag-orange,
|
||||||
.dark .ant-alert-warning {
|
.dark .ant-alert-warning {
|
||||||
background-color: #2b1d11;
|
background-color: var(--dark-color-tag-orange-bg);
|
||||||
border-color: #593815;
|
border-color: var(--dark-color-tag-orange-border);
|
||||||
color: #e89a3c;
|
color: var(--dark-color-tag-orange-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-green {
|
.dark .ant-tag-green {
|
||||||
background-color: #112421;
|
background-color: var(--dark-color-tag-green-bg);
|
||||||
border-color: #195544;
|
border-color: var(--dark-color-tag-green-border);
|
||||||
color: #59cbac;
|
color: var(--dark-color-tag-green-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-purple {
|
.dark .ant-tag-purple {
|
||||||
background-color: #241121;
|
background-color: var(--dark-color-tag-purple-bg);
|
||||||
border-color: #5a2969;
|
border-color: var(--dark-color-tag-purple-border);
|
||||||
color: #d686ca;
|
color: var(--dark-color-tag-purple-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-modal-content,
|
.dark .ant-modal-content,
|
||||||
.dark .ant-modal-header {
|
.dark .ant-modal-header {
|
||||||
background-color: #101113;
|
background-color: var(--dark-color-surface-700);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-next-month-btn-day .ant-calendar-date,
|
.dark .ant-calendar-next-month-btn-day .ant-calendar-date,
|
||||||
@@ -786,13 +868,13 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-selected-day .ant-calendar-date {
|
.dark .ant-calendar-selected-day .ant-calendar-date {
|
||||||
background-color: #008771 !important;
|
background-color: var(--color-primary-100) !important;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-date:hover,
|
.dark .ant-calendar-date:hover,
|
||||||
.dark .ant-calendar-time-picker-select li:hover {
|
.dark .ant-calendar-time-picker-select li:hover {
|
||||||
background-color: var(--dark-color-surface-300);
|
background-color: var(--dark-color-surface-600);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -806,13 +888,15 @@ style attribute {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-time-picker-select {
|
.dark .ant-calendar-time-picker-select {
|
||||||
border-right-color: var(--dark-color-surface-300);
|
border-right-color: var(--dark-color-surface-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-warning .ant-select-selection,
|
||||||
|
.has-warning .ant-select-selection:hover,
|
||||||
.has-warning .ant-input,
|
.has-warning .ant-input,
|
||||||
.has-warning .ant-input:hover {
|
.has-warning .ant-input:hover {
|
||||||
background-color: #ffeee1;
|
background-color: #ffeee1;
|
||||||
@@ -827,6 +911,8 @@ style attribute {
|
|||||||
border-color: #fec093;
|
border-color: #fec093;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .has-warning .ant-select-selection,
|
||||||
|
.dark .has-warning .ant-select-selection:hover,
|
||||||
.dark .has-warning .ant-input,
|
.dark .has-warning .ant-input,
|
||||||
.dark .has-warning .ant-input:hover {
|
.dark .has-warning .ant-input:hover {
|
||||||
border-color: #784e1d;
|
border-color: #784e1d;
|
||||||
@@ -842,7 +928,7 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .has-success .anticon {
|
.dark .has-success .anticon {
|
||||||
color: #61bf39;
|
color: var(--color-primary-100);
|
||||||
animation-name: diffZoomIn1 !important;
|
animation-name: diffZoomIn1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,19 +974,19 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-calendar-today .ant-calendar-date {
|
.ant-calendar-today .ant-calendar-date {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-today .ant-calendar-date {
|
.dark .ant-calendar-today .ant-calendar-date {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-calendar-selected-day .ant-calendar-date {
|
.ant-calendar-selected-day .ant-calendar-date {
|
||||||
background: #008771;
|
background: var(--color-primary-100);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,7 +1020,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
.ant-select-dropdown.ant-select-dropdown--multiple
|
.ant-select-dropdown.ant-select-dropdown--multiple
|
||||||
.ant-select-dropdown-menu-item-selected:hover
|
.ant-select-dropdown-menu-item-selected:hover
|
||||||
.ant-select-selected-icon {
|
.ant-select-selected-icon {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
.ant-select-selection:hover,
|
.ant-select-selection:hover,
|
||||||
.ant-input-number-focused,
|
.ant-input-number-focused,
|
||||||
@@ -943,7 +1029,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-input-number-handler:active {
|
.dark .ant-input-number-handler:active {
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-input-number-handler:hover .ant-input-number-handler-down-inner,
|
.dark .ant-input-number-handler:hover .ant-input-number-handler-down-inner,
|
||||||
@@ -979,10 +1065,11 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
color: rgba(255, 255, 255, 0.35);
|
color: rgba(255, 255, 255, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-dark,
|
||||||
.dark .ant-calendar-year-panel-year:hover,
|
.dark .ant-calendar-year-panel-year:hover,
|
||||||
.dark .ant-calendar-month-panel-month:hover,
|
.dark .ant-calendar-month-panel-month:hover,
|
||||||
.dark .ant-calendar-decade-panel-decade:hover {
|
.dark .ant-calendar-decade-panel-decade:hover {
|
||||||
background-color: var(--dark-color-surface-600);
|
background-color: var(--dark-color-surface-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-header a:hover {
|
.dark .ant-calendar-header a:hover {
|
||||||
@@ -1014,7 +1101,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
.ant-calendar-decade-panel-selected-cell
|
.ant-calendar-decade-panel-selected-cell
|
||||||
.ant-calendar-decade-panel-decade:hover {
|
.ant-calendar-decade-panel-decade:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-last-month-cell .ant-calendar-date,
|
.dark .ant-calendar-last-month-cell .ant-calendar-date,
|
||||||
@@ -1028,8 +1115,8 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
|
|
||||||
.dark .ant-calendar-today .ant-calendar-date:hover {
|
.dark .ant-calendar-today .ant-calendar-date:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark
|
.dark
|
||||||
@@ -1052,8 +1139,8 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-checkbox-checked .ant-checkbox-inner {
|
.dark .ant-checkbox-checked .ant-checkbox-inner {
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-input {
|
.dark .ant-calendar-input {
|
||||||
@@ -1117,7 +1204,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
.dark .ant-dropdown-menu-submenu-title:hover,
|
.dark .ant-dropdown-menu-submenu-title:hover,
|
||||||
.dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),
|
.dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),
|
||||||
.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) {
|
.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) {
|
||||||
background-color: var(--dark-color-surface-300);
|
background-color: var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-select-dropdown,
|
.ant-select-dropdown,
|
||||||
@@ -1140,7 +1227,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-group-addon:not(:first-child):not(:last-child), .ant-input-group-wrap:not(:first-child):not(:last-child), .ant-input-group>.ant-input:not(:first-child):not(:last-child) {
|
.ant-input-group-addon:not(:first-child):not(:last-child) {
|
||||||
border-radius: 0rem 1rem 1rem 0rem;
|
border-radius: 0rem 1rem 1rem 0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1157,9 +1244,9 @@ b, strong {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-message-notice-content {
|
.dark .ant-message-notice-content {
|
||||||
background-color: #000000;
|
background-color: var(--dark-color-surface-200);
|
||||||
border: 1px solid #303134;
|
border: 1px solid var(--dark-color-surface-300);
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn-danger {
|
.ant-btn-danger {
|
||||||
@@ -1175,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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ const supportLangs = [
|
|||||||
value: 'id-ID',
|
value: 'id-ID',
|
||||||
icon: '🇮🇩',
|
icon: '🇮🇩',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Український',
|
||||||
|
value: 'uk-UA',
|
||||||
|
icon: '🇺🇦',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getLang() {
|
function getLang() {
|
||||||
|
|||||||
@@ -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') {
|
||||||
|
|||||||
@@ -206,15 +206,15 @@ jdp-container .jdp-day-name {
|
|||||||
}
|
}
|
||||||
jdp-container .jdp-day-name.today,
|
jdp-container .jdp-day-name.today,
|
||||||
jdp-container .jdp-day.today {
|
jdp-container .jdp-day.today {
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-day-name.selected,
|
.dark jdp-container .jdp-day-name.selected,
|
||||||
.dark jdp-container .jdp-day.selected,
|
.dark jdp-container .jdp-day.selected,
|
||||||
jdp-container .jdp-day-name.selected,
|
jdp-container .jdp-day-name.selected,
|
||||||
jdp-container .jdp-day.selected {
|
jdp-container .jdp-day.selected {
|
||||||
background-color: #008771 !important;
|
background-color: var(--color-primary-100) !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
@@ -267,7 +267,7 @@ jdp-container .jdp-btn-empty,
|
|||||||
jdp-container .jdp-btn-today {
|
jdp-container .jdp-btn-today {
|
||||||
background: #00877000;
|
background: #00877000;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
@@ -369,26 +369,26 @@ jdp-container .jdp-time-container.jdp-only-time .jdp-time:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-container .jdp-days {
|
.dark jdp-container .jdp-days {
|
||||||
border-color: #32353b;
|
border-color: var(--dark-color-surface-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-overlay {
|
.dark jdp-overlay {
|
||||||
background-color: #181f2c;
|
background-color: #181f2c;
|
||||||
}
|
}
|
||||||
.dark jdp-container {
|
.dark jdp-container {
|
||||||
background: #000000;
|
background: var(--dark-color-background);
|
||||||
border-color: #2c3950;
|
border-color: #2c3950;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,.15);
|
box-shadow: 0 2px 8px rgba(0,0,0,.15);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-icon-minus,
|
.dark jdp-container .jdp-icon-minus,
|
||||||
.dark jdp-container .jdp-icon-plus {
|
.dark jdp-container .jdp-icon-plus {
|
||||||
outline-color: #32353b;
|
outline-color: var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-container .jdp-icon-minus:hover,
|
.dark jdp-container .jdp-icon-minus:hover,
|
||||||
.dark jdp-container .jdp-icon-plus:hover {
|
.dark jdp-container .jdp-icon-plus:hover {
|
||||||
background-color: #32353b;
|
background-color: var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-container .jdp-months,
|
.dark jdp-container .jdp-months,
|
||||||
@@ -405,27 +405,27 @@ jdp-container .jdp-time-container.jdp-only-time .jdp-time:after {
|
|||||||
.dark jdp-container .jdp-year,
|
.dark jdp-container .jdp-year,
|
||||||
.dark jdp-container .jdp-year input,
|
.dark jdp-container .jdp-year input,
|
||||||
.dark jdp-container .jdp-year select {
|
.dark jdp-container .jdp-year select {
|
||||||
background: #000000;
|
background: var(--dark-color-background);
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-day,
|
.dark jdp-container .jdp-day,
|
||||||
.dark jdp-container .jdp-day-name {
|
.dark jdp-container .jdp-day-name {
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-day-name.today,
|
.dark jdp-container .jdp-day-name.today,
|
||||||
.dark jdp-container .jdp-day.today {
|
.dark jdp-container .jdp-day.today {
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-day.disabled-day {
|
.dark jdp-container .jdp-day.disabled-day {
|
||||||
opacity: 0.15;
|
opacity: 0.15;
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-day:not(.disabled-day):hover {
|
.dark jdp-container .jdp-day:not(.disabled-day):hover {
|
||||||
background-color: #32353b;
|
background-color: var(--dark-color-surface-600);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-footer {
|
.dark jdp-container .jdp-footer {
|
||||||
border-color: #32353b;
|
border-color: var(--dark-color-surface-400);
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-btn-close,
|
.dark jdp-container .jdp-btn-close,
|
||||||
.dark jdp-container .jdp-btn-empty,
|
.dark jdp-container .jdp-btn-empty,
|
||||||
@@ -446,10 +446,10 @@ jdp-container .jdp-time-container.jdp-only-time .jdp-time:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-container .jdp-time-container .jdp-time select:hover {
|
.dark jdp-container .jdp-time-container .jdp-time select:hover {
|
||||||
background-color: #32353b;
|
background-color: var(--dark-color-surface-600);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-container .jdp-time-container .jdp-time select {
|
.dark jdp-container .jdp-time-container .jdp-time select {
|
||||||
border: 1px solid #32353b;
|
border: 1px solid var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIController) getAllInbounds(c *gin.Context) {
|
inboundRoutes := []struct {
|
||||||
a.inboundController.getInbounds(c)
|
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) getSingleInbound(c *gin.Context) {
|
for _, route := range inboundRoutes {
|
||||||
a.inboundController.getInbound(c)
|
g.Handle(route.Method, route.Path, route.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
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,6 +1,7 @@
|
|||||||
{{define "promptModal"}}
|
{{define "promptModal"}}
|
||||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||||
|
:confirm-loading="promptModal.confirmLoading"
|
||||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||||
v-model="promptModal.value"
|
v-model="promptModal.value"
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
value: '',
|
value: '',
|
||||||
okText: '{{ i18n "sure"}}',
|
okText: '{{ i18n "sure"}}',
|
||||||
visible: false,
|
visible: false,
|
||||||
|
confirmLoading: false,
|
||||||
keyEnter(e) {
|
keyEnter(e) {
|
||||||
if (this.type !== 'textarea') {
|
if (this.type !== 'textarea') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -30,7 +32,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ok() {
|
ok() {
|
||||||
promptModal.close();
|
|
||||||
promptModal.confirm(promptModal.value);
|
promptModal.confirm(promptModal.value);
|
||||||
},
|
},
|
||||||
confirm() {},
|
confirm() {},
|
||||||
@@ -53,7 +54,10 @@
|
|||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
}
|
},
|
||||||
|
loading(loading=true) {
|
||||||
|
this.confirmLoading = loading;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const promptModalApp = new Vue({
|
const promptModalApp = new Vue({
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -49,6 +49,9 @@
|
|||||||
border-radius: 2rem;
|
border-radius: 2rem;
|
||||||
padding: 3rem;
|
padding: 3rem;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
user-select:none;
|
||||||
|
-webkit-user-select:none;
|
||||||
|
-moz-user-select: none;
|
||||||
}
|
}
|
||||||
#login:hover {
|
#login:hover {
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||||
@@ -68,10 +71,10 @@
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
.dark .under {
|
.dark .under {
|
||||||
background-color: #0f2d32;
|
background-color: var(--dark-color-login-wave);
|
||||||
}
|
}
|
||||||
.dark #login {
|
.dark #login {
|
||||||
background-color: #101113;
|
background-color: var(--dark-color-surface-100);
|
||||||
}
|
}
|
||||||
.dark h1 {
|
.dark h1 {
|
||||||
color: rgba(255, 255, 255);
|
color: rgba(255, 255, 255);
|
||||||
@@ -199,7 +202,7 @@
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
.dark .waves-header {
|
.dark .waves-header {
|
||||||
background-color: #0a2227;
|
background-color: var(--dark-color-login-background);
|
||||||
}
|
}
|
||||||
.waves-inner-header {
|
.waves-inner-header {
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
@@ -219,7 +222,7 @@
|
|||||||
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
|
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
|
||||||
}
|
}
|
||||||
.dark .parallax > use {
|
.dark .parallax > use {
|
||||||
fill: #0f2d32;
|
fill: var(--dark-color-login-wave);
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(1) {
|
.parallax > use:nth-child(1) {
|
||||||
animation-delay: -2s;
|
animation-delay: -2s;
|
||||||
@@ -371,14 +374,21 @@
|
|||||||
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">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-layout-content class="under" style="min-height: 0;">
|
<a-layout-content class="under" style="min-height: 0;">
|
||||||
<div class="waves-header">
|
<div class="waves-header">
|
||||||
<div class="waves-inner-header"></div>
|
<div class="waves-inner-header"></div>
|
||||||
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
|
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
|
||||||
<defs>
|
<defs>
|
||||||
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
|
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
|
||||||
</defs>
|
</defs>
|
||||||
@@ -396,8 +406,8 @@
|
|||||||
<a-col style="width: 100%;">
|
<a-col style="width: 100%;">
|
||||||
<h1 class="title headline zoom">
|
<h1 class="title headline zoom">
|
||||||
<span class="words-wrapper">
|
<span class="words-wrapper">
|
||||||
<b class="is-visible">{{ i18n "pages.login.title" }}</b>
|
<b class="is-visible">{{ i18n "pages.login.hello" }}</b>
|
||||||
<b>3X-UI</b>
|
<b>{{ i18n "pages.login.title" }}</b>
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -406,26 +416,30 @@
|
|||||||
<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 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" }}' @keydown.enter.native="login">
|
placeholder='{{ i18n "password" }}'
|
||||||
|
@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" }}' @keydown.enter.native="login">
|
placeholder='{{ i18n "secretToken" }}'
|
||||||
|
@keydown.enter.native="login">
|
||||||
</password-input>
|
</password-input>
|
||||||
</a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<div class="wave-btn-bg wave-btn-bg-cl" :style="loading ? { width: '52px' } : { display: 'inline-block' }">
|
<div style="height: 50px;" class="wave-btn-bg wave-btn-bg-cl"
|
||||||
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined">
|
:style="loading ? { width: '52px' } : { display: 'inline-block' }">
|
||||||
|
<a-button class="ant-btn-primary-login" type="primary"
|
||||||
|
:loading="loading" @click="login"
|
||||||
|
:icon="loading ? 'poweroff' : undefined">
|
||||||
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -434,7 +448,9 @@
|
|||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" style="width: 150px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select ref="selectLang" v-model="lang"
|
||||||
|
@change="setLang(lang)" style="width: 150px;"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
@@ -445,12 +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>
|
<theme-switch></theme-switch>
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
|
||||||
</a-col>
|
|
||||||
<a-col>
|
|
||||||
<theme-switch />
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@@ -460,7 +471,7 @@
|
|||||||
</a-row>
|
</a-row>
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</transition>
|
</transition>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
{{template "component/password" .}}
|
{{template "component/password" .}}
|
||||||
|
|||||||
@@ -15,10 +15,6 @@
|
|||||||
<a-icon type="tool"></a-icon>
|
<a-icon type="tool"></a-icon>
|
||||||
<span><b>{{ i18n "menu.xray"}}</b></span>
|
<span><b>{{ i18n "menu.xray"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
|
||||||
<!-- <a-icon type="laptop"></a-icon>-->
|
|
||||||
<!-- <span>Client</span>-->
|
|
||||||
<!--</a-menu-item>-->
|
|
||||||
<a-menu-item key="{{ .base_path }}logout">
|
<a-menu-item key="{{ .base_path }}logout">
|
||||||
<a-icon type="logout"></a-icon>
|
<a-icon type="logout"></a-icon>
|
||||||
<span><b>{{ i18n "menu.logout"}}</b></span>
|
<span><b>{{ i18n "menu.logout"}}</b></span>
|
||||||
@@ -28,12 +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 />
|
|
||||||
</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" .}}
|
||||||
@@ -47,12 +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 />
|
|
||||||
</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>
|
||||||
|
|||||||
236
web/html/xui/component/sortableTable.html
Normal file
236
web/html/xui/component/sortableTable.html
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
{{define "component/sortableTableTrigger"}}
|
||||||
|
<a-icon type="drag"
|
||||||
|
class="sortable-icon"
|
||||||
|
style="cursor: move;"
|
||||||
|
@mouseup="mouseUpHandler"
|
||||||
|
@mousedown="mouseDownHandler"
|
||||||
|
@click="clickHandler" />
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "component/sortableTable"}}
|
||||||
|
<script>
|
||||||
|
const DRAGGABLE_ROW_CLASS = 'draggable-row';
|
||||||
|
|
||||||
|
const findParentRowElement = (el) => {
|
||||||
|
if (!el || !el.tagName) {
|
||||||
|
return null;
|
||||||
|
} else if (el.classList.contains(DRAGGABLE_ROW_CLASS)) {
|
||||||
|
return el;
|
||||||
|
} else if (el.parentNode) {
|
||||||
|
return findParentRowElement(el.parentNode);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.component('a-table-sortable', {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sortingElementIndex: null,
|
||||||
|
newElementIndex: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: ['data-source', 'customRow'],
|
||||||
|
inheritAttrs: false,
|
||||||
|
provide() {
|
||||||
|
const sortable = {}
|
||||||
|
|
||||||
|
Object.defineProperty(sortable, "setSortableIndex", {
|
||||||
|
enumerable: true,
|
||||||
|
get: () => this.setCurrentSortableIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(sortable, "resetSortableIndex", {
|
||||||
|
enumerable: true,
|
||||||
|
get: () => this.resetSortableIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
sortable,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function (createElement) {
|
||||||
|
return createElement('a-table', {
|
||||||
|
class: {
|
||||||
|
'ant-table-is-sorting': this.isDragging(),
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
...this.$attrs,
|
||||||
|
'data-source': this.records,
|
||||||
|
customRow: (record, index) => this.customRowRender(record, index),
|
||||||
|
},
|
||||||
|
on: this.$listeners,
|
||||||
|
nativeOn: {
|
||||||
|
drop: (e) => this.dropHandler(e),
|
||||||
|
},
|
||||||
|
scopedSlots: this.$scopedSlots,
|
||||||
|
}, this.$slots.default, )
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$memoSort = {};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isDragging() {
|
||||||
|
const currentIndex = this.sortingElementIndex;
|
||||||
|
return currentIndex !== null && currentIndex !== undefined;
|
||||||
|
},
|
||||||
|
resetSortableIndex(e, index) {
|
||||||
|
this.sortingElementIndex = null;
|
||||||
|
this.newElementIndex = null;
|
||||||
|
this.$memoSort = {};
|
||||||
|
},
|
||||||
|
setCurrentSortableIndex(e, index) {
|
||||||
|
this.sortingElementIndex = index;
|
||||||
|
},
|
||||||
|
dragStartHandler(e, index) {
|
||||||
|
if (!this.isDragging()) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hideDragImage = this.$el.cloneNode(true);
|
||||||
|
hideDragImage.id = "hideDragImage-hide";
|
||||||
|
hideDragImage.style.opacity = 0;
|
||||||
|
e.dataTransfer.setDragImage(hideDragImage, 0, 0);
|
||||||
|
},
|
||||||
|
dragStopHandler(e, index) {
|
||||||
|
const hideDragImage = document.getElementById('hideDragImage-hide');
|
||||||
|
if (hideDragImage) hideDragImage.remove();
|
||||||
|
this.resetSortableIndex(e, index);
|
||||||
|
},
|
||||||
|
dragOverHandler(e, index) {
|
||||||
|
if (!this.isDragging()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const currentIndex = this.sortingElementIndex;
|
||||||
|
if (index === currentIndex) {
|
||||||
|
this.newElementIndex = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = findParentRowElement(e.target);
|
||||||
|
if (!row) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = row.getBoundingClientRect();
|
||||||
|
const offsetTop = e.pageY - rect.top;
|
||||||
|
|
||||||
|
if (offsetTop < rect.height / 2) {
|
||||||
|
this.newElementIndex = Math.max(index - 1, 0);
|
||||||
|
} else {
|
||||||
|
this.newElementIndex = index;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dropHandler(e) {
|
||||||
|
if (this.isDragging()) {
|
||||||
|
this.$emit('onsort', this.sortingElementIndex, this.newElementIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customRowRender(record, index) {
|
||||||
|
const parentMethodResult = this.customRow?.(record, index) || {};
|
||||||
|
const newIndex = this.newElementIndex;
|
||||||
|
const currentIndex = this.sortingElementIndex;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...parentMethodResult,
|
||||||
|
attrs: {
|
||||||
|
...(parentMethodResult?.attrs || {}),
|
||||||
|
draggable: true,
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
...(parentMethodResult?.on || {}),
|
||||||
|
dragstart: (e) => this.dragStartHandler(e, index),
|
||||||
|
dragend: (e) => this.dragStopHandler(e, index),
|
||||||
|
dragover: (e) => this.dragOverHandler(e, index),
|
||||||
|
},
|
||||||
|
class: {
|
||||||
|
...(parentMethodResult?.class || {}),
|
||||||
|
[DRAGGABLE_ROW_CLASS]: true,
|
||||||
|
['dragging']: this.isDragging()
|
||||||
|
? (newIndex === null ? index === currentIndex : index === newIndex)
|
||||||
|
: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
records() {
|
||||||
|
const newIndex = this.newElementIndex;
|
||||||
|
const currentIndex = this.sortingElementIndex;
|
||||||
|
|
||||||
|
if (!this.isDragging() || newIndex === null || currentIndex === newIndex) {
|
||||||
|
return this.dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$memoSort.newIndex === newIndex) {
|
||||||
|
return this.$memoSort.list;
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = [...this.dataSource];
|
||||||
|
list.splice(newIndex, 0, list.splice(currentIndex, 1)[0]);
|
||||||
|
|
||||||
|
this.$memoSort = {
|
||||||
|
newIndex,
|
||||||
|
list,
|
||||||
|
};
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('table-sort-trigger', {
|
||||||
|
template: `{{template "component/sortableTableTrigger"}}`,
|
||||||
|
props: ['item-index'],
|
||||||
|
inject: ['sortable'],
|
||||||
|
methods: {
|
||||||
|
mouseDownHandler(e) {
|
||||||
|
if (this.sortable) {
|
||||||
|
this.sortable.setSortableIndex(e, this.itemIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mouseUpHandler(e) {
|
||||||
|
if (this.sortable) {
|
||||||
|
this.sortable.resetSortableIndex(e, this.itemIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickHandler(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@media only screen and (max-width: 767px) {
|
||||||
|
.sortable-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-table-is-sorting .draggable-row td {
|
||||||
|
background-color: #ffffff !important;
|
||||||
|
}
|
||||||
|
.dark .ant-table-is-sorting .draggable-row td {
|
||||||
|
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 {
|
||||||
|
opacity: 1;
|
||||||
|
box-shadow: 1px -2px 2px #008771;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.ant-table-is-sorting .dragging .ant-table-row-index {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{{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}}
|
||||||
|
|
||||||
@@ -10,30 +16,46 @@
|
|||||||
<script>
|
<script>
|
||||||
function createThemeSwitcher() {
|
function createThemeSwitcher() {
|
||||||
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||||
|
const isUltra = localStorage.getItem('isUltraDarkThemeEnabled') === 'true';
|
||||||
|
if (isUltra) {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'ultra-dark');
|
||||||
|
}
|
||||||
const theme = isDarkTheme ? 'dark' : 'light';
|
const theme = isDarkTheme ? 'dark' : 'light';
|
||||||
document.querySelector('body').setAttribute('class', theme)
|
document.querySelector('body').setAttribute('class', theme);
|
||||||
return {
|
return {
|
||||||
isDarkTheme,
|
isDarkTheme,
|
||||||
|
isUltra,
|
||||||
get currentTheme() {
|
get currentTheme() {
|
||||||
return this.isDarkTheme ? 'dark' : 'light';
|
return this.isDarkTheme ? 'dark' : 'light';
|
||||||
},
|
},
|
||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
this.isDarkTheme = !this.isDarkTheme;
|
this.isDarkTheme = !this.isDarkTheme;
|
||||||
localStorage.setItem('dark-mode', this.isDarkTheme);
|
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||||
document.querySelector('body').setAttribute('class', this.isDarkTheme ? 'dark' : 'light')
|
document.querySelector('body').setAttribute('class', this.isDarkTheme ? 'dark' : 'light');
|
||||||
document.getElementById('message').className = themeSwitcher.currentTheme;
|
document.getElementById('message').className = themeSwitcher.currentTheme;
|
||||||
},
|
},
|
||||||
|
toggleUltra() {
|
||||||
|
this.isUltra = !this.isUltra;
|
||||||
|
if (this.isUltra) {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'ultra-dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.removeAttribute('data-theme');
|
||||||
|
}
|
||||||
|
localStorage.setItem('isUltraDarkThemeEnabled', this.isUltra.toString());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const themeSwitcher = createThemeSwitcher();
|
const themeSwitcher = createThemeSwitcher();
|
||||||
|
|
||||||
Vue.component('theme-switch', {
|
Vue.component('theme-switch', {
|
||||||
props: [],
|
props: [],
|
||||||
template: `{{template "component/themeSwitchTemplate"}}`,
|
template: `{{template "component/themeSwitchTemplate"}}`,
|
||||||
data: () => ({ themeSwitcher }),
|
data: () => ({
|
||||||
|
themeSwitcher
|
||||||
|
}),
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$message.config({getContainer: () => document.getElementById('message')});
|
this.$message.config({
|
||||||
|
getContainer: () => document.getElementById('message')
|
||||||
|
});
|
||||||
document.getElementById('message').className = themeSwitcher.currentTheme;
|
document.getElementById('message').className = themeSwitcher.currentTheme;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
|
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
|
||||||
:closable="true" :mask-closable="false"
|
:closable="true" :mask-closable="false"
|
||||||
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
||||||
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<a-modal id="fakedns-modal" v-model="fakednsModal.visible" :title="fakednsModal.title" @ok="fakednsModal.ok"
|
<a-modal id="fakedns-modal" v-model="fakednsModal.visible" :title="fakednsModal.title" @ok="fakednsModal.ok"
|
||||||
:closable="true" :mask-closable="false"
|
:closable="true" :mask-closable="false"
|
||||||
:ok-text="fakednsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="fakednsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.fakedns.ipPool" }}'>
|
<a-form-item label='{{ i18n "pages.xray.fakedns.ipPool" }}'>
|
||||||
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
|
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -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,13 +75,13 @@
|
|||||||
</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="app.ipLimitEnable && client.email && isEdit">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlog" }} </span>
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "form/inbound"}}
|
{{define "form/inbound"}}
|
||||||
<!-- base -->
|
<!-- base -->
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "enable" }}'>
|
<a-form-item label='{{ i18n "enable" }}'>
|
||||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/dokodemo"}}
|
{{define "form/dokodemo"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
||||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "form/http"}}
|
{{define "form/http"}}
|
||||||
<a-form>
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
<table style="width: 100%; text-align: center; margin: 1rem 0;">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</template>
|
</template>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
|
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/socks"}}
|
{{define "form/socks"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
||||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.settings.auth === 'password'">
|
<template v-if="inbound.settings.auth === 'password'">
|
||||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
<table style="width: 100%; text-align: center; margin: 1rem 0;">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
|
|||||||
@@ -19,23 +19,19 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
||||||
<a-button type="primary" size="small"
|
|
||||||
@click="inbound.settings.addFallback()">
|
|
||||||
+
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- trojan fallbacks -->
|
<!-- trojan fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }"
|
||||||
|
:wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;">
|
<a-divider style="margin:0;">
|
||||||
Fallback [[ index + 1 ]]
|
Fallback [[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;" />
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='SNI'>
|
<a-form-item label='SNI'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
@@ -53,6 +49,6 @@
|
|||||||
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider style="margin:0;"></a-divider>
|
<a-divider style="margin:5px 0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -21,23 +21,19 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
||||||
<a-button type="primary" size="small"
|
|
||||||
@click="inbound.settings.addFallback()">
|
|
||||||
+
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- vless fallbacks -->
|
<!-- vless fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }"
|
||||||
|
:wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;">
|
<a-divider style="margin:0;">
|
||||||
Fallback [[ index + 1 ]]
|
Fallback [[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;" />
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='SNI'>
|
<a-form-item label='SNI'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/wireguard"}}
|
{{define "form/wireguard"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<a-form-item label="Peers">
|
<a-form-item label="Peers">
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
|
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;">
|
<a-divider style="margin:0;">
|
||||||
Peer [[ index + 1 ]]
|
Peer [[ index + 1 ]]
|
||||||
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
|
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "form/sniffing"}}
|
{{define "form/sniffing"}}
|
||||||
<a-divider style="margin:5px 0 0;"></a-divider>
|
<a-divider style="margin:5px 0 0;"></a-divider>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Sniffing
|
Sniffing
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
{{define "form/streamGRPC"}}
|
{{define "form/streamGRPC"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<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>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/streamHTTP"}}
|
{{define "form/streamHTTP"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
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}}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.kcp.type" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None</a-select-option>
|
<a-select-option value="none">None</a-select-option>
|
||||||
<a-select-option value="srtp">SRTP</a-select-option>
|
<a-select-option value="srtp">SRTP</a-select-option>
|
||||||
<a-select-option value="utp">uTP</a-select-option>
|
<a-select-option value="utp">uTP</a-select-option>
|
||||||
@@ -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}}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/streamQUIC"}}
|
{{define "form/streamQUIC"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None</a-select-option>
|
<a-select-option value="none">None</a-select-option>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.quic.type" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None</a-select-option>
|
<a-select-option value="none">None</a-select-option>
|
||||||
<a-select-option value="srtp">SRTP</a-select-option>
|
<a-select-option value="srtp">SRTP</a-select-option>
|
||||||
<a-select-option value="utp">uTP</a-select-option>
|
<a-select-option value="utp">uTP</a-select-option>
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
{{define "form/streamSettings"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :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" @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"}}
|
||||||
|
|||||||
@@ -34,16 +34,16 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Min/Max Version">
|
<a-form-item label="Min/Max Version">
|
||||||
<a-input-group compact>
|
<a-input-group compact>
|
||||||
<a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.tls.minVersion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<a-form-item label="uTLS">
|
||||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 50%"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value=''>None</a-select-option>
|
<a-select-option value=''>None</a-select-option>
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
@@ -73,10 +73,10 @@
|
|||||||
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="cert.useFile">
|
<template v-if="cert.useFile">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input v-model.trim="cert.certFile"></a-input>
|
<a-input v-model.trim="cert.certFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input v-model.trim="cert.keyFile"></a-input>
|
<a-input v-model.trim="cert.keyFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
@@ -85,10 +85,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -124,10 +124,10 @@
|
|||||||
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="cert.useFile">
|
<template v-if="cert.useFile">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input v-model.trim="cert.certFile"></a-input>
|
<a-input v-model.trim="cert.certFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input v-model.trim="cert.keyFile"></a-input>
|
<a-input v-model.trim="cert.keyFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
@@ -136,10 +136,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='uTLS'>
|
<a-form-item label='uTLS'>
|
||||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 50%"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -180,10 +180,10 @@
|
|||||||
<a-form-item label='SpiderX'>
|
<a-form-item label='SpiderX'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Private Key'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Public Key'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -848,9 +843,7 @@
|
|||||||
okText: '{{ i18n "pages.inbounds.create"}}',
|
okText: '{{ i18n "pages.inbounds.create"}}',
|
||||||
cancelText: '{{ i18n "close" }}',
|
cancelText: '{{ i18n "close" }}',
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (inbound, dbInbound) => {
|
||||||
inModal.loading();
|
await this.addInbound(inbound, dbInbound, inModal);
|
||||||
await this.addInbound(inbound, dbInbound);
|
|
||||||
inModal.close();
|
|
||||||
},
|
},
|
||||||
isEdit: false
|
isEdit: false
|
||||||
});
|
});
|
||||||
@@ -865,9 +858,7 @@
|
|||||||
inbound: inbound,
|
inbound: inbound,
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (inbound, dbInbound) => {
|
||||||
inModal.loading();
|
|
||||||
await this.updateInbound(inbound, dbInbound);
|
await this.updateInbound(inbound, dbInbound);
|
||||||
inModal.close();
|
|
||||||
},
|
},
|
||||||
isEdit: true
|
isEdit: true
|
||||||
});
|
});
|
||||||
@@ -917,9 +908,7 @@
|
|||||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (clients, dbInboundId) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientModal.loading();
|
await this.addClient(clients, dbInboundId, clientModal);
|
||||||
await this.addClient(clients, dbInboundId);
|
|
||||||
clientModal.close();
|
|
||||||
},
|
},
|
||||||
isEdit: false
|
isEdit: false
|
||||||
});
|
});
|
||||||
@@ -931,9 +920,7 @@
|
|||||||
okText: '{{ i18n "pages.client.bulk"}}',
|
okText: '{{ i18n "pages.client.bulk"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (clients, dbInboundId) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientsBulkModal.loading();
|
await this.addClient(clients, dbInboundId, clientsBulkModal);
|
||||||
await this.addClient(clients, dbInboundId);
|
|
||||||
clientsBulkModal.close();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -962,19 +949,19 @@
|
|||||||
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async addClient(clients, dbInboundId) {
|
async addClient(clients, dbInboundId, modal) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + clients.toString() + ']}',
|
settings: '{"clients": [' + clients.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/addClient`, data);
|
await this.submit(`/panel/inbound/addClient`, data, modal);
|
||||||
},
|
},
|
||||||
async updateClient(client, dbInboundId, clientId) {
|
async updateClient(client, dbInboundId, clientId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + client.toString() + ']}',
|
settings: '{"clients": [' + client.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
|
await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
|
||||||
},
|
},
|
||||||
resetTraffic(dbInboundId) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
@@ -1077,8 +1064,8 @@
|
|||||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
async submit(url, data) {
|
async submit(url, data, modal) {
|
||||||
const msg = await HttpUtil.postWithModal(url, data);
|
const msg = await HttpUtil.postWithModal(url, data, modal);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
await this.getDBInbounds();
|
await this.getDBInbounds();
|
||||||
}
|
}
|
||||||
@@ -1237,7 +1224,6 @@
|
|||||||
okText: '{{ i18n "pages.inbounds.import" }}',
|
okText: '{{ i18n "pages.inbounds.import" }}',
|
||||||
confirm: async (dbInboundText) => {
|
confirm: async (dbInboundText) => {
|
||||||
await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal);
|
await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal);
|
||||||
promptModal.close();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,23 +10,11 @@
|
|||||||
margin-inline: 0.3rem;
|
margin-inline: 0.3rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark h2 {
|
.ant-card-dark h2 {
|
||||||
color: hsla(0, 0%, 100%, .65);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
|
||||||
|
|
||||||
.dark .ant-card-hoverable:hover,
|
|
||||||
.dark .ant-space-item > .ant-tabs:hover {
|
|
||||||
transform: scale(0.987);
|
|
||||||
outline-color: #40434d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .ant-card-bordered {
|
|
||||||
outline: 2px solid var(--dark-color-background);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -104,8 +92,8 @@
|
|||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
||||||
<a-tag color="green">Xray [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
<a-tag :color="status.xray.color">Xray: [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
<a-tag color="green">OS [[ formatSecond(status.uptime) ]]</a-tag>
|
<a-tag color="green">OS: [[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :lg="12">
|
||||||
@@ -153,10 +141,10 @@
|
|||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>{{ i18n "usage"}}:</b>
|
<b>{{ i18n "usage"}}:</b>
|
||||||
<a-tag color="green">
|
<a-tag color="green">
|
||||||
RAM [[ sizeFormat(status.appStats.mem) ]]
|
RAM: [[ sizeFormat(status.appStats.mem) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tag color="green">
|
<a-tag color="green">
|
||||||
Threads [[ status.appStats.threads ]]
|
Threads: [[ status.appStats.threads ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -271,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>
|
||||||
|
|||||||
@@ -76,21 +76,15 @@
|
|||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
<a-alert type="error" v-if="confAlerts.length>0" style="margin: 10px 5px;"
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
color="red"
|
color="red"
|
||||||
description='{{ i18n "secAlertSsl" }}'
|
show-icon
|
||||||
show-icon closable
|
closable
|
||||||
>
|
|
||||||
</a-alert>
|
|
||||||
<a-alert type="error" v-if="confAlerts.length>0" style="margin-bottom: 10px"
|
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
|
||||||
color="red"
|
|
||||||
show-icon closable
|
|
||||||
>
|
>
|
||||||
<template slot="description">
|
<template slot="description">
|
||||||
{{ i18n "secAlertConf" }}
|
<b>{{ i18n "secAlertConf" }}</b>
|
||||||
<li v-for="a in confAlerts">- [[ a ]]</li>
|
<ul><li v-for="a in confAlerts">[[ a ]]</li></ul>
|
||||||
</template>
|
</template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
@@ -144,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>
|
||||||
@@ -203,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>
|
||||||
@@ -263,7 +257,6 @@
|
|||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta title="Telegram Bot Language" />
|
<a-list-item-meta title="Telegram Bot Language" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select
|
||||||
@@ -289,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">
|
||||||
@@ -302,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>
|
||||||
@@ -333,7 +377,6 @@
|
|||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
user: {},
|
user: {},
|
||||||
lang: getLang(),
|
lang: getLang(),
|
||||||
showAlert: false,
|
|
||||||
remarkModels: {i:'Inbound',e:'Email',o:'Other'},
|
remarkModels: {i:'Inbound',e:'Email',o:'Other'},
|
||||||
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
|
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
|
||||||
datepickerList: [{name:'Gregorian (Standard)', value: 'gregorian'}, {name:'Jalalian (شمسی)', value: 'jalalian'}],
|
datepickerList: [{name:'Gregorian (Standard)', value: 'gregorian'}, {name:'Jalalian (شمسی)', value: 'jalalian'}],
|
||||||
@@ -352,10 +395,44 @@
|
|||||||
streamSettings: {
|
streamSettings: {
|
||||||
sockopt: {
|
sockopt: {
|
||||||
tcpKeepAliveIdle: 100,
|
tcpKeepAliveIdle: 100,
|
||||||
TcpNoDelay: true
|
tcpNoDelay: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
defaultMux: {
|
||||||
|
enabled: true,
|
||||||
|
concurrency: 8,
|
||||||
|
xudpConcurrency: 16,
|
||||||
|
xudpProxyUDP443: "reject"
|
||||||
|
},
|
||||||
|
defaultRules: [
|
||||||
|
{
|
||||||
|
type: "field",
|
||||||
|
outboundTag: "direct",
|
||||||
|
domain: [
|
||||||
|
"geosite:category-ir",
|
||||||
|
"geosite:cn"
|
||||||
|
],
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "field",
|
||||||
|
outboundTag: "direct",
|
||||||
|
ip: [
|
||||||
|
"geoip:private",
|
||||||
|
"geoip:ir",
|
||||||
|
"geoip:cn"
|
||||||
|
],
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
],
|
||||||
|
countryOptions: [
|
||||||
|
{ label: 'Private IP/Domain', value: 'private' },
|
||||||
|
{ label: '🇮🇷 Iran', value: 'ir' },
|
||||||
|
{ label: '🇨🇳 China', value: 'cn' },
|
||||||
|
{ label: '🇷🇺 Russia', value: 'ru' },
|
||||||
|
{ label: '🇻🇳 Vietnam', value: 'vn' },
|
||||||
|
],
|
||||||
get remarkModel() {
|
get remarkModel() {
|
||||||
rm = this.allSetting.remarkModel;
|
rm = this.allSetting.remarkModel;
|
||||||
return rm.length>1 ? rm.substring(1).split('') : [];
|
return rm.length>1 ? rm.substring(1).split('') : [];
|
||||||
@@ -414,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() {
|
||||||
@@ -451,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();
|
||||||
@@ -491,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) {
|
||||||
@@ -511,27 +596,80 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
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 [];
|
||||||
var alerts = []
|
var alerts = []
|
||||||
if (this.allSetting.port == 54321) alerts.push('{{ i18n "pages.settings.panelPort"}}');
|
if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}');
|
||||||
|
if (this.allSetting.webPort == 54321) alerts.push('{{ i18n "secAlertPanelPort" }}');
|
||||||
panelPath = window.location.pathname.split('/').length<4
|
panelPath = window.location.pathname.split('/').length<4
|
||||||
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "pages.settings.panelSettings"}} {{ i18n "pages.settings.panelUrlPath"}}');
|
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}');
|
||||||
if (this.allSetting.subEnable) {
|
if (this.allSetting.subEnable) {
|
||||||
subPath = this.allSetting.subURI.length >0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
|
subPath = this.allSetting.subURI.length >0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
|
||||||
if (subPath == '/sub/') alerts.push('{{ i18n "pages.settings.subSettings"}} {{ i18n "pages.settings.subPath"}}');
|
if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
|
||||||
subJsonPath = this.allSetting.subJsonURI.length >0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
|
subJsonPath = this.allSetting.subJsonURI.length >0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
|
||||||
if (subJsonPath == '/json/') alerts.push('JSON {{ i18n "pages.settings.subPath"}}');
|
if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
|
||||||
}
|
}
|
||||||
return alerts
|
return alerts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
if (window.location.protocol !== "https:") {
|
|
||||||
this.showAlert = true;
|
|
||||||
}
|
|
||||||
await this.getAllSetting();
|
await this.getAllSetting();
|
||||||
while (true) {
|
while (true) {
|
||||||
await PromiseUtil.sleep(1000);
|
await PromiseUtil.sleep(1000);
|
||||||
|
|||||||
@@ -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:6} }" :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>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css?{{ .cur_ver }}">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .cur_ver }}">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .cur_ver }}">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
|
||||||
|
|
||||||
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/codemirror.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/codemirror.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
|
||||||
@@ -74,8 +74,8 @@
|
|||||||
</transition>
|
</transition>
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
<a-card hoverable style="margin-bottom: .5rem;">
|
<a-card hoverable style="margin-bottom: .5rem;">
|
||||||
<a-row>
|
<a-row style="display: flex; flex-wrap: wrap; align-items: center;">
|
||||||
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
<a-col :xs="24" :sm="10" style="padding: 4px;">
|
||||||
<a-space direction="horizontal">
|
<a-space direction="horizontal">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.xray.save" }}</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.xray.save" }}</a-button>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">{{ i18n "pages.xray.restart" }}</a-button>
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">{{ i18n "pages.xray.restart" }}</a-button>
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
</a-popover>
|
</a-popover>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="16">
|
<a-col :xs="24" :sm="14">
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
||||||
@@ -180,7 +180,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 +193,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>
|
||||||
@@ -290,15 +290,19 @@
|
|||||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||||
message='{{ i18n "pages.xray.RoutingsDesc"}}' show-icon></a-alert>
|
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 :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
|
<a-table-sortable :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
:data-source="routingRuleData"
|
:data-source="routingRuleData"
|
||||||
:scroll="isMobile ? {} : { x: 1000 }"
|
:scroll="isMobile ? {} : { x: 1000 }"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
:indent-size="0"
|
:indent-size="0"
|
||||||
:style="isMobile ? 'padding: 5px 0' : 'margin-top: 10px;'">
|
:style="isMobile ? 'padding: 5px 0' : 'margin-top: 10px;'"
|
||||||
|
v-on:onSort="replaceRule">
|
||||||
<template slot="action" slot-scope="text, rule, index">
|
<template slot="action" slot-scope="text, rule, index">
|
||||||
|
<table-sort-trigger :item-index="index"></table-sort-trigger>
|
||||||
|
<span class="ant-table-row-index">
|
||||||
[[ index+1 ]]
|
[[ index+1 ]]
|
||||||
|
</span>
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
@@ -404,7 +408,7 @@
|
|||||||
</a-button>
|
</a-button>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</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-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-row>
|
<a-row>
|
||||||
@@ -415,7 +419,14 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
||||||
<a-icon type="sync" :spin="refreshing" @click="refreshOutboundTraffic()" style="margin: 0 5px;"></a-icon>
|
<a-icon type="sync" :spin="refreshing" @click="refreshOutboundTraffic()" style="margin: 0 5px;"></a-icon>
|
||||||
<a-icon type="retweet" @click="resetOutboundTraffic(-1)"></a-icon>
|
<a-popconfirm placement="topRight" @confirm="resetOutboundTraffic(-1)"
|
||||||
|
title='{{ i18n "pages.inbounds.resetTrafficContent"}}'
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "reset"}}'
|
||||||
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
|
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: #008771' : 'color: #008771'"></a-icon>
|
||||||
|
<a-icon type="retweet" style="cursor: pointer;"></a-icon>
|
||||||
|
</a-popconfirm>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-table :columns="outboundColumns" bordered
|
<a-table :columns="outboundColumns" bordered
|
||||||
@@ -526,6 +537,8 @@
|
|||||||
<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>
|
||||||
@@ -535,6 +548,7 @@
|
|||||||
<a-tab-pane key="tpl-6" tab='DNS' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-6" 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">
|
||||||
@@ -630,6 +644,7 @@
|
|||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
|
{{template "component/sortableTable" .}}
|
||||||
{{template "component/setting"}}
|
{{template "component/setting"}}
|
||||||
{{template "ruleModal"}}
|
{{template "ruleModal"}}
|
||||||
{{template "outModal"}}
|
{{template "outModal"}}
|
||||||
@@ -753,8 +768,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"],
|
||||||
@@ -857,7 +872,7 @@
|
|||||||
},
|
},
|
||||||
async getXrayResult() {
|
async getXrayResult() {
|
||||||
const msg = await HttpUtil.get("/panel/xray/getXrayResult");
|
const msg = await HttpUtil.get("/panel/xray/getXrayResult");
|
||||||
if(msg.success){
|
if (msg.success) {
|
||||||
this.restartResult=msg.obj;
|
this.restartResult=msg.obj;
|
||||||
if(msg.obj.length > 1) Vue.prototype.$message.error(msg.obj);
|
if(msg.obj.length > 1) Vue.prototype.$message.error(msg.obj);
|
||||||
}
|
}
|
||||||
@@ -989,7 +1004,7 @@
|
|||||||
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
||||||
this.cm.on('change',editor => {
|
this.cm.on('change',editor => {
|
||||||
value = editor.getValue();
|
value = editor.getValue();
|
||||||
if(this.isJsonString(value)){
|
if (this.isJsonString(value)) {
|
||||||
this[this.advSettings] = value;
|
this[this.advSettings] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1118,7 +1133,7 @@
|
|||||||
'tag': balancer.tag,
|
'tag': balancer.tag,
|
||||||
'selector': balancer.selector
|
'selector': balancer.selector
|
||||||
};
|
};
|
||||||
if (balancer.strategy == 'roundRobin') {
|
if (balancer.strategy === 'roundRobin' || balancer.strategy === 'leastload' || balancer.strategy === 'leastping') {
|
||||||
tmpBalancer.strategy = {
|
tmpBalancer.strategy = {
|
||||||
'type': balancer.strategy
|
'type': balancer.strategy
|
||||||
};
|
};
|
||||||
@@ -1145,7 +1160,7 @@
|
|||||||
'tag': balancer.tag,
|
'tag': balancer.tag,
|
||||||
'selector': balancer.selector
|
'selector': balancer.selector
|
||||||
};
|
};
|
||||||
if (balancer.strategy == 'roundRobin') {
|
if (balancer.strategy === 'roundRobin' || balancer.strategy === 'leastload' || balancer.strategy === 'leastping') {
|
||||||
tmpBalancer.strategy = {
|
tmpBalancer.strategy = {
|
||||||
'type': balancer.strategy
|
'type': balancer.strategy
|
||||||
};
|
};
|
||||||
@@ -1167,24 +1182,26 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteBalancer(index) {
|
deleteBalancer(index) {
|
||||||
newTemplateSettings = this.templateSettings;
|
let newTemplateSettings = { ...this.templateSettings };
|
||||||
|
|
||||||
//remove from balancers
|
// Remove from balancers
|
||||||
const oldTag = this.balancersData[index].tag;
|
const removedBalancer = this.balancersData.splice(index, 1)[0];
|
||||||
this.balancersData.splice(index, 1);
|
|
||||||
|
|
||||||
// remove from settings
|
// Remove from settings
|
||||||
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag == oldTag);
|
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
|
||||||
newTemplateSettings.routing.balancers.splice(realIndex, 1);
|
newTemplateSettings.routing.balancers.splice(realIndex, 1);
|
||||||
|
|
||||||
// remove related routing rules
|
// Remove related routing rules
|
||||||
let rules = [];
|
newTemplateSettings.routing.rules.forEach((rule) => {
|
||||||
newTemplateSettings.routing.rules.forEach((r) => {
|
if (rule.balancerTag === removedBalancer.tag) {
|
||||||
if (!r.balancerTag || r.balancerTag != oldTag) {
|
delete rule.balancerTag;
|
||||||
rules.push(r);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
newTemplateSettings.routing.rules = rules;
|
|
||||||
|
// 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.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
addReverse(){
|
addReverse(){
|
||||||
@@ -1391,8 +1408,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; },
|
||||||
@@ -1445,8 +1485,8 @@
|
|||||||
if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) {
|
if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) {
|
||||||
this.templateSettings.routing.balancers.forEach((o, index) => {
|
this.templateSettings.routing.balancers.forEach((o, index) => {
|
||||||
let strategy = "random"
|
let strategy = "random"
|
||||||
if (o.strategy && o.strategy.type == "roundRobin") {
|
if (o.strategy && (o.strategy.type == "roundRobin" || o.strategy.type == "leastload" || o.strategy.type == "leastping")) {
|
||||||
strategy = o.strategy.type
|
strategy = o.strategy.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.push({
|
data.push({
|
||||||
@@ -1999,7 +2039,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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2025,7 +2081,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;
|
||||||
newTemplateSettings.fakedns = newValue.length >0 ? newValue : null;
|
if (this.enableDNS) {
|
||||||
|
newTemplateSettings.fakedns = newValue.length > 0 ? newValue : null;
|
||||||
|
} else {
|
||||||
|
delete newTemplateSettings.fakedns;
|
||||||
|
}
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
:ok-text="balancerModal.okText"
|
:ok-text="balancerModal.okText"
|
||||||
cancel-text='{{ i18n "close" }}'
|
cancel-text='{{ i18n "close" }}'
|
||||||
:class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
|
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
|
||||||
:validate-status="balancerModal.duplicateTag? 'warning' : 'success'">
|
:validate-status="balancerModal.duplicateTag? 'warning' : 'success'">
|
||||||
<a-input v-model.trim="balancerModal.balancer.tag" @change="balancerModal.check()"
|
<a-input v-model.trim="balancerModal.balancer.tag" @change="balancerModal.check()"
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
||||||
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
<a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
|
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
|
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
|
||||||
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='Domain Matcher'>
|
<a-form-item label='Domain Matcher'>
|
||||||
<a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
|
<a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
|
||||||
@@ -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,50 @@ 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("fail2ban is not installed. IP limiting may not work properly.")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !j.hasLimitIp() && xray.GetAccessLogPath() == "./access.log" {
|
if shouldClearAccessLog || isAccessLogAvailable && time.Now().Unix()-j.lastClear > 3600 {
|
||||||
go j.clearLogTime()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *CheckClientIpJob) clearLogTime() {
|
|
||||||
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)
|
j.checkError(err)
|
||||||
defer logAccessP.Close()
|
|
||||||
|
|
||||||
// reopen the access log file for reading
|
// reopen the access log file for reading
|
||||||
|
accessLogPath := xray.GetAccessLogPath()
|
||||||
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 +106,18 @@ func (j *CheckClientIpJob) hasLimitIp() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) checkFail2BanInstalled() {
|
func (j *CheckClientIpJob) checkFail2BanInstalled() bool {
|
||||||
cmd := "fail2ban-client"
|
cmd := "fail2ban-client"
|
||||||
args := []string{"-h"}
|
args := []string{"-h"}
|
||||||
|
|
||||||
err := exec.Command(cmd, args...).Run()
|
err := exec.Command(cmd, args...).Run()
|
||||||
if err != nil {
|
return err == nil
|
||||||
logger.Warning("fail2ban is not installed. IP limiting may not work properly.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) processLogFile() {
|
func (j *CheckClientIpJob) processLogFile() bool {
|
||||||
accessLogPath := xray.GetAccessLogPath()
|
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 +153,7 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
j.checkError(scanner.Err())
|
j.checkError(scanner.Err())
|
||||||
|
file.Close()
|
||||||
|
|
||||||
shouldCleanLog := false
|
shouldCleanLog := false
|
||||||
|
|
||||||
@@ -189,12 +167,26 @@ 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 {
|
func (j *CheckClientIpJob) checkAccessLogAvailable(doWarning bool) bool {
|
||||||
j.clearAccessLog()
|
accessLogPath := xray.GetAccessLogPath()
|
||||||
|
isAvailable := true
|
||||||
|
warningMsg := ""
|
||||||
|
// 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 doWarning && warningMsg != "" {
|
||||||
|
logger.Warning(warningMsg)
|
||||||
|
}
|
||||||
|
return isAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) checkError(e error) {
|
func (j *CheckClientIpJob) checkError(e error) {
|
||||||
@@ -272,7 +264,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 +297,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"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func (j *CheckXrayRunningJob) Run() {
|
|||||||
j.checkTime = 0
|
j.checkTime = 0
|
||||||
} else {
|
} else {
|
||||||
j.checkTime++
|
j.checkTime++
|
||||||
//only restart if it's down 2 times in a row
|
// only restart if it's down 2 times in a row
|
||||||
if j.checkTime > 1 {
|
if j.checkTime > 1 {
|
||||||
err := j.xrayService.RestartXray(false)
|
err := j.xrayService.RestartXray(false)
|
||||||
j.checkTime = 0
|
j.checkTime = 0
|
||||||
|
|||||||
@@ -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,10 +1069,7 @@ 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 {
|
||||||
@@ -1074,12 +1080,10 @@ 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 {
|
||||||
@@ -1204,10 +1208,7 @@ 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) {
|
||||||
@@ -1354,10 +1355,7 @@ 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 {
|
||||||
@@ -1414,10 +1412,7 @@ 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 {
|
||||||
@@ -1477,10 +1472,7 @@ 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 {
|
||||||
@@ -1573,11 +1565,7 @@ 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 {
|
||||||
@@ -1588,11 +1576,7 @@ 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) {
|
||||||
@@ -1666,11 +1650,7 @@ 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) {
|
||||||
@@ -1814,7 +1794,7 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
clients, ok := settings["clients"].([]interface{})
|
clients, ok := settings["clients"].([]interface{})
|
||||||
if ok {
|
if ok {
|
||||||
// Fix Clinet configuration problems
|
// Fix Client configuration problems
|
||||||
var newClients []interface{}
|
var newClients []interface{}
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -60,12 +61,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 +439,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 +454,30 @@ 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) {
|
||||||
|
templateConfig, err := s.GetXrayConfigTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var xrayConfig map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(templateConfig), &xrayConfig)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if logConfig, ok := xrayConfig["log"].(map[string]interface{}); ok {
|
||||||
|
if accessLogPath, ok := logConfig["access"].(string); ok {
|
||||||
|
return accessLogPath == "./access.log", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, 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 +522,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 {
|
||||||
@@ -866,7 +868,7 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R
|
|||||||
Text: message,
|
Text: message,
|
||||||
ParseMode: "HTML",
|
ParseMode: "HTML",
|
||||||
}
|
}
|
||||||
//only add replyMarkup to last message
|
// only add replyMarkup to last message
|
||||||
if len(replyMarkup) > 0 && n == (len(allMessages)-1) {
|
if len(replyMarkup) > 0 && n == (len(allMessages)-1) {
|
||||||
params.ReplyMarkup = replyMarkup[0]
|
params.ReplyMarkup = replyMarkup[0]
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"online" = "Online"
|
"online" = "Online"
|
||||||
"domainName" = "Domain Name"
|
"domainName" = "Domain Name"
|
||||||
"monitor" = "Listen IP"
|
"monitor" = "Listen IP"
|
||||||
"certificate" = "Certificate"
|
"certificate" = "Digital Certificate"
|
||||||
"fail" = " Failed"
|
"fail" = " Failed"
|
||||||
"success" = " Successful"
|
"success" = " Successful"
|
||||||
"getVersion" = "Get Version"
|
"getVersion" = "Get Version"
|
||||||
@@ -54,7 +54,12 @@
|
|||||||
"security" = "Security"
|
"security" = "Security"
|
||||||
"secAlertTitle" = "Security Alert"
|
"secAlertTitle" = "Security Alert"
|
||||||
"secAlertSsl" = "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection."
|
"secAlertSsl" = "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection."
|
||||||
"secAlertConf" = "Certain configurations have been identified as susceptible to attacks, prompting immediate action to reinforce security protocols and safeguard against potential security breaches."
|
"secAlertConf" = "Certain settings are vulnerable to attacks. It is recommended to reinforce security protocols to prevent potential breaches."
|
||||||
|
"secAlertSSL" = "Panel lacks secure connection. Please install TLS certificate for data protection."
|
||||||
|
"secAlertPanelPort" = "Panel default port is vulnerable. Please configure a random or specific port."
|
||||||
|
"secAlertPanelURI" = "Panel default URI path is insecure. Please configure a complex URI path."
|
||||||
|
"secAlertSubURI" = "Subscription default URI path is insecure. Please configure a complex URI path."
|
||||||
|
"secAlertSubJsonURI" = "Subscription JSON default URI path is insecure. Please configure a complex URI path."
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Overview"
|
"dashboard" = "Overview"
|
||||||
@@ -65,6 +70,7 @@
|
|||||||
"link" = "Manage"
|
"link" = "Manage"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "Hello"
|
||||||
"title" = "Welcome"
|
"title" = "Welcome"
|
||||||
"loginAgain" = "Your session has expired, please log in again"
|
"loginAgain" = "Your session has expired, please log in again"
|
||||||
|
|
||||||
@@ -143,10 +149,8 @@
|
|||||||
"noRecommendKeepDefault" = "It is recommended to keep the default"
|
"noRecommendKeepDefault" = "It is recommended to keep the default"
|
||||||
"certificatePath" = "File Path"
|
"certificatePath" = "File Path"
|
||||||
"certificateContent" = "File Content"
|
"certificateContent" = "File Content"
|
||||||
"publicKeyPath" = "Public Key Path"
|
"publicKey" = "Public Key"
|
||||||
"publicKeyContent" = "Public Key Content"
|
"privatekey" = "Private Key"
|
||||||
"keyPath" = "Private Key Path"
|
|
||||||
"keyContent" = "Private Key Content"
|
|
||||||
"clickOnQRcode" = "Click on QR Code to Copy"
|
"clickOnQRcode" = "Click on QR Code to Copy"
|
||||||
"client" = "Client"
|
"client" = "Client"
|
||||||
"export" = "Export All URLs"
|
"export" = "Export All URLs"
|
||||||
@@ -197,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"
|
||||||
@@ -323,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"
|
||||||
@@ -442,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"
|
||||||
@@ -463,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"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"online" = "en línea"
|
"online" = "en línea"
|
||||||
"domainName" = "Nombre de dominio"
|
"domainName" = "Nombre de dominio"
|
||||||
"monitor" = "Listening IP"
|
"monitor" = "Listening IP"
|
||||||
"certificate" = "Certificado"
|
"certificate" = "Certificado Digital"
|
||||||
"fail" = "Falló"
|
"fail" = "Falló"
|
||||||
"success" = "Éxito"
|
"success" = "Éxito"
|
||||||
"getVersion" = "Obtener versión"
|
"getVersion" = "Obtener versión"
|
||||||
@@ -52,9 +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" = "Se han identificado ciertas configuraciones como susceptibles a ataques, lo que genera acciones inmediatas para reforzar los protocolos de seguridad y proteger contra posibles violaciones de seguridad."
|
"secAlertConf" = "Ciertas configuraciones son vulnerables a ataques. Se recomienda reforzar los protocolos de seguridad para prevenir posibles violaciones."
|
||||||
|
"secAlertSSL" = "El panel carece de una conexión segura. Por favor, instale un certificado TLS para la protección de datos."
|
||||||
|
"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."
|
||||||
|
"secAlertSubURI" = "La ruta URI predeterminada de la suscripción 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"
|
||||||
@@ -65,6 +70,7 @@
|
|||||||
"link" = "Gestionar"
|
"link" = "Gestionar"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "Hola"
|
||||||
"title" = "Bienvenido"
|
"title" = "Bienvenido"
|
||||||
"loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente."
|
"loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente."
|
||||||
|
|
||||||
@@ -141,12 +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"
|
||||||
"publicKeyPath" = "Ruta de la Clave Pública"
|
"publicKey" = "Clave Pública"
|
||||||
"publicKeyContent" = "Contenido de la Clave Pública"
|
"privatekey" = "Clave Privada"
|
||||||
"keyPath" = "Ruta de la Clave Privada"
|
|
||||||
"keyContent" = "Contenido de la 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"
|
||||||
@@ -197,11 +201,11 @@
|
|||||||
"last" = "Último"
|
"last" = "Último"
|
||||||
"prefix" = "Prefijo"
|
"prefix" = "Prefijo"
|
||||||
"postfix" = "Sufijo"
|
"postfix" = "Sufijo"
|
||||||
"delayedStart" = "Iniciar después del primer uso"
|
"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"
|
||||||
@@ -306,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"
|
||||||
@@ -317,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"
|
||||||
@@ -342,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."
|
||||||
@@ -383,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"
|
||||||
@@ -410,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]
|
||||||
@@ -442,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"
|
||||||
@@ -462,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"
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"online" = "آنلاین"
|
"online" = "آنلاین"
|
||||||
"domainName" = "آدرس دامنه"
|
"domainName" = "آدرس دامنه"
|
||||||
"monitor" = "آیپی اتصال"
|
"monitor" = "آیپی اتصال"
|
||||||
"certificate" = "گواهی"
|
"certificate" = "گواهی دیجیتال"
|
||||||
"fail" = "ناموفق"
|
"fail" = "ناموفق"
|
||||||
"success" = " موفق"
|
"success" = " موفق"
|
||||||
"getVersion" = "دریافت نسخه"
|
"getVersion" = "دریافت نسخه"
|
||||||
@@ -54,7 +54,12 @@
|
|||||||
"security" = "امنیت"
|
"security" = "امنیت"
|
||||||
"secAlertTitle" = "هشدارامنیتی"
|
"secAlertTitle" = "هشدارامنیتی"
|
||||||
"secAlertSsl" = "ایناتصالامن نیست. لطفا تازمانیکه تیالاس برای محافظت از دادهها فعال نشدهاست، از وارد کردن اطلاعات حساس خودداری کنید"
|
"secAlertSsl" = "ایناتصالامن نیست. لطفا تازمانیکه تیالاس برای محافظت از دادهها فعال نشدهاست، از وارد کردن اطلاعات حساس خودداری کنید"
|
||||||
"secAlertConf" = "پیکربندیهای خاصی مستعد حملات سایبری شناسایی شدهاند، اقدام فوری برای تقویت پروتکلهای امنیتی و محافظت در برابر نقضهای امنیتی لازم است"
|
"secAlertConf" = "تنظیمات خاصی در برابر حملات آسیب پذیر هستند. توصیه میشود پروتکلهای امنیتی را برای جلوگیری از نفوذ احتمالی تقویت کنید"
|
||||||
|
"secAlertSSL" = "پنل فاقد ارتباط امن است. لطفاً یک گواهینامه تیالاس برای محافظت از دادهها نصب کنید"
|
||||||
|
"secAlertPanelPort" = "استفاده از پورت پیشفرض پنل ناامن است. لطفاً یک پورت تصادفی یا خاص تنظیم کنید"
|
||||||
|
"secAlertPanelURI" = "مسیر پیشفرض لینک پنل ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
||||||
|
"secAlertSubURI" = "مسیر پیشفرض لینک سابسکریپشن ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
||||||
|
"secAlertSubJsonURI" = "مسیر پیشفرض لینک سابسکریپشن جیسون ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "نمای کلی"
|
"dashboard" = "نمای کلی"
|
||||||
@@ -65,6 +70,7 @@
|
|||||||
"link" = "مدیریت"
|
"link" = "مدیریت"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "سلام"
|
||||||
"title" = "خوشآمدید"
|
"title" = "خوشآمدید"
|
||||||
"loginAgain" = "مدت زمان استفاده بهاتمامرسیده، لطفا دوباره وارد شوید"
|
"loginAgain" = "مدت زمان استفاده بهاتمامرسیده، لطفا دوباره وارد شوید"
|
||||||
|
|
||||||
@@ -137,16 +143,14 @@
|
|||||||
"destinationPort" = "پورت مقصد"
|
"destinationPort" = "پورت مقصد"
|
||||||
"targetAddress" = "آدرس مقصد"
|
"targetAddress" = "آدرس مقصد"
|
||||||
"monitorDesc" = "بهطور پیشفرض خالیبگذارید"
|
"monitorDesc" = "بهطور پیشفرض خالیبگذارید"
|
||||||
"meansNoLimit" = " = واحد: گیگابایت) نامحدود)"
|
"meansNoLimit" = "0 = واحد: گیگابایت) نامحدود)"
|
||||||
"totalFlow" = "ترافیک کل"
|
"totalFlow" = "ترافیک کل"
|
||||||
"leaveBlankToNeverExpire" = "برای منقضینشدن خالیبگذارید"
|
"leaveBlankToNeverExpire" = "برای منقضینشدن خالیبگذارید"
|
||||||
"noRecommendKeepDefault" = "توصیهمیشود بهطور پیشفرض حفظشود"
|
"noRecommendKeepDefault" = "توصیهمیشود بهطور پیشفرض حفظشود"
|
||||||
"certificatePath" = "مسیر فایل"
|
"certificatePath" = "مسیر فایل"
|
||||||
"certificateContent" = "محتوای فایل"
|
"certificateContent" = "محتوای فایل"
|
||||||
"publicKeyPath" = "مسیر کلید عمومی"
|
"publicKey" = "کلید عمومی"
|
||||||
"publicKeyContent" = "محتوای کلید عمومی"
|
"privatekey" = "کلید خصوصی"
|
||||||
"keyPath" = "مسیر کلید خصوصی"
|
|
||||||
"keyContent" = "محتوای کلید خصوصی"
|
|
||||||
"clickOnQRcode" = "برای کپی بر روی کدتصویری کلیک کنید"
|
"clickOnQRcode" = "برای کپی بر روی کدتصویری کلیک کنید"
|
||||||
"client" = "کاربر"
|
"client" = "کاربر"
|
||||||
"export" = "استخراج لینکها"
|
"export" = "استخراج لینکها"
|
||||||
@@ -323,7 +327,7 @@
|
|||||||
"blockCountryConfigs" = "مسدودسازی کشور"
|
"blockCountryConfigs" = "مسدودسازی کشور"
|
||||||
"blockCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص مسدود میکند"
|
"blockCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص مسدود میکند"
|
||||||
"directCountryConfigs" = "اتصال مستقیم کشور"
|
"directCountryConfigs" = "اتصال مستقیم کشور"
|
||||||
"directCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص بصورت مستقیم ارسال میکند"
|
"directCountryConfigsDesc" = "اتصال مستقیم اطمینان حاصل میکند که ترافیک خاص از طریق یک سرور دیگر هدایت نمیشود."
|
||||||
"ipv4Configs" = "IPv4 مسیریابی"
|
"ipv4Configs" = "IPv4 مسیریابی"
|
||||||
"ipv4ConfigsDesc" = "این گزینهها ترافیک را از طریق آیپینسخه4 به مقصد هدایت میکند"
|
"ipv4ConfigsDesc" = "این گزینهها ترافیک را از طریق آیپینسخه4 به مقصد هدایت میکند"
|
||||||
"warpConfigs" = "WARP مسیریابی"
|
"warpConfigs" = "WARP مسیریابی"
|
||||||
@@ -442,6 +446,10 @@
|
|||||||
"bridge" = "پل"
|
"bridge" = "پل"
|
||||||
"portal" = "پورتال"
|
"portal" = "پورتال"
|
||||||
"intercon" = "اتصال میانی"
|
"intercon" = "اتصال میانی"
|
||||||
|
"settings" = "تنظیمات"
|
||||||
|
"accountInfo" = "اطلاعات حساب"
|
||||||
|
"outboundStatus" = "وضعیت خروجی"
|
||||||
|
"sendThrough" = "ارسال با"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
[pages.xray.balancer]
|
||||||
"addBalancer" = "افزودن بالانسر"
|
"addBalancer" = "افزودن بالانسر"
|
||||||
@@ -463,6 +471,8 @@
|
|||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "فعال کردن حل دامنه"
|
"enable" = "فعال کردن حل دامنه"
|
||||||
"enableDesc" = "سرور حل دامنه داخلی را فعال کنید"
|
"enableDesc" = "سرور حل دامنه داخلی را فعال کنید"
|
||||||
|
"tag" = "برچسب"
|
||||||
|
"tagDesc" = "این برچسب در قوانین مسیریابی به عنوان یک برچسب ورودی قابل استفاده خواهد بود"
|
||||||
"strategy" = "استراتژی پرسوجو"
|
"strategy" = "استراتژی پرسوجو"
|
||||||
"strategyDesc" = "استراتژی کلی برای حل نام دامنه"
|
"strategyDesc" = "استراتژی کلی برای حل نام دامنه"
|
||||||
"add" = "افزودن سرور"
|
"add" = "افزودن سرور"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"online" = "Online"
|
"online" = "Online"
|
||||||
"domainName" = "Nama Domain"
|
"domainName" = "Nama Domain"
|
||||||
"monitor" = "IP Pemantauan"
|
"monitor" = "IP Pemantauan"
|
||||||
"certificate" = "Sertifikat"
|
"certificate" = "Sertifikat Digital"
|
||||||
"fail" = "Gagal"
|
"fail" = "Gagal"
|
||||||
"success" = "Berhasil"
|
"success" = "Berhasil"
|
||||||
"getVersion" = "Dapatkan Versi"
|
"getVersion" = "Dapatkan Versi"
|
||||||
@@ -54,7 +54,12 @@
|
|||||||
"security" = "Keamanan"
|
"security" = "Keamanan"
|
||||||
"secAlertTitle" = "Peringatan keamanan"
|
"secAlertTitle" = "Peringatan keamanan"
|
||||||
"secAlertSsl" = "Koneksi ini tidak aman. Harap hindari memasukkan informasi sensitif sampai TLS diaktifkan untuk perlindungan data."
|
"secAlertSsl" = "Koneksi ini tidak aman. Harap hindari memasukkan informasi sensitif sampai TLS diaktifkan untuk perlindungan data."
|
||||||
"secAlertConf" = "Konfigurasi tertentu telah diidentifikasi rentan terhadap serangan, sehingga mendorong tindakan segera untuk memperkuat protokol keamanan dan melindungi dari potensi pelanggaran keamanan."
|
"secAlertConf" = "Beberapa pengaturan rentan terhadap serangan. Disarankan untuk memperkuat protokol keamanan guna mencegah pelanggaran potensial."
|
||||||
|
"secAlertSSL" = "Panel kekurangan koneksi yang aman. Harap instal sertifikat TLS untuk perlindungan data."
|
||||||
|
"secAlertPanelPort" = "Port default panel rentan. Harap konfigurasi port acak atau tertentu."
|
||||||
|
"secAlertPanelURI" = "Jalur URI default panel tidak aman. Harap konfigurasi jalur URI kompleks."
|
||||||
|
"secAlertSubURI" = "Jalur URI default langganan tidak aman. Harap konfigurasi jalur URI kompleks."
|
||||||
|
"secAlertSubJsonURI" = "Jalur URI default JSON langganan tidak aman. Harap konfigurasikan jalur URI kompleks."
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Ikhtisar"
|
"dashboard" = "Ikhtisar"
|
||||||
@@ -65,6 +70,7 @@
|
|||||||
"link" = "Kelola"
|
"link" = "Kelola"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "Halo"
|
||||||
"title" = "Selamat Datang"
|
"title" = "Selamat Datang"
|
||||||
"loginAgain" = "Sesi Anda telah berakhir, harap masuk kembali"
|
"loginAgain" = "Sesi Anda telah berakhir, harap masuk kembali"
|
||||||
|
|
||||||
@@ -143,10 +149,8 @@
|
|||||||
"noRecommendKeepDefault" = "Disarankan untuk tetap menggunakan pengaturan default"
|
"noRecommendKeepDefault" = "Disarankan untuk tetap menggunakan pengaturan default"
|
||||||
"certificatePath" = "Path Berkas"
|
"certificatePath" = "Path Berkas"
|
||||||
"certificateContent" = "Konten Berkas"
|
"certificateContent" = "Konten Berkas"
|
||||||
"publicKeyPath" = "Path Kunci Publik"
|
"publicKey" = "Kunci Publik"
|
||||||
"publicKeyContent" = "Konten Kunci Publik"
|
"privatekey" = "Kunci Pribadi"
|
||||||
"keyPath" = "Path Kunci Privat"
|
|
||||||
"keyContent" = "Konten Kunci Privat"
|
|
||||||
"clickOnQRcode" = "Klik pada Kode QR untuk Menyalin"
|
"clickOnQRcode" = "Klik pada Kode QR untuk Menyalin"
|
||||||
"client" = "Klien"
|
"client" = "Klien"
|
||||||
"export" = "Ekspor Semua URL"
|
"export" = "Ekspor Semua URL"
|
||||||
@@ -197,7 +201,7 @@
|
|||||||
"last" = "Terakhir"
|
"last" = "Terakhir"
|
||||||
"prefix" = "Awalan"
|
"prefix" = "Awalan"
|
||||||
"postfix" = "Akhiran"
|
"postfix" = "Akhiran"
|
||||||
"delayedStart" = "Mulai saat Penggunaan Awal"
|
"delayedStart" = "Mulai Awal"
|
||||||
"expireDays" = "Durasi"
|
"expireDays" = "Durasi"
|
||||||
"days" = "Hari"
|
"days" = "Hari"
|
||||||
"renew" = "Perpanjang Otomatis"
|
"renew" = "Perpanjang Otomatis"
|
||||||
@@ -323,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"
|
||||||
@@ -442,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"
|
||||||
@@ -463,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"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"online" = "Онлайн"
|
"online" = "Онлайн"
|
||||||
"domainName" = "Домен"
|
"domainName" = "Домен"
|
||||||
"monitor" = "Порт IP"
|
"monitor" = "Порт IP"
|
||||||
"certificate" = "Сертификат"
|
"certificate" = "Цифровой сертификат"
|
||||||
"fail" = "Неудачно"
|
"fail" = "Неудачно"
|
||||||
"success" = "Успешно"
|
"success" = "Успешно"
|
||||||
"getVersion" = "Узнать версию"
|
"getVersion" = "Узнать версию"
|
||||||
@@ -54,7 +54,12 @@
|
|||||||
"security" = "Безопасность"
|
"security" = "Безопасность"
|
||||||
"secAlertTitle" = "Предупреждение системы безопасности"
|
"secAlertTitle" = "Предупреждение системы безопасности"
|
||||||
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
|
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
|
||||||
"secAlertConf" = "Некоторые конфигурации были определены как уязвимые для атак, что требует немедленных действий по усилению протоколов безопасности и защите от потенциальных нарушений безопасности."
|
"secAlertConf" = "Некоторые настройки уязвимы для атак. Рекомендуется усилить протоколы безопасности, чтобы предотвратить потенциальные нарушения."
|
||||||
|
"secAlertSSL" = "В панели отсутствует безопасное соединение. Пожалуйста, установите сертификат TLS для защиты данных."
|
||||||
|
"secAlertPanelPort" = "Порт по умолчанию панели небезопасен. Пожалуйста, настройте случайный или определенный порт."
|
||||||
|
"secAlertPanelURI" = "URI-путь по умолчанию панели небезопасен. Пожалуйста, настройте сложный URI-путь."
|
||||||
|
"secAlertSubURI" = "URI-путь по умолчанию подписки небезопасен. Пожалуйста, настройте сложный URI-путь."
|
||||||
|
"secAlertSubJsonURI" = "URI-путь по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-путь."
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Статус системы"
|
"dashboard" = "Статус системы"
|
||||||
@@ -65,6 +70,7 @@
|
|||||||
"link" = "менеджмент"
|
"link" = "менеджмент"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "Привет"
|
||||||
"title" = "Добро пожаловать"
|
"title" = "Добро пожаловать"
|
||||||
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова"
|
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова"
|
||||||
|
|
||||||
@@ -143,10 +149,8 @@
|
|||||||
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
|
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
|
||||||
"certificatePath" = "Путь файла"
|
"certificatePath" = "Путь файла"
|
||||||
"certificateContent" = "Содержимое файла"
|
"certificateContent" = "Содержимое файла"
|
||||||
"publicKeyPath" = "Путь к публичному ключу"
|
"publicKey" = "Публичный ключ"
|
||||||
"publicKeyContent" = "Содержимое публичного ключа"
|
"privatekey" = "Приватный ключ"
|
||||||
"keyPath" = "Путь к приватному ключу"
|
|
||||||
"keyContent" = "Содержимое приватного ключа"
|
|
||||||
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
|
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
|
||||||
"client" = "Клиент"
|
"client" = "Клиент"
|
||||||
"export" = "Экспорт ключей"
|
"export" = "Экспорт ключей"
|
||||||
@@ -197,7 +201,7 @@
|
|||||||
"last" = "Последний"
|
"last" = "Последний"
|
||||||
"prefix" = "Префикс"
|
"prefix" = "Префикс"
|
||||||
"postfix" = "Постфикс"
|
"postfix" = "Постфикс"
|
||||||
"delayedStart" = "Начать с момента первого подключения"
|
"delayedStart" = "Начало использования"
|
||||||
"expireDays" = "Длительность"
|
"expireDays" = "Длительность"
|
||||||
"days" = "дней"
|
"days" = "дней"
|
||||||
"renew" = "Автопродление"
|
"renew" = "Автопродление"
|
||||||
@@ -323,7 +327,7 @@
|
|||||||
"blockCountryConfigs" = "Конфигурации блокировки страны"
|
"blockCountryConfigs" = "Конфигурации блокировки страны"
|
||||||
"blockCountryConfigsDesc" = "Эти параметры не позволят пользователям подключаться к доменам определенной страны"
|
"blockCountryConfigsDesc" = "Эти параметры не позволят пользователям подключаться к доменам определенной страны"
|
||||||
"directCountryConfigs" = "Настройки прямого подключения для страны"
|
"directCountryConfigs" = "Настройки прямого подключения для страны"
|
||||||
"directCountryConfigsDesc" = "Эти параметры позволят пользователям подключаться напрямую к доменам определенной страны"
|
"directCountryConfigsDesc" = "Прямое подключение обеспечивает, что конкретный трафик не направляется через другой сервер."
|
||||||
"ipv4Configs" = "Настройки IPv4"
|
"ipv4Configs" = "Настройки IPv4"
|
||||||
"ipv4ConfigsDesc" = "Эти параметры позволят пользователям маршрутизироваться к целевым доменам только через IPv4"
|
"ipv4ConfigsDesc" = "Эти параметры позволят пользователям маршрутизироваться к целевым доменам только через IPv4"
|
||||||
"warpConfigs" = "Настройки WARP"
|
"warpConfigs" = "Настройки WARP"
|
||||||
@@ -442,6 +446,10 @@
|
|||||||
"bridge" = "Мост"
|
"bridge" = "Мост"
|
||||||
"portal" = "Портал"
|
"portal" = "Портал"
|
||||||
"intercon" = "Соединение"
|
"intercon" = "Соединение"
|
||||||
|
"settings" = "Настройки"
|
||||||
|
"accountInfo" = "Информация Об Учетной Записи"
|
||||||
|
"outboundStatus" = "Исходящий статус"
|
||||||
|
"sendThrough" = "Отправить через"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
[pages.xray.balancer]
|
||||||
"addBalancer" = "Добавить балансир"
|
"addBalancer" = "Добавить балансир"
|
||||||
@@ -454,7 +462,7 @@
|
|||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "Секретный ключ"
|
"secretKey" = "Секретный ключ"
|
||||||
"publicKey" = "Открытый ключ"
|
"publicKey" = "Публичный ключ"
|
||||||
"allowedIPs" = "Разрешенные IP-адреса"
|
"allowedIPs" = "Разрешенные IP-адреса"
|
||||||
"endpoint" = "Конечная точка"
|
"endpoint" = "Конечная точка"
|
||||||
"psk" = "Общий ключ"
|
"psk" = "Общий ключ"
|
||||||
@@ -463,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" = "Добавить сервер"
|
||||||
|
|||||||
631
web/translation/translate.uk_UA.toml
Normal file
631
web/translation/translate.uk_UA.toml
Normal file
@@ -0,0 +1,631 @@
|
|||||||
|
"username" = "Ім'я користувача"
|
||||||
|
"password" = "Пароль"
|
||||||
|
"login" = "Увійти"
|
||||||
|
"confirm" = "Підтвердити"
|
||||||
|
"cancel" = "Скасувати"
|
||||||
|
"close" = "Закрити"
|
||||||
|
"copy" = "Копіювати"
|
||||||
|
"copied" = "Скопійовано"
|
||||||
|
"download" = "Завантажити"
|
||||||
|
"remark" = "Примітка"
|
||||||
|
"enable" = "Увімкнути"
|
||||||
|
"protocol" = "Протокол"
|
||||||
|
"search" = "Пошук"
|
||||||
|
"filter" = "Фільтр"
|
||||||
|
"loading" = "Завантаження..."
|
||||||
|
"second" = "Секунда"
|
||||||
|
"minute" = "Хвилина"
|
||||||
|
"hour" = "Година"
|
||||||
|
"day" = "День"
|
||||||
|
"check" = "Перевірка"
|
||||||
|
"indefinite" = "Безстроково"
|
||||||
|
"unlimited" = "Безлімітний"
|
||||||
|
"none" = "Немає"
|
||||||
|
"qrCode" = "QR-Код"
|
||||||
|
"info" = "Більше інформації"
|
||||||
|
"edit" = "Редагувати"
|
||||||
|
"delete" = "Видалити"
|
||||||
|
"reset" = "Скидання"
|
||||||
|
"copySuccess" = "Скопійовано успішно"
|
||||||
|
"sure" = "Звичайно"
|
||||||
|
"encryption" = "Шифрування"
|
||||||
|
"transmission" = "Протокол передачи"
|
||||||
|
"host" = "Хост"
|
||||||
|
"path" = "Шлях"
|
||||||
|
"camouflage" = "Маскування"
|
||||||
|
"status" = "Статус"
|
||||||
|
"enabled" = "Увімкнено"
|
||||||
|
"disabled" = "Вимкнено"
|
||||||
|
"depleted" = "Вичерпано"
|
||||||
|
"depletingSoon" = "Вичерпується"
|
||||||
|
"offline" = "Офлайн"
|
||||||
|
"online" = "Онлайн"
|
||||||
|
"domainName" = "Доменне ім`я"
|
||||||
|
"monitor" = "Слухати IP"
|
||||||
|
"certificate" = "Цифровий сертифікат"
|
||||||
|
"fail" = " Помилка"
|
||||||
|
"success" = " Успішно"
|
||||||
|
"getVersion" = "Отримати версію"
|
||||||
|
"install" = "Встановити"
|
||||||
|
"clients" = "Клієнти"
|
||||||
|
"usage" = "Використання"
|
||||||
|
"secretToken" = "Секретний маркер"
|
||||||
|
"remained" = "Залишилося"
|
||||||
|
"security" = "Беспека"
|
||||||
|
"secAlertTitle" = "Попередження системи безпеки"
|
||||||
|
"secAlertSsl" = "Це з'єднання не є безпечним. Будь ласка, уникайте введення конфіденційної інформації, поки TLS не буде активовано для захисту даних."
|
||||||
|
"secAlertConf" = "Деякі налаштування вразливі до атак. Рекомендується посилити протоколи безпеки, щоб запобігти можливим порушенням."
|
||||||
|
"secAlertSSL" = "Панель не має безпечного з'єднання. Будь ласка, встановіть сертифікат TLS для захисту даних."
|
||||||
|
"secAlertPanelPort" = "Стандартний порт панелі вразливий. Будь ласка, сконфігуруйте випадковий або конкретний порт."
|
||||||
|
"secAlertPanelURI" = "Стандартний URI-шлях панелі небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
|
||||||
|
"secAlertSubURI" = "Стандартний URI-шлях підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
|
||||||
|
"secAlertSubJsonURI" = "Стандартний URI-шлях JSON підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
|
||||||
|
|
||||||
|
[menu]
|
||||||
|
"dashboard" = "Огляд"
|
||||||
|
"inbounds" = "Вхідні"
|
||||||
|
"settings" = "Параметри панелі"
|
||||||
|
"xray" = "Конфігурації Xray"
|
||||||
|
"logout" = "Вийти"
|
||||||
|
"link" = "Керувати"
|
||||||
|
|
||||||
|
[pages.login]
|
||||||
|
"hello" = "Привіт"
|
||||||
|
"title" = "Ласкаво просимо"
|
||||||
|
"loginAgain" = "Ваш сеанс закінчився, увійдіть знову"
|
||||||
|
|
||||||
|
[pages.login.toasts]
|
||||||
|
"invalidFormData" = "Формат вхідних даних недійсний."
|
||||||
|
"emptyUsername" = "Потрібне ім'я користувача"
|
||||||
|
"emptyPassword" = "Потрібен пароль"
|
||||||
|
"wrongUsernameOrPassword" = "Невірне ім'я користувача або пароль."
|
||||||
|
"successLogin" = "Вхід"
|
||||||
|
|
||||||
|
[pages.index]
|
||||||
|
"title" = "Огляд"
|
||||||
|
"memory" = "Пам'ять"
|
||||||
|
"hard" = "Диск"
|
||||||
|
"xrayStatus" = "Xray"
|
||||||
|
"stopXray" = "Зупинити"
|
||||||
|
"restartXray" = "Перезапустити"
|
||||||
|
"xraySwitch" = "Версія"
|
||||||
|
"xraySwitchClick" = "Виберіть версію, на яку ви хочете перейти."
|
||||||
|
"xraySwitchClickDesk" = "Вибирайте уважно, оскільки старіші версії можуть бути несумісними з поточними конфігураціями."
|
||||||
|
"operationHours" = "Час роботи"
|
||||||
|
"systemLoad" = "Завантаження системи"
|
||||||
|
"systemLoadDesc" = "Середнє завантаження системи за останні 1, 5 і 15 хвилин"
|
||||||
|
"connectionTcpCountDesc" = "Загальна кількість TCP-з'єднань у системі"
|
||||||
|
"connectionUdpCountDesc" = "Загальна кількість UDP-з'єднань у системі"
|
||||||
|
"connectionCount" = "Статистика з'єднання"
|
||||||
|
"upSpeed" = "Загальна швидкість завантаження в системі"
|
||||||
|
"downSpeed" = "Загальна швидкість завантаження в системі"
|
||||||
|
"totalSent" = "Загальна кількість даних, надісланих через систему з моменту запуску ОС"
|
||||||
|
"totalReceive" = "Загальна кількість даних, отриманих системою з моменту запуску ОС"
|
||||||
|
"xraySwitchVersionDialog" = "Змінити версію Xray"
|
||||||
|
"xraySwitchVersionDialogDesc" = "Ви впевнені, що бажаєте змінити версію Xray на"
|
||||||
|
"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку"
|
||||||
|
"logs" = "Журнали"
|
||||||
|
"config" = "Конфігурація"
|
||||||
|
"backup" = "Резервне копіювання та відновлення"
|
||||||
|
"backupTitle" = "Резервне копіювання та відновлення бази даних"
|
||||||
|
"backupDescription" = "Рекомендується зробити резервну копію перед відновленням бази даних."
|
||||||
|
"exportDatabase" = "Резервне копіювання"
|
||||||
|
"importDatabase" = "Відновити"
|
||||||
|
|
||||||
|
[pages.inbounds]
|
||||||
|
"title" = "Вхідні"
|
||||||
|
"totalDownUp" = "Всього надісланих/отриманих"
|
||||||
|
"totalUsage" = "Всього використанно"
|
||||||
|
"inboundCount" = "Загальна кількість вхідних"
|
||||||
|
"operate" = "Меню"
|
||||||
|
"enable" = "Увімкнено"
|
||||||
|
"remark" = "Примітка"
|
||||||
|
"protocol" = "Протокол"
|
||||||
|
"port" = "Порт"
|
||||||
|
"traffic" = "Трафік"
|
||||||
|
"details" = "Деталі"
|
||||||
|
"transportConfig" = "Транспорт"
|
||||||
|
"expireDate" = "Тривалість"
|
||||||
|
"resetTraffic" = "Скинути трафік"
|
||||||
|
"addInbound" = "Додати вхідний"
|
||||||
|
"generalActions" = "Загальні дії"
|
||||||
|
"create" = "Створити"
|
||||||
|
"update" = "Оновити"
|
||||||
|
"modifyInbound" = "Змінити вхідний"
|
||||||
|
"deleteInbound" = "Видалити вхідні"
|
||||||
|
"deleteInboundContent" = "Ви впевнені, що хочете видалити вхідні?"
|
||||||
|
"deleteClient" = "Видалити клієнта"
|
||||||
|
"deleteClientContent" = "Ви впевнені, що хочете видалити клієнт?"
|
||||||
|
"resetTrafficContent" = "Ви впевнені, що хочете скинути трафік?"
|
||||||
|
"copyLink" = "Копіювати URL"
|
||||||
|
"address" = "Адреса"
|
||||||
|
"network" = "Мережа"
|
||||||
|
"destinationPort" = "Порт призначення"
|
||||||
|
"targetAddress" = "Цільова адреса"
|
||||||
|
"monitorDesc" = "Залиште порожнім, щоб слухати всі IP-адреси"
|
||||||
|
"meansNoLimit" = " = Необмежено. (одиниця: ГБ)"
|
||||||
|
"totalFlow" = "Загальна витрата"
|
||||||
|
"leaveBlankToNeverExpire" = "Залиште порожнім, щоб ніколи не закінчувався"
|
||||||
|
"noRecommendKeepDefault" = "Рекомендується зберегти значення за замовчуванням"
|
||||||
|
"certificatePath" = "Шлях до файлу"
|
||||||
|
"certificateContent" = "Вміст файлу"
|
||||||
|
"publicKey" = "Публічний ключ"
|
||||||
|
"privatekey" = "Закритий ключ"
|
||||||
|
"clickOnQRcode" = "Натисніть QR-код, щоб скопіювати"
|
||||||
|
"client" = "Клієнт"
|
||||||
|
"export" = "Експортувати всі URL-адреси"
|
||||||
|
"clone" = "Клон"
|
||||||
|
"cloneInbound" = "Клонувати"
|
||||||
|
"cloneInboundContent" = "Усі налаштування цього вхідного потоку, крім порту, IP-адреси прослуховування та клієнтів, будуть застосовані до клону."
|
||||||
|
"cloneInboundOk" = "Клонувати"
|
||||||
|
"resetAllTraffic" = "Скинути весь вхідний трафік"
|
||||||
|
"resetAllTrafficTitle" = "Скинути весь вхідний трафік"
|
||||||
|
"resetAllTrafficContent" = "Ви впевнені, що бажаєте скинути трафік усіх вхідних?"
|
||||||
|
"resetInboundClientTraffics" = "Скинути трафік клієнтів"
|
||||||
|
"resetInboundClientTrafficTitle" = "Скинути трафік клієнтів"
|
||||||
|
"resetInboundClientTrafficContent" = "Ви впевнені, що бажаєте скинути трафік клієнтів цього вхідного потоку?"
|
||||||
|
"resetAllClientTraffics" = "Скинути весь трафік клієнтів"
|
||||||
|
"resetAllClientTrafficTitle" = "Скинути весь трафік клієнтів"
|
||||||
|
"resetAllClientTrafficContent" = "Ви впевнені, що бажаєте скинути трафік усіх клієнтів?"
|
||||||
|
"delDepletedClients" = "Видалити вичерпані клієнти"
|
||||||
|
"delDepletedClientsTitle" = "Видалити вичерпані клієнти"
|
||||||
|
"delDepletedClientsContent" = "Ви впевнені, що хочете видалити всі вичерпані клієнти?"
|
||||||
|
"email" = "Електронна пошта"
|
||||||
|
"emailDesc" = "Будь ласка, надайте унікальну адресу електронної пошти."
|
||||||
|
"IPLimit" = "Обмеження IP"
|
||||||
|
"IPLimitDesc" = "Вимикає вхідний, якщо кількість перевищує встановлене значення. (0 = вимкнено)"
|
||||||
|
"IPLimitlog" = "Журнал IP"
|
||||||
|
"IPLimitlogDesc" = "Журнал історії IP-адрес. (щоб увімкнути вхідну після вимкнення, очистіть журнал)"
|
||||||
|
"IPLimitlogclear" = "Очистити журнал"
|
||||||
|
"setDefaultCert" = "Установити сертифікат з панелі"
|
||||||
|
"xtlsDesc" = "Xray має бути v1.7.5"
|
||||||
|
"realityDesc" = "Xray має бути v1.8.0+"
|
||||||
|
"telegramDesc" = "Будь ласка, надайте ідентифікатори Telegram або чату без використання '@'. (отримайте його тут @userinfobot) або (використайте команду '/id' у боті)"
|
||||||
|
"subscriptionDesc" = "Щоб знайти URL-адресу вашої підписки, перейдіть до «Деталі». Крім того, ви можете використовувати одне ім'я для кількох клієнтів."
|
||||||
|
"info" = "Інформація"
|
||||||
|
"same" = "Те саме"
|
||||||
|
"inboundData" = "Вхідні дані"
|
||||||
|
"exportInbound" = "Експортувати вхідні"
|
||||||
|
"import" = "Імпорт"
|
||||||
|
"importInbound" = "Імпортувати вхідний"
|
||||||
|
|
||||||
|
[pages.client]
|
||||||
|
"add" = "Додати клієнта"
|
||||||
|
"edit" = "Редагувати клієнта"
|
||||||
|
"submitAdd" = "Додати клієнта"
|
||||||
|
"submitEdit" = "Зберегти зміни"
|
||||||
|
"clientCount" = "Кількість клієнтів"
|
||||||
|
"bulk" = "Додати групу"
|
||||||
|
"method" = "Метод"
|
||||||
|
"first" = "Перший"
|
||||||
|
"last" = "Останній"
|
||||||
|
"prefix" = "Префікс"
|
||||||
|
"postfix" = "Постфікс"
|
||||||
|
"delayedStart" = "Початок використання"
|
||||||
|
"expireDays" = "Тривалість"
|
||||||
|
"days" = "Дні(в)"
|
||||||
|
"renew" = "Автоматичне оновлення"
|
||||||
|
"renewDesc" = "Автоматичне поновлення після закінчення терміну дії. (0 = вимкнено)(одиниця: день)"
|
||||||
|
|
||||||
|
[pages.inbounds.toasts]
|
||||||
|
"obtain" = "Отримати"
|
||||||
|
|
||||||
|
[pages.inbounds.stream.general]
|
||||||
|
"request" = "Запит"
|
||||||
|
"response" = "Відповідь"
|
||||||
|
"name" = "Ім'я"
|
||||||
|
"value" = "Значення"
|
||||||
|
|
||||||
|
[pages.inbounds.stream.tcp]
|
||||||
|
"version" = "Версія"
|
||||||
|
"method" = "Метод"
|
||||||
|
"path" = "Шлях"
|
||||||
|
"status" = "Статус"
|
||||||
|
"statusDescription" = "Опис стану"
|
||||||
|
"requestHeader" = "Заголовок запиту"
|
||||||
|
"responseHeader" = "Заголовок відповіді"
|
||||||
|
|
||||||
|
[pages.inbounds.stream.quic]
|
||||||
|
"encryption" = "Шифрування"
|
||||||
|
|
||||||
|
[pages.settings]
|
||||||
|
"title" = "Параметри панелі"
|
||||||
|
"save" = "Зберегти"
|
||||||
|
"infoDesc" = "Кожна внесена тут зміна повинна бути збережена. Перезапустіть панель, щоб застосувати зміни."
|
||||||
|
"restartPanel" = "Перезапустити панель"
|
||||||
|
"restartPanelDesc" = "Ви впевнені, що бажаєте перезапустити панель? Якщо ви не можете отримати доступ до панелі після перезапуску, будь ласка, перегляньте інформацію журналу панелі на сервері."
|
||||||
|
"actions" = "Дії"
|
||||||
|
"resetDefaultConfig" = "Відновити значення за замовчуванням"
|
||||||
|
"panelSettings" = "Загальні"
|
||||||
|
"securitySettings" = "Автентифікація"
|
||||||
|
"TGBotSettings" = "Telegram Бот"
|
||||||
|
"panelListeningIP" = "Слухати IP"
|
||||||
|
"panelListeningIPDesc" = "IP-адреса для веб-панелі. (залиште порожнім, щоб слухати всі IP-адреси)"
|
||||||
|
"panelListeningDomain" = "Домен прослуховування"
|
||||||
|
"panelListeningDomainDesc" = "Доменне ім'я для веб-панелі. (залиште порожнім, щоб слухати всі домени та IP-адреси)"
|
||||||
|
"panelPort" = "Порт прослуховування"
|
||||||
|
"panelPortDesc" = "Номер порту для веб-панелі. (має бути невикористаний порт)"
|
||||||
|
"publicKeyPath" = "Шлях відкритого ключа"
|
||||||
|
"publicKeyPathDesc" = "Шлях до файлу відкритого ключа для веб-панелі. (починається з ‘/‘)"
|
||||||
|
"privateKeyPath" = "Шлях приватного ключа"
|
||||||
|
"privateKeyPathDesc" = "Шлях до файлу приватного ключа для веб-панелі. (починається з ‘/‘)"
|
||||||
|
"panelUrlPath" = "Шлях URL"
|
||||||
|
"panelUrlPathDesc" = "Шлях URL для веб-панелі. (починається з ‘/‘ і закінчується ‘/‘)"
|
||||||
|
"pageSize" = "Розмір сторінки"
|
||||||
|
"pageSizeDesc" = "Визначити розмір сторінки для вхідної таблиці. (0 = вимкнено)"
|
||||||
|
"remarkModel" = "Модель зауваження та роздільний символ"
|
||||||
|
"datepicker" = "Тип календаря"
|
||||||
|
"datepickerPlaceholder" = "Виберіть дату"
|
||||||
|
"datepickerDescription" = "Заплановані завдання виконуватимуться на основі цього календаря."
|
||||||
|
"sampleRemark" = "Зразок зауваження"
|
||||||
|
"oldUsername" = "Поточне ім'я користувача"
|
||||||
|
"currentPassword" = "Поточний пароль"
|
||||||
|
"newUsername" = "Нове ім'я користувача"
|
||||||
|
"newPassword" = "Новий пароль"
|
||||||
|
"telegramBotEnable" = "Увімкнути Telegram Bot"
|
||||||
|
"telegramBotEnableDesc" = "Вмикає бота Telegram."
|
||||||
|
"telegramToken" = "Telegram Токен"
|
||||||
|
"telegramTokenDesc" = "Токен бота Telegram, отриманий від '@BotFather'."
|
||||||
|
"telegramProxy" = "SOCKS Проксі"
|
||||||
|
"telegramProxyDesc" = "Вмикає проксі-сервер SOCKS5 для підключення до Telegram. (відкоригуйте параметри відповідно до посібника)"
|
||||||
|
"telegramChatId" = "Ідентифікатор чату адміністратора"
|
||||||
|
"telegramChatIdDesc" = "Ідентифікатори чату адміністратора Telegram. (розділені комами) (отримайте тут @userinfobot) або (використовуйте команду '/id' у боті)"
|
||||||
|
"telegramNotifyTime" = "Час сповіщення"
|
||||||
|
"telegramNotifyTimeDesc" = "Час повідомлення бота Telegram, встановлений для періодичних звітів. (використовуйте формат часу crontab)"
|
||||||
|
"tgNotifyBackup" = "Резервне копіювання бази даних"
|
||||||
|
"tgNotifyBackupDesc" = "Надіслати файл резервної копії бази даних зі звітом."
|
||||||
|
"tgNotifyLogin" = "Сповіщення про вхід"
|
||||||
|
"tgNotifyLoginDesc" = "Отримувати сповіщення про ім'я користувача, IP-адресу та час щоразу, коли хтось намагається увійти у вашу веб-панель."
|
||||||
|
"sessionMaxAge" = "Тривалість сеансу"
|
||||||
|
"sessionMaxAgeDesc" = "Тривалість, протягом якої ви можете залишатися в системі. (одиниця: хвилина)"
|
||||||
|
"expireTimeDiff" = "Повідомлення про дату закінчення"
|
||||||
|
"expireTimeDiffDesc" = "Отримувати сповіщення про термін дії при досягненні цього порогу. (одиниця: день)"
|
||||||
|
"trafficDiff" = "Повідомлення про обмеження трафіку"
|
||||||
|
"trafficDiffDesc" = "Отримувати сповіщення про обмеження трафіку при досягненні цього порогу. (одиниця: ГБ)"
|
||||||
|
"tgNotifyCpu" = "Сповіщення про завантаження ЦП"
|
||||||
|
"tgNotifyCpuDesc" = "Отримувати сповіщення, якщо навантаження ЦП перевищує це порогове значення. (одиниця: %)"
|
||||||
|
"timeZone" = "Часовий пояс"
|
||||||
|
"timeZoneDesc" = "Заплановані завдання виконуватимуться на основі цього часового поясу."
|
||||||
|
"subSettings" = "Підписка"
|
||||||
|
"subEnable" = "Увімкнути службу підписки"
|
||||||
|
"subEnableDesc" = "Вмикає службу підписки."
|
||||||
|
"subListen" = "Слухати IP"
|
||||||
|
"subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)"
|
||||||
|
"subPort" = "Слухати порт"
|
||||||
|
"subPortDesc" = "Номер порту для служби підписки. (має бути невикористаний порт)"
|
||||||
|
"subCertPath" = "Шлях відкритого ключа"
|
||||||
|
"subCertPathDesc" = "Шлях до файлу відкритого ключа для служби підписки. (починається з ‘/‘)"
|
||||||
|
"subKeyPath" = "Шлях приватного ключа"
|
||||||
|
"subKeyPathDesc" = "Шлях до файлу приватного ключа для служби підписки. (починається з ‘/‘)"
|
||||||
|
"subPath" = "Шлях URI"
|
||||||
|
"subPathDesc" = "Шлях URI для служби підписки. (починається з ‘/‘ і закінчується ‘/‘)"
|
||||||
|
"subDomain" = "Домен прослуховування"
|
||||||
|
"subDomainDesc" = "Ім'я домену для служби підписки. (залиште порожнім, щоб слухати всі домени та IP-адреси)"
|
||||||
|
"subUpdates" = "Інтервали оновлення"
|
||||||
|
"subUpdatesDesc" = "Інтервали оновлення URL-адреси підписки в клієнтських програмах. (одиниця: година)"
|
||||||
|
"subEncrypt" = "Закодувати"
|
||||||
|
"subEncryptDesc" = "Повернений вміст послуги підписки матиме кодування Base64."
|
||||||
|
"subShowInfo" = "Показати інформацію про використання"
|
||||||
|
"subShowInfoDesc" = "Залишок трафіку та дата відображатимуться в клієнтських програмах."
|
||||||
|
"subURI" = "URI зворотного проксі"
|
||||||
|
"subURIDesc" = "URI до URL-адреси підписки для використання за проксі."
|
||||||
|
"fragment" = "Фрагментація"
|
||||||
|
"fragmentDesc" = "Увімкнути фрагментацію для пакету привітання TLS"
|
||||||
|
|
||||||
|
[pages.xray]
|
||||||
|
"title" = "Xray конфігурації"
|
||||||
|
"save" = "Зберегти"
|
||||||
|
"restart" = "Перезапустити Xray"
|
||||||
|
"basicTemplate" = "Базовий шаблон"
|
||||||
|
"advancedTemplate" = "Додатково"
|
||||||
|
"generalConfigs" = "Загальні конфігурації"
|
||||||
|
"generalConfigsDesc" = "Ці параметри визначатимуть загальні налаштування."
|
||||||
|
"logConfigs" = "Журнал"
|
||||||
|
"logConfigsDesc" = "Журнали можуть вплинути на ефективність вашого сервера. Рекомендується вмикати його з розумом лише у випадку ваших потреб"
|
||||||
|
"blockConfigs" = "Захисний екран"
|
||||||
|
"blockConfigsDesc" = "Ці параметри блокуватимуть трафік на основі конкретних запитуваних протоколів і веб-сайтів."
|
||||||
|
"blockCountryConfigs" = "Заблокувати країну"
|
||||||
|
"blockCountryConfigsDesc" = "Ці параметри блокуватимуть трафік на основі конкретної запитуваної країни."
|
||||||
|
"directCountryConfigs" = "Пряма країна"
|
||||||
|
"directCountryConfigsDesc" = "Пряме підключення забезпечує, що конкретний трафік не маршрутизується через інший сервер."
|
||||||
|
"ipv4Configs" = "Маршрутизація IPv4"
|
||||||
|
"ipv4ConfigsDesc" = "Ці параметри спрямовуватимуть трафік на основі певного призначення через IPv4."
|
||||||
|
"warpConfigs" = "WARP маршрутизація"
|
||||||
|
"warpConfigsDesc" = "Ці параметри маршрутизуватимуть трафік на основі певного пункту призначення через WARP."
|
||||||
|
"Template" = "Шаблон розширеної конфігурації Xray"
|
||||||
|
"TemplateDesc" = "Остаточний конфігураційний файл Xray буде створено на основі цього шаблону."
|
||||||
|
"FreedomStrategy" = "Стратегія протоколу свободи"
|
||||||
|
"FreedomStrategyDesc" = "Установити стратегію виведення для мережі в протоколі свободи."
|
||||||
|
"RoutingStrategy" = "Загальна стратегія маршрутизації"
|
||||||
|
"RoutingStrategyDesc" = "Установити загальну стратегію маршрутизації трафіку для вирішення всіх запитів."
|
||||||
|
"Torrent" = "Блокувати протокол BitTorrent"
|
||||||
|
"TorrentDesc" = "Блокує протокол BitTorrent."
|
||||||
|
"PrivateIp" = "Блокувати підключення до приватних IP-адрес"
|
||||||
|
"PrivateIpDesc" = "Блокує встановлення підключень до приватних діапазонів IP."
|
||||||
|
"Ads" = "Блокувати рекламу"
|
||||||
|
"AdsDesc" = "Блокує рекламні веб-сайти."
|
||||||
|
"Family" = "Захист сім'ї"
|
||||||
|
"FamilyDesc" = "Блокує вміст для дорослих і веб-сайти з шкідливими програмами."
|
||||||
|
"Security" = "Щит безпеки"
|
||||||
|
"SecurityDesc" = "Блокує веб-сайти шкідливого програмного забезпечення, фішингу та майнерів."
|
||||||
|
"Speedtest" = "Заблокувати Speedtest"
|
||||||
|
"SpeedtestDesc" = "Блокує підключення до веб-сайтів для тестування швидкості."
|
||||||
|
"IRIp" = "Блокувати підключення до IP-адрес Ірану"
|
||||||
|
"IRIpDesc" = "Блокує встановлення з'єднань з діапазонами IP Ірану."
|
||||||
|
"IRDomain" = "Блокувати підключення до доменів Ірану"
|
||||||
|
"IRDomainDesc" = "Блокує встановлення з'єднань з доменами Ірану."
|
||||||
|
"ChinaIp" = "Блокувати підключення до IP-адрес Китаю"
|
||||||
|
"ChinaIpDesc" = "Блокує встановлення з'єднань із діапазонами IP-адрес Китаю."
|
||||||
|
"ChinaDomain" = "Блокувати підключення до китайських доменів"
|
||||||
|
"ChinaDomainDesc" = "Блокує встановлення підключень до доменів Китаю."
|
||||||
|
"RussiaIp" = "Блокувати підключення до російських IP-адрес"
|
||||||
|
"RussiaIpDesc" = "Блокує встановлення з'єднань з діапазонами IP-адрес Росії."
|
||||||
|
"RussiaDomain" = "Блокувати підключення до російських доменів"
|
||||||
|
"RussiaDomainDesc" = "Блокує встановлення з'єднань з російськими доменами."
|
||||||
|
"VNIp" = "Блокувати підключення до IP-адрес В'єтнаму"
|
||||||
|
"VNIpDesc" = "Блокує встановлення з'єднань із діапазонами IP-адрес В'єтнаму."
|
||||||
|
"VNDomain" = "Блокувати підключення до доменів В'єтнаму"
|
||||||
|
"VNDomainDesc" = "Блокує встановлення з'єднань із доменами В'єтнаму."
|
||||||
|
"DirectIRIp" = "Пряме підключення до IP-адрес Ірану"
|
||||||
|
"DirectIRIpDesc" = "Безпосередньо встановлює з'єднання з діапазонами IP Ірану."
|
||||||
|
"DirectIRDomain" = "Пряме підключення до доменів Ірану"
|
||||||
|
"DirectIRDomainDesc" = "Безпосередньо встановлює підключення до доменів Ірану."
|
||||||
|
"DirectChinaIp" = "Пряме підключення до китайських IP-адрес"
|
||||||
|
"DirectChinaIpDesc" = "Безпосередньо встановлює підключення до IP-діапазонів Китаю."
|
||||||
|
"DirectChinaDomain" = "Пряме підключення до китайських доменів"
|
||||||
|
"DirectChinaDomainDesc" = "Безпосередньо встановлює підключення до доменів Китаю."
|
||||||
|
"DirectRussiaIp" = "Пряме підключення до IP-адрес Росії"
|
||||||
|
"DirectRussiaIpDesc" = "Безпосередньо встановлює з'єднання з діапазонами IP-адрес Росії."
|
||||||
|
"DirectRussiaDomain" = "Пряме підключення до російських доменів"
|
||||||
|
"DirectRussiaDomainDesc" = "Безпосередньо встановлює підключення до російських доменів."
|
||||||
|
"DirectVNIp" = "Пряме підключення до IP-адрес В'єтнаму"
|
||||||
|
"DirectVNIpDesc" = "Безпосередньо встановлює з'єднання з діапазонами IP-адрес В'єтнаму."
|
||||||
|
"DirectVNDomain" = "Пряме підключення до доменів В'єтнаму"
|
||||||
|
"DirectVNDomainDesc" = "Безпосередньо встановлює з'єднання з доменами В'єтнаму."
|
||||||
|
"GoogleIPv4" = "Google"
|
||||||
|
"GoogleIPv4Desc" = "Направляє трафік до Google через IPv4."
|
||||||
|
"NetflixIPv4" = "Netflix"
|
||||||
|
"NetflixIPv4Desc" = "Направляє трафік до Netflix через IPv4."
|
||||||
|
"GoogleWARP" = "Google"
|
||||||
|
"GoogleWARPDesc" = "Додати маршрутизацію для Google через WARP."
|
||||||
|
"OpenAIWARP" = "ChatGPT"
|
||||||
|
"OpenAIWARPDesc" = "Направляє трафік до ChatGPT через WARP."
|
||||||
|
"NetflixWARP" = "Netflix"
|
||||||
|
"NetflixWARPDesc" = "Направляє трафік до Netflix через WARP."
|
||||||
|
"MetaWARP" = "Meta"
|
||||||
|
"MetaWARPDesc" = "Направляє трафік до Meta (Instagram, Facebook, WhatsApp, Threads,...) через WARP."
|
||||||
|
"AppleWARP" = "Apple"
|
||||||
|
"AppleWARPDesc" = "Направляє трафік до Apple через WARP."
|
||||||
|
"RedditWARP" = "Reddit"
|
||||||
|
"RedditWARPDesc" = "Направляє трафік до Reddit через WARP."
|
||||||
|
"SpotifyWARP" = "Spotify"
|
||||||
|
"SpotifyWARPDesc" = "Направляє трафік до Spotify через WARP."
|
||||||
|
"IRWARP" = "Іранські домени"
|
||||||
|
"IRWARPDesc" = "Направляє трафік до доменів Ірану через WARP"
|
||||||
|
"Inbounds" = "Вхідні"
|
||||||
|
"InboundsDesc" = "Прийняття певних клієнтів."
|
||||||
|
"Outbounds" = "Вихід"
|
||||||
|
"Balancers" = "Балансери"
|
||||||
|
"OutboundsDesc" = "Встановити шлях вихідного трафіку."
|
||||||
|
"Routings" = "Правила маршрутизації"
|
||||||
|
"RoutingsDesc" = "Пріоритет кожного правила важливий!"
|
||||||
|
"completeTemplate" = "Усі"
|
||||||
|
"logLevel" = "Рівень журналу"
|
||||||
|
"logLevelDesc" = "Рівень журналу для журналів помилок із зазначенням інформації, яку потрібно записати."
|
||||||
|
"accessLog" = "Журнал доступу"
|
||||||
|
"accessLogDesc" = "Шлях до файлу журналу доступу. Спеціальне значення 'none' вимикає журнали доступу"
|
||||||
|
"errorLog" = "Журнал помилок"
|
||||||
|
"errorLogDesc" = "Шлях до файлу журналу помилок. Спеціальне значення 'none' вимикає журнали помилок"
|
||||||
|
|
||||||
|
[pages.xray.rules]
|
||||||
|
"first" = "Перший"
|
||||||
|
"last" = "Останній"
|
||||||
|
"up" = "Вгору"
|
||||||
|
"down" = "Вниз"
|
||||||
|
"source" = "Джерело"
|
||||||
|
"dest" = "Пункт призначення"
|
||||||
|
"inbound" = "Вхідний"
|
||||||
|
"outbound" = "Вихідний"
|
||||||
|
"balancer" = "Балансувальник"
|
||||||
|
"info" = "Інформація"
|
||||||
|
"add" = "Додати правило"
|
||||||
|
"edit" = "Редагувати правило"
|
||||||
|
"useComma" = "Елементи, розділені комами"
|
||||||
|
|
||||||
|
[pages.xray.outbound]
|
||||||
|
"addOutbound" = "Додати вихідний"
|
||||||
|
"addReverse" = "Додати реверс"
|
||||||
|
"editOutbound" = "Редагувати вихідні"
|
||||||
|
"editReverse" = "Редагувати реверс"
|
||||||
|
"tag" = "Тег"
|
||||||
|
"tagDesc" = "Унікальний тег"
|
||||||
|
"address" = "Адреса"
|
||||||
|
"reverse" = "Зворотний"
|
||||||
|
"domain" = "Домен"
|
||||||
|
"type" = "Тип"
|
||||||
|
"bridge" = "Міст"
|
||||||
|
"portal" = "Портал"
|
||||||
|
"intercon" = "Взаємозв'язок"
|
||||||
|
"settings" = "Налаштування"
|
||||||
|
"accountInfo" = "Інформація про обліковий запис"
|
||||||
|
"outboundStatus" = "Статус виходу"
|
||||||
|
"sendThrough" = "Надіслати через"
|
||||||
|
|
||||||
|
[pages.xray.balancer]
|
||||||
|
"addBalancer" = "Додати балансир"
|
||||||
|
"editBalancer" = "Редагувати балансир"
|
||||||
|
"balancerStrategy" = "Стратегія"
|
||||||
|
"balancerSelectors" = "Селектори"
|
||||||
|
"tag" = "Тег"
|
||||||
|
"tagDesc" = "Унікальний тег"
|
||||||
|
"balancerDesc" = "Неможливо використовувати balancerTag і outboundTag одночасно. Якщо використовувати одночасно, працюватиме лише outboundTag."
|
||||||
|
|
||||||
|
[pages.xray.wireguard]
|
||||||
|
"secretKey" = "Приватний ключ"
|
||||||
|
"publicKey" = "Публічний ключ"
|
||||||
|
"allowedIPs" = "Дозволені IP-адреси"
|
||||||
|
"endpoint" = "Кінцева точка"
|
||||||
|
"psk" = "Спільний ключ"
|
||||||
|
"domainStrategy" = "Стратегія домену"
|
||||||
|
|
||||||
|
[pages.xray.dns]
|
||||||
|
"enable" = "Увімкнути DNS"
|
||||||
|
"enableDesc" = "Увімкнути вбудований DNS-сервер"
|
||||||
|
"tag" = "Мітка вхідного DNS"
|
||||||
|
"tagDesc" = "Ця мітка буде доступна як вхідна мітка в правилах маршрутизації."
|
||||||
|
"strategy" = "Стратегія запиту"
|
||||||
|
"strategyDesc" = "Загальна стратегія вирішення доменних імен"
|
||||||
|
"add" = "Додати сервер"
|
||||||
|
"edit" = "Редагувати сервер"
|
||||||
|
"domains" = "Домени"
|
||||||
|
|
||||||
|
[pages.xray.fakedns]
|
||||||
|
"add" = "Додати підроблений DNS"
|
||||||
|
"edit" = "Редагувати підроблений DNS"
|
||||||
|
"ipPool" = "Підмережа IP-пулу"
|
||||||
|
"poolSize" = "Розмір пулу"
|
||||||
|
|
||||||
|
[pages.settings.security]
|
||||||
|
"admin" = "Адміністратор"
|
||||||
|
"secret" = "Секретний маркер"
|
||||||
|
"loginSecurity" = "Безпечний вхід"
|
||||||
|
"loginSecurityDesc" = "Додає додатковий рівень автентифікації для забезпечення більшої безпеки."
|
||||||
|
"secretToken" = "Секретний маркер"
|
||||||
|
"secretTokenDesc" = "Будь ласка, надійно зберігайте цей маркер у безпечному місці. Цей маркер потрібен для входу, і його неможливо відновити."
|
||||||
|
|
||||||
|
[pages.settings.toasts]
|
||||||
|
"modifySettings" = "Змінити налаштування"
|
||||||
|
"getSettings" = "Отримати налаштування"
|
||||||
|
"modifyUser" = "Змінити адміністратора"
|
||||||
|
"originalUserPassIncorrect" = "Поточне ім'я користувача або пароль недійсні"
|
||||||
|
"userPassMustBeNotEmpty" = "Нове ім'я користувача та пароль порожні"
|
||||||
|
|
||||||
|
[tgbot]
|
||||||
|
"keyboardClosed" = "❌ Спеціальна клавіатура закрита!"
|
||||||
|
"noResult" = "❗ Немає результату!"
|
||||||
|
"noQuery" = "❌ Запит не знайдено! Скористайтеся командою ще раз!"
|
||||||
|
"wentWrong" = "❌ Щось пішло не так!"
|
||||||
|
"noIpRecord" = "❗ Немає IP-запису!"
|
||||||
|
"noInbounds" = "❗ Вхідних не знайдено!"
|
||||||
|
"unlimited" = "♾ Необмежений (скинути)"
|
||||||
|
"add" = "Додати"
|
||||||
|
"month" = "Місяць"
|
||||||
|
"months" = "Місяці"
|
||||||
|
"day" = "День"
|
||||||
|
"days" = "Дні"
|
||||||
|
"hours" = "Годинник"
|
||||||
|
"unknown" = "Невідомо"
|
||||||
|
"inbounds" = "Вхідні"
|
||||||
|
"clients" = "Клієнти"
|
||||||
|
"offline" = "🔴 Офлайн"
|
||||||
|
"online" = "🟢 Онлайн"
|
||||||
|
|
||||||
|
[tgbot.commands]
|
||||||
|
"unknown" = "❗ Невідома команда."
|
||||||
|
"pleaseChoose" = "👇 Будь ласка, виберіть:\r\n"
|
||||||
|
"help" = "🤖 Ласкаво просимо до цього бота! Він розроблений, щоб надавати певні дані з веб-панелі та дозволяє вносити зміни за потреби.\r\n\r\n"
|
||||||
|
"start" = "👋 Привіт <i>{{ .Firstname }}</i>.\r\n"
|
||||||
|
"welcome" = "🤖 Ласкаво просимо до <b>{{ .Hostname }}</b> бота керування.\r\n"
|
||||||
|
"status" = "✅ Бот в порядку!"
|
||||||
|
"usage" = "❗ Введіть текст для пошуку!"
|
||||||
|
"getID" = "🆔 Ваш ідентифікатор: <code>{{ .ID }}</code>"
|
||||||
|
"helpAdminCommands" = "Для пошуку електронної пошти клієнта:\r\n<code>/usage [Email]</code>\r\n\r\nДля пошуку вхідних листів (зі статистикою клієнта):\r\n<code>/inbound [Примітка]</code>"
|
||||||
|
"helpClientCommands" = "Для пошуку статистики скористайтеся такою командою:\r\n\r\n<code>/usage [Email]</code>"
|
||||||
|
|
||||||
|
[tgbot.messages]
|
||||||
|
"cpuThreshold" = "🔴 Навантаження ЦП {{ .Percent }}% перевищує порогове значення {{ .Threshold }}%"
|
||||||
|
"selectUserFailed" = "❌ Помилка під час вибору користувача!"
|
||||||
|
"userSaved" = "✅ Користувача Telegram збережено."
|
||||||
|
"loginSuccess" = "✅ Успішно ввійшли в панель\r\n"
|
||||||
|
"loginFailed" = "❗️ Помилка входу в панель.\r\n"
|
||||||
|
"report" = "🕰 Заплановані звіти: {{ .RunTime }}\r\n"
|
||||||
|
"datetime" = "⏰ Дата й час: {{ .DateTime }}\r\n"
|
||||||
|
"hostname" = "💻 Хост: {{ .Hostname }}\r\n"
|
||||||
|
"version" = "🚀 3X-UI Версія: {{ .Version }}\r\n"
|
||||||
|
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
|
||||||
|
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
|
||||||
|
"ip" = "🌐 IP: {{ .IP }}\r\n"
|
||||||
|
"ips" = "🔢 IP-адреси:\r\n{{ .IPs }}\r\n"
|
||||||
|
"serverUpTime" = "⏳ Час роботи: {{ .UpTime }} {{ .Unit }}\r\n"
|
||||||
|
"serverLoad" = "📈 Завантаження системи: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
||||||
|
"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n"
|
||||||
|
"tcpCount" = "🔹 TCP: {{ .Count }}\r\n"
|
||||||
|
"udpCount" = "🔸 UDP: {{ .Count }}\r\n"
|
||||||
|
"traffic" = "🚦 Трафік: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||||
|
"xrayStatus" = "ℹ️ Статус: {{ .State }}\r\n"
|
||||||
|
"username" = "👤 Ім'я користувача: {{ .Username }}\r\n"
|
||||||
|
"time" = "⏰ Час: {{ .Time }}\r\n"
|
||||||
|
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
||||||
|
"port" = "🔌 Порт: {{ .Port }}\r\n"
|
||||||
|
"expire" = "📅 Дата закінчення: {{ .Time }}\r\n"
|
||||||
|
"expireIn" = "📅 Термін дії: {{ .Time }}\r\n"
|
||||||
|
"active" = "💡 Активний: {{ .Enable }}\r\n"
|
||||||
|
"enabled" = "🚨 Увімкнено: {{ .Enable }}\r\n"
|
||||||
|
"online" = "🌐 Стан підключення: {{ .Status }}\r\n"
|
||||||
|
"email" = "📧 Електронна пошта: {{ .Email }}\r\n"
|
||||||
|
"upload" = "🔼 Upload: ↑{{ .Upload }}\r\n"
|
||||||
|
"download" = "🔽 Download: ↓{{ .Download }}\r\n"
|
||||||
|
"total" = "📊 Всього: ↑↓{{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
|
"TGUser" = "👤 Користувач Telegram: {{ .TelegramID }}\r\n"
|
||||||
|
"exhaustedMsg" = "🚨 Вичерпано {{ .Type }}:\r\n"
|
||||||
|
"exhaustedCount" = "🚨 Вичерпано кількість {{ .Type }} count:\r\n"
|
||||||
|
"onlinesCount" = "🌐 Онлайн-клієнти: {{ .Count }}\r\n"
|
||||||
|
"disabled" = "🛑 Вимкнено: {{ .Disabled }}\r\n"
|
||||||
|
"depleteSoon" = "🔜 Скоро вичерпається: {{ .Deplete }}\r\n\r\n"
|
||||||
|
"backupTime" = "🗄 Час резервного копіювання: {{ .Time }}\r\n"
|
||||||
|
"refreshedOn" = "\r\n📋🔄 Оновлено: {{ .Time }}\r\n\r\n"
|
||||||
|
"yes" = "✅ Так"
|
||||||
|
"no" = "❌ Ні"
|
||||||
|
|
||||||
|
[tgbot.buttons]
|
||||||
|
"closeKeyboard" = "❌ Закрити клавіатуру"
|
||||||
|
"cancel" = "❌ Скасувати"
|
||||||
|
"cancelReset" = "❌ Скасувати скидання"
|
||||||
|
"cancelIpLimit" = "❌ Скасувати обмеження IP"
|
||||||
|
"confirmResetTraffic" = "✅ Підтвердити скидання трафіку?"
|
||||||
|
"confirmClearIps" = "✅ Підтвердити очищення IP-адрес?"
|
||||||
|
"confirmRemoveTGUser" = "✅ Підтвердити видалення користувача Telegram?"
|
||||||
|
"confirmToggle" = "✅ Підтвердити ввімкнути/вимкнути користувача?"
|
||||||
|
"dbBackup" = "Отримати резервну копію БД"
|
||||||
|
"serverUsage" = "Використання сервера"
|
||||||
|
"getInbounds" = "Отримати вхідні"
|
||||||
|
"depleteSoon" = "Скоро вичерпати"
|
||||||
|
"clientUsage" = "Отримати використання"
|
||||||
|
"onlines" = "Онлайн-клієнти"
|
||||||
|
"commands" = "Команди"
|
||||||
|
"refresh" = "🔄 Оновити"
|
||||||
|
"clearIPs" = "❌ Очистити IP-адреси"
|
||||||
|
"removeTGUser" = "❌ Видалити користувача Telegram"
|
||||||
|
"selectTGUser" = "👤 Виберіть користувача Telegram"
|
||||||
|
"selectOneTGUser" = "👤 Виберіть користувача Telegram:"
|
||||||
|
"resetTraffic" = "📈 Скинути трафік"
|
||||||
|
"resetExpire" = "📅 Змінити термін дії"
|
||||||
|
"ipLog" = "🔢 IP журнал"
|
||||||
|
"ipLimit" = "🔢 IP Ліміт"
|
||||||
|
"setTGUser" = "👤 Встановити користувача Telegram"
|
||||||
|
"toggle" = "🔘 Увімкнути / Вимкнути"
|
||||||
|
"custom" = "🔢 Custom"
|
||||||
|
"confirmNumber" = "✅ Підтвердити: {{ .Num }}"
|
||||||
|
"confirmNumberAdd" = "✅ Підтвердити додавання: {{ .Num }}"
|
||||||
|
"limitTraffic" = "🚧 Ліміт трафіку"
|
||||||
|
"getBanLogs" = "Отримати журнали заборон"
|
||||||
|
|
||||||
|
[tgbot.answers]
|
||||||
|
"successfulOperation" = "✅ Операція успішна!"
|
||||||
|
"errorOperation" = "❗ Помилка в роботі."
|
||||||
|
"getInboundsFailed" = "❌ Не вдалося отримати вхідні повідомлення."
|
||||||
|
"canceled" = "❌ {{ .Email }}: Операцію скасовано."
|
||||||
|
"clientRefreshSuccess" = "✅ {{ .Email }}: Клієнт успішно оновлено."
|
||||||
|
"IpRefreshSuccess" = "✅ {{ .Email }}: IP-адреси успішно оновлено."
|
||||||
|
"TGIdRefreshSuccess" = "✅ {{ .Email }}: Користувач Telegram клієнта успішно оновлено."
|
||||||
|
"resetTrafficSuccess" = "✅ {{ .Email }}: Трафік скинуто успішно."
|
||||||
|
"setTrafficLimitSuccess" = "✅ {{ .Email }}: Ліміт трафіку успішно збережено."
|
||||||
|
"expireResetSuccess" = "✅ {{ .Email }}: Успішно скинуто дні закінчення терміну дії."
|
||||||
|
"resetIpSuccess" = "✅ {{ .Email }}: IP обмеження {{ .Count }} успішно збережено."
|
||||||
|
"clearIpSuccess" = "✅ {{ .Email }}: IP успішно очищено."
|
||||||
|
"getIpLog" = "✅ {{ .Email }}: Отримати IP-журнал."
|
||||||
|
"getUserInfo" = "✅ {{ .Email }}: Отримати інформацію про користувача Telegram."
|
||||||
|
"removedTGUserSuccess" = "✅ {{ .Email }}: Користувача Telegram видалено успішно."
|
||||||
|
"enableSuccess" = "✅ {{ .Email }}: Увімкнути успішно."
|
||||||
|
"disableSuccess" = "✅ {{ .Email }}: Успішно вимкнено."
|
||||||
|
"askToAddUserId" = "Вашу конфігурацію не знайдено!\r\nБудь ласка, попросіть свого адміністратора використовувати ваш ідентифікатор Telegram у вашій конфігурації.\r\n\r\nВаш ідентифікатор користувача: <code>{{ .TgUserID }}</code>"
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"online" = "Trực tuyến"
|
"online" = "Trực tuyến"
|
||||||
"domainName" = "Tên miền"
|
"domainName" = "Tên miền"
|
||||||
"monitor" = "Listening IP"
|
"monitor" = "Listening IP"
|
||||||
"certificate" = "Chứng chỉ"
|
"certificate" = "Chứng chỉ số"
|
||||||
"fail" = "Thất bại"
|
"fail" = "Thất bại"
|
||||||
"success" = "Thành công"
|
"success" = "Thành công"
|
||||||
"getVersion" = "Lấy phiên bản"
|
"getVersion" = "Lấy phiên bản"
|
||||||
@@ -54,7 +54,12 @@
|
|||||||
"security" = "Bảo vệ"
|
"security" = "Bảo vệ"
|
||||||
"secAlertTitle" = "Cảnh báo an ninh-Tiếng Việt by Ohoang7"
|
"secAlertTitle" = "Cảnh báo an ninh-Tiếng Việt by Ohoang7"
|
||||||
"secAlertSsl" = "Kết nối này không an toàn; Vui lòng không nhập thông tin nhạy cảm cho đến khi TLS được kích hoạt để bảo vệ dữ liệu của Bạn"
|
"secAlertSsl" = "Kết nối này không an toàn; Vui lòng không nhập thông tin nhạy cảm cho đến khi TLS được kích hoạt để bảo vệ dữ liệu của Bạn"
|
||||||
"secAlertConf" = "Một số cấu hình nhất định đã được xác định là dễ bị tấn công, thúc đẩy hành động ngay lập tức để củng cố các giao thức bảo mật và bảo vệ chống lại các vi phạm bảo mật tiềm ẩn."
|
"secAlertConf" = "Một số cài đặt có thể dễ bị tấn công. Đề xuất tăng cường các giao thức bảo mật để ngăn chặn các vi phạm tiềm ẩn."
|
||||||
|
"secAlertSSL" = "Bảng điều khiển thiếu kết nối an toàn. Vui lòng cài đặt chứng chỉ TLS để bảo vệ dữ liệu."
|
||||||
|
"secAlertPanelPort" = "Cổng mặc định của bảng điều khiển có thể dễ bị tấn công. Vui lòng cấu hình một cổng ngẫu nhiên hoặc cụ thể."
|
||||||
|
"secAlertPanelURI" = "Đường dẫn URI mặc định của bảng điều khiển không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp."
|
||||||
|
"secAlertSubURI" = "Đường dẫn URI mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp."
|
||||||
|
"secAlertSubJsonURI" = "Đường dẫn URI JSON mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp."
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Trạng thái hệ thống"
|
"dashboard" = "Trạng thái hệ thống"
|
||||||
@@ -65,6 +70,7 @@
|
|||||||
"link" = "Quản lý"
|
"link" = "Quản lý"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "Xin chào"
|
||||||
"title" = "Chào mừng"
|
"title" = "Chào mừng"
|
||||||
"loginAgain" = "Thời hạn đăng nhập đã hết. Vui lòng đăng nhập lại."
|
"loginAgain" = "Thời hạn đăng nhập đã hết. Vui lòng đăng nhập lại."
|
||||||
|
|
||||||
@@ -143,10 +149,8 @@
|
|||||||
"noRecommendKeepDefault" = "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định"
|
"noRecommendKeepDefault" = "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định"
|
||||||
"certificatePath" = "Đường dẫn tập"
|
"certificatePath" = "Đường dẫn tập"
|
||||||
"certificateContent" = "Nội dung tập"
|
"certificateContent" = "Nội dung tập"
|
||||||
"publicKeyPath" = "Đường dẫn khóa công khai"
|
"publicKey" = "Khóa công khai"
|
||||||
"publicKeyContent" = "Nội dung khóa công khai"
|
"privatekey" = "Khóa cá nhân"
|
||||||
"keyPath" = "Đường dẫn khóa riêng tư"
|
|
||||||
"keyContent" = "Nội dung khóa riêng tư"
|
|
||||||
"clickOnQRcode" = "Nhấn vào Mã QR để sao chép"
|
"clickOnQRcode" = "Nhấn vào Mã QR để sao chép"
|
||||||
"client" = "Người dùng"
|
"client" = "Người dùng"
|
||||||
"export" = "Xuất liên kết"
|
"export" = "Xuất liên kết"
|
||||||
@@ -197,7 +201,7 @@
|
|||||||
"last" = "Cuối cùng"
|
"last" = "Cuối cùng"
|
||||||
"prefix" = "Tiền tố"
|
"prefix" = "Tiền tố"
|
||||||
"postfix" = "Hậu tố"
|
"postfix" = "Hậu tố"
|
||||||
"delayedStart" = "Bắt đầu sau khi sử dụng lần đầu"
|
"delayedStart" = "Bắt đầu ở Lần Đầu"
|
||||||
"expireDays" = "Khoảng thời gian"
|
"expireDays" = "Khoảng thời gian"
|
||||||
"days" = "ngày"
|
"days" = "ngày"
|
||||||
"renew" = "Tự động gia hạn"
|
"renew" = "Tự động gia hạn"
|
||||||
@@ -323,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"
|
||||||
@@ -442,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"
|
||||||
@@ -463,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ủ"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user