mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-19 17:15:49 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9c703ea44 |
49
.github/workflows/docker.yml
vendored
49
.github/workflows/docker.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Release 3X-UI for Docker
|
name: Release 3X-UI for Docker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -6,36 +7,44 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_push:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the code
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to GHCR
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
submodules: true
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: |
|
||||||
|
ghcr.io/mhsanaei/3x-ui
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=tag
|
||||||
|
type=pep440,pattern={{version}}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Set up QEMU
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/setup-qemu-action@v3.0.0
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3.0.0
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3.0.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v5.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: true
|
||||||
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
|
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6,linux/386
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
13
.github/workflows/release.yml
vendored
13
.github/workflows/release.yml
vendored
@@ -20,12 +20,12 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5.0.0
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.21'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
cd x-ui/bin
|
cd x-ui/bin
|
||||||
|
|
||||||
# Download dependencies
|
# Download dependencies
|
||||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.8/"
|
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.7/"
|
||||||
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
|
||||||
@@ -116,11 +116,12 @@ jobs:
|
|||||||
- name: Package
|
- name: Package
|
||||||
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
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@2.7.0
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
overwrite: true
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ case $1 in
|
|||||||
esac
|
esac
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
cd build/bin
|
cd build/bin
|
||||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.8/Xray-linux-${ARCH}.zip"
|
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.7/Xray-linux-${ARCH}.zip"
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# ========================================================
|
# ========================================================
|
||||||
# Stage: Builder
|
# Stage: Builder
|
||||||
# ========================================================
|
# ========================================================
|
||||||
FROM golang:1.22-alpine AS builder
|
FROM golang:1.21-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
@@ -28,14 +28,12 @@ WORKDIR /app
|
|||||||
RUN apk add --no-cache --update \
|
RUN apk add --no-cache --update \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
fail2ban \
|
fail2ban
|
||||||
bash
|
|
||||||
|
|
||||||
COPY --from=builder /app/build/ /app/
|
COPY --from=builder /app/build/ /app/
|
||||||
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
||||||
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
||||||
|
|
||||||
|
|
||||||
# Configure fail2ban
|
# Configure fail2ban
|
||||||
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
|
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
|
||||||
&& cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \
|
&& cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \
|
||||||
@@ -49,5 +47,4 @@ RUN chmod +x \
|
|||||||
/usr/bin/x-ui
|
/usr/bin/x-ui
|
||||||
|
|
||||||
VOLUME [ "/etc/x-ui" ]
|
VOLUME [ "/etc/x-ui" ]
|
||||||
CMD [ "./x-ui" ]
|
|
||||||
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
||||||
|
|||||||
118
README.md
118
README.md
@@ -1,7 +1,5 @@
|
|||||||
# 3X-UI
|
# 3X-UI
|
||||||
|
|
||||||
<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)
|
||||||
@@ -14,7 +12,8 @@
|
|||||||
|
|
||||||
**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:
|
||||||
|
|
||||||
<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>
|
<a href="#">
|
||||||
|
<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`
|
||||||
|
|
||||||
@@ -26,39 +25,11 @@ 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.1`:
|
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.1.2`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.1
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.2
|
||||||
```
|
```
|
||||||
|
|
||||||
## SSL Certificate
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Click for SSL Certificate</summary>
|
|
||||||
|
|
||||||
### Cloudflare
|
|
||||||
|
|
||||||
The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
|
|
||||||
|
|
||||||
- Cloudflare registered email
|
|
||||||
- Cloudflare Global API Key
|
|
||||||
- The domain name has been resolved to the current server through cloudflare
|
|
||||||
|
|
||||||
**1:** Run the`x-ui`command on the terminal, then choose `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 is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Manual Install & Upgrade
|
## Manual Install & Upgrade
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -70,17 +41,7 @@ certbot renew --dry-run
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
case "${ARCH}" in
|
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
|
||||||
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
|
||||||
i*86 | x86) XUI_ARCH="386" ;;
|
|
||||||
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
|
||||||
armv7* | armv7) XUI_ARCH="armv7" ;;
|
|
||||||
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
|
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -88,16 +49,7 @@ wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
case "${ARCH}" in
|
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
|
||||||
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
|
||||||
i*86 | x86) XUI_ARCH="386" ;;
|
|
||||||
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
|
||||||
armv7* | armv7) XUI_ARCH="armv7" ;;
|
|
||||||
armv6* | armv6) XUI_ARCH="armv6" ;;
|
|
||||||
armv5* | armv5) XUI_ARCH="armv5" ;;
|
|
||||||
*) XUI_ARCH="amd64" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
cd /root/
|
cd /root/
|
||||||
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
||||||
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
@@ -184,26 +136,22 @@ remove 3x-ui from docker
|
|||||||
- AlmaLinux 9+
|
- AlmaLinux 9+
|
||||||
- Rockylinux 9+
|
- Rockylinux 9+
|
||||||
|
|
||||||
## Supported Architectures and Devices
|
## Compatible Architectures & Devices
|
||||||
|
|
||||||
<details>
|
Supports a variety of different architectures and devices. Here are some of the main architectures that we support:
|
||||||
<summary>Click for Supported Architectures and devices details</summary>
|
|
||||||
|
|
||||||
Our platform offers compatibility with a diverse range of architectures and devices, ensuring flexibility across various computing environments. The following are key architectures that we support:
|
- **amd64**: This is the most common architecture for personal computers and servers. It supports most modern operating systems.
|
||||||
|
|
||||||
- **amd64**: This prevalent architecture is the standard for personal computers and servers, accommodating most modern operating systems seamlessly.
|
- **x86 / i386**: This architecture is prevalent in desktop and laptop computers. It's widely supported by various operating systems and applications. (Ex: Most Windows, macOS, and Linux systems)
|
||||||
|
|
||||||
- **x86 / i386**: Widely adopted in desktop and laptop computers, this architecture enjoys broad support from numerous operating systems and applications, including but not limited to Windows, macOS, and Linux systems.
|
- **armv8 / arm64 / aarch64**: This is the architecture for modern mobile and embedded devices, including smartphones and tablets. (Ex: Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS,...)
|
||||||
|
|
||||||
- **armv8 / arm64 / aarch64**: Tailored for contemporary mobile and embedded devices, such as smartphones and tablets, this architecture is exemplified by devices like Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS, and more.
|
- **armv7 / arm / arm32**: This is the architecture for older mobile and embedded devices. It is still widely used in many devices. (Ex: Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2,...)
|
||||||
|
|
||||||
- **armv7 / arm / arm32**: Serving as the architecture for older mobile and embedded devices, it remains widely utilized in devices like Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2, among others.
|
- **armv6 / arm / arm32**: This is the architecture for very old embedded devices. While not as common as before, there are still some devices using this architecture. (Ex: Raspberry Pi 1, Raspberry Pi Zero/Zero W,...)
|
||||||
|
|
||||||
- **armv6 / arm / arm32**: Geared towards very old embedded devices, this architecture, while less prevalent, is still in use. Devices such as Raspberry Pi 1, Raspberry Pi Zero/Zero W, rely on this architecture.
|
|
||||||
|
|
||||||
- **armv5 / arm / arm32**: An older architecture primarily associated with early embedded systems, it is less common today but may still be found in legacy devices like early Raspberry Pi versions and some older smartphones.
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
- **armv5 / arm / arm32**: This is an older architecture primarily used in early embedded systems. While it's less common today, some legacy devices may still rely on this architecture. (Ex: Early versions of Raspberry Pi, some older smartphones)
|
||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
- English
|
- English
|
||||||
@@ -212,8 +160,6 @@ Our platform offers compatibility with a diverse range of architectures and devi
|
|||||||
- Russian
|
- Russian
|
||||||
- Vietnamese
|
- Vietnamese
|
||||||
- Spanish
|
- Spanish
|
||||||
- Indonesian
|
|
||||||
- Ukrainian
|
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -255,6 +201,34 @@ Our platform offers compatibility with a diverse range of architectures and devi
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
## SSL Certificate
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for SSL Certificate</summary>
|
||||||
|
|
||||||
|
### Cloudflare
|
||||||
|
|
||||||
|
The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
|
||||||
|
|
||||||
|
- Cloudflare registered email
|
||||||
|
- Cloudflare Global API Key
|
||||||
|
- The domain name has been resolved to the current server through cloudflare
|
||||||
|
|
||||||
|
**1:** Run the`x-ui`command on the terminal, then choose `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 is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
|
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -307,13 +281,13 @@ If you want to use routing to WARP before v2.1.0 follow steps as below:
|
|||||||
2. Select `IP Limit Management`.
|
2. Select `IP Limit Management`.
|
||||||
3. Choose the appropriate options based on your needs.
|
3. Choose the appropriate options based on your needs.
|
||||||
|
|
||||||
- make sure you have ./access.log on your Xray Configuration after v2.1.3 we have an option for it
|
- make sure you have access.log on your Xray Configuration
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
"log": {
|
"log": {
|
||||||
|
"loglevel": "warning",
|
||||||
"access": "./access.log",
|
"access": "./access.log",
|
||||||
"dnsLog": false,
|
"error": "./error.log"
|
||||||
"loglevel": "warning"
|
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.2.1
|
2.1.2
|
||||||
@@ -21,7 +21,6 @@ var db *gorm.DB
|
|||||||
var initializers = []func() error{
|
var initializers = []func() error{
|
||||||
initUser,
|
initUser,
|
||||||
initInbound,
|
initInbound,
|
||||||
initOutbound,
|
|
||||||
initSetting,
|
initSetting,
|
||||||
initInboundClientIps,
|
initInboundClientIps,
|
||||||
initClientTraffic,
|
initClientTraffic,
|
||||||
@@ -52,10 +51,6 @@ func initInbound() error {
|
|||||||
return db.AutoMigrate(&model.Inbound{})
|
return db.AutoMigrate(&model.Inbound{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func initOutbound() error {
|
|
||||||
return db.AutoMigrate(&model.OutboundTraffics{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func initSetting() error {
|
func initSetting() error {
|
||||||
return db.AutoMigrate(&model.Setting{})
|
return db.AutoMigrate(&model.Setting{})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,15 +44,6 @@ type Inbound struct {
|
|||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
Sniffing string `json:"sniffing" form:"sniffing"`
|
Sniffing string `json:"sniffing" form:"sniffing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundTraffics struct {
|
|
||||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
|
||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
|
||||||
Up int64 `json:"up" form:"up" gorm:"default:0"`
|
|
||||||
Down int64 `json:"down" form:"down" gorm:"default:0"`
|
|
||||||
Total int64 `json:"total" form:"total" gorm:"default:0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type InboundClientIps struct {
|
type InboundClientIps struct {
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
||||||
|
|||||||
51
go.mod
51
go.mod
@@ -1,29 +1,29 @@
|
|||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.22.0
|
go 1.21.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Calidity/gin-sessions v1.3.1
|
github.com/Calidity/gin-sessions v1.3.1
|
||||||
github.com/gin-contrib/gzip v0.0.6
|
github.com/gin-contrib/gzip v0.0.6
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/mymmrac/telego v0.29.1
|
github.com/mymmrac/telego v0.28.0
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
github.com/nicksnyder/go-i18n/v2 v2.3.0
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1
|
github.com/pelletier/go-toml/v2 v2.1.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.23.12
|
||||||
github.com/valyala/fasthttp v1.52.0
|
github.com/valyala/fasthttp v1.51.0
|
||||||
github.com/xtls/xray-core v1.8.8
|
github.com/xtls/xray-core v1.8.7
|
||||||
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.61.0
|
||||||
gorm.io/driver/sqlite v1.5.5
|
gorm.io/driver/sqlite v1.5.4
|
||||||
gorm.io/gorm v1.25.7
|
gorm.io/gorm v1.25.6
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||||
github.com/bytedance/sonic v1.10.2 // indirect
|
github.com/bytedance/sonic v1.10.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
|
||||||
@@ -40,31 +40,31 @@ require (
|
|||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/pprof v0.0.0-20240225044709-fd706174c886 // indirect
|
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // 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
|
||||||
github.com/gorilla/websocket v1.5.1 // indirect
|
github.com/gorilla/websocket v1.5.1 // indirect
|
||||||
github.com/grbit/go-json v0.11.0 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.7 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
|
github.com/lufia/plan9stats v0.0.0-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.19 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
|
||||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||||
github.com/quic-go/quic-go v0.41.0 // indirect
|
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||||
github.com/refraction-networking/utls v1.6.3 // indirect
|
github.com/quic-go/quic-go v0.40.1 // 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.2 // indirect
|
github.com/sagernet/sing v0.3.0 // 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-20230208104028-c358bd845dee // indirect
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||||
@@ -75,7 +75,6 @@ require (
|
|||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fastjson v1.6.4 // indirect
|
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
|
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
|
||||||
@@ -83,16 +82,16 @@ require (
|
|||||||
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.18.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
|
||||||
golang.org/x/mod v0.15.0 // indirect
|
golang.org/x/mod v0.14.0 // indirect
|
||||||
golang.org/x/net v0.21.0 // indirect
|
golang.org/x/net v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.17.0 // indirect
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.18.0 // indirect
|
golang.org/x/tools v0.16.1 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
google.golang.org/protobuf v1.32.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
|
||||||
|
|||||||
110
go.sum
110
go.sum
@@ -12,8 +12,8 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8
|
|||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
||||||
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||||
@@ -88,8 +88,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
|||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
@@ -111,8 +111,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
|||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20240225044709-fd706174c886 h1:JSJUTZTQT1Gzb2ROdAKOY3HwzBYcclS2GgumhMfHqjw=
|
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8=
|
||||||
github.com/google/pprof v0.0.0-20240225044709-fd706174c886/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/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=
|
||||||
@@ -124,8 +124,6 @@ github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTj
|
|||||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc=
|
|
||||||
github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek=
|
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
@@ -138,11 +136,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.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/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=
|
||||||
@@ -169,24 +167,24 @@ github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbW
|
|||||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mymmrac/telego v0.29.1 h1:nsNnK0mS18OL+unoDjDI6BVfafJBbT8Wtj7rCzEWoM8=
|
github.com/mymmrac/telego v0.28.0 h1:DNXaYISeZw1J9oB81vCNdskLow8gCRRUJxufqLuH3XE=
|
||||||
github.com/mymmrac/telego v0.29.1/go.mod h1:ZLD1+L2TQRr97NPOCoN1V2w8y9kmFov33OfZ3qT8cF4=
|
github.com/mymmrac/telego v0.28.0/go.mod h1:oRperySNzJq8dRTl24+uBF1Uy7tlQGIjid/JQtHDsZg=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||||
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
|
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||||
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
|
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
@@ -208,10 +206,12 @@ 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/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
|
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||||
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
|
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||||
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
|
github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
|
||||||
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
|
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
||||||
|
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=
|
||||||
@@ -221,8 +221,8 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
|||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/sagernet/sing v0.3.2 h1:CwWcxUBPkMvwgfe2/zUgY5oHG9qOL8Aob/evIFYK9jo=
|
github.com/sagernet/sing v0.3.0 h1:PIDVFZHnQAAYRL1UYqNM+0k5s8f/tb1lUW6UDcQiOc8=
|
||||||
github.com/sagernet/sing v0.3.2/go.mod h1:qHySJ7u8po9DABtMYEkNBcOumx7ZZJf/fbv2sfTkNHE=
|
github.com/sagernet/sing v0.3.0/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
|
||||||
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-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
@@ -230,8 +230,8 @@ github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJ
|
|||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI=
|
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
|
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
@@ -288,10 +288,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
|
|||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
|
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||||
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
|
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
|
||||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
|
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
|
||||||
@@ -301,8 +299,8 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
|
|||||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI=
|
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI=
|
||||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||||
github.com/xtls/xray-core v1.8.8 h1:6ApBa5pNkPZ+I7jyJDZod3v5sadizS/BZr0pW7zcs8o=
|
github.com/xtls/xray-core v1.8.7 h1:lb8O1l3/eAg3YAXA6tLm5M6N7BsX8wxW9sJLjU3dHkA=
|
||||||
github.com/xtls/xray-core v1.8.8/go.mod h1:Zp33A8cxnhP5Kt6nguQrMgNH4A/tgq7LE8cBedeNje8=
|
github.com/xtls/xray-core v1.8.7/go.mod h1:9rFpflfQbgFeH1VKJw7yUmEy7myOyDCgNXXl0bmmyOo=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
@@ -321,16 +319,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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
|
||||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -340,8 +338,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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@@ -371,9 +369,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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -390,8 +388,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
@@ -409,14 +407,14 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
|
|||||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
|
||||||
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.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
||||||
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
@@ -437,10 +435,10 @@ gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||||
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A=
|
||||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
|
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
|
||||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||||
|
|||||||
@@ -82,16 +82,16 @@ fi
|
|||||||
install_base() {
|
install_base() {
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
centos | almalinux | rocky)
|
centos | almalinux | rocky)
|
||||||
yum -y update && yum install -y -q wget curl tar tzdata
|
yum -y update && yum install -y -q wget curl tar
|
||||||
;;
|
;;
|
||||||
fedora)
|
fedora)
|
||||||
dnf -y update && dnf install -y -q wget curl tar tzdata
|
dnf -y update && dnf install -y -q wget curl tar
|
||||||
;;
|
;;
|
||||||
arch | manjaro)
|
arch | manjaro)
|
||||||
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
|
pacman -Syu && pacman -Syu --noconfirm wget curl tar
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
apt-get update && apt install -y -q wget curl tar tzdata
|
apt-get update && apt install -y -q wget curl tar
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,16 +65,6 @@ func Infof(format string, args ...interface{}) {
|
|||||||
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Notice(args ...interface{}) {
|
|
||||||
logger.Notice(args...)
|
|
||||||
addToBuffer("NOTICE", fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Noticef(format string, args ...interface{}) {
|
|
||||||
logger.Noticef(format, args...)
|
|
||||||
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Warning(args ...interface{}) {
|
func Warning(args ...interface{}) {
|
||||||
logger.Warning(args...)
|
logger.Warning(args...)
|
||||||
addToBuffer("WARNING", fmt.Sprint(args...))
|
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||||
|
|||||||
BIN
media/3X-UI.png
BIN
media/3X-UI.png
Binary file not shown.
|
Before Width: | Height: | Size: 198 KiB |
105
sub/default.json
105
sub/default.json
@@ -1,105 +0,0 @@
|
|||||||
{
|
|
||||||
"dns": {
|
|
||||||
"tag": "dns_out",
|
|
||||||
"queryStrategy": "UseIP",
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"address": "8.8.8.8",
|
|
||||||
"skipFallback": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"port": 10808,
|
|
||||||
"protocol": "socks",
|
|
||||||
"settings": {
|
|
||||||
"auth": "noauth",
|
|
||||||
"udp": true,
|
|
||||||
"userLevel": 8
|
|
||||||
},
|
|
||||||
"sniffing": {
|
|
||||||
"destOverride": [
|
|
||||||
"http",
|
|
||||||
"tls",
|
|
||||||
"fakedns"
|
|
||||||
],
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"tag": "socks"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"port": 10809,
|
|
||||||
"protocol": "http",
|
|
||||||
"settings": {
|
|
||||||
"userLevel": 8
|
|
||||||
},
|
|
||||||
"tag": "http"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"log": {
|
|
||||||
"loglevel": "warning"
|
|
||||||
},
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"tag": "direct",
|
|
||||||
"protocol": "freedom",
|
|
||||||
"settings": {
|
|
||||||
"domainStrategy": "UseIP"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "block",
|
|
||||||
"protocol": "blackhole",
|
|
||||||
"settings": {
|
|
||||||
"response": {
|
|
||||||
"type": "http"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"policy": {
|
|
||||||
"levels": {
|
|
||||||
"8": {
|
|
||||||
"connIdle": 300,
|
|
||||||
"downlinkOnly": 1,
|
|
||||||
"handshake": 4,
|
|
||||||
"uplinkOnly": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"system": {
|
|
||||||
"statsOutboundUplink": true,
|
|
||||||
"statsOutboundDownlink": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"routing": {
|
|
||||||
"domainStrategy": "AsIs",
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"type": "field",
|
|
||||||
"network": "tcp,udp",
|
|
||||||
"balancerTag": "all"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"balancers": [
|
|
||||||
{
|
|
||||||
"tag": "all",
|
|
||||||
"selector": [
|
|
||||||
"proxy"
|
|
||||||
],
|
|
||||||
"strategy": {
|
|
||||||
"type": "leastPing"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"observatory": {
|
|
||||||
"probeInterval": "5m",
|
|
||||||
"probeURL": "https://api.github.com/_private/browser/stats",
|
|
||||||
"subjectSelector": [
|
|
||||||
"proxy"
|
|
||||||
],
|
|
||||||
"EnableConcurrency": true
|
|
||||||
},
|
|
||||||
"stats": {}
|
|
||||||
}
|
|
||||||
44
sub/sub.go
44
sub/sub.go
@@ -47,6 +47,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
|
|
||||||
engine := gin.Default()
|
engine := gin.Default()
|
||||||
|
|
||||||
|
subPath, err := s.settingService.GetSubPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
subDomain, err := s.settingService.GetSubDomain()
|
subDomain, err := s.settingService.GetSubDomain()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -56,44 +61,9 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
||||||
}
|
}
|
||||||
|
|
||||||
LinksPath, err := s.settingService.GetSubPath()
|
g := engine.Group(subPath)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonPath, err := s.settingService.GetSubJsonPath()
|
s.sub = NewSUBController(g)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
Encrypt, err := s.settingService.GetSubEncrypt()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowInfo, err := s.settingService.GetSubShowInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
RemarkModel, err := s.settingService.GetRemarkModel()
|
|
||||||
if err != nil {
|
|
||||||
RemarkModel = "-ieo"
|
|
||||||
}
|
|
||||||
|
|
||||||
SubUpdates, err := s.settingService.GetSubUpdates()
|
|
||||||
if err != nil {
|
|
||||||
SubUpdates = "10"
|
|
||||||
}
|
|
||||||
|
|
||||||
SubJsonFragment, err := s.settingService.GetSubJsonFragment()
|
|
||||||
if err != nil {
|
|
||||||
SubJsonFragment = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
g := engine.Group("/")
|
|
||||||
|
|
||||||
s.sub = NewSUBController(g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates, SubJsonFragment)
|
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,56 +3,34 @@ package sub
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"strings"
|
"strings"
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SUBController struct {
|
type SUBController struct {
|
||||||
subPath string
|
subService SubService
|
||||||
subJsonPath string
|
settingService service.SettingService
|
||||||
subEncrypt bool
|
|
||||||
updateInterval string
|
|
||||||
|
|
||||||
subService *SubService
|
|
||||||
subJsonService *SubJsonService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSUBController(
|
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
||||||
g *gin.RouterGroup,
|
a := &SUBController{}
|
||||||
subPath string,
|
|
||||||
jsonPath string,
|
|
||||||
encrypt bool,
|
|
||||||
showInfo bool,
|
|
||||||
rModel string,
|
|
||||||
update string,
|
|
||||||
jsonFragment string) *SUBController {
|
|
||||||
|
|
||||||
a := &SUBController{
|
|
||||||
subPath: subPath,
|
|
||||||
subJsonPath: jsonPath,
|
|
||||||
subEncrypt: encrypt,
|
|
||||||
updateInterval: update,
|
|
||||||
|
|
||||||
subService: NewSubService(showInfo, rModel),
|
|
||||||
subJsonService: NewSubJsonService(jsonFragment),
|
|
||||||
}
|
|
||||||
a.initRouter(g)
|
a.initRouter(g)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||||
gLink := g.Group(a.subPath)
|
g = g.Group("/")
|
||||||
gJson := g.Group(a.subJsonPath)
|
|
||||||
|
|
||||||
gLink.GET(":subid", a.subs)
|
g.GET("/:subid", a.subs)
|
||||||
|
|
||||||
gJson.GET(":subid", a.subJsons)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) subs(c *gin.Context) {
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
|
subEncrypt, _ := a.settingService.GetSubEncrypt()
|
||||||
|
subShowInfo, _ := a.settingService.GetSubShowInfo()
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
subs, header, err := a.subService.GetSubs(subId, host)
|
subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo)
|
||||||
if err != nil || len(subs) == 0 {
|
if err != nil || len(subs) == 0 {
|
||||||
c.String(400, "Error!")
|
c.String(400, "Error!")
|
||||||
} else {
|
} else {
|
||||||
@@ -62,31 +40,14 @@ func (a *SUBController) subs(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add headers
|
// Add headers
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
||||||
c.Writer.Header().Set("Profile-Title", subId)
|
c.Writer.Header().Set("Profile-Title", headers[2])
|
||||||
|
|
||||||
if a.subEncrypt {
|
if subEncrypt {
|
||||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||||
} else {
|
} else {
|
||||||
c.String(200, result)
|
c.String(200, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) subJsons(c *gin.Context) {
|
|
||||||
subId := c.Param("subid")
|
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
|
||||||
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
|
|
||||||
if err != nil || len(jsonSub) == 0 {
|
|
||||||
c.String(400, "Error!")
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Add headers
|
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
|
||||||
c.Writer.Header().Set("Profile-Title", subId)
|
|
||||||
|
|
||||||
c.String(200, jsonSub)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,355 +0,0 @@
|
|||||||
package sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"x-ui/database/model"
|
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/util/json_util"
|
|
||||||
"x-ui/util/random"
|
|
||||||
"x-ui/web/service"
|
|
||||||
"x-ui/xray"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed default.json
|
|
||||||
var defaultJson string
|
|
||||||
|
|
||||||
type SubJsonService struct {
|
|
||||||
fragmanet string
|
|
||||||
|
|
||||||
inboundService service.InboundService
|
|
||||||
SubService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSubJsonService(fragment string) *SubJsonService {
|
|
||||||
return &SubJsonService{
|
|
||||||
fragmanet: fragment,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) {
|
|
||||||
inbounds, err := s.SubService.getInboundsBySubId(subId)
|
|
||||||
if err != nil || len(inbounds) == 0 {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var header string
|
|
||||||
var traffic xray.ClientTraffic
|
|
||||||
var clientTraffics []xray.ClientTraffic
|
|
||||||
var 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outbounds := []json_util.RawMessage{}
|
|
||||||
startIndex := 0
|
|
||||||
// Prepare Inbounds
|
|
||||||
for _, inbound := range inbounds {
|
|
||||||
clients, err := s.inboundService.GetClients(inbound)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("SubJsonService - GetClients: Unable to get clients from inbound")
|
|
||||||
}
|
|
||||||
if clients == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
|
||||||
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
|
|
||||||
if err == nil {
|
|
||||||
inbound.Listen = listen
|
|
||||||
inbound.Port = port
|
|
||||||
inbound.StreamSettings = streamSettings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var subClients []model.Client
|
|
||||||
for _, client := range clients {
|
|
||||||
if client.Enable && client.SubID == subId {
|
|
||||||
subClients = append(subClients, client)
|
|
||||||
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outbound := s.getOutbound(inbound, subClients, host, startIndex)
|
|
||||||
if outbound != nil {
|
|
||||||
outbounds = append(outbounds, outbound...)
|
|
||||||
startIndex += len(outbound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(outbounds) == 0 {
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare statistics
|
|
||||||
for index, clientTraffic := range clientTraffics {
|
|
||||||
if index == 0 {
|
|
||||||
traffic.Up = clientTraffic.Up
|
|
||||||
traffic.Down = clientTraffic.Down
|
|
||||||
traffic.Total = clientTraffic.Total
|
|
||||||
if clientTraffic.ExpiryTime > 0 {
|
|
||||||
traffic.ExpiryTime = clientTraffic.ExpiryTime
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
traffic.Up += clientTraffic.Up
|
|
||||||
traffic.Down += clientTraffic.Down
|
|
||||||
if traffic.Total == 0 || clientTraffic.Total == 0 {
|
|
||||||
traffic.Total = 0
|
|
||||||
} else {
|
|
||||||
traffic.Total += clientTraffic.Total
|
|
||||||
}
|
|
||||||
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
|
|
||||||
traffic.ExpiryTime = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.fragmanet != "" {
|
|
||||||
outbounds = append(outbounds, json_util.RawMessage(s.fragmanet))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combile outbounds
|
|
||||||
outbounds = append(outbounds, defaultOutbounds...)
|
|
||||||
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)
|
|
||||||
return string(finalJson), header, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Client, host string, startIndex int) []json_util.RawMessage {
|
|
||||||
var newOutbounds []json_util.RawMessage
|
|
||||||
stream := s.streamData(inbound.StreamSettings)
|
|
||||||
|
|
||||||
externalProxies, ok := stream["externalProxy"].([]interface{})
|
|
||||||
if !ok || len(externalProxies) == 0 {
|
|
||||||
externalProxies = []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"forceTls": "same",
|
|
||||||
"dest": host,
|
|
||||||
"port": float64(inbound.Port),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(stream, "externalProxy")
|
|
||||||
|
|
||||||
config_index := startIndex
|
|
||||||
for _, ep := range externalProxies {
|
|
||||||
extPrxy := ep.(map[string]interface{})
|
|
||||||
inbound.Listen = extPrxy["dest"].(string)
|
|
||||||
inbound.Port = int(extPrxy["port"].(float64))
|
|
||||||
newStream := stream
|
|
||||||
switch extPrxy["forceTls"].(string) {
|
|
||||||
case "tls":
|
|
||||||
if newStream["security"] != "tls" {
|
|
||||||
newStream["security"] = "tls"
|
|
||||||
newStream["tslSettings"] = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
case "none":
|
|
||||||
if newStream["security"] != "none" {
|
|
||||||
newStream["security"] = "none"
|
|
||||||
delete(newStream, "tslSettings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
|
|
||||||
inbound.StreamSettings = string(streamSettings)
|
|
||||||
|
|
||||||
for _, client := range clients {
|
|
||||||
inbound.Tag = fmt.Sprintf("proxy_%d", config_index)
|
|
||||||
switch inbound.Protocol {
|
|
||||||
case "vmess", "vless":
|
|
||||||
newOutbounds = append(newOutbounds, s.genVnext(inbound, client))
|
|
||||||
case "trojan", "shadowsocks":
|
|
||||||
newOutbounds = append(newOutbounds, s.genServer(inbound, client))
|
|
||||||
}
|
|
||||||
config_index += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newOutbounds
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
|
||||||
var streamSettings map[string]interface{}
|
|
||||||
json.Unmarshal([]byte(stream), &streamSettings)
|
|
||||||
security, _ := streamSettings["security"].(string)
|
|
||||||
if security == "tls" {
|
|
||||||
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{}))
|
|
||||||
} else if security == "reality" {
|
|
||||||
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{}))
|
|
||||||
}
|
|
||||||
delete(streamSettings, "sockopt")
|
|
||||||
|
|
||||||
if s.fragmanet != "" {
|
|
||||||
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove proxy protocol
|
|
||||||
network, _ := streamSettings["network"].(string)
|
|
||||||
switch network {
|
|
||||||
case "tcp":
|
|
||||||
streamSettings["tcpSettings"] = s.removeAcceptProxy(streamSettings["tcpSettings"])
|
|
||||||
case "ws":
|
|
||||||
streamSettings["wsSettings"] = s.removeAcceptProxy(streamSettings["wsSettings"])
|
|
||||||
}
|
|
||||||
|
|
||||||
return streamSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} {
|
|
||||||
netSettings, ok := setting.(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
delete(netSettings, "acceptProxyProtocol")
|
|
||||||
}
|
|
||||||
return netSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
|
|
||||||
tlsData := make(map[string]interface{}, 1)
|
|
||||||
tlsClientSettings := tData["settings"].(map[string]interface{})
|
|
||||||
|
|
||||||
tlsData["serverName"] = tData["serverName"]
|
|
||||||
tlsData["alpn"] = tData["alpn"]
|
|
||||||
if allowInsecure, ok := tlsClientSettings["allowInsecure"].(bool); ok {
|
|
||||||
tlsData["allowInsecure"] = allowInsecure
|
|
||||||
}
|
|
||||||
if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
|
|
||||||
tlsData["fingerprint"] = fingerprint
|
|
||||||
}
|
|
||||||
return tlsData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
|
|
||||||
rltyData := make(map[string]interface{}, 1)
|
|
||||||
rltyClientSettings := rData["settings"].(map[string]interface{})
|
|
||||||
|
|
||||||
rltyData["show"] = false
|
|
||||||
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
|
||||||
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
|
|
||||||
|
|
||||||
// Set random data
|
|
||||||
rltyData["spiderX"] = "/" + random.Seq(15)
|
|
||||||
shortIds, ok := rData["shortIds"].([]interface{})
|
|
||||||
if ok && len(shortIds) > 0 {
|
|
||||||
rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
|
|
||||||
} else {
|
|
||||||
rltyData["shortId"] = ""
|
|
||||||
}
|
|
||||||
serverNames, ok := rData["serverNames"].([]interface{})
|
|
||||||
if ok && len(serverNames) > 0 {
|
|
||||||
rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
|
|
||||||
} else {
|
|
||||||
rltyData["serverName"] = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return rltyData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) json_util.RawMessage {
|
|
||||||
outbound := Outbound{}
|
|
||||||
usersData := make([]UserVnext, 1)
|
|
||||||
|
|
||||||
usersData[0].ID = client.ID
|
|
||||||
usersData[0].Level = 8
|
|
||||||
if inbound.Protocol == model.VLESS {
|
|
||||||
usersData[0].Flow = client.Flow
|
|
||||||
usersData[0].Encryption = "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
vnextData := make([]VnextSetting, 1)
|
|
||||||
vnextData[0] = VnextSetting{
|
|
||||||
Address: inbound.Listen,
|
|
||||||
Port: inbound.Port,
|
|
||||||
Users: usersData,
|
|
||||||
}
|
|
||||||
|
|
||||||
outbound.Protocol = string(inbound.Protocol)
|
|
||||||
outbound.Tag = inbound.Tag
|
|
||||||
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
|
|
||||||
outbound.Settings = OutboundSettings{
|
|
||||||
Vnext: vnextData,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) genServer(inbound *model.Inbound, client model.Client) json_util.RawMessage {
|
|
||||||
outbound := Outbound{}
|
|
||||||
|
|
||||||
serverData := make([]ServerSetting, 1)
|
|
||||||
serverData[0] = ServerSetting{
|
|
||||||
Address: inbound.Listen,
|
|
||||||
Port: inbound.Port,
|
|
||||||
Level: 8,
|
|
||||||
Password: client.Password,
|
|
||||||
}
|
|
||||||
|
|
||||||
if inbound.Protocol == model.Shadowsocks {
|
|
||||||
var inboundSettings map[string]interface{}
|
|
||||||
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
|
|
||||||
method, _ := inboundSettings["method"].(string)
|
|
||||||
serverData[0].Method = method
|
|
||||||
|
|
||||||
// server password in multi-user 2022 protocols
|
|
||||||
if strings.HasPrefix(method, "2022") {
|
|
||||||
if serverPassword, ok := inboundSettings["password"].(string); ok {
|
|
||||||
serverData[0].Password = fmt.Sprintf("%s:%s", serverPassword, client.Password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outbound.Protocol = string(inbound.Protocol)
|
|
||||||
outbound.Tag = inbound.Tag
|
|
||||||
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
|
|
||||||
outbound.Settings = OutboundSettings{
|
|
||||||
Servers: serverData,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
type Outbound struct {
|
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
Tag string `json:"tag"`
|
|
||||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
|
||||||
Mux map[string]interface{} `json:"mux,omitempty"`
|
|
||||||
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
|
|
||||||
Settings OutboundSettings `json:"settings,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundSettings struct {
|
|
||||||
Vnext []VnextSetting `json:"vnext,omitempty"`
|
|
||||||
Servers []ServerSetting `json:"servers,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VnextSetting struct {
|
|
||||||
Address string `json:"address"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Users []UserVnext `json:"users"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserVnext struct {
|
|
||||||
Encryption string `json:"encryption,omitempty"`
|
|
||||||
Flow string `json:"flow,omitempty"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
Level int `json:"level"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerSetting struct {
|
|
||||||
Password string `json:"password"`
|
|
||||||
Level int `json:"level"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Flow string `json:"flow,omitempty"`
|
|
||||||
Method string `json:"method,omitempty"`
|
|
||||||
}
|
|
||||||
@@ -25,42 +25,47 @@ type SubService struct {
|
|||||||
settingService service.SettingService
|
settingService service.SettingService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSubService(showInfo bool, remarkModel string) *SubService {
|
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
|
||||||
return &SubService{
|
|
||||||
showInfo: showInfo,
|
|
||||||
remarkModel: remarkModel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
|
|
||||||
s.address = host
|
s.address = host
|
||||||
|
s.showInfo = showInfo
|
||||||
var result []string
|
var result []string
|
||||||
var header string
|
var headers []string
|
||||||
var traffic xray.ClientTraffic
|
var traffic xray.ClientTraffic
|
||||||
var clientTraffics []xray.ClientTraffic
|
var clientTraffics []xray.ClientTraffic
|
||||||
inbounds, err := s.getInboundsBySubId(subId)
|
inbounds, err := s.getInboundsBySubId(subId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
s.remarkModel, err = s.settingService.GetRemarkModel()
|
||||||
s.datepicker, err = s.settingService.GetDatepicker()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.datepicker = "gregorian"
|
s.remarkModel = "-ieo"
|
||||||
}
|
}
|
||||||
|
s.datepicker, err = s.settingService.GetDatepicker()
|
||||||
|
if err != nil {
|
||||||
|
s.datepicker = "gregorian"
|
||||||
|
}
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.inboundService.GetClients(inbound)
|
clients, err := s.inboundService.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("SubService - GetClients: Unable to get clients from inbound")
|
logger.Error("SubService - GetSub: Unable to get clients from inbound")
|
||||||
}
|
}
|
||||||
if clients == nil {
|
if clients == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
||||||
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
|
fallbackMaster, err := s.getFallbackMaster(inbound.Listen)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
inbound.Listen = listen
|
inbound.Listen = fallbackMaster.Listen
|
||||||
inbound.Port = port
|
inbound.Port = fallbackMaster.Port
|
||||||
inbound.StreamSettings = streamSettings
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
|
var masterStream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
|
||||||
|
stream["security"] = masterStream["security"]
|
||||||
|
stream["tlsSettings"] = masterStream["tlsSettings"]
|
||||||
|
stream["externalProxy"] = masterStream["externalProxy"]
|
||||||
|
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
||||||
|
inbound.StreamSettings = string(modifiedStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
@@ -71,8 +76,6 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare statistics
|
|
||||||
for index, clientTraffic := range clientTraffics {
|
for index, clientTraffic := range clientTraffics {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
traffic.Up = clientTraffic.Up
|
traffic.Up = clientTraffic.Up
|
||||||
@@ -94,8 +97,11 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
|
||||||
return result, header, nil
|
updateInterval, _ := s.settingService.GetSubUpdates()
|
||||||
|
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
||||||
|
headers = append(headers, subId)
|
||||||
|
return result, headers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
@@ -124,7 +130,7 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
|
|||||||
return xray.ClientTraffic{}
|
return xray.ClientTraffic{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) {
|
func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbound *model.Inbound
|
var inbound *model.Inbound
|
||||||
err := db.Model(model.Inbound{}).
|
err := db.Model(model.Inbound{}).
|
||||||
@@ -132,19 +138,9 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri
|
|||||||
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
|
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
|
||||||
Find(&inbound).Error
|
Find(&inbound).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return inbound, nil
|
||||||
var stream map[string]interface{}
|
|
||||||
json.Unmarshal([]byte(streamSettings), &stream)
|
|
||||||
var masterStream map[string]interface{}
|
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
|
|
||||||
stream["security"] = masterStream["security"]
|
|
||||||
stream["tlsSettings"] = masterStream["tlsSettings"]
|
|
||||||
stream["externalProxy"] = masterStream["externalProxy"]
|
|
||||||
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
|
||||||
|
|
||||||
return inbound.Listen, inbound.Port, string(modifiedStream), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||||
@@ -582,7 +578,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
||||||
params["sni"], _ = sniValue.(string)
|
params["sni"], _ = sniValue.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
if tlsSetting != nil {
|
if tlsSetting != nil {
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package random
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var numSeq [10]rune
|
var numSeq [10]rune
|
||||||
@@ -12,6 +13,8 @@ var numUpperSeq [36]rune
|
|||||||
var allSeq [62]rune
|
var allSeq [62]rune
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
numSeq[i] = rune('0' + i)
|
numSeq[i] = rune('0' + i)
|
||||||
}
|
}
|
||||||
@@ -38,7 +41,3 @@ func Seq(n int) string {
|
|||||||
}
|
}
|
||||||
return string(runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Num(n int) int {
|
|
||||||
return rand.Intn(n)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -538,7 +538,7 @@
|
|||||||
|
|
||||||
var on = function(emitter, type, f) {
|
var on = function(emitter, type, f) {
|
||||||
if (emitter.addEventListener) {
|
if (emitter.addEventListener) {
|
||||||
emitter.addEventListener(type, f, { passive: false });
|
emitter.addEventListener(type, f, false);
|
||||||
} else if (emitter.attachEvent) {
|
} else if (emitter.attachEvent) {
|
||||||
emitter.attachEvent("on" + type, f);
|
emitter.attachEvent("on" + type, f);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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: var(--dark-color-surface-200); border-color: var(--dark-color-surface-300); color: rgb(255 255 255 / 65%); }
|
.dark .cm-s-xq.CodeMirror { background-color: #222D42; border-color: #2c3950; color: rgb(255 255 255 / 65%); }
|
||||||
.dark .cm-s-xq.CodeMirror:hover { background-color: rgb(0 50 42 / 30%); 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: var(--dark-color-codemirror-line-selection); }
|
.dark .cm-s-xq div.CodeMirror-selected { background: rgba(0, 0, 0, 0.5); }
|
||||||
.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::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::-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-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-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid var(--dark-color-surface-300); }
|
.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid #2c3950; }
|
||||||
.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: var(--dark-color-codemirror-line-hover) !important; }
|
.dark .Line-Hover:hover{ background-color: rgb(0 0 0 / 20%) !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,87 +1,3 @@
|
|||||||
: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-surface-100: #0c0e12;
|
|
||||||
--dark-color-surface-200: #222327;
|
|
||||||
--dark-color-surface-300: #32353b;
|
|
||||||
--dark-color-surface-400: rgba(255, 255, 255, 0.1);
|
|
||||||
--dark-color-surface-500: #3b404b;
|
|
||||||
--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-stroke: #202025;
|
|
||||||
--dark-color-tag-green-bg: #112421;
|
|
||||||
--dark-color-tag-green-border: #1d5f4d;
|
|
||||||
--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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@@ -100,7 +16,7 @@ body {
|
|||||||
font-feature-settings: "tnum";
|
font-feature-settings: "tnum";
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
--antd-wave-shadow-color: var(--color-primary-100);
|
--antd-wave-shadow-color: #008771;
|
||||||
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%;
|
||||||
@@ -110,7 +26,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
color: var(--color-primary-100);
|
color: #008771;
|
||||||
background-color: #cfe8e4;
|
background-color: #cfe8e4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +134,7 @@ style attribute {
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
.ant-modal-body {
|
.ant-modal-body {
|
||||||
padding: 20px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
.ant-form-item-label {
|
.ant-form-item-label {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@@ -302,7 +218,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: var(--color-primary-100);
|
color: #008771;
|
||||||
background-color: rgb(232 244 242);
|
background-color: rgb(232 244 242);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
@@ -564,14 +480,14 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.normal-icon:hover {
|
.normal-icon:hover {
|
||||||
color: var(--color-primary-100);
|
color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DARK THEME */
|
/* DARK THEME */
|
||||||
|
|
||||||
.dark ::selection {
|
.dark ::selection {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: var(--color-primary-100);
|
background-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .normal-icon:hover {
|
.dark .normal-icon:hover {
|
||||||
@@ -586,14 +502,13 @@ style attribute {
|
|||||||
.dark .ant-table,
|
.dark .ant-table,
|
||||||
.dark .ant-collapse-content,
|
.dark .ant-collapse-content,
|
||||||
.dark .ant-tabs {
|
.dark .ant-tabs {
|
||||||
background-color: var(--dark-color-surface-100);
|
background-color: #151f31;
|
||||||
color: var(--dark-color-text-primary);
|
color: #ffffffa6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 1px 10px -1px rgb(154 175 238 / 80%);
|
||||||
box-shadow: 0 2px 8px transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark > .ant-layout,
|
.dark > .ant-layout,
|
||||||
@@ -603,8 +518,8 @@ style attribute {
|
|||||||
.dark .ant-table-expanded-row:hover,
|
.dark .ant-table-expanded-row:hover,
|
||||||
.dark .ant-table-expanded-row .ant-table-tbody,
|
.dark .ant-table-expanded-row .ant-table-tbody,
|
||||||
.dark .ant-calendar {
|
.dark .ant-calendar {
|
||||||
background-color: var(--dark-color-background);
|
background-color: #101828;
|
||||||
color: var(--dark-color-text-primary);
|
color: rgb(255 255 255 /65%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-expanded-row .ant-table-thead > tr:first-child > th {
|
.dark .ant-table-expanded-row .ant-table-thead > tr:first-child > th {
|
||||||
@@ -613,7 +528,7 @@ style attribute {
|
|||||||
|
|
||||||
.dark .ant-calendar,
|
.dark .ant-calendar,
|
||||||
.dark .ant-card-bordered {
|
.dark .ant-card-bordered {
|
||||||
border-color: var(--dark-color-background);
|
border-color: #151f31;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-bordered,
|
.dark .ant-table-bordered,
|
||||||
@@ -625,7 +540,7 @@ style attribute {
|
|||||||
.dark .ant-table-bordered .ant-table-thead > tr:not(:last-child) > th,
|
.dark .ant-table-bordered .ant-table-thead > tr:not(:last-child) > th,
|
||||||
.dark .ant-table-bordered .ant-table-tbody > tr > td,
|
.dark .ant-table-bordered .ant-table-tbody > tr > td,
|
||||||
.dark .ant-table-bordered .ant-table-thead > tr > th {
|
.dark .ant-table-bordered .ant-table-thead > tr > th {
|
||||||
border-color: var(--dark-color-surface-400);
|
border-color: #2c3950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-tbody > tr > td,
|
.dark .ant-table-tbody > tr > td,
|
||||||
@@ -638,7 +553,7 @@ style attribute {
|
|||||||
.dark .ant-popover-title,
|
.dark .ant-popover-title,
|
||||||
.dark .ant-calendar-header,
|
.dark .ant-calendar-header,
|
||||||
.dark .ant-calendar-input-wrap {
|
.dark .ant-calendar-input-wrap {
|
||||||
border-bottom-color: var(--dark-color-surface-400);
|
border-bottom-color: #2c3950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-modal-footer,
|
.dark .ant-modal-footer,
|
||||||
@@ -646,7 +561,7 @@ style attribute {
|
|||||||
.dark .ant-calendar-footer,
|
.dark .ant-calendar-footer,
|
||||||
.dark .ant-divider-horizontal.ant-divider-with-text-center:before,
|
.dark .ant-divider-horizontal.ant-divider-with-text-center:before,
|
||||||
.dark .ant-divider-horizontal.ant-divider-with-text-center:after {
|
.dark .ant-divider-horizontal.ant-divider-with-text-center:after {
|
||||||
border-top-color: var(--dark-color-surface-300);
|
border-top-color: #2c3950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-progress-text,
|
.dark .ant-progress-text,
|
||||||
@@ -682,7 +597,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: var(--dark-color-text-primary);
|
color: rgba(255, 255, 255, 0.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-list-item-meta-description {
|
.dark .ant-list-item-meta-description {
|
||||||
@@ -708,12 +623,13 @@ style attribute {
|
|||||||
.dark .ant-select-dropdown li,
|
.dark .ant-select-dropdown li,
|
||||||
.dark .ant-select-dropdown-menu-item,
|
.dark .ant-select-dropdown-menu-item,
|
||||||
.dark .ant-divider:not(.ant-divider-with-text-center),
|
.dark .ant-divider:not(.ant-divider-with-text-center),
|
||||||
|
.dark .ant-calendar-input,
|
||||||
.dark .client-table-header,
|
.dark .client-table-header,
|
||||||
.dark .ant-select-selection--multiple .ant-select-selection__choice,
|
.dark .ant-select-selection--multiple .ant-select-selection__choice,
|
||||||
.dark .ant-calendar-time-picker-inner {
|
.dark .ant-calendar-time-picker-inner {
|
||||||
background-color: var(--dark-color-surface-200);
|
background-color: #222d42;
|
||||||
border-color: var(--dark-color-surface-300);
|
border-color: #2c3950;
|
||||||
color: var(--dark-color-text-primary);
|
color: rgba(255, 255, 255, 0.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-select-selection:hover,
|
.dark .ant-select-selection:hover,
|
||||||
@@ -723,34 +639,34 @@ 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: var(--color-primary-100);
|
border-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) {
|
.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) {
|
||||||
color: var(--dark-color-text-primary);
|
color: rgba(255, 255, 255, 0.65);
|
||||||
background-color: rgb(10 117 87 / 30%);
|
background-color: rgb(10 117 87 / 30%);
|
||||||
border: 1px solid var(--color-primary-100);
|
border: 1px solid #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-radio-button-wrapper,
|
.dark .ant-radio-button-wrapper,
|
||||||
.dark .ant-radio-button-wrapper:before {
|
.dark .ant-radio-button-wrapper:before {
|
||||||
color: var(--dark-color-text-primary);
|
color: rgb(255 255 255 / 65%);
|
||||||
background-color: rgba(0, 135, 113, 0.3);
|
background-color: rgba(0, 135, 113, 0.3);
|
||||||
border-color: var(--color-primary-100);
|
border-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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: var(--color-primary-100);
|
border-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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: #2c3950;
|
||||||
border-color: #42516c;
|
border-color: #42516c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,7 +675,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: var(--dark-color-table-hover);
|
background-color: #00877122;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-row-expand-icon {
|
.dark .ant-table-row-expand-icon {
|
||||||
@@ -769,37 +685,38 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-row-expand-icon:hover {
|
.dark .ant-table-row-expand-icon:hover {
|
||||||
color: var(--color-primary-100);
|
color: #008771;
|
||||||
background-color: #fff0;
|
background-color: #fff0;
|
||||||
border-color: var(--color-primary-100);
|
border-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-switch:not(.ant-switch-checked),
|
.dark .ant-switch:not(.ant-switch-checked),
|
||||||
.dark .ant-progress-line .ant-progress-inner {
|
.dark .ant-progress-line .ant-progress-inner {
|
||||||
background-color: var(--dark-color-surface-500);
|
background-color: #2c3950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-progress-circle-trail {
|
.dark .ant-progress-circle-trail {
|
||||||
stroke: var(--dark-color-stroke) !important;
|
stroke: #2c3950 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-dark,
|
||||||
.dark .ant-popover-inner {
|
.dark .ant-popover-inner {
|
||||||
background-color: var(--dark-color-surface-500);
|
background-color: #222d42;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark > .ant-popover-content > .ant-popover-arrow {
|
.dark > .ant-popover-content > .ant-popover-arrow {
|
||||||
border-color: var(--dark-color-surface-500);
|
border-color: #222d42;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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-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: #313f5a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-dark .ant-menu-item:hover {
|
.ant-menu-dark .ant-menu-item:hover {
|
||||||
background-color: var(--dark-color-surface-300);
|
background-color: #2c3950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-alert-message {
|
.dark .ant-alert-message {
|
||||||
@@ -807,61 +724,61 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag {
|
.dark .ant-tag {
|
||||||
color: var(--dark-color-tag-color);
|
color: rgba(255, 255, 255, 0.65);
|
||||||
background-color: var(--dark-color-tag-bg);
|
background-color: #ffffff0a;
|
||||||
border-color: var(--dark-color-tag-border);
|
border-color: #344461;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-blue {
|
.dark .ant-tag-blue {
|
||||||
background-color: var(--dark-color-tag-blue-bg);
|
background-color: #111a2c;
|
||||||
border-color: var(--dark-color-tag-blue-border);
|
border-color: #0f367e;
|
||||||
color: var(--dark-color-tag-blue-color);
|
color: #3c89e8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-red,
|
.dark .ant-tag-red,
|
||||||
.dark .ant-alert-error {
|
.dark .ant-alert-error {
|
||||||
background-color: var(--dark-color-tag-red-bg);
|
background-color: #291515;
|
||||||
border-color: var(--dark-color-tag-red-border);
|
border-color: #5c2626;
|
||||||
color: var(--dark-color-tag-red-color);
|
color: #e04141;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-orange,
|
.dark .ant-tag-orange,
|
||||||
.dark .ant-alert-warning {
|
.dark .ant-alert-warning {
|
||||||
background-color: var(--dark-color-tag-orange-bg);
|
background-color: #312313;
|
||||||
border-color: var(--dark-color-tag-orange-border);
|
border-color: #593914;
|
||||||
color: var(--dark-color-tag-orange-color);
|
color: #ffa031;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-green {
|
.dark .ant-tag-green {
|
||||||
background-color: var(--dark-color-tag-green-bg);
|
background-color: #112421;
|
||||||
border-color: var(--dark-color-tag-green-border);
|
border-color: #144840;
|
||||||
color: var(--dark-color-tag-green-color);
|
color: #33bca5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-purple {
|
.dark .ant-tag-purple {
|
||||||
background-color: var(--dark-color-tag-purple-bg);
|
background-color: #2c1e32;
|
||||||
border-color: var(--dark-color-tag-purple-border);
|
border-color: #49394e;
|
||||||
color: var(--dark-color-tag-purple-color);
|
color: #cfb9cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-modal-content,
|
.dark .ant-modal-content,
|
||||||
.dark .ant-modal-header {
|
.dark .ant-modal-header {
|
||||||
background-color: var(--dark-color-surface-700);
|
background-color: #181f2c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-next-month-btn-day .ant-calendar-date,
|
.dark .ant-calendar-next-month-btn-day .ant-calendar-date,
|
||||||
.dark .ant-calendar-last-month-cell .ant-calendar-date {
|
.dark .ant-calendar-last-month-cell .ant-calendar-date {
|
||||||
color: var(--dark-color-surface-300);
|
color: #2c3950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-selected-day .ant-calendar-date {
|
.dark .ant-calendar-selected-day .ant-calendar-date {
|
||||||
background-color: var(--color-primary-100) !important;
|
background-color: #008771 !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-600);
|
background-color: #313f5a;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,15 +792,13 @@ style attribute {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: var(--color-primary-100);
|
background-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-time-picker-select {
|
.dark .ant-calendar-time-picker-select {
|
||||||
border-right-color: var(--dark-color-surface-300);
|
border-right-color: #2c3950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
||||||
@@ -898,8 +813,6 @@ 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;
|
||||||
@@ -915,7 +828,7 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .has-success .anticon {
|
.dark .has-success .anticon {
|
||||||
color: var(--color-primary-100);
|
color: #61bf39;
|
||||||
animation-name: diffZoomIn1 !important;
|
animation-name: diffZoomIn1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -961,19 +874,19 @@ style attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-calendar-today .ant-calendar-date {
|
.ant-calendar-today .ant-calendar-date {
|
||||||
color: var(--color-primary-100);
|
color: #008771;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
border-color: var(--color-primary-100);
|
border-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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: var(--color-primary-100);
|
border-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-calendar-selected-day .ant-calendar-date {
|
.ant-calendar-selected-day .ant-calendar-date {
|
||||||
background: var(--color-primary-100);
|
background: #008771;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1007,7 +920,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: var(--color-primary-100);
|
color: #008771;
|
||||||
}
|
}
|
||||||
.ant-select-selection:hover,
|
.ant-select-selection:hover,
|
||||||
.ant-input-number-focused,
|
.ant-input-number-focused,
|
||||||
@@ -1016,7 +929,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-input-number-handler:active {
|
.dark .ant-input-number-handler:active {
|
||||||
background-color: var(--color-primary-100);
|
background-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-input-number-handler:hover .ant-input-number-handler-down-inner,
|
.dark .ant-input-number-handler:hover .ant-input-number-handler-down-inner,
|
||||||
@@ -1044,7 +957,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-year-panel-header {
|
.dark .ant-calendar-year-panel-header {
|
||||||
border-bottom: 1px solid var(--dark-color-surface-200);
|
border-bottom: 1px solid #222d42;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-year-panel-last-decade-cell .ant-calendar-year-panel-year,
|
.dark .ant-calendar-year-panel-last-decade-cell .ant-calendar-year-panel-year,
|
||||||
@@ -1052,11 +965,10 @@ 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-200);
|
background-color: #222d42;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-header a:hover {
|
.dark .ant-calendar-header a:hover {
|
||||||
@@ -1064,13 +976,13 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-month-panel-header {
|
.dark .ant-calendar-month-panel-header {
|
||||||
background-color: var(--dark-color-background);
|
background-color: #101828;
|
||||||
border-bottom: 1px solid var(--dark-color-surface-200);
|
border-bottom: 1px solid #222d42;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-year-panel,
|
.dark .ant-calendar-year-panel,
|
||||||
.dark .ant-calendar table {
|
.dark .ant-calendar table {
|
||||||
background-color: var(--dark-color-background);
|
background-color: #101828;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year,
|
.dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year,
|
||||||
@@ -1088,7 +1000,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: var(--color-primary-100);
|
background-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-last-month-cell .ant-calendar-date,
|
.dark .ant-calendar-last-month-cell .ant-calendar-date,
|
||||||
@@ -1102,8 +1014,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: var(--color-primary-100);
|
border-color: #008771;
|
||||||
background-color: var(--color-primary-100);
|
background-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark
|
.dark
|
||||||
@@ -1116,8 +1028,8 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-decade-panel-header {
|
.dark .ant-calendar-decade-panel-header {
|
||||||
border-bottom: 1px solid var(--dark-color-surface-200);
|
border-bottom: 1px solid #222d42;
|
||||||
background-color: var(--dark-color-background);
|
background-color: #101828;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-checkbox-inner {
|
.dark .ant-checkbox-inner {
|
||||||
@@ -1126,30 +1038,24 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-checkbox-checked .ant-checkbox-inner {
|
.dark .ant-checkbox-checked .ant-checkbox-inner {
|
||||||
background-color: var(--color-primary-100);
|
background-color: #008771;
|
||||||
border-color: var(--color-primary-100);
|
border-color: #008771;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-input {
|
.dark .ant-calendar-input {
|
||||||
background-color: var(--dark-color-background);
|
background-color: #101828;
|
||||||
color: var(--dark-color-text-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-input::placeholder {
|
.dark .ant-calendar-input::placeholder {
|
||||||
color: rgba(255, 255, 255, 0.25);
|
color: rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(
|
|
||||||
:last-child
|
|
||||||
),
|
|
||||||
.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(
|
|
||||||
:last-child
|
|
||||||
),
|
|
||||||
.ant-input-group.ant-input-group-compact
|
|
||||||
> .ant-input:not(:first-child):not(:last-child),
|
|
||||||
.ant-input-number-handler,
|
|
||||||
.ant-input-number-handler-wrap {
|
.ant-input-number-handler-wrap {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-number-handler {
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-number {
|
.ant-input-number {
|
||||||
@@ -1183,15 +1089,15 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
> td,
|
> td,
|
||||||
.ant-table-thead
|
.ant-table-thead
|
||||||
> 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 {
|
||||||
.ant-calendar-time-picker-select li:hover {
|
|
||||||
background-color: rgb(232 244 242);
|
background-color: rgb(232 244 242);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .ant-dropdown-menu-item:hover,
|
||||||
.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-600);
|
background-color: #313f5a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-select-dropdown,
|
.ant-select-dropdown,
|
||||||
@@ -1204,8 +1110,6 @@ li.ant-select-dropdown-menu-item:empty:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.qr-bg {
|
.qr-bg {
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -1214,38 +1118,6 @@ 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-addon:not(:first-child):not(:last-child), .ant-input-group-wrap:not(:first-child):not(:last-child), .ant-input-group>.ant-input:not(:first-child):not(:last-child) {
|
||||||
border-radius: 0rem 1rem 1rem 0rem;
|
border-radius: 0rem 1rem 1rem 0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tag {
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
b, strong {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-collapse>.ant-collapse-item>.ant-collapse-header {
|
|
||||||
padding: 10px 16px 10px 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .ant-message-notice-content {
|
|
||||||
background-color: var(--dark-color-surface-200);
|
|
||||||
border: 1px solid var(--dark-color-surface-300);
|
|
||||||
color: var(--dark-color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-btn-danger {
|
|
||||||
background-color: var(--dark-color-btn-danger);
|
|
||||||
border-color: var(--dark-color-btn-danger-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-btn-danger:focus, .ant-btn-danger:hover {
|
|
||||||
background-color: var(--dark-color-btn-danger-hover);
|
|
||||||
border-color: var(--dark-color-btn-danger-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .ant-alert-close-icon .anticon-close:hover {
|
|
||||||
color: rgb(255 255 255);
|
|
||||||
}
|
|
||||||
|
|||||||
BIN
web/assets/favicon.ico
Normal file
BIN
web/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -29,16 +29,6 @@ const supportLangs = [
|
|||||||
value: 'es-ES',
|
value: 'es-ES',
|
||||||
icon: '🇪🇸',
|
icon: '🇪🇸',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Indonesian',
|
|
||||||
value: 'id-ID',
|
|
||||||
icon: '🇮🇩',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Український',
|
|
||||||
value: 'uk-UA',
|
|
||||||
icon: '🇺🇦',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function getLang() {
|
function getLang() {
|
||||||
|
|||||||
@@ -418,7 +418,7 @@ class Outbound extends CommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
|
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
||||||
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
|
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -861,13 +861,13 @@ Outbound.SocksSettings = class extends CommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
let servers = json.servers;
|
servers = json.servers;
|
||||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
return new Outbound.SocksSettings(
|
return new Outbound.SocksSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
|
ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -891,13 +891,13 @@ Outbound.HttpSettings = class extends CommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
let servers = json.servers;
|
servers = json.servers;
|
||||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
return new Outbound.HttpSettings(
|
return new Outbound.HttpSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
|
ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,8 +914,8 @@ Outbound.HttpSettings = class extends CommonClass {
|
|||||||
|
|
||||||
Outbound.WireguardSettings = class extends CommonClass {
|
Outbound.WireguardSettings = class extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
mtu=1420, secretKey='',
|
mtu=1420, secretKey=Wireguard.generateKeypair().privateKey,
|
||||||
address=[''], workers=2, domainStrategy='', reserved='',
|
address=[''], workers=2, domainStrategy='ForceIPv6v4', reserved='',
|
||||||
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
|
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
|
||||||
super();
|
super();
|
||||||
this.mtu = mtu;
|
this.mtu = mtu;
|
||||||
@@ -957,7 +957,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
|||||||
address: this.address ? this.address.split(",") : [],
|
address: this.address ? this.address.split(",") : [],
|
||||||
workers: this.workers?? undefined,
|
workers: this.workers?? undefined,
|
||||||
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
|
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
|
||||||
reserved: this.reserved ? this.reserved.split(",").map(Number) : undefined,
|
reserved: this.reserved ? this.reserved.split(",") : undefined,
|
||||||
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
||||||
kernelMode: this.kernelMode,
|
kernelMode: this.kernelMode,
|
||||||
};
|
};
|
||||||
@@ -965,7 +965,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Outbound.WireguardSettings.Peer = class extends CommonClass {
|
Outbound.WireguardSettings.Peer = class extends CommonClass {
|
||||||
constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
|
constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
|
||||||
super();
|
super();
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.psk = psk;
|
this.psk = psk;
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class AllSetting {
|
|||||||
this.subListen = "";
|
this.subListen = "";
|
||||||
this.subPort = "2096";
|
this.subPort = "2096";
|
||||||
this.subPath = "/sub/";
|
this.subPath = "/sub/";
|
||||||
this.subJsonPath = "/json/";
|
|
||||||
this.subDomain = "";
|
this.subDomain = "";
|
||||||
this.subCertFile = "";
|
this.subCertFile = "";
|
||||||
this.subKeyFile = "";
|
this.subKeyFile = "";
|
||||||
@@ -36,8 +35,6 @@ class AllSetting {
|
|||||||
this.subEncrypt = true;
|
this.subEncrypt = true;
|
||||||
this.subShowInfo = false;
|
this.subShowInfo = false;
|
||||||
this.subURI = '';
|
this.subURI = '';
|
||||||
this.subJsonURI = '';
|
|
||||||
this.subJsonFragment = '';
|
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
|
|||||||
@@ -1146,6 +1146,10 @@ class Inbound extends XrayCommonClass {
|
|||||||
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canSniffing() {
|
||||||
|
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.port = RandomUtil.randomIntRange(10000, 60000);
|
this.port = RandomUtil.randomIntRange(10000, 60000);
|
||||||
this.listen = '';
|
this.listen = '';
|
||||||
@@ -1530,28 +1534,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
getWireguardLink(address, port, remark, peerId) {
|
|
||||||
let txt = `[Interface]\n`
|
|
||||||
txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n`
|
|
||||||
txt += `Address = ${this.settings.peers[peerId].allowedIPs[0]}\n`
|
|
||||||
txt += `DNS = 1.1.1.1, 1.0.0.1\n`
|
|
||||||
if (this.settings.mtu) {
|
|
||||||
txt += `MTU = ${this.settings.mtu}\n`
|
|
||||||
}
|
|
||||||
txt += `\n# ${remark}\n`
|
|
||||||
txt += `[Peer]\n`
|
|
||||||
txt += `PublicKey = ${this.settings.pubKey}\n`
|
|
||||||
txt += `AllowedIPs = 0.0.0.0/0, ::/0\n`
|
|
||||||
txt += `Endpoint = ${address}:${port}`
|
|
||||||
if (this.settings.peers[peerId].psk) {
|
|
||||||
txt += `\nPresharedKey = ${this.settings.peers[peerId].psk}`
|
|
||||||
}
|
|
||||||
if (this.settings.peers[peerId].keepAlive) {
|
|
||||||
txt += `\nPersistentKeepalive = ${this.settings.peers[peerId].keepAlive}\n`
|
|
||||||
}
|
|
||||||
return txt;
|
|
||||||
}
|
|
||||||
|
|
||||||
genLink(address='', port=this.port, forceTls='same', remark='', client) {
|
genLink(address='', port=this.port, forceTls='same', remark='', client) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
@@ -1575,7 +1557,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
const orderChars = remarkModel.slice(1);
|
const orderChars = remarkModel.slice(1);
|
||||||
let orders = {
|
let orders = {
|
||||||
'i': remark,
|
'i': remark,
|
||||||
'e': email,
|
'e': client ? client.email : '',
|
||||||
'o': '',
|
'o': '',
|
||||||
};
|
};
|
||||||
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
|
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
|
||||||
@@ -1598,7 +1580,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
genInboundLinks(remark = '', remarkModel = '-ieo') {
|
genInboundLinks(remark = '', remarkModel = '-ieo') {
|
||||||
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
|
|
||||||
if(this.clients){
|
if(this.clients){
|
||||||
let links = [];
|
let links = [];
|
||||||
this.clients.forEach((client) => {
|
this.clients.forEach((client) => {
|
||||||
@@ -1608,14 +1589,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
});
|
});
|
||||||
return links.join('\r\n');
|
return links.join('\r\n');
|
||||||
} else {
|
} else {
|
||||||
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(addr, this.port, 'same', remark);
|
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(this.listen, this.port, 'same', remark);
|
||||||
if(this.protocol == Protocols.WIREGUARD) {
|
|
||||||
let links = [];
|
|
||||||
this.settings.peers.forEach((p,index) => {
|
|
||||||
links.push(this.getWireguardLink(addr,this.port,remark + remarkModel.charAt(0) + (index+1),index));
|
|
||||||
});
|
|
||||||
return links.join('\r\n');
|
|
||||||
}
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2295,7 +2269,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addPeer() {
|
addPeer() {
|
||||||
this.peers.push(new Inbound.WireguardSettings.Peer(null,null,'',['10.0.0.' + (this.peers.length+2)]));
|
this.peers.push(new Inbound.WireguardSettings.Peer());
|
||||||
}
|
}
|
||||||
|
|
||||||
delPeer(index) {
|
delPeer(index) {
|
||||||
@@ -2323,24 +2297,16 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
||||||
constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.2/32'], keepAlive=0) {
|
constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], keepAlive=0) {
|
||||||
super();
|
super();
|
||||||
this.privateKey = privateKey
|
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
if (!this.publicKey){
|
|
||||||
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
|
|
||||||
}
|
|
||||||
this.psk = psk;
|
this.psk = psk;
|
||||||
allowedIPs.forEach((a,index) => {
|
|
||||||
if (a.length>0 && !a.includes('/')) allowedIPs[index] += '/32';
|
|
||||||
})
|
|
||||||
this.allowedIPs = allowedIPs;
|
this.allowedIPs = allowedIPs;
|
||||||
this.keepAlive = keepAlive;
|
this.keepAlive = keepAlive;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}){
|
static fromJson(json={}){
|
||||||
return new Inbound.WireguardSettings.Peer(
|
return new Inbound.WireguardSettings.Peer(
|
||||||
json.privateKey,
|
|
||||||
json.publicKey,
|
json.publicKey,
|
||||||
json.preSharedKey,
|
json.preSharedKey,
|
||||||
json.allowedIPs,
|
json.allowedIPs,
|
||||||
@@ -2349,11 +2315,7 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
this.allowedIPs.forEach((a,index) => {
|
|
||||||
if (a.length>0 && !a.includes('/')) this.allowedIPs[index] += '/32';
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
privateKey: this.privateKey,
|
|
||||||
publicKey: this.publicKey,
|
publicKey: this.publicKey,
|
||||||
preSharedKey: this.psk.length>0 ? this.psk : undefined,
|
preSharedKey: this.psk.length>0 ? this.psk : undefined,
|
||||||
allowedIPs: this.allowedIPs,
|
allowedIPs: this.allowedIPs,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -86,7 +86,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
inbound.UserId = user.Id
|
inbound.UserId = user.Id
|
||||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
|
||||||
} else {
|
} else {
|
||||||
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
||||||
}
|
}
|
||||||
@@ -283,7 +283,7 @@ func (a *InboundController) importInbound(c *gin.Context) {
|
|||||||
inbound.Id = 0
|
inbound.Id = 0
|
||||||
inbound.UserId = user.Id
|
inbound.UserId = user.Id
|
||||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
|
||||||
} else {
|
} else {
|
||||||
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ type XraySettingController struct {
|
|||||||
XraySettingService service.XraySettingService
|
XraySettingService service.XraySettingService
|
||||||
SettingService service.SettingService
|
SettingService service.SettingService
|
||||||
InboundService service.InboundService
|
InboundService service.InboundService
|
||||||
OutboundService service.OutboundService
|
|
||||||
XrayService service.XrayService
|
XrayService service.XrayService
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,8 +27,6 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.GET("/getXrayResult", a.getXrayResult)
|
g.GET("/getXrayResult", a.getXrayResult)
|
||||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
g.POST("/warp/:action", a.warp)
|
g.POST("/warp/:action", a.warp)
|
||||||
g.GET("/getOutboundsTraffic", a.getOutboundsTraffic)
|
|
||||||
g.POST("/resetOutboundsTraffic", a.resetOutboundsTraffic)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
||||||
@@ -81,27 +78,9 @@ func (a *XraySettingController) warp(c *gin.Context) {
|
|||||||
resp, err = a.XraySettingService.RegWarp(skey, pkey)
|
resp, err = a.XraySettingService.RegWarp(skey, pkey)
|
||||||
case "license":
|
case "license":
|
||||||
license := c.PostForm("license")
|
license := c.PostForm("license")
|
||||||
|
println(license)
|
||||||
resp, err = a.XraySettingService.SetWarpLicence(license)
|
resp, err = a.XraySettingService.SetWarpLicence(license)
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonObj(c, resp, err)
|
jsonObj(c, resp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XraySettingController) getOutboundsTraffic(c *gin.Context) {
|
|
||||||
outboundsTraffic, err := a.OutboundService.GetOutboundsTraffic()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "Error getting traffics", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jsonObj(c, outboundsTraffic, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *XraySettingController) resetOutboundsTraffic(c *gin.Context) {
|
|
||||||
tag := c.PostForm("tag")
|
|
||||||
err := a.OutboundService.ResetOutboundTraffic(tag)
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "Error in reset outbound traffics", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jsonObj(c, "", nil)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -48,9 +48,6 @@ type AllSetting struct {
|
|||||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||||
SubURI string `json:"subURI" form:"subURI"`
|
SubURI string `json:"subURI" form:"subURI"`
|
||||||
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
|
|
||||||
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
|
|
||||||
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
|
|
||||||
Datepicker string `json:"datepicker" form:"datepicker"`
|
Datepicker string `json:"datepicker" form:"datepicker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,13 +105,6 @@ func (s *AllSetting) CheckValid() error {
|
|||||||
s.SubPath += "/"
|
s.SubPath += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(s.SubJsonPath, "/") {
|
|
||||||
s.SubJsonPath = "/" + s.SubJsonPath
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(s.SubJsonPath, "/") {
|
|
||||||
s.SubJsonPath += "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := time.LoadLocation(s.TimeLocation)
|
_, err := time.LoadLocation(s.TimeLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewError("time location not exist:", s.TimeLocation)
|
return common.NewError("time location not exist:", s.TimeLocation)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
||||||
|
<link rel=”icon” type=”image/x-icon” href="{{ .base_path }}assets/favicon.ico">
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="{{ .base_path }}assets/favicon.ico">
|
||||||
<style>
|
<style>
|
||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -28,5 +30,4 @@
|
|||||||
</style>
|
</style>
|
||||||
<title>{{ .host }}-{{ i18n .title}}</title>
|
<title>{{ .host }}-{{ i18n .title}}</title>
|
||||||
</head>
|
</head>
|
||||||
<div id="message"></div>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{{define "promptModal"}}
|
{{define "promptModal"}}
|
||||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||||
:confirm-loading="promptModal.confirmLoading"
|
|
||||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||||
v-model="promptModal.value"
|
v-model="promptModal.value"
|
||||||
@@ -18,7 +17,6 @@
|
|||||||
value: '',
|
value: '',
|
||||||
okText: '{{ i18n "sure"}}',
|
okText: '{{ i18n "sure"}}',
|
||||||
visible: false,
|
visible: false,
|
||||||
confirmLoading: false,
|
|
||||||
keyEnter(e) {
|
keyEnter(e) {
|
||||||
if (this.type !== 'textarea') {
|
if (this.type !== 'textarea') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -32,6 +30,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ok() {
|
ok() {
|
||||||
|
promptModal.close();
|
||||||
promptModal.confirm(promptModal.value);
|
promptModal.confirm(promptModal.value);
|
||||||
},
|
},
|
||||||
confirm() {},
|
confirm() {},
|
||||||
@@ -54,10 +53,7 @@
|
|||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
},
|
}
|
||||||
loading(loading=true) {
|
|
||||||
this.confirmLoading = loading;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const promptModalApp = new Vue({
|
const promptModalApp = new Vue({
|
||||||
|
|||||||
@@ -8,23 +8,13 @@
|
|||||||
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<template v-if="app.subSettings.enable && qrModal.subId">
|
<template v-if="app.subSettings.enable && qrModal.subId">
|
||||||
<a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
|
<a-divider>Subscription</a-divider>
|
||||||
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
|
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas></div>
|
||||||
id="qrCode-sub"
|
|
||||||
class="qr-bg">
|
|
||||||
</canvas>
|
|
||||||
<a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider>
|
|
||||||
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))"
|
|
||||||
id="qrCode-subJson"
|
|
||||||
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
|
|
||||||
</canvas>
|
|
||||||
</template>
|
</template>
|
||||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
<template v-for="(row, index) in qrModal.qrcodes">
|
<template v-for="(row, index) in qrModal.qrcodes">
|
||||||
<a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
|
<a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
|
||||||
<canvas @click="copyToClipboard('qrCode-'+index, row.link)"
|
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas></div>
|
||||||
:id="'qrCode-'+index"
|
|
||||||
class="qr-bg"></canvas>
|
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
@@ -45,21 +35,12 @@
|
|||||||
this.client = client;
|
this.client = client;
|
||||||
this.subId = '';
|
this.subId = '';
|
||||||
this.qrcodes = [];
|
this.qrcodes = [];
|
||||||
if (this.inbound.protocol == Protocols.WIREGUARD){
|
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
|
||||||
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l,index) =>{
|
this.qrcodes.push({
|
||||||
this.qrcodes.push({
|
remark: l.remark,
|
||||||
remark: "Peer " + (index+1),
|
link: l.link
|
||||||
link: l
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
});
|
||||||
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
|
|
||||||
this.qrcodes.push({
|
|
||||||
remark: l.remark,
|
|
||||||
link: l.link
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function () {
|
||||||
@@ -92,16 +73,12 @@
|
|||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
return app.subSettings.subURI+subID;
|
return app.subSettings.subURI+subID;
|
||||||
},
|
|
||||||
genSubJsonLink(subID) {
|
|
||||||
return app.subSettings.subJsonURI+subID;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
if (qrModal.client && qrModal.client.subId) {
|
if (qrModal.client && qrModal.client.subId) {
|
||||||
qrModal.subId = qrModal.client.subId;
|
qrModal.subId = qrModal.client.subId;
|
||||||
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
|
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
|
||||||
this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
|
|
||||||
}
|
}
|
||||||
qrModal.qrcodes.forEach((element, index) => {
|
qrModal.qrcodes.forEach((element, index) => {
|
||||||
this.setQrCode("qrCode-" + index, element.link);
|
this.setQrCode("qrCode-" + index, element.link);
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
{{define "textModal"}}
|
{{define "textModal"}}
|
||||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
||||||
:closable="true"
|
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
||||||
:class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme"
|
||||||
<template slot="footer">
|
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
||||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" icon="download"
|
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||||
:download="txtModal.fileName">[[ txtModal.fileName ]]
|
:download="txtModal.fileName">
|
||||||
</a-button>
|
{{ i18n "download" }} [[ txtModal.fileName ]]
|
||||||
<a-button type="primary" id="copy-btn">{{ i18n "copy" }}</a-button>
|
</a-button>
|
||||||
</template>
|
<a-input type="textarea" v-model="txtModal.content"
|
||||||
<a-input style="overflow-y: auto;" type="textarea" v-model="txtModal.content"
|
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
||||||
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -29,7 +28,7 @@
|
|||||||
this.visible = true;
|
this.visible = true;
|
||||||
textModalApp.$nextTick(() => {
|
textModalApp.$nextTick(() => {
|
||||||
if (this.clipboard === null) {
|
if (this.clipboard === null) {
|
||||||
this.clipboard = new ClipboardJS('#copy-btn', {
|
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
|
||||||
text: () => this.content,
|
text: () => this.content,
|
||||||
});
|
});
|
||||||
this.clipboard.on('success', () => {
|
this.clipboard.on('success', () => {
|
||||||
@@ -53,4 +52,4 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -2,14 +2,9 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
<style>
|
<style>
|
||||||
html * {
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
/* margin: 20px 0 50px 0;*/
|
margin: 20px 0 50px 0;
|
||||||
height: 110px;
|
|
||||||
}
|
}
|
||||||
.ant-btn,
|
.ant-btn,
|
||||||
.ant-input {
|
.ant-input {
|
||||||
@@ -36,9 +31,7 @@
|
|||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
}
|
font-weight: bold;
|
||||||
.title b {
|
|
||||||
font-weight: bold !important;
|
|
||||||
}
|
}
|
||||||
#app {
|
#app {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -49,9 +42,6 @@
|
|||||||
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);
|
||||||
@@ -71,13 +61,13 @@
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
.dark .under {
|
.dark .under {
|
||||||
background-color: var(--dark-color-login-wave);
|
background-color: #0f2d32;
|
||||||
}
|
}
|
||||||
.dark #login {
|
.dark #login {
|
||||||
background-color: var(--dark-color-surface-100);
|
background-color: #151f31;
|
||||||
}
|
}
|
||||||
.dark h1 {
|
.dark h1 {
|
||||||
color: rgba(255, 255, 255);
|
color: rgba(255, 255, 255, 0.85);
|
||||||
}
|
}
|
||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@@ -202,7 +192,7 @@
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
.dark .waves-header {
|
.dark .waves-header {
|
||||||
background-color: var(--dark-color-login-background);
|
background-color: #101828;
|
||||||
}
|
}
|
||||||
.waves-inner-header {
|
.waves-inner-header {
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
@@ -214,7 +204,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 15vh;
|
height: 15vh;
|
||||||
margin-bottom: -8px; /*Fix for safari gap*/
|
margin-bottom: -5px; /*Fix for safari gap*/
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
}
|
}
|
||||||
@@ -222,27 +212,23 @@
|
|||||||
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: var(--dark-color-login-wave);
|
fill: rgb(10 117 87 / 20%);
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(1) {
|
.parallax > use:nth-child(1) {
|
||||||
animation-delay: -2s;
|
animation-delay: -2s;
|
||||||
animation-duration: 4s;
|
animation-duration: 7s;
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(2) {
|
.parallax > use:nth-child(2) {
|
||||||
animation-delay: -3s;
|
animation-delay: -3s;
|
||||||
animation-duration: 7s;
|
animation-duration: 10s;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(3) {
|
.parallax > use:nth-child(3) {
|
||||||
animation-delay: -4s;
|
animation-delay: -4s;
|
||||||
animation-duration: 10s;
|
animation-duration: 13s;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(4) {
|
|
||||||
animation-delay: -5s;
|
|
||||||
animation-duration: 13s;
|
|
||||||
}
|
|
||||||
@keyframes move-forever {
|
@keyframes move-forever {
|
||||||
0% {
|
0% {
|
||||||
transform: translate3d(-90px, 0, 0);
|
transform: translate3d(-90px, 0, 0);
|
||||||
@@ -257,225 +243,90 @@
|
|||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.words-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.words-wrapper b {
|
|
||||||
width: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
.words-wrapper b.is-visible {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.headline.zoom .words-wrapper {
|
|
||||||
-webkit-perspective: 300px;
|
|
||||||
-moz-perspective: 300px;
|
|
||||||
perspective: 300px;
|
|
||||||
}
|
|
||||||
.headline {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.headline.zoom b {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.headline.zoom b.is-visible {
|
|
||||||
opacity: 1;
|
|
||||||
-webkit-animation: zoom-in 0.8s;
|
|
||||||
-moz-animation: zoom-in 0.8s;
|
|
||||||
animation: cubic-bezier(0.215, 0.610, 0.355, 1.000) zoom-in 0.8s;
|
|
||||||
}
|
|
||||||
.headline.zoom b.is-hidden {
|
|
||||||
-webkit-animation: zoom-out 0.8s;
|
|
||||||
-moz-animation: zoom-out 0.8s;
|
|
||||||
animation: cubic-bezier(0.215, 0.610, 0.355, 1.000) zoom-out 0.4s;
|
|
||||||
}
|
|
||||||
@-webkit-keyframes zoom-in {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
-webkit-transform: translateZ(100px);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
-webkit-transform: translateZ(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@-moz-keyframes zoom-in {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
-moz-transform: translateZ(100px);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
-moz-transform: translateZ(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes zoom-in {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
-webkit-transform: translateZ(100px);
|
|
||||||
-moz-transform: translateZ(100px);
|
|
||||||
-ms-transform: translateZ(100px);
|
|
||||||
-o-transform: translateZ(100px);
|
|
||||||
transform: translateZ(100px);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
-webkit-transform: translateZ(0);
|
|
||||||
-moz-transform: translateZ(0);
|
|
||||||
-ms-transform: translateZ(0);
|
|
||||||
-o-transform: translateZ(0);
|
|
||||||
transform: translateZ(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@-webkit-keyframes zoom-out {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
-webkit-transform: translateZ(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
-webkit-transform: translateZ(-100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@-moz-keyframes zoom-out {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
-moz-transform: translateZ(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
-moz-transform: translateZ(-100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes zoom-out {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
-webkit-transform: translateZ(0);
|
|
||||||
-moz-transform: translateZ(0);
|
|
||||||
-ms-transform: translateZ(0);
|
|
||||||
-o-transform: translateZ(0);
|
|
||||||
transform: translateZ(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
-webkit-transform: translateZ(-100px);
|
|
||||||
-moz-transform: translateZ(-100px);
|
|
||||||
-ms-transform: translateZ(-100px);
|
|
||||||
-o-transform: translateZ(-100px);
|
|
||||||
transform: translateZ(-100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</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"
|
<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">
|
||||||
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>
|
<g class="parallax">
|
||||||
<g class="parallax">
|
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(0, 135, 113, 0.08)" />
|
||||||
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(0, 135, 113, 0.08)" />
|
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(0, 135, 113, 0.08)" />
|
||||||
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(0, 135, 113, 0.08)" />
|
<use xlink:href="#gentle-wave" x="48" y="7" fill="rgba(0, 135, 113, 0.08)" />
|
||||||
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(0, 135, 113, 0.08)" />
|
</g>
|
||||||
<use xlink:href="#gentle-wave" x="48" y="7" fill="#c7ebe2" />
|
</svg>
|
||||||
</g>
|
</div>
|
||||||
</svg>
|
<a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto;">
|
||||||
</div>
|
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
|
||||||
<a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto;">
|
|
||||||
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
|
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
<a-col style="width: 100%;">
|
<a-col>
|
||||||
<h1 class="title headline zoom">
|
<h1 class="title">{{ i18n "pages.login.title" }}</h1>
|
||||||
<span class="words-wrapper">
|
</a-col>
|
||||||
<b class="is-visible">{{ i18n "pages.login.hello" }}</b>
|
|
||||||
<b>{{ i18n "pages.login.title" }}</b>
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
<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 v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
||||||
@keydown.enter.native="login" autofocus>
|
@keydown.enter.native="login" autofocus>
|
||||||
<a-icon slot="prefix" type="user" style="font-size: 16px;"></a-icon>
|
<a-icon slot="prefix" type="user" style="font-size: 16px;"/>
|
||||||
</a-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 icon="lock" v-model.trim="user.password"
|
||||||
placeholder='{{ i18n "password" }}'
|
placeholder='{{ i18n "password" }}' @keydown.enter.native="login">
|
||||||
@keydown.enter.native="login">
|
</password-input>
|
||||||
</password-input>
|
</a-form-item>
|
||||||
</a-form-item>
|
<a-form-item v-if="secretEnable">
|
||||||
<a-form-item v-if="secretEnable">
|
<password-input icon="key" v-model.trim="user.loginSecret"
|
||||||
<password-input icon="key" v-model.trim="user.loginSecret"
|
placeholder='{{ i18n "secretToken" }}' @keydown.enter.native="login">
|
||||||
placeholder='{{ i18n "secretToken" }}'
|
</password-input>
|
||||||
@keydown.enter.native="login">
|
</a-input>
|
||||||
</password-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: '54px' } : { 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' }">
|
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||||
<a-button class="ant-btn-primary-login" type="primary"
|
</a-button>
|
||||||
:loading="loading" @click="login"
|
</div>
|
||||||
:icon="loading ? 'poweroff' : undefined">
|
</a-row>
|
||||||
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
</a-form-item>
|
||||||
</a-button>
|
<a-form-item>
|
||||||
</div>
|
<a-row justify="center" class="centered">
|
||||||
</a-row>
|
<a-col :span="24">
|
||||||
</a-form-item>
|
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" style="width: 150px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-form-item>
|
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
||||||
<a-row justify="center" class="centered">
|
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||||
<a-col :span="24">
|
<span v-text="l.name"></span>
|
||||||
<a-select ref="selectLang" v-model="lang"
|
</a-select-option>
|
||||||
@change="setLang(lang)" style="width: 150px;"
|
</a-select>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
</a-col>
|
||||||
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
</a-row>
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
</a-form-item>
|
||||||
<span v-text="l.name"></span>
|
<a-form-item>
|
||||||
</a-select-option>
|
<a-row justify="center" class="centered">
|
||||||
</a-select>
|
<a-col>
|
||||||
</a-col>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
</a-row>
|
</a-col>
|
||||||
</a-form-item>
|
<a-col>
|
||||||
<a-form-item>
|
<theme-switch />
|
||||||
<a-row justify="center" class="centered">
|
</a-col>
|
||||||
<a-col>
|
</a-row>
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
</a-form-item>
|
||||||
</a-col>
|
</a-form>
|
||||||
<a-col>
|
</a-col>
|
||||||
<theme-switch></theme-switch>
|
|
||||||
<a-checkbox v-if="themeSwitcher.isDarkTheme" style="padding-left: 1rem; vertical-align: middle;"
|
|
||||||
:checked="themeSwitcher.isUltra"
|
|
||||||
@click="themeSwitcher.toggleUltra()">
|
|
||||||
Ultra
|
|
||||||
</a-checkbox>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-col>
|
</a-col>
|
||||||
</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" .}}
|
||||||
@@ -521,42 +372,6 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
var animationDelay = 2000;
|
|
||||||
initHeadline();
|
|
||||||
|
|
||||||
function initHeadline() {
|
|
||||||
animateHeadline(document.querySelectorAll('.headline'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function animateHeadline(headlines) {
|
|
||||||
var duration = animationDelay;
|
|
||||||
headlines.forEach(function(headline) {
|
|
||||||
setTimeout(function() {
|
|
||||||
hideWord(headline.querySelector('.is-visible'));
|
|
||||||
}, duration);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideWord(word) {
|
|
||||||
var nextWord = takeNext(word);
|
|
||||||
switchWord(word, nextWord);
|
|
||||||
setTimeout(function() {
|
|
||||||
hideWord(nextWord);
|
|
||||||
}, animationDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
function takeNext(word) {
|
|
||||||
return (word.nextElementSibling) ? word.nextElementSibling : word.parentElement.firstElementChild;
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchWord(oldWord, newWord) {
|
|
||||||
oldWord.classList.remove('is-visible');
|
|
||||||
oldWord.classList.add('is-hidden');
|
|
||||||
newWord.classList.remove('is-hidden');
|
|
||||||
newWord.classList.add('is-visible');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -220,7 +220,7 @@
|
|||||||
clientsBulkModal.visible = false;
|
clientsBulkModal.visible = false;
|
||||||
clientsBulkModal.loading(false);
|
clientsBulkModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading) {
|
||||||
clientsBulkModal.confirmLoading = loading;
|
clientsBulkModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
clientModal.visible = false;
|
clientModal.visible = false;
|
||||||
clientModal.loading(false);
|
clientModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading) {
|
||||||
clientModal.confirmLoading = loading;
|
clientModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
{{define "menuItems"}}
|
{{define "menuItems"}}
|
||||||
<a-menu-item key="{{ .base_path }}panel/">
|
<a-menu-item key="{{ .base_path }}panel/">
|
||||||
<a-icon type="dashboard"></a-icon>
|
<a-icon type="dashboard"></a-icon>
|
||||||
<span><b>{{ i18n "menu.dashboard"}}</b></span>
|
<span>{{ i18n "menu.dashboard"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}panel/inbounds">
|
<a-menu-item key="{{ .base_path }}panel/inbounds">
|
||||||
<a-icon type="user"></a-icon>
|
<a-icon type="user"></a-icon>
|
||||||
<span><b>{{ i18n "menu.inbounds"}}</b></span>
|
<span>{{ i18n "menu.inbounds"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}panel/settings">
|
<a-menu-item key="{{ .base_path }}panel/settings">
|
||||||
<a-icon type="setting"></a-icon>
|
<a-icon type="setting"></a-icon>
|
||||||
<span><b>{{ i18n "menu.settings"}}</b></span>
|
<span>{{ i18n "menu.settings"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}panel/xray">
|
<a-menu-item key="{{ .base_path }}panel/xray">
|
||||||
<a-icon type="tool"></a-icon>
|
<a-icon type="tool"></a-icon>
|
||||||
<span><b>{{ i18n "menu.xray"}}</b></span>
|
<span>{{ i18n "menu.xray"}}</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>{{ i18n "menu.logout"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
@@ -27,13 +31,7 @@
|
|||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
<theme-switch>
|
<theme-switch />
|
||||||
</theme-switch>
|
|
||||||
<a-checkbox v-if="themeSwitcher.isDarkTheme" style="padding-left: 1rem; vertical-align: middle;"
|
|
||||||
:checked="themeSwitcher.isUltra"
|
|
||||||
@click="themeSwitcher.toggleUltra()">
|
|
||||||
Ultra
|
|
||||||
</a-checkbox>
|
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||||
@@ -52,13 +50,7 @@
|
|||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
<theme-switch>
|
<theme-switch />
|
||||||
</theme-switch>
|
|
||||||
<a-checkbox v-if="themeSwitcher.isDarkTheme" style="padding-left: 1rem; vertical-align: middle;"
|
|
||||||
:checked="themeSwitcher.isUltra"
|
|
||||||
@click="themeSwitcher.toggleUltra()">
|
|
||||||
Ultra
|
|
||||||
</a-checkbox>
|
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
|
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
|
||||||
:placeholder="placeholder">
|
:placeholder="placeholder">
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
<a-icon type="calendar" style="font-size: 14px; opacity: 0.5;"/>
|
<a-icon type="calendar" style="font-size: 16px;"/>
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,4 +57,4 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
{{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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dragStopHandler(e, index) {
|
|
||||||
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: white !important;
|
|
||||||
}
|
|
||||||
.dark .ant-table-is-sorting .draggable-row td {
|
|
||||||
background-color: var(--dark-color-surface-100) !important;
|
|
||||||
}
|
|
||||||
.ant-table-is-sorting .dragging {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
.ant-table-is-sorting .dragging .ant-table-row-index {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{{end}}
|
|
||||||
@@ -10,48 +10,27 @@
|
|||||||
<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;
|
|
||||||
},
|
},
|
||||||
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: () => ({
|
data: () => ({ themeSwitcher }),
|
||||||
themeSwitcher
|
|
||||||
}),
|
|
||||||
mounted() {
|
|
||||||
this.$message.config({
|
|
||||||
getContainer: () => document.getElementById('message')
|
|
||||||
});
|
|
||||||
document.getElementById('message').className = themeSwitcher.currentTheme;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
{{define "dnsModal"}}
|
|
||||||
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
|
|
||||||
:closable="true" :mask-closable="false"
|
|
||||||
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
|
||||||
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
|
|
||||||
<a-button size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')">+</a-button>
|
|
||||||
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
|
|
||||||
<a-input v-model.trim="dnsModal.dnsServer.domains[index]">
|
|
||||||
<a-button size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)">-</a-button>
|
|
||||||
</a-input>
|
|
||||||
</template>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
|
|
||||||
<a-select
|
|
||||||
v-model="dnsModal.dnsServer.queryStrategy"
|
|
||||||
style="width: 100%"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
|
|
||||||
[[ l ]]
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</a-modal>
|
|
||||||
<script>
|
|
||||||
const dnsModal = {
|
|
||||||
title: '',
|
|
||||||
visible: false,
|
|
||||||
okText: '{{ i18n "confirm" }}',
|
|
||||||
isEdit: false,
|
|
||||||
confirm: null,
|
|
||||||
dnsServer: {
|
|
||||||
address: "localhost",
|
|
||||||
domains: [],
|
|
||||||
queryStrategy: 'UseIP',
|
|
||||||
},
|
|
||||||
ok() {
|
|
||||||
domains = dnsModal.dnsServer.domains.filter(d => d.length>0);
|
|
||||||
dnsModal.dnsServer.domains = domains;
|
|
||||||
newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address;
|
|
||||||
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
|
|
||||||
},
|
|
||||||
show({ title='', okText='{{ i18n "confirm" }}', dnsServer, confirm=(dnsServer)=>{}, isEdit=false }) {
|
|
||||||
this.title = title;
|
|
||||||
this.okText = okText;
|
|
||||||
this.confirm = confirm;
|
|
||||||
this.visible = true;
|
|
||||||
if(isEdit) {
|
|
||||||
if (typeof dnsServer == 'object'){
|
|
||||||
this.dnsServer = dnsServer;
|
|
||||||
} else {
|
|
||||||
this.dnsServer.address = dnsServer?? '';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.dnsServer = {
|
|
||||||
address: "localhost",
|
|
||||||
domains: [],
|
|
||||||
queryStrategy: 'UseIP',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.isEdit = isEdit;
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
dnsModal.visible = false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
delimiters: ['[[', ']]'],
|
|
||||||
el: '#dns-modal',
|
|
||||||
data: {
|
|
||||||
dnsModal: dnsModal,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isAdvanced: {
|
|
||||||
get: function () { return dnsModal.dnsServer.domains.length>0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{{end}}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
{{define "fakednsModal"}}
|
|
||||||
<a-modal id="fakedns-modal" v-model="fakednsModal.visible" :title="fakednsModal.title" @ok="fakednsModal.ok"
|
|
||||||
:closable="true" :mask-closable="false"
|
|
||||||
:ok-text="fakednsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.fakedns.ipPool" }}'>
|
|
||||||
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.fakedns.poolSize" }}'>
|
|
||||||
<a-input-number style="width: 100%;" type="number" min="1" v-model.trim="fakednsModal.fakeDns.poolSize"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</a-modal>
|
|
||||||
<script>
|
|
||||||
const fakednsModal = {
|
|
||||||
title: '',
|
|
||||||
visible: false,
|
|
||||||
okText: '{{ i18n "confirm" }}',
|
|
||||||
isEdit: false,
|
|
||||||
confirm: null,
|
|
||||||
fakeDns: {
|
|
||||||
ipPool: "198.18.0.0/16",
|
|
||||||
poolSize: 65535,
|
|
||||||
},
|
|
||||||
ok() {
|
|
||||||
ObjectUtil.execute(fakednsModal.confirm, fakednsModal.fakeDns);
|
|
||||||
},
|
|
||||||
show({ title='', okText='{{ i18n "confirm" }}', fakeDns, confirm=(fakeDns)=>{}, isEdit=false }) {
|
|
||||||
this.title = title;
|
|
||||||
this.okText = okText;
|
|
||||||
this.confirm = confirm;
|
|
||||||
this.visible = true;
|
|
||||||
if(isEdit) {
|
|
||||||
this.fakeDns = fakeDns;
|
|
||||||
} else {
|
|
||||||
this.fakeDns = {
|
|
||||||
ipPool: "198.18.0.0/16",
|
|
||||||
poolSize: 65535,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.isEdit = isEdit;
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
fakednsModal.visible = false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
delimiters: ['[[', ']]'],
|
|
||||||
el: '#fakedns-modal',
|
|
||||||
data: {
|
|
||||||
fakednsModal: fakednsModal,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{{end}}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "form/inbound"}}
|
{{define "form/inbound"}}
|
||||||
<!-- base -->
|
<!-- base -->
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ 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>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="dbInbound._expiryTime"></a-date-picker>
|
v-model="dbInbound._expiryTime"></a-date-picker>
|
||||||
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- sniffing -->
|
<!-- sniffing -->
|
||||||
<template>
|
<template v-if="inbound.canSniffing()">
|
||||||
{{template "form/sniffing"}}
|
{{template "form/sniffing"}}
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -134,10 +134,28 @@
|
|||||||
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
|
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
|
||||||
<a-input v-model.trim="peer.endpoint"></a-input>
|
<a-input v-model.trim="peer.endpoint"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "reset" }}</span>
|
||||||
|
</template>
|
||||||
|
{{ i18n "pages.xray.wireguard.publicKey" }}
|
||||||
|
<a-icon @click="peer.publicKey = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
<a-input v-model.trim="peer.publicKey"></a-input>
|
<a-input v-model.trim="peer.publicKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'>
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "reset" }}</span>
|
||||||
|
</template>
|
||||||
|
{{ i18n "pages.xray.wireguard.psk" }}
|
||||||
|
<a-icon @click="peer.psk = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
<a-input v-model.trim="peer.psk"></a-input>
|
<a-input v-model.trim="peer.psk"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -171,6 +189,7 @@
|
|||||||
<a-form-item label='ID'>
|
<a-form-item label='ID'>
|
||||||
<a-input v-model.trim="outbound.settings.id"></a-input>
|
<a-input v-model.trim="outbound.settings.id"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- vless settings -->
|
<!-- vless settings -->
|
||||||
<template v-if="outbound.canEnableTlsFlow()">
|
<template v-if="outbound.canEnableTlsFlow()">
|
||||||
<a-form-item label='Flow'>
|
<a-form-item label='Flow'>
|
||||||
@@ -193,23 +212,18 @@
|
|||||||
<a-input v-model.trim="outbound.settings.pass"></a-input>
|
<a-input v-model.trim="outbound.settings.pass"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<!-- trojan/shadowsocks -->
|
|
||||||
<template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
|
<!-- shadowsocks -->
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<template v-if="outbound.protocol === Protocols.Shadowsocks">
|
||||||
<a-input v-model.trim="outbound.settings.password"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
<!-- shadowsocks -->
|
|
||||||
<template v-if="outbound.protocol === Protocols.Shadowsocks">
|
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="outbound.settings.method" :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>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='UDP over TCP'>
|
<a-form-item label='UDP over TCP'>
|
||||||
<a-switch v-model="outbound.settings.uot"></a-switch>
|
<a-switch v-model="outbound.settings.uot"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- stream settings -->
|
<!-- stream settings -->
|
||||||
@@ -349,15 +363,13 @@
|
|||||||
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
|
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<a-form-item label="uTLS">
|
||||||
<a-select v-model="outbound.stream.tls.fingerprint"
|
<a-select v-model="outbound.stream.tls.fingerprint" :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>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="ALPN">
|
<a-form-item label="ALPN">
|
||||||
<a-select mode="multiple"
|
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
v-model="outbound.stream.tls.alpn">
|
v-model="outbound.stream.tls.alpn">
|
||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -369,12 +381,11 @@
|
|||||||
|
|
||||||
<!-- reality settings -->
|
<!-- reality settings -->
|
||||||
<template v-if="outbound.stream.isReality">
|
<template v-if="outbound.stream.isReality">
|
||||||
<a-form-item label="SNI">
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
|
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<a-form-item label="uTLS">
|
||||||
<a-select v-model="outbound.stream.reality.fingerprint"
|
<a-select v-model="outbound.stream.reality.fingerprint" :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>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/dokodemo"}}
|
{{define "form/dokodemo"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ 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 :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form>
|
||||||
<table style="width: 100%; text-align: center; margin: 1rem 0;">
|
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||||
<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>
|
||||||
@@ -18,4 +18,4 @@
|
|||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</template>
|
</template>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ 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:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :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: 1rem 0;">
|
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||||
<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,27 @@
|
|||||||
</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 :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
<a-row>
|
||||||
|
<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:8} }"
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
: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>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='ALPN'>
|
<a-form-item label='ALPN'>
|
||||||
<a-input v-model="fallback.alpn"></a-input>
|
<a-input v-model="fallback.alpn"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -49,6 +53,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:5px 0;"></a-divider>
|
<a-divider style="margin:0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -21,23 +21,27 @@
|
|||||||
</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 :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
<a-row>
|
||||||
|
<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:8} }"
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
: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>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='ALPN'>
|
<a-form-item label='ALPN'>
|
||||||
<a-input v-model="fallback.alpn"></a-input>
|
<a-input v-model="fallback.alpn"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/wireguard"}}
|
{{define "form/wireguard"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item>
|
<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:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;">
|
<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)"
|
||||||
@@ -38,16 +38,10 @@
|
|||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "reset" }}</span>
|
<span>{{ i18n "reset" }}</span>
|
||||||
</template>
|
</template>
|
||||||
{{ i18n "pages.xray.wireguard.secretKey" }}
|
{{ i18n "pages.xray.wireguard.publicKey" }}
|
||||||
<a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())"type="sync"> </a-icon>
|
<a-icon @click="peer.publicKey = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="peer.privateKey"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<template slot="label">
|
|
||||||
{{ i18n "pages.xray.wireguard.publicKey" }}
|
|
||||||
</template>
|
|
||||||
<a-input v-model.trim="peer.publicKey"></a-input>
|
<a-input v-model.trim="peer.publicKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -57,7 +51,7 @@
|
|||||||
<span>{{ i18n "reset" }}</span>
|
<span>{{ i18n "reset" }}</span>
|
||||||
</template>
|
</template>
|
||||||
{{ i18n "pages.xray.wireguard.psk" }}
|
{{ i18n "pages.xray.wireguard.psk" }}
|
||||||
<a-icon @click="peer.psk = Wireguard.keyToBase64(Wireguard.generatePresharedKey())"type="sync"> </a-icon>
|
<a-icon @click="peer.psk = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="peer.psk"></a-input>
|
<a-input v-model.trim="peer.psk"></a-input>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
||||||
<a-button style="width: 10%; margin: 0px; border-radius: 0 1rem 1rem 0;" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
|
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/streamGRPC"}}
|
{{define "form/streamGRPC"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="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>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/streamHTTP"}}
|
{{define "form/streamHTTP"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ 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>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.kcp.type" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.kcp.type" :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,5 +1,5 @@
|
|||||||
{{define "form/streamQUIC"}}
|
{{define "form/streamQUIC"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ 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" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.quic.type" :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,8 +1,8 @@
|
|||||||
{{define "form/streamSettings"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="inbound.stream.network" style="width: 50%" @change="streamNetworkChange"
|
<a-select v-model="inbound.stream.network" @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>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('Host', '')">+</a-button>
|
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('host', '')">+</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||||
@@ -79,4 +79,4 @@
|
|||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
<a-button size="small" @click="inbound.stream.ws.addHeader('Host', '')">+</a-button>
|
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
|
|||||||
@@ -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" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.tls.minVersion" :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" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.tls.maxVersion" :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" style="width: 50%"
|
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||||
: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.publicKey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||||
<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.privatekey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||||
<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.publicKey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
<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.privatekey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||||
<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.publicKey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||||
<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.privatekey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||||
<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.publicKey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
<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.privatekey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||||
<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" style="width: 50%"
|
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||||
: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='{{ i18n "pages.inbounds.privatekey" }}'>
|
<a-form-item label='Private Key'>
|
||||||
<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='{{ i18n "pages.inbounds.publicKey" }}'>
|
<a-form-item label='Public Key'>
|
||||||
<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=" ">
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
||||||
</template>
|
</template>
|
||||||
<template slot="online" slot-scope="text, client, index">
|
<template slot="online" slot-scope="text, client, index">
|
||||||
<template v-if="client.enable && isClientOnline(client.email)">
|
<template v-if="isClientOnline(client.email)">
|
||||||
<a-tag color="green">{{ i18n "online" }}</a-tag>
|
<a-tag color="green">{{ i18n "online" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
<template slot="title">
|
<template slot="title">
|
||||||
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
|
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
|
||||||
<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="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 :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
|
||||||
</a-badge>
|
</a-badge>
|
||||||
|
|||||||
@@ -166,7 +166,7 @@
|
|||||||
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
||||||
<a-divider>Subscription URL</a-divider>
|
<a-divider>Subscription URL</a-divider>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sx="24" :md="22">SUB: <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
<a-col :sx="24" :md="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
||||||
<a-col :sx="24" :md="2" style="text-align: right;">
|
<a-col :sx="24" :md="2" style="text-align: right;">
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
||||||
@@ -175,24 +175,14 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row>
|
|
||||||
<a-col :sx="24" :md="22">JSON: <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a></a-col>
|
|
||||||
<a-col :sx="24" :md="2" style="text-align: right; margin-top: 5px;">
|
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)">
|
|
||||||
<a-icon type="snippets"></a-icon>
|
|
||||||
</button>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||||
<a-divider>Telegram ID</a-divider>
|
<a-divider>Telegram ID</a-divider>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sx="24" :md="22">[[ infoModal.clientSettings.tgId ]]</a-col>
|
<a-col :sx="24" :md="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
|
||||||
<a-col :sx="24" :md="2" style="text-align: right;">
|
<a-col :sx="24" :md="2" style="text-align: right;">
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', infoModal.clientSettings.tgId)">
|
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)">
|
||||||
<a-icon type="snippets"></a-icon>
|
<a-icon type="snippets"></a-icon>
|
||||||
</button>
|
</button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -293,50 +283,24 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<template v-for="(peer, index) in inbound.settings.peers">
|
<template v-for="(peer, index) in inbound.settings.peers">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><a-divider>Peer [[ index + 1 ]]</a-divider></td>
|
<td colspan="2"><a-tag>Peer [[ index + 1 ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="client-table-odd-row">
|
<tr class="client-table-odd-row">
|
||||||
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
|
|
||||||
<td>[[ peer.privateKey ]]</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
|
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
|
||||||
<td>[[ peer.publicKey ]]</td>
|
<td>[[ peer.publicKey ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="client-table-odd-row">
|
<tr>
|
||||||
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
|
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
|
||||||
<td>[[ peer.psk ]]</td>
|
<td>[[ peer.psk ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr class="client-table-odd-row">
|
||||||
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
|
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
|
||||||
<td>[[ peer.allowedIPs.join(",") ]]</td>
|
<td>[[ peer.allowedIPs.join(",") ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="client-table-odd-row">
|
<tr>
|
||||||
<td>Keep Alive</td>
|
<td>Keep Alive</td>
|
||||||
<td>[[ peer.keepAlive ]]</td>
|
<td>[[ peer.keepAlive ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<a-row>
|
|
||||||
<a-col :span="22" style="overflow-wrap: anywhere;">
|
|
||||||
<a-tag color="blue">Config</a-tag>
|
|
||||||
<div
|
|
||||||
v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)"
|
|
||||||
style="border-radius: 1rem; padding: 0.5rem;"
|
|
||||||
class="client-table-odd-row"></div>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="2" style="text-align: right;">
|
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
|
||||||
<button class="ant-btn ant-btn-primary"
|
|
||||||
:id="'copy-url-link-'+index"
|
|
||||||
@click="copyToClipboard('copy-url-link-'+index, infoModal.links[index])">
|
|
||||||
<a-icon type="snippets"></a-icon>
|
|
||||||
</button>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -355,7 +319,7 @@
|
|||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
subLink: '',
|
subLink: '',
|
||||||
subJsonLink: '',
|
tgLink: '',
|
||||||
show(dbInbound, index) {
|
show(dbInbound, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
@@ -363,15 +327,13 @@
|
|||||||
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
||||||
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
|
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
|
||||||
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||||
if (this.inbound.protocol == Protocols.WIREGUARD){
|
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
|
||||||
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
|
|
||||||
} else {
|
|
||||||
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
|
|
||||||
}
|
|
||||||
if (this.clientSettings) {
|
if (this.clientSettings) {
|
||||||
if (this.clientSettings.subId) {
|
if (this.clientSettings.subId) {
|
||||||
this.subLink = this.genSubLink(this.clientSettings.subId);
|
this.subLink = this.genSubLink(this.clientSettings.subId);
|
||||||
this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
|
}
|
||||||
|
if (this.clientSettings.tgId) {
|
||||||
|
this.tgLink = "https://t.me/" + this.clientSettings.tgId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
@@ -381,9 +343,6 @@
|
|||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
return app.subSettings.subURI+subID;
|
return app.subSettings.subURI+subID;
|
||||||
},
|
|
||||||
genSubJsonLink(subID) {
|
|
||||||
return app.subSettings.subJsonURI+subID;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
inModal.visible = false;
|
inModal.visible = false;
|
||||||
inModal.loading(false);
|
inModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading) {
|
||||||
inModal.confirmLoading = loading;
|
inModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,13 +56,9 @@
|
|||||||
<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-tag v-if="false" color="red" style="margin-bottom: 10px">
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
|
||||||
color="red"
|
</a-tag>
|
||||||
description='{{ i18n "secAlertSsl" }}'
|
|
||||||
show-icon closable
|
|
||||||
>
|
|
||||||
</a-alert>
|
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
@@ -137,10 +133,6 @@
|
|||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export" }}
|
{{ i18n "pages.inbounds.export" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="subs" v-if="subSettings.enable">
|
|
||||||
<a-icon type="export"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="resetInbounds">
|
<a-menu-item key="resetInbounds">
|
||||||
<a-icon type="reload"></a-icon>
|
<a-icon type="reload"></a-icon>
|
||||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||||
@@ -149,7 +141,7 @@
|
|||||||
<a-icon type="file-done"></a-icon>
|
<a-icon type="file-done"></a-icon>
|
||||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
<a-menu-item key="delDepletedClients">
|
||||||
<a-icon type="rest"></a-icon>
|
<a-icon type="rest"></a-icon>
|
||||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@@ -186,7 +178,7 @@
|
|||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<a-back-top></a-back-top>
|
<a-back-top></a-back-top>
|
||||||
<a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
|
<a-table :columns="isMobile ? mobileColums : columns" :row-key="dbInbound => dbInbound.id"
|
||||||
:data-source="searchedInbounds"
|
:data-source="searchedInbounds"
|
||||||
:scroll="isMobile ? {} : { x: 1000 }"
|
:scroll="isMobile ? {} : { x: 1000 }"
|
||||||
:pagination=pagination(searchedInbounds)
|
:pagination=pagination(searchedInbounds)
|
||||||
@@ -204,7 +196,7 @@
|
|||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
{{ i18n "edit" }}
|
{{ i18n "edit" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
|
<a-menu-item key="qrcode" v-if="dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser">
|
||||||
<a-icon type="qrcode"></a-icon>
|
<a-icon type="qrcode"></a-icon>
|
||||||
{{ i18n "qrCode" }}
|
{{ i18n "qrCode" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@@ -225,11 +217,7 @@
|
|||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export"}}
|
{{ i18n "pages.inbounds.export"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="subs" v-if="subSettings.enable">
|
<a-menu-item key="delDepletedClients">
|
||||||
<a-icon type="export"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
|
||||||
<a-icon type="rest"></a-icon>
|
<a-icon type="rest"></a-icon>
|
||||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@@ -508,7 +496,7 @@
|
|||||||
scopedSlots: { customRender: 'expiryTime' },
|
scopedSlots: { customRender: 'expiryTime' },
|
||||||
}];
|
}];
|
||||||
|
|
||||||
const mobileColumns = [{
|
const mobileColums = [{
|
||||||
title: "ID",
|
title: "ID",
|
||||||
align: 'right',
|
align: 'right',
|
||||||
dataIndex: "id",
|
dataIndex: "id",
|
||||||
@@ -571,13 +559,11 @@
|
|||||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
subSettings: {
|
subSettings: {
|
||||||
enable : false,
|
enable : false,
|
||||||
subURI : '',
|
subURI : ''
|
||||||
subJsonURI : '',
|
|
||||||
},
|
},
|
||||||
remarkModel: '-ieo',
|
remarkModel: '-ieo',
|
||||||
datepicker: 'gregorian',
|
datepicker: 'gregorian',
|
||||||
tgBotEnable: false,
|
tgBotEnable: false,
|
||||||
showAlert: false,
|
|
||||||
pageSize: 0,
|
pageSize: 0,
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
},
|
},
|
||||||
@@ -592,7 +578,6 @@
|
|||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.getOnlineUsers();
|
await this.getOnlineUsers();
|
||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -619,8 +604,7 @@
|
|||||||
this.tgBotEnable = tgBotEnable;
|
this.tgBotEnable = tgBotEnable;
|
||||||
this.subSettings = {
|
this.subSettings = {
|
||||||
enable : subEnable,
|
enable : subEnable,
|
||||||
subURI: subURI,
|
subURI: subURI
|
||||||
subJsonURI: subJsonURI
|
|
||||||
};
|
};
|
||||||
this.pageSize = pageSize;
|
this.pageSize = pageSize;
|
||||||
this.remarkModel = remarkModel;
|
this.remarkModel = remarkModel;
|
||||||
@@ -658,12 +642,8 @@
|
|||||||
clientCount = clients.length;
|
clientCount = clients.length;
|
||||||
if (dbInbound.enable) {
|
if (dbInbound.enable) {
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
if (client.enable) {
|
client.enable ? active.push(client.email) : deactive.push(client.email);
|
||||||
active.push(client.email);
|
if(this.isClientOnline(client.email)) online.push(client.email);
|
||||||
if (this.isClientOnline(client.email)) online.push(client.email);
|
|
||||||
} else {
|
|
||||||
deactive.push(client.email);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
clientStats.forEach(client => {
|
clientStats.forEach(client => {
|
||||||
if (!client.enable) {
|
if (!client.enable) {
|
||||||
@@ -688,7 +668,6 @@
|
|||||||
online: online,
|
online: online,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
searchInbounds(key) {
|
searchInbounds(key) {
|
||||||
if (ObjectUtil.isEmpty(key)) {
|
if (ObjectUtil.isEmpty(key)) {
|
||||||
this.searchedInbounds = this.dbInbounds.slice();
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
@@ -752,9 +731,6 @@
|
|||||||
case "export":
|
case "export":
|
||||||
this.exportAllLinks();
|
this.exportAllLinks();
|
||||||
break;
|
break;
|
||||||
case "subs":
|
|
||||||
this.exportAllSubs();
|
|
||||||
break;
|
|
||||||
case "resetInbounds":
|
case "resetInbounds":
|
||||||
this.resetAllTraffic();
|
this.resetAllTraffic();
|
||||||
break;
|
break;
|
||||||
@@ -786,9 +762,6 @@
|
|||||||
case "export":
|
case "export":
|
||||||
this.inboundLinks(dbInbound.id);
|
this.inboundLinks(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
case "subs":
|
|
||||||
this.exportSubs(dbInbound.id);
|
|
||||||
break;
|
|
||||||
case "clipboard":
|
case "clipboard":
|
||||||
this.copyToClipboard(dbInbound.id);
|
this.copyToClipboard(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
@@ -838,7 +811,7 @@
|
|||||||
protocol: baseInbound.protocol,
|
protocol: baseInbound.protocol,
|
||||||
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
|
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
|
||||||
streamSettings: baseInbound.stream.toString(),
|
streamSettings: baseInbound.stream.toString(),
|
||||||
sniffing: baseInbound.sniffing.toString(),
|
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
|
||||||
};
|
};
|
||||||
await this.submit('/panel/inbound/add', data, inModal);
|
await this.submit('/panel/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
@@ -848,7 +821,9 @@
|
|||||||
okText: '{{ i18n "pages.inbounds.create"}}',
|
okText: '{{ i18n "pages.inbounds.create"}}',
|
||||||
cancelText: '{{ i18n "close" }}',
|
cancelText: '{{ i18n "close" }}',
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (inbound, dbInbound) => {
|
||||||
await this.addInbound(inbound, dbInbound, inModal);
|
inModal.loading();
|
||||||
|
await this.addInbound(inbound, dbInbound);
|
||||||
|
inModal.close();
|
||||||
},
|
},
|
||||||
isEdit: false
|
isEdit: false
|
||||||
});
|
});
|
||||||
@@ -863,7 +838,9 @@
|
|||||||
inbound: inbound,
|
inbound: inbound,
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (inbound, dbInbound) => {
|
||||||
|
inModal.loading();
|
||||||
await this.updateInbound(inbound, dbInbound);
|
await this.updateInbound(inbound, dbInbound);
|
||||||
|
inModal.close();
|
||||||
},
|
},
|
||||||
isEdit: true
|
isEdit: true
|
||||||
});
|
});
|
||||||
@@ -883,7 +860,7 @@
|
|||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
data.sniffing = inbound.sniffing.toString();
|
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit('/panel/inbound/add', data, inModal);
|
await this.submit('/panel/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
@@ -902,7 +879,7 @@
|
|||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
data.sniffing = inbound.sniffing.toString();
|
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
||||||
},
|
},
|
||||||
@@ -913,7 +890,9 @@
|
|||||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (clients, dbInboundId) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
await this.addClient(clients, dbInboundId, clientModal);
|
clientModal.loading();
|
||||||
|
await this.addClient(clients, dbInboundId);
|
||||||
|
clientModal.close();
|
||||||
},
|
},
|
||||||
isEdit: false
|
isEdit: false
|
||||||
});
|
});
|
||||||
@@ -925,7 +904,9 @@
|
|||||||
okText: '{{ i18n "pages.client.bulk"}}',
|
okText: '{{ i18n "pages.client.bulk"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (clients, dbInboundId) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
await this.addClient(clients, dbInboundId, clientsBulkModal);
|
clientsBulkModal.loading();
|
||||||
|
await this.addClient(clients, dbInboundId);
|
||||||
|
clientsBulkModal.close();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -954,19 +935,19 @@
|
|||||||
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async addClient(clients, dbInboundId, modal) {
|
async addClient(clients, dbInboundId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + clients.toString() + ']}',
|
settings: '{"clients": [' + clients.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/addClient`, data, modal);
|
await this.submit(`/panel/inbound/addClient`, data);
|
||||||
},
|
},
|
||||||
async updateClient(client, dbInboundId, clientId) {
|
async updateClient(client, dbInboundId, clientId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + client.toString() + ']}',
|
settings: '{"clients": [' + client.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
|
await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
|
||||||
},
|
},
|
||||||
resetTraffic(dbInboundId) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
@@ -986,7 +967,7 @@
|
|||||||
},
|
},
|
||||||
delInbound(dbInboundId) {
|
delInbound(dbInboundId) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}' + ' #' + dbInboundId,
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
@@ -999,7 +980,7 @@
|
|||||||
clientId = this.getClientId(dbInbound.protocol, client);
|
clientId = this.getClientId(dbInbound.protocol, client);
|
||||||
if (confirmation){
|
if (confirmation){
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
|
title: '{{ i18n "pages.inbounds.deleteClient"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
@@ -1069,8 +1050,8 @@
|
|||||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
async submit(url, data, modal) {
|
async submit(url, data) {
|
||||||
const msg = await HttpUtil.postWithModal(url, data, modal);
|
const msg = await HttpUtil.postWithModal(url, data);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
await this.getDBInbounds();
|
await this.getDBInbounds();
|
||||||
}
|
}
|
||||||
@@ -1205,22 +1186,6 @@
|
|||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), newDbInbound.remark);
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), newDbInbound.remark);
|
||||||
},
|
},
|
||||||
exportSubs(dbInboundId) {
|
|
||||||
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
|
||||||
const clients = this.getInboundClients(dbInbound);
|
|
||||||
let subLinks = []
|
|
||||||
if (clients != null){
|
|
||||||
clients.forEach(c => {
|
|
||||||
if (c.subId && c.subId.length>0){
|
|
||||||
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
txtModal.show(
|
|
||||||
'{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
|
|
||||||
[...new Set(subLinks)].join('\n'),
|
|
||||||
dbInbound.remark + "-Subs");
|
|
||||||
},
|
|
||||||
importInbound() {
|
importInbound() {
|
||||||
promptModal.open({
|
promptModal.open({
|
||||||
title: '{{ i18n "pages.inbounds.importInbound" }}',
|
title: '{{ i18n "pages.inbounds.importInbound" }}',
|
||||||
@@ -1229,26 +1194,10 @@
|
|||||||
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();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
exportAllSubs() {
|
|
||||||
let subLinks = []
|
|
||||||
for (const dbInbound of this.dbInbounds) {
|
|
||||||
const clients = this.getInboundClients(dbInbound);
|
|
||||||
if (clients != null){
|
|
||||||
clients.forEach(c => {
|
|
||||||
if (c.subId && c.subId.length>0){
|
|
||||||
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
txtModal.show(
|
|
||||||
'{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
|
|
||||||
[...new Set(subLinks)].join('\r\n'),
|
|
||||||
'All-Inbounds-Subs');
|
|
||||||
},
|
|
||||||
exportAllLinks() {
|
exportAllLinks() {
|
||||||
let copyText = [];
|
let copyText = [];
|
||||||
for (const dbInbound of this.dbInbounds) {
|
for (const dbInbound of this.dbInbounds) {
|
||||||
@@ -1289,7 +1238,7 @@
|
|||||||
pagination(obj){
|
pagination(obj){
|
||||||
if (this.pageSize > 0 && obj.length>this.pageSize) {
|
if (this.pageSize > 0 && obj.length>this.pageSize) {
|
||||||
// Set page options based on object size
|
// Set page options based on object size
|
||||||
sizeOptions = [];
|
sizeOptions = []
|
||||||
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
|
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
|
||||||
sizeOptions.push(i.toString());
|
sizeOptions.push(i.toString());
|
||||||
}
|
}
|
||||||
@@ -1302,8 +1251,8 @@
|
|||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
pageSize: this.pageSize,
|
pageSize: this.pageSize,
|
||||||
pageSizeOptions: sizeOptions
|
pageSizeOptions: sizeOptions
|
||||||
};
|
}
|
||||||
return p;
|
return p
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
@@ -1317,9 +1266,6 @@
|
|||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (window.location.protocol !== "https:") {
|
|
||||||
this.showAlert = true;
|
|
||||||
}
|
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener('resize', this.onResize);
|
||||||
this.onResize();
|
this.onResize();
|
||||||
this.loading();
|
this.loading();
|
||||||
@@ -1357,6 +1303,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{template "inboundModal"}}
|
{{template "inboundModal"}}
|
||||||
@@ -1366,5 +1313,6 @@
|
|||||||
{{template "inboundInfoModal"}}
|
{{template "inboundInfoModal"}}
|
||||||
{{template "clientsModal"}}
|
{{template "clientsModal"}}
|
||||||
{{template "clientsBulkModal"}}
|
{{template "clientsBulkModal"}}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -10,11 +10,13 @@
|
|||||||
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: var(--dark-color-text-primary);
|
color: hsla(0, 0%, 100%, .65);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -24,15 +26,6 @@
|
|||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
||||||
<transition name="list" appear>
|
|
||||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
|
||||||
color="red"
|
|
||||||
description='{{ i18n "secAlertSsl" }}'
|
|
||||||
show-icon closable
|
|
||||||
>
|
|
||||||
</a-alert>
|
|
||||||
</transition>
|
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
@@ -43,15 +36,15 @@
|
|||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.cpu.color"
|
:stroke-color="status.cpu.color"
|
||||||
:percent="status.cpu.percent"></a-progress>
|
:percent="status.cpu.percent"></a-progress>
|
||||||
<div><b>CPU:</b> [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
<div>CPU: [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
||||||
<div><b>Speed:</b> [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
<div>Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.mem.color"
|
:stroke-color="status.mem.color"
|
||||||
:percent="status.mem.percent"></a-progress>
|
:percent="status.mem.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
<b>{{ i18n "pages.index.memory"}}:</b> [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -63,7 +56,7 @@
|
|||||||
:stroke-color="status.swap.color"
|
:stroke-color="status.swap.color"
|
||||||
:percent="status.swap.percent"></a-progress>
|
:percent="status.swap.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
<b>Swap:</b> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
@@ -71,7 +64,7 @@
|
|||||||
:stroke-color="status.disk.color"
|
:stroke-color="status.disk.color"
|
||||||
:percent="status.disk.percent"></a-progress>
|
:percent="status.disk.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
<b>{{ i18n "pages.index.hard"}}:</b> [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -82,25 +75,25 @@
|
|||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>3X-UI:</b>
|
3X-UI <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||||
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
Xray <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
||||||
<a rel="noopener" href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@Panel3xui</a-tag></a>
|
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
{{ i18n "menu.link" }}:
|
||||||
<a-tag :color="status.xray.color">Xray: [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tag color="green">OS: [[ formatSecond(status.uptime) ]]</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
|
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>{{ i18n "pages.index.xrayStatus" }}:</b>
|
{{ i18n "pages.index.xrayStatus" }}:
|
||||||
<a-tag style="text-transform: capitalize;" :color="status.xray.color">[[ status.xray.state ]]
|
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||||
</a-tag>
|
|
||||||
<a-popover v-if="status.xray.state === State.Error"
|
<a-popover v-if="status.xray.state === State.Error"
|
||||||
:overlay-class-name="themeSwitcher.currentTheme">
|
:overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
||||||
@@ -113,143 +106,137 @@
|
|||||||
</a-popover>
|
</a-popover>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>{{ i18n "menu.link" }}:</b>
|
{{ i18n "pages.index.operationHours" }}:
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
Xray
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
OS
|
||||||
|
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>{{ i18n "pages.index.systemLoad" }}:</b>
|
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
<a-tag color="green">
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.systemLoadDesc" }}
|
{{ i18n "pages.index.systemLoadDesc" }}
|
||||||
</template>
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>{{ i18n "usage"}}:</b>
|
{{ i18n "usage"}}:
|
||||||
<a-tag color="green">
|
RAM [[ sizeFormat(status.appStats.mem) ]] -
|
||||||
RAM: [[ sizeFormat(status.appStats.mem) ]]
|
Threads [[ status.appStats.threads ]]
|
||||||
</a-tag>
|
</a-tooltip>
|
||||||
<a-tag color="green">
|
|
||||||
Threads: [[ status.appStats.threads ]]
|
|
||||||
</a-tag>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-icon type="global"></a-icon>
|
||||||
|
IPv4:
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="global"></a-icon> IPv4
|
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
[[ status.publicIP.ipv4 ]]
|
[[ status.publicIP.ipv4 ]]
|
||||||
</template>
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
</a-col>
|
||||||
</a-col>
|
<a-col :span="12">
|
||||||
<a-col :span="12">
|
<a-icon type="global"></a-icon>
|
||||||
<a-tag>
|
IPv6:
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="global"></a-icon> IPv6
|
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
[[ status.publicIP.ipv6 ]]
|
[[ status.publicIP.ipv6 ]]
|
||||||
</template>
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-icon type="swap"></a-icon>
|
||||||
|
TCP: [[ status.tcpCount ]]
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
|
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
||||||
</template>
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-icon type="swap"></a-icon>
|
||||||
|
UDP: [[ status.udpCount ]]
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
|
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
||||||
</template>
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
|
||||||
<a-tooltip>
|
|
||||||
<a-icon type="arrow-up"></a-icon>
|
<a-icon type="arrow-up"></a-icon>
|
||||||
Up: [[ sizeFormat(status.netIO.up) ]]/s
|
[[ sizeFormat(status.netIO.up) ]]/s
|
||||||
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.upSpeed" }}
|
{{ i18n "pages.index.upSpeed" }}
|
||||||
</template>
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
|
||||||
<a-tooltip>
|
|
||||||
<a-icon type="arrow-down"></a-icon>
|
<a-icon type="arrow-down"></a-icon>
|
||||||
Down: [[ sizeFormat(status.netIO.down) ]]/s
|
[[ sizeFormat(status.netIO.down) ]]/s
|
||||||
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.downSpeed" }}
|
{{ i18n "pages.index.downSpeed" }}
|
||||||
</template>
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-icon type="cloud-upload"></a-icon>
|
||||||
|
[[ sizeFormat(status.netTraffic.sent) ]]
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.totalSent" }}
|
{{ i18n "pages.index.totalSent" }}
|
||||||
</template> Out: [[ sizeFormat(status.netTraffic.sent) ]]
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-icon type="cloud-download"></a-icon>
|
||||||
|
[[ sizeFormat(status.netTraffic.recv) ]]
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="cloud-download"></a-icon>
|
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.totalReceive" }}
|
{{ i18n "pages.index.totalReceive" }}
|
||||||
</template> In: [[ sizeFormat(status.netTraffic.recv) ]]
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
@@ -269,51 +256,51 @@
|
|||||||
></a-alert>
|
></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: 10px" @click="switchV2rayVersion(version)">
|
style="margin: 10px" @click="switchV2rayVersion(version)">
|
||||||
[[ version ]]
|
[[ version ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="log-modal" v-model="logModal.visible"
|
<a-modal id="log-modal" v-model="logModal.visible" title="Logs"
|
||||||
:closable="true" @cancel="() => logModal.visible = false"
|
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
||||||
:class="themeSwitcher.currentTheme"
|
:class="themeSwitcher.currentTheme"
|
||||||
width="800px" footer="">
|
width="800px"
|
||||||
<template slot="title">
|
footer="">
|
||||||
{{ i18n "pages.index.logs" }}
|
|
||||||
<a-icon :spin="logModal.loading"
|
|
||||||
type="sync"
|
|
||||||
style="vertical-align: middle; margin-left: 10px;"
|
|
||||||
:disabled="logModal.loading"
|
|
||||||
@click="openLogs()">
|
|
||||||
</a-icon>
|
|
||||||
</template>
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item label="Count">
|
||||||
<a-input-group compact>
|
<a-select v-model="logModal.rows"
|
||||||
<a-select v-model="logModal.rows" style="width:70px;"
|
style="width: 80px"
|
||||||
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
@change="openLogs()"
|
||||||
<a-select-option value="10">10</a-select-option>
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="50">50</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
<a-select-option value="100">100</a-select-option>
|
<a-select-option value="50">50</a-select-option>
|
||||||
</a-select>
|
<a-select-option value="100">100</a-select-option>
|
||||||
<a-select v-model="logModal.level" style="width:100px;"
|
</a-select>
|
||||||
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
</a-form-item>
|
||||||
<a-select-option value="debug">Debug</a-select-option>
|
<a-form-item label="Log Level">
|
||||||
<a-select-option value="info">Info</a-select-option>
|
<a-select v-model="logModal.level"
|
||||||
<a-select-option value="notice">Notice</a-select-option>
|
style="width: 120px"
|
||||||
<a-select-option value="warning">Warning</a-select-option>
|
@change="openLogs()"
|
||||||
<a-select-option value="err">Error</a-select-option>
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
</a-select>
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
</a-input-group>
|
<a-select-option value="info">Info</a-select-option>
|
||||||
|
<a-select-option value="notice">Notice</a-select-option>
|
||||||
|
<a-select-option value="warning">Warning</a-select-option>
|
||||||
|
<a-select-option value="err">Error</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="SysLog">
|
||||||
|
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox>
|
<a-button class="ant-btn ant-btn-primary" :loading="logModal.loading" @click="openLogs()"><a-icon :spin="logModal.loading" type="sync"></a-icon> Reload</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item style="float: right;">
|
<a-form-item>
|
||||||
<a-button type="primary" icon="download"
|
<a-button type="primary" style="margin-bottom: 10px;"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs.join('\n'))" download="x-ui.log">
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs.join('\n'))" download="x-ui.log">
|
||||||
|
{{ i18n "download" }} x-ui.log
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@@ -321,8 +308,8 @@
|
|||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
||||||
:closable="true" footer=""
|
:closable="true" :class="themeSwitcher.currentTheme"
|
||||||
:class="themeSwitcher.currentTheme">
|
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
||||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||||
:message="backupModal.description"
|
:message="backupModal.description"
|
||||||
show-icon
|
show-icon
|
||||||
@@ -446,14 +433,15 @@
|
|||||||
const logModal = {
|
const logModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
logs: [],
|
logs: [],
|
||||||
|
formattedLogs: '',
|
||||||
rows: 20,
|
rows: 20,
|
||||||
level: 'info',
|
level: 'info',
|
||||||
syslog: false,
|
syslog: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
show(logs) {
|
show(logs) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.logs = logs;
|
this.logs = logs;
|
||||||
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
|
this.formattedLogs = logs.length > 0 ? this.formatLogs(logs) : "No Record...";
|
||||||
},
|
},
|
||||||
formatLogs(logs) {
|
formatLogs(logs) {
|
||||||
let formattedLogs = '';
|
let formattedLogs = '';
|
||||||
@@ -531,7 +519,6 @@
|
|||||||
backupModal,
|
backupModal,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
loadingTip: '{{ i18n "loading"}}',
|
loadingTip: '{{ i18n "loading"}}',
|
||||||
showAlert: false,
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning, tip = '{{ i18n "loading"}}') {
|
loading(spinning, tip = '{{ i18n "loading"}}') {
|
||||||
@@ -655,14 +642,14 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
if (window.location.protocol !== "https:") {
|
let retries = 0;
|
||||||
this.showAlert = true;
|
while (retries < 5) {
|
||||||
}
|
|
||||||
while (true) {
|
|
||||||
try {
|
try {
|
||||||
await this.getStatus();
|
await this.getStatus();
|
||||||
|
retries = 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error("Error occurred while fetching status:", e);
|
||||||
|
retries++;
|
||||||
}
|
}
|
||||||
await PromiseUtil.sleep(2000);
|
await PromiseUtil.sleep(2000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,37 +75,28 @@
|
|||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<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>
|
|
||||||
<a-alert type="error" v-if="confAlerts.length>0" style="margin: 10px 5px;"
|
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
|
||||||
color="red"
|
|
||||||
show-icon
|
|
||||||
closable
|
|
||||||
>
|
|
||||||
<template slot="description">
|
|
||||||
<b>{{ i18n "secAlertConf" }}</b>
|
|
||||||
<ul><li v-for="a in confAlerts">[[ a ]]</li></ul>
|
|
||||||
</template>
|
|
||||||
</a-alert>
|
|
||||||
</transition>
|
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
<a-card hoverable style="margin-bottom: .5rem; overflow-x: hidden;">
|
<a-card hoverable style="margin-bottom: .5rem;">
|
||||||
<a-row style="display: flex; flex-wrap: wrap; align-items: center;">
|
<a-row>
|
||||||
<a-col :xs="24" :sm="10" style="padding: 4px;">
|
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
||||||
<a-space direction="horizontal">
|
<a-space direction="horizontal">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="14">
|
<a-col :xs="24" :sm="16">
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
<template>
|
||||||
</a-back-top>
|
<div>
|
||||||
<a-alert type="warning" style="float: right; width: fit-content"
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
||||||
message='{{ i18n "pages.settings.infoDesc" }}'
|
</a-back-top>
|
||||||
show-icon
|
<a-alert type="warning" style="float: right; width: fit-content"
|
||||||
>
|
message='{{ i18n "pages.settings.infoDesc" }}'
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -173,6 +164,7 @@
|
|||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta title="Language" />
|
<a-list-item-meta title="Language" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select
|
||||||
@@ -242,6 +234,7 @@
|
|||||||
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
||||||
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
||||||
<a-list item-layout="horizontal">
|
<a-list item-layout="horizontal">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
||||||
@@ -257,13 +250,15 @@
|
|||||||
<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
|
||||||
ref="selectBotLang"
|
ref="selectBotLang"
|
||||||
v-model="allSetting.tgLang"
|
v-model="allSetting.tgLang"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
style="width: 100%">
|
style="width: 100%"
|
||||||
|
>
|
||||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
@@ -290,17 +285,6 @@
|
|||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable">
|
|
||||||
<a-list item-layout="horizontal">
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item>
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.fragment"}}' desc='{{ i18n "pages.settings.fragmentDesc"}}' v-model="fragment"></setting-list-item>
|
|
||||||
<template v-if="fragment">
|
|
||||||
<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>
|
|
||||||
</template>
|
|
||||||
</a-list>
|
|
||||||
</a-tab-pane>
|
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
@@ -326,28 +310,11 @@
|
|||||||
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'}],
|
||||||
remarkSample: '',
|
remarkSample: '',
|
||||||
defaultFragment: {
|
|
||||||
tag: "fragment",
|
|
||||||
protocol: "freedom",
|
|
||||||
settings: {
|
|
||||||
domainStrategy: "AsIs",
|
|
||||||
fragment: {
|
|
||||||
packets: "tlshello",
|
|
||||||
length: "100-200",
|
|
||||||
interval: "10-20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
streamSettings: {
|
|
||||||
sockopt: {
|
|
||||||
tcpKeepAliveIdle: 100,
|
|
||||||
tcpNoDelay: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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('') : [];
|
||||||
@@ -476,59 +443,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
fragment: {
|
|
||||||
get: function() { return this.allSetting?.subJsonFragment != ""; },
|
|
||||||
set: function (v) {
|
|
||||||
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fragmentLength: {
|
|
||||||
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
|
|
||||||
set: function(v) {
|
|
||||||
if (v != ""){
|
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
|
||||||
newFragment.settings.fragment.length = v;
|
|
||||||
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fragmentInterval: {
|
|
||||||
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
|
|
||||||
set: function(v) {
|
|
||||||
if (v != ""){
|
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
|
||||||
newFragment.settings.fragment.interval = v;
|
|
||||||
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confAlerts: {
|
|
||||||
get: function() {
|
|
||||||
if (!this.allSetting) return [];
|
|
||||||
var alerts = []
|
|
||||||
if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}');
|
|
||||||
if (this.allSetting.webPort == 54321) alerts.push('{{ i18n "secAlertPanelPort" }}');
|
|
||||||
panelPath = window.location.pathname.split('/').length<4
|
|
||||||
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}');
|
|
||||||
if (this.allSetting.subEnable) {
|
|
||||||
subPath = this.allSetting.subURI.length >0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
|
|
||||||
if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
|
|
||||||
subJsonPath = this.allSetting.subJsonURI.length >0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
|
|
||||||
if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
|
|
||||||
}
|
|
||||||
return alerts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.getAllSetting();
|
await this.getAllSetting();
|
||||||
while (true) {
|
while (true) {
|
||||||
await PromiseUtil.sleep(1000);
|
await PromiseUtil.sleep(600);
|
||||||
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<a-divider style="margin: 0;">{{ i18n "pages.settings.toasts.modifySettings" }}</a-divider>
|
<a-divider style="margin: 0;">{{ i18n "pages.settings.toasts.modifySettings" }}</a-divider>
|
||||||
<a-collapse style="margin: 10px 0;">
|
<a-collapse style="margin: 10px 0;">
|
||||||
<a-collapse-panel header='WARP/WARP+ License Key'>
|
<a-collapse-panel header='WARP/WARP+ License Key'>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="License Key">
|
<a-form-item label="License 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>
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
this.visible = false;
|
this.visible = false;
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading) {
|
||||||
this.confirmLoading = loading;
|
this.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
async getData(){
|
async getData(){
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
mtu: 1420,
|
mtu: 1420,
|
||||||
secretKey: warpModal.warpData.private_key,
|
secretKey: warpModal.warpData.private_key,
|
||||||
address: Object.values(config.interface.addresses),
|
address: Object.values(config.interface.addresses),
|
||||||
domainStrategy: 'ForceIP',
|
domainStrategy: 'ForceIPv6v4',
|
||||||
peers: [{
|
peers: [{
|
||||||
publicKey: peer.public_key,
|
publicKey: peer.public_key,
|
||||||
endpoint: peer.endpoint.host,
|
endpoint: peer.endpoint.host,
|
||||||
|
|||||||
@@ -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?{{ .cur_ver }}">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .cur_ver }}">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .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?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/codemirror/codemirror.js"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
|
||||||
@@ -63,19 +63,10 @@
|
|||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<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>
|
|
||||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
|
||||||
color="red"
|
|
||||||
description='{{ i18n "secAlertSsl" }}'
|
|
||||||
show-icon closable
|
|
||||||
>
|
|
||||||
</a-alert>
|
|
||||||
</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 style="display: flex; flex-wrap: wrap; align-items: center;">
|
<a-row>
|
||||||
<a-col :xs="24" :sm="10" style="padding: 4px;">
|
<a-col :xs="24" :sm="8" 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 +80,7 @@
|
|||||||
</a-popover>
|
</a-popover>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="14">
|
<a-col :xs="24" :sm="16">
|
||||||
<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">
|
||||||
@@ -108,7 +99,7 @@
|
|||||||
:class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme">
|
||||||
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.xray.basicTemplate"}}'>
|
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.xray.basicTemplate"}}'>
|
||||||
<a-space direction="horizontal" style="padding: 20px 20px">
|
<a-space direction="horizontal" style="padding: 20px 20px">
|
||||||
<a-button type="danger" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-collapse>
|
<a-collapse>
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||||
@@ -123,12 +114,15 @@
|
|||||||
<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">
|
||||||
<a-list-item-meta title='{{ i18n "pages.xray.FreedomStrategy" }}'
|
<a-list-item-meta
|
||||||
description='{{ i18n "pages.xray.FreedomStrategyDesc" }}' />
|
title='{{ i18n "pages.xray.FreedomStrategy" }}'
|
||||||
|
description='{{ i18n "pages.xray.FreedomStrategyDesc" }}'/>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select v-model="freedomStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
<a-select
|
||||||
|
v-model="freedomStrategy"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
style="width: 100%">
|
style="width: 100%">
|
||||||
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -138,67 +132,22 @@
|
|||||||
</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">
|
||||||
<a-list-item-meta title='{{ i18n "pages.xray.RoutingStrategy" }}'
|
<a-list-item-meta
|
||||||
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}' />
|
title='{{ i18n "pages.xray.RoutingStrategy" }}'
|
||||||
|
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}'/>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-select v-model="routingStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
<template>
|
||||||
|
<a-select
|
||||||
|
v-model="routingStrategy"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
style="width: 100%">
|
style="width: 100%">
|
||||||
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.logConfigs" }}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<a-alert type="warning" style="text-align: center;">
|
|
||||||
<template slot="message">
|
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
||||||
{{ i18n "pages.xray.logConfigsDesc" }}
|
|
||||||
</template>
|
|
||||||
</a-alert>
|
|
||||||
</a-row>
|
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta title='{{ i18n "pages.xray.logLevel" }}'
|
|
||||||
description='{{ i18n "pages.xray.logLevelDesc" }}' />
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<a-select v-model="setLogLevel" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
|
||||||
<a-select-option v-for="s in logLevel" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row style="padding: 20px">
|
</a-list-item>
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta title='{{ i18n "pages.xray.accessLog" }}'
|
|
||||||
description='{{ i18n "pages.xray.accessLogDesc" }}' />
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<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>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta title='{{ i18n "pages.xray.errorLog" }}'
|
|
||||||
description='{{ i18n "pages.xray.errorLogDesc" }}' />
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<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>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
@@ -278,11 +227,8 @@
|
|||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.OpenAIWARP"}}' desc='{{ i18n "pages.xray.OpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.OpenAIWARP"}}' desc='{{ i18n "pages.xray.OpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixWARP"}}' desc='{{ i18n "pages.xray.NetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixWARP"}}' desc='{{ i18n "pages.xray.NetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.SpotifyWARP"}}' desc='{{ i18n "pages.xray.SpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.SpotifyWARP"}}' desc='{{ i18n "pages.xray.SpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.MetaWARP"}}' desc='{{ i18n "pages.xray.MetaWARPDesc"}}' v-model="MetaWARPSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.AppleWARP"}}' desc='{{ i18n "pages.xray.AppleWARPDesc"}}' v-model="AppleWARPSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.RedditWARP"}}' desc='{{ i18n "pages.xray.RedditWARPDesc"}}' v-model="RedditWARPSettings"></setting-list-item>
|
|
||||||
</template>
|
</template>
|
||||||
<a-button v-else type="primary" icon="cloud" style="margin: 15px 20px;" @click="showWarp()">WARP</a-button>
|
<a-button v-else style="margin: 10px 0;" @click="showWarp">WARP {{ i18n "pages.xray.rules.outbound" }}</a-button>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
@@ -290,19 +236,15 @@
|
|||||||
<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-sortable :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
|
<a-table :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>
|
[[ index+1 ]]
|
||||||
<span class="ant-table-row-index">
|
|
||||||
[[ 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">
|
||||||
@@ -351,14 +293,6 @@
|
|||||||
[[ rule.outboundTag ]]
|
[[ rule.outboundTag ]]
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="balancer" slot-scope="text, rule, index">
|
|
||||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
|
||||||
<template slot="content">
|
|
||||||
<p v-if="rule.balancerTag">Balancer Tag: [[ rule.balancerTag ]]</p>
|
|
||||||
</template>
|
|
||||||
[[ rule.balancerTag ]]
|
|
||||||
</a-popover>
|
|
||||||
</template>
|
|
||||||
<template slot="info" slot-scope="text, rule, index">
|
<template slot="info" slot-scope="text, rule, index">
|
||||||
<a-popover placement="bottomRight"
|
<a-popover placement="bottomRight"
|
||||||
v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
|
v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
|
||||||
@@ -397,10 +331,6 @@
|
|||||||
<td>Port</td>
|
<td>Port</td>
|
||||||
<td><a-tag color="green" v-for="r in rule.port.split(',')">[[ r ]]</a-tag></td>
|
<td><a-tag color="green" v-for="r in rule.port.split(',')">[[ r ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="rule.balancerTag">
|
|
||||||
<td>Balancer Tag</td>
|
|
||||||
<td><a-tag color="blue">[[ rule.balancerTag ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
||||||
@@ -408,27 +338,11 @@
|
|||||||
</a-button>
|
</a-button>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
</a-table-sortable>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-row>
|
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||||
<a-col :xs="12" :sm="12" :lg="12">
|
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
||||||
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n
|
|
||||||
"pages.xray.outbound.addOutbound" }}</a-button>
|
|
||||||
<a-button type="primary" icon="cloud" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
|
||||||
</a-col>
|
|
||||||
<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-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-row>
|
|
||||||
<a-table :columns="outboundColumns" bordered
|
<a-table :columns="outboundColumns" bordered
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
:data-source="outboundData"
|
:data-source="outboundData"
|
||||||
@@ -441,19 +355,10 @@
|
|||||||
<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">
|
||||||
<a-menu-item v-if="index>0" @click="setFirstOutbound(index)">
|
|
||||||
<a-icon type="vertical-align-top"></a-icon>
|
|
||||||
{{ i18n "pages.xray.rules.first"}}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="editOutbound(index)">
|
<a-menu-item @click="editOutbound(index)">
|
||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
{{ i18n "edit" }}
|
{{ i18n "edit" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item @click="resetOutboundTraffic(index)">
|
|
||||||
<span>
|
|
||||||
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic"}}
|
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="deleteOutbound(index)">
|
<a-menu-item @click="deleteOutbound(index)">
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
@@ -473,14 +378,11 @@
|
|||||||
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
|
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, outbound, index">
|
|
||||||
<a-tag color="green">[[ findOutboundTraffic(outbound) ]]</a-tag>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-button type="primary" icon="plus" @click="addReverse()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addReverse" }}</a-button>
|
<a-button type="primary" icon="plus" @click="addReverse()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addReverse" }}</a-button>
|
||||||
<a-table :columns="reverseColumns" bordered v-if="reverseData.length>0"
|
<a-table :columns="reverseColumns" bordered
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
:data-source="reverseData"
|
:data-source="reverseData"
|
||||||
:scroll="isMobile ? {} : { x: 200 }"
|
:scroll="isMobile ? {} : { x: 200 }"
|
||||||
@@ -506,123 +408,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-5" tab='{{ i18n "pages.xray.Balancers"}}' style="padding-top: 20px;" force-render="true">
|
|
||||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
|
||||||
message='{{ i18n "pages.xray.balancer.balancerDesc" }}' show-icon></a-alert>
|
|
||||||
<a-button type="primary" icon="plus" @click="addBalancer()" style="margin-bottom: 10px;">{{ i18n "pages.xray.balancer.addBalancer"}}</a-button>
|
|
||||||
<a-table :columns="balancerColumns" bordered v-if="balancersData.length>0"
|
|
||||||
:row-key="r => r.key"
|
|
||||||
:data-source="balancersData"
|
|
||||||
:scroll="isMobile ? {} : { x: 200 }"
|
|
||||||
:pagination="false"
|
|
||||||
:indent-size="0"
|
|
||||||
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
|
||||||
<template slot="action" slot-scope="text, balancer, index">
|
|
||||||
[[ index+1 ]]
|
|
||||||
<a-dropdown :trigger="['click']">
|
|
||||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
|
||||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
|
||||||
<a-menu-item @click="editBalancer(index)">
|
|
||||||
<a-icon type="edit"></a-icon>
|
|
||||||
{{ i18n "edit" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="deleteBalancer(index)">
|
|
||||||
<span style="color: #FF4D4F">
|
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
</a-dropdown>
|
|
||||||
</template>
|
|
||||||
<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=='roundRobin'" color="green">Round Robin</a-tag>
|
|
||||||
</template>
|
|
||||||
<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>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
|
||||||
</a-tab-pane>
|
|
||||||
<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>
|
|
||||||
<template v-if="enableDNS">
|
|
||||||
<a-list-item>
|
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta title='{{ i18n "pages.xray.dns.strategy" }}' description='{{ i18n "pages.xray.dns.strategyDesc" }}' />
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-select
|
|
||||||
v-model="dnsStrategy"
|
|
||||||
style="width: 100%"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
|
|
||||||
[[ l ]]
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
<a-divider>DNS</a-divider>
|
|
||||||
<a-button type="primary" icon="plus" @click="addDNSServer()" style="margin-bottom: 10px;">{{ i18n "pages.xray.dns.add" }}</a-button>
|
|
||||||
<a-table :columns="dnsColumns" bordered v-if="dnsServers.length>0"
|
|
||||||
:row-key="r => r.key"
|
|
||||||
:data-source="dnsServers"
|
|
||||||
:scroll="isMobile ? {} : { x: 200 }"
|
|
||||||
:pagination="false"
|
|
||||||
:indent-size="0"
|
|
||||||
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
|
||||||
<template slot="action" slot-scope="text,dns,index">
|
|
||||||
[[ index+1 ]]
|
|
||||||
<a-dropdown :trigger="['click']">
|
|
||||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
|
||||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
|
||||||
<a-menu-item @click="editDNSServer(index)">
|
|
||||||
<a-icon type="edit"></a-icon>
|
|
||||||
{{ i18n "edit" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="deleteDNSServer(index)">
|
|
||||||
<span style="color: #FF4D4F">
|
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
</a-dropdown>
|
|
||||||
</template>
|
|
||||||
<template slot="address" slot-scope="dns,index">
|
|
||||||
<span v-if="typeof dns == 'object'">[[ dns.address ]]</span>
|
|
||||||
<span v-else>[[ dns ]]</span>
|
|
||||||
</template>
|
|
||||||
<template slot="domain" slot-scope="dns,index">
|
|
||||||
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
|
||||||
<a-divider>Fake DNS</a-divider>
|
|
||||||
<a-button type="primary" icon="plus" @click="addFakedns()" style="margin-bottom: 10px;">{{ i18n "pages.xray.fakedns.add" }}</a-button>
|
|
||||||
<a-table :columns="fakednsColumns" bordered v-if="fakeDns && fakeDns.length>0" :row-key="r => r.key"
|
|
||||||
:data-source="fakeDns" :scroll="isMobile ? {} : { x: 200 }" :pagination="false" :indent-size="0"
|
|
||||||
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
|
||||||
<template slot="action" slot-scope="text,fakedns,index">
|
|
||||||
[[ index+1 ]]
|
|
||||||
<a-dropdown :trigger="['click']">
|
|
||||||
<a-icon @click="e => e.preventDefault()" type="more"
|
|
||||||
style="font-size: 16px; text-decoration: bold;"></a-icon>
|
|
||||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
|
||||||
<a-menu-item @click="editFakedns(index)">
|
|
||||||
<a-icon type="edit"></a-icon>
|
|
||||||
{{ i18n "edit" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="deleteFakedns(index)">
|
|
||||||
<span style="color: #FF4D4F">
|
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
</a-dropdown>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
|
||||||
</template>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
|
<a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
|
||||||
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
|
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
|
||||||
@@ -641,14 +426,10 @@
|
|||||||
</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"}}
|
||||||
{{template "reverseModal"}}
|
{{template "reverseModal"}}
|
||||||
{{template "balancerModal"}}
|
|
||||||
{{template "dnsModal"}}
|
|
||||||
{{template "fakednsModal"}}
|
|
||||||
{{template "warpModal"}}
|
{{template "warpModal"}}
|
||||||
<script>
|
<script>
|
||||||
const rulesColumns = [
|
const rulesColumns = [
|
||||||
@@ -665,10 +446,9 @@
|
|||||||
{ title: 'Domain', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true },
|
{ title: 'Domain', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true },
|
||||||
{ title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
|
{ title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
|
||||||
{ title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
|
{ title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
|
||||||
{ title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 15, ellipsis: true },
|
{ title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 20, ellipsis: true },
|
||||||
{ title: 'Client Email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]},
|
{ title: 'Client Email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]},
|
||||||
{ title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 15 },
|
{ title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 20 },
|
||||||
{ title: '{{ i18n "pages.xray.rules.balancer"}}', dataIndex: 'balancerTag', align: 'center', width: 15 },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const rulesMobileColumns = [
|
const rulesMobileColumns = [
|
||||||
@@ -683,7 +463,6 @@
|
|||||||
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
||||||
{ title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
|
{ title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
|
||||||
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', align: 'center', width: 50, scopedSlots: { customRender: 'traffic' } },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const reverseColumns = [
|
const reverseColumns = [
|
||||||
@@ -693,25 +472,6 @@
|
|||||||
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
|
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const dnsColumns = [
|
|
||||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
|
||||||
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
|
||||||
{ title: '{{ i18n "pages.xray.dns.domains"}}', align: 'center', width: 50, scopedSlots: { customRender: 'domain' } },
|
|
||||||
];
|
|
||||||
|
|
||||||
const fakednsColumns = [
|
|
||||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
|
||||||
{ title: '{{ i18n "pages.xray.fakedns.ipPool"}}', dataIndex: 'ipPool', align: 'center', width: 50 },
|
|
||||||
{ title: '{{ i18n "pages.xray.fakedns.poolSize"}}', dataIndex: 'poolSize', align: 'center', width: 50 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const balancerColumns = [
|
|
||||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
|
||||||
{ title: '{{ i18n "pages.xray.balancer.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
|
||||||
{ title: '{{ i18n "pages.xray.balancer.balancerStrategy"}}', align: 'center', width: 50, scopedSlots: { customRender: 'strategy' }},
|
|
||||||
{ title: '{{ i18n "pages.xray.balancer.balancerSelectors"}}', align: 'center', width: 100, scopedSlots: { customRender: 'selector' }},
|
|
||||||
];
|
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
@@ -723,11 +483,8 @@
|
|||||||
oldXraySetting: '',
|
oldXraySetting: '',
|
||||||
xraySetting: '',
|
xraySetting: '',
|
||||||
inboundTags: [],
|
inboundTags: [],
|
||||||
outboundsTraffic: [],
|
|
||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
refreshing: false,
|
|
||||||
restartResult: '',
|
restartResult: '',
|
||||||
showAlert: false,
|
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
advSettings: 'xraySetting',
|
advSettings: 'xraySetting',
|
||||||
cm: null,
|
cm: null,
|
||||||
@@ -764,9 +521,6 @@
|
|||||||
protocol: "freedom"
|
protocol: "freedom"
|
||||||
},
|
},
|
||||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
||||||
logLevel: ["none" , "debug" , "info" , "warning", "error"],
|
|
||||||
access: ["none" , "./access.log" ],
|
|
||||||
error: ["none" , "./error.log" ],
|
|
||||||
settingsData: {
|
settingsData: {
|
||||||
protocols: {
|
protocols: {
|
||||||
bittorrent: ["bittorrent"],
|
bittorrent: ["bittorrent"],
|
||||||
@@ -793,9 +547,6 @@
|
|||||||
google: ["geosite:google"],
|
google: ["geosite:google"],
|
||||||
spotify: ["geosite:spotify"],
|
spotify: ["geosite:spotify"],
|
||||||
netflix: ["geosite:netflix"],
|
netflix: ["geosite:netflix"],
|
||||||
meta: ["geosite:meta"],
|
|
||||||
apple: ["geosite:apple"],
|
|
||||||
reddit: ["geosite:reddit"],
|
|
||||||
cn: [
|
cn: [
|
||||||
"geosite:cn",
|
"geosite:cn",
|
||||||
"regexp:.*\\.cn$"
|
"regexp:.*\\.cn$"
|
||||||
@@ -830,12 +581,6 @@
|
|||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
this.spinning = spinning;
|
this.spinning = spinning;
|
||||||
},
|
},
|
||||||
async getOutboundsTraffic() {
|
|
||||||
const msg = await HttpUtil.get("/panel/xray/getOutboundsTraffic");
|
|
||||||
if (msg.success) {
|
|
||||||
this.outboundsTraffic = msg.obj;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async getXraySetting() {
|
async getXraySetting() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/panel/xray/");
|
const msg = await HttpUtil.post("/panel/xray/");
|
||||||
@@ -1014,14 +759,6 @@
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
findOutboundTraffic(o) {
|
|
||||||
for (const otraffic of this.outboundsTraffic) {
|
|
||||||
if (otraffic.tag == o.tag) {
|
|
||||||
return sizeFormat(otraffic.up) + ' / ' + sizeFormat(otraffic.down);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sizeFormat(0) + ' / ' + sizeFormat(0);
|
|
||||||
},
|
|
||||||
findOutboundAddress(o) {
|
findOutboundAddress(o) {
|
||||||
serverObj = null;
|
serverObj = null;
|
||||||
switch(o.protocol){
|
switch(o.protocol){
|
||||||
@@ -1079,126 +816,6 @@
|
|||||||
outbounds.splice(index,1);
|
outbounds.splice(index,1);
|
||||||
this.outboundSettings = JSON.stringify(outbounds);
|
this.outboundSettings = JSON.stringify(outbounds);
|
||||||
},
|
},
|
||||||
setFirstOutbound(index){
|
|
||||||
outbounds = this.templateSettings.outbounds;
|
|
||||||
outbounds.splice(0, 0, outbounds.splice(index, 1)[0]);
|
|
||||||
this.outboundSettings = JSON.stringify(outbounds);
|
|
||||||
},
|
|
||||||
async refreshOutboundTraffic() {
|
|
||||||
if (!this.refreshing) {
|
|
||||||
this.refreshing = true;
|
|
||||||
await this.getOutboundsTraffic();
|
|
||||||
|
|
||||||
data = []
|
|
||||||
if (this.templateSettings != null) {
|
|
||||||
this.templateSettings.outbounds.forEach((o, index) => {
|
|
||||||
data.push({'key': index, ...o});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.outboundData = data;
|
|
||||||
this.refreshing = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async resetOutboundTraffic(index) {
|
|
||||||
let tag = "-alltags-";
|
|
||||||
if (index >= 0) {
|
|
||||||
tag = this.outboundData[index].tag ? this.outboundData[index].tag : ""
|
|
||||||
}
|
|
||||||
const msg = await HttpUtil.post("/panel/xray/resetOutboundsTraffic", { tag: tag });
|
|
||||||
if (msg.success) {
|
|
||||||
await this.refreshOutboundTraffic();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addBalancer() {
|
|
||||||
balancerModal.show({
|
|
||||||
title: '{{ i18n "pages.xray.balancer.addBalancer"}}',
|
|
||||||
okText: '{{ i18n "pages.xray.balancer.addBalancer"}}',
|
|
||||||
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
|
|
||||||
balancer: {
|
|
||||||
tag: '',
|
|
||||||
strategy: 'random',
|
|
||||||
selector: []
|
|
||||||
},
|
|
||||||
confirm: (balancer) => {
|
|
||||||
balancerModal.loading();
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
if (newTemplateSettings.routing.balancers == undefined) {
|
|
||||||
newTemplateSettings.routing.balancers = [];
|
|
||||||
}
|
|
||||||
let tmpBalancer = {
|
|
||||||
'tag': balancer.tag,
|
|
||||||
'selector': balancer.selector
|
|
||||||
};
|
|
||||||
if (balancer.strategy == 'roundRobin') {
|
|
||||||
tmpBalancer.strategy = {
|
|
||||||
'type': balancer.strategy
|
|
||||||
};
|
|
||||||
}
|
|
||||||
newTemplateSettings.routing.balancers.push(tmpBalancer);
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
balancerModal.close();
|
|
||||||
},
|
|
||||||
isEdit: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
editBalancer(index) {
|
|
||||||
const oldTag = this.balancersData[index].tag;
|
|
||||||
balancerModal.show({
|
|
||||||
title: '{{ i18n "pages.xray.balancer.editBalancer"}}',
|
|
||||||
okText: '{{ i18n "sure" }}',
|
|
||||||
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
|
|
||||||
balancer: this.balancersData[index],
|
|
||||||
confirm: (balancer) => {
|
|
||||||
balancerModal.loading();
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
|
|
||||||
let tmpBalancer = {
|
|
||||||
'tag': balancer.tag,
|
|
||||||
'selector': balancer.selector
|
|
||||||
};
|
|
||||||
if (balancer.strategy == 'roundRobin') {
|
|
||||||
tmpBalancer.strategy = {
|
|
||||||
'type': balancer.strategy
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
newTemplateSettings.routing.balancers[index] = tmpBalancer;
|
|
||||||
// change edited tag if used in rule section
|
|
||||||
if (oldTag != balancer.tag) {
|
|
||||||
newTemplateSettings.routing.rules.forEach((rule) => {
|
|
||||||
if (rule.balancerTag && rule.balancerTag == oldTag) {
|
|
||||||
rule.balancerTag = balancer.tag;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
balancerModal.close();
|
|
||||||
},
|
|
||||||
isEdit: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deleteBalancer(index) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
|
|
||||||
//remove from balancers
|
|
||||||
const oldTag = this.balancersData[index].tag;
|
|
||||||
this.balancersData.splice(index, 1);
|
|
||||||
|
|
||||||
// remove from settings
|
|
||||||
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag == oldTag);
|
|
||||||
newTemplateSettings.routing.balancers.splice(realIndex, 1);
|
|
||||||
|
|
||||||
// remove related routing rules
|
|
||||||
let rules = [];
|
|
||||||
newTemplateSettings.routing.rules.forEach((r) => {
|
|
||||||
if (!r.balancerTag || r.balancerTag != oldTag) {
|
|
||||||
rules.push(r);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
newTemplateSettings.routing.rules = rules;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
addReverse(){
|
addReverse(){
|
||||||
reverseModal.show({
|
reverseModal.show({
|
||||||
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
||||||
@@ -1281,69 +898,9 @@
|
|||||||
newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag);
|
newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag);
|
||||||
}
|
}
|
||||||
newTemplateSettings.routing.rules = newRules;
|
newTemplateSettings.routing.rules = newRules;
|
||||||
|
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
addDNSServer(){
|
|
||||||
dnsModal.show({
|
|
||||||
title: '{{ i18n "pages.xray.dns.add" }}',
|
|
||||||
confirm: (dnsServer) => {
|
|
||||||
dnsServers = this.dnsServers;
|
|
||||||
dnsServers.push(dnsServer);
|
|
||||||
this.dnsServers = dnsServers;
|
|
||||||
dnsModal.close();
|
|
||||||
},
|
|
||||||
isEdit: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
editDNSServer(index){
|
|
||||||
dnsModal.show({
|
|
||||||
title: '{{ i18n "pages.xray.dns.edit" }} #' + (index+1),
|
|
||||||
dnsServer: this.dnsServers[index],
|
|
||||||
confirm: (dnsServer) => {
|
|
||||||
dnsServers = this.dnsServers;
|
|
||||||
dnsServers[index] = dnsServer;
|
|
||||||
this.dnsServers = dnsServers;
|
|
||||||
dnsModal.close();
|
|
||||||
},
|
|
||||||
isEdit: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deleteDNSServer(index){
|
|
||||||
newDnsServers = this.dnsServers;
|
|
||||||
newDnsServers.splice(index,1);
|
|
||||||
this.dnsServers = newDnsServers;
|
|
||||||
},
|
|
||||||
addFakedns() {
|
|
||||||
fakednsModal.show({
|
|
||||||
title: '{{ i18n "pages.xray.fakedns.add" }}',
|
|
||||||
confirm: (item) => {
|
|
||||||
fakeDns = this.fakeDns?? [];
|
|
||||||
fakeDns.push(item);
|
|
||||||
this.fakeDns = fakeDns;
|
|
||||||
fakednsModal.close();
|
|
||||||
},
|
|
||||||
isEdit: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
editFakedns(index){
|
|
||||||
fakednsModal.show({
|
|
||||||
title: '{{ i18n "pages.xray.fakedns.edit" }} #' + (index+1),
|
|
||||||
fakeDns: this.fakeDns[index],
|
|
||||||
confirm: (item) => {
|
|
||||||
fakeDns = this.fakeDns;
|
|
||||||
fakeDns[index] = item;
|
|
||||||
this.fakeDns = fakeDns;
|
|
||||||
fakednsModal.close();
|
|
||||||
},
|
|
||||||
isEdit: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deleteFakedns(index){
|
|
||||||
fakeDns = this.fakeDns;
|
|
||||||
fakeDns.splice(index,1);
|
|
||||||
this.fakeDns = fakeDns;
|
|
||||||
},
|
|
||||||
addRule(){
|
addRule(){
|
||||||
ruleModal.show({
|
ruleModal.show({
|
||||||
title: '{{ i18n "pages.xray.rules.add"}}',
|
title: '{{ i18n "pages.xray.rules.add"}}',
|
||||||
@@ -1390,12 +947,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
if (window.location.protocol !== "https:") {
|
|
||||||
this.showAlert = true;
|
|
||||||
}
|
|
||||||
await this.getXraySetting();
|
await this.getXraySetting();
|
||||||
await this.getXrayResult();
|
await this.getXrayResult();
|
||||||
await this.getOutboundsTraffic();
|
|
||||||
while (true) {
|
while (true) {
|
||||||
await PromiseUtil.sleep(800);
|
await PromiseUtil.sleep(800);
|
||||||
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
||||||
@@ -1451,27 +1004,6 @@
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
balancersData: {
|
|
||||||
get: function () {
|
|
||||||
data = []
|
|
||||||
if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) {
|
|
||||||
this.templateSettings.routing.balancers.forEach((o, index) => {
|
|
||||||
let strategy = "random"
|
|
||||||
if (o.strategy && o.strategy.type == "roundRobin") {
|
|
||||||
strategy = o.strategy.type
|
|
||||||
}
|
|
||||||
|
|
||||||
data.push({
|
|
||||||
'key': index,
|
|
||||||
'tag': o.tag ? o.tag : "",
|
|
||||||
'strategy': strategy,
|
|
||||||
'selector': o.selector ? o.selector : []
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
routingRuleSettings: {
|
routingRuleSettings: {
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
@@ -1533,39 +1065,6 @@
|
|||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setLogLevel: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.loglevel) return "warning";
|
|
||||||
return this.templateSettings.log.loglevel;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.log.loglevel = newValue;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
accessLog: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "";
|
|
||||||
return this.templateSettings.log.access;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.log.access = newValue;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
errorLog: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.error) return "";
|
|
||||||
return this.templateSettings.log.error;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.log.error = newValue;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blockedIPs: {
|
blockedIPs: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
||||||
@@ -1688,14 +1187,14 @@
|
|||||||
familyProtectSettings: {
|
familyProtectSettings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
||||||
return doAllItemsExist(this.settingsData.familyProtectDNS.servers, this.templateSettings.dns.servers);
|
return doAllItemsExist(this.templateSettings.dns.servers, this.settingsData.familyProtectDNS.servers);
|
||||||
},
|
},
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
newTemplateSettings = this.templateSettings;
|
newTemplateSettings = this.templateSettings;
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
||||||
} else {
|
} else {
|
||||||
newTemplateSettings.dns.servers = newTemplateSettings.dns?.servers?.filter(data => !this.settingsData.familyProtectDNS.servers.includes(data))
|
delete newTemplateSettings.dns;
|
||||||
}
|
}
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
@@ -1957,42 +1456,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MetaWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.meta, this.warpDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.meta];
|
|
||||||
} else {
|
|
||||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.meta.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AppleWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.apple, this.warpDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.apple];
|
|
||||||
} else {
|
|
||||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.apple.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RedditWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.reddit, this.warpDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.reddit];
|
|
||||||
} else {
|
|
||||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.reddit.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SpotifyWARPSettings: {
|
SpotifyWARPSettings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return doAllItemsExist(this.settingsData.domains.spotify, this.warpDomains);
|
return doAllItemsExist(this.settingsData.domains.spotify, this.warpDomains);
|
||||||
@@ -2005,42 +1468,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
enableDNS: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateSettings ? this.templateSettings.dns != null : false;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.dns = newValue ? { servers: [], queryStrategy: "UseIP" } : null;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dnsStrategy: {
|
|
||||||
get: function () {
|
|
||||||
return this.enableDNS ? this.templateSettings.dns.queryStrategy : null;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.dns.queryStrategy = newValue;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dnsServers: {
|
|
||||||
get: function () { return this.enableDNS ? this.templateSettings.dns.servers : []; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.dns.servers = newValue;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fakeDns: {
|
|
||||||
get: function () { return this.templateSettings && this.templateSettings.fakedns ? this.templateSettings.fakedns : []; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.fakedns = newValue.length >0 ? newValue : null;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
{{define "balancerModal"}}
|
|
||||||
<a-modal
|
|
||||||
id="balancer-modal"
|
|
||||||
v-model="balancerModal.visible"
|
|
||||||
:title="balancerModal.title"
|
|
||||||
@ok="balancerModal.ok"
|
|
||||||
:confirm-loading="balancerModal.confirmLoading"
|
|
||||||
:ok-button-props="{ props: { disabled: !balancerModal.isValid } }"
|
|
||||||
:closable="true"
|
|
||||||
:mask-closable="false"
|
|
||||||
:ok-text="balancerModal.okText"
|
|
||||||
cancel-text='{{ i18n "close" }}'
|
|
||||||
:class="themeSwitcher.currentTheme">
|
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
|
|
||||||
:validate-status="balancerModal.duplicateTag? 'warning' : 'success'">
|
|
||||||
<a-input v-model.trim="balancerModal.balancer.tag" @change="balancerModal.check()"
|
|
||||||
placeholder='{{ i18n "pages.xray.balancer.tagDesc" }}'></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.balancer.balancerStrategy" }}'>
|
|
||||||
<a-select v-model="balancerModal.balancer.strategy" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option value="random">Random</a-select-option>
|
|
||||||
<a-select-option value="roundRobin">Round Robin</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback
|
|
||||||
:validate-status="balancerModal.emptySelector? 'warning' : 'success'">
|
|
||||||
<a-select v-model="balancerModal.balancer.selector" mode="tags" @change="balancerModal.checkSelector()"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</table>
|
|
||||||
</a-form>
|
|
||||||
</a-modal>
|
|
||||||
<script>
|
|
||||||
const balancerModal = {
|
|
||||||
title: '',
|
|
||||||
visible: false,
|
|
||||||
confirmLoading: false,
|
|
||||||
okText: '{{ i18n "sure" }}',
|
|
||||||
isEdit: false,
|
|
||||||
confirm: null,
|
|
||||||
duplicateTag: false,
|
|
||||||
emptySelector: false,
|
|
||||||
balancer: {
|
|
||||||
tag: '',
|
|
||||||
strategy: 'random',
|
|
||||||
selector: []
|
|
||||||
},
|
|
||||||
outboundTags: [],
|
|
||||||
balancerTags:[],
|
|
||||||
ok() {
|
|
||||||
if (balancerModal.balancer.selector.length == 0) {
|
|
||||||
balancerModal.emptySelector = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
balancerModal.emptySelector = false;
|
|
||||||
ObjectUtil.execute(balancerModal.confirm, balancerModal.balancer);
|
|
||||||
},
|
|
||||||
show({ title = '', okText = '{{ i18n "sure" }}', balancerTags = [], balancer, confirm = (balancer) => { }, isEdit = false }) {
|
|
||||||
this.title = title;
|
|
||||||
this.okText = okText;
|
|
||||||
this.confirm = confirm;
|
|
||||||
this.visible = true;
|
|
||||||
if (isEdit) {
|
|
||||||
balancerModal.balancer = balancer;
|
|
||||||
} else {
|
|
||||||
balancerModal.balancer = {
|
|
||||||
tag: '',
|
|
||||||
strategy: 'random',
|
|
||||||
selector: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
|
|
||||||
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
|
||||||
this.isEdit = isEdit;
|
|
||||||
this.check();
|
|
||||||
this.checkSelector();
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
this.visible = false;
|
|
||||||
this.loading(false);
|
|
||||||
},
|
|
||||||
loading(loading=true) {
|
|
||||||
this.confirmLoading = loading;
|
|
||||||
},
|
|
||||||
check() {
|
|
||||||
if (this.balancer.tag == '' || this.balancerTags.includes(this.balancer.tag)) {
|
|
||||||
this.duplicateTag = true;
|
|
||||||
this.isValid = false;
|
|
||||||
} else {
|
|
||||||
this.duplicateTag = false;
|
|
||||||
this.isValid = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
checkSelector() {
|
|
||||||
this.emptySelector = this.balancer.selector.length == 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
delimiters: ['[[', ']]'],
|
|
||||||
el: '#balancer-modal',
|
|
||||||
data: {
|
|
||||||
balancerModal: balancerModal
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{{end}}
|
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
outModal.visible = false;
|
outModal.visible = false;
|
||||||
outModal.loading(false);
|
outModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading) {
|
||||||
outModal.confirmLoading = loading;
|
outModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
check(){
|
check(){
|
||||||
|
|||||||
@@ -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:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :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>
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
reverseModal.visible = false;
|
reverseModal.visible = false;
|
||||||
reverseModal.loading(false);
|
reverseModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading) {
|
||||||
reverseModal.confirmLoading = loading;
|
reverseModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -132,6 +132,8 @@
|
|||||||
reverseModal: reverseModal,
|
reverseModal: reverseModal,
|
||||||
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
|
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -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:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :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>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<a-input v-model.trim="ruleModal.rule.source"></a-input>
|
<a-input v-model.trim="ruleModal.rule.source"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">Source Port
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
@@ -107,19 +107,6 @@
|
|||||||
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
|
||||||
<template slot="label">
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.xray.balancer.balancerDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
Balancer Tag <a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
|
||||||
<a-select v-model="ruleModal.rule.balancerTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option v-for="tag in ruleModal.balancerTags" :value="tag">[[ tag ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</table>
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -146,12 +133,11 @@
|
|||||||
protocol: [],
|
protocol: [],
|
||||||
attrs: [],
|
attrs: [],
|
||||||
outboundTag: "",
|
outboundTag: "",
|
||||||
balancerTag: "",
|
|
||||||
},
|
},
|
||||||
inboundTags: [],
|
inboundTags: [],
|
||||||
outboundTags: [],
|
outboundTags: [],
|
||||||
users: [],
|
users: [],
|
||||||
balancerTags: [],
|
balancerTag: [],
|
||||||
ok() {
|
ok() {
|
||||||
newRule = ruleModal.getResult();
|
newRule = ruleModal.getResult();
|
||||||
ObjectUtil.execute(ruleModal.confirm, newRule);
|
ObjectUtil.execute(ruleModal.confirm, newRule);
|
||||||
@@ -174,7 +160,6 @@
|
|||||||
this.rule.protocol = rule.protocol;
|
this.rule.protocol = rule.protocol;
|
||||||
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
|
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
|
||||||
this.rule.outboundTag = rule.outboundTag;
|
this.rule.outboundTag = rule.outboundTag;
|
||||||
this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : "";
|
|
||||||
} else {
|
} else {
|
||||||
this.rule = {
|
this.rule = {
|
||||||
domainMatcher: "",
|
domainMatcher: "",
|
||||||
@@ -189,29 +174,24 @@
|
|||||||
protocol: [],
|
protocol: [],
|
||||||
attrs: [],
|
attrs: [],
|
||||||
outboundTag: "",
|
outboundTag: "",
|
||||||
balancerTag: "",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||||
this.inboundTags.push(...app.inboundTags);
|
this.inboundTags.push(...app.inboundTags);
|
||||||
this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
|
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
||||||
if(app.templateSettings.reverse){
|
if(app.templateSettings.reverse){
|
||||||
if(app.templateSettings.reverse.bridges) {
|
if(app.templateSettings.reverse.bridges) {
|
||||||
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
|
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
|
||||||
}
|
}
|
||||||
if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
|
if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.templateSettings.routing && app.templateSettings.routing.balancers) {
|
|
||||||
this.balancerTags = [ "", ...app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
ruleModal.visible = false;
|
ruleModal.visible = false;
|
||||||
ruleModal.loading(false);
|
ruleModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading) {
|
||||||
ruleModal.confirmLoading = loading;
|
ruleModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
getResult() {
|
getResult() {
|
||||||
@@ -230,8 +210,7 @@
|
|||||||
rule.inboundTag = value.inboundTag;
|
rule.inboundTag = value.inboundTag;
|
||||||
rule.protocol = value.protocol;
|
rule.protocol = value.protocol;
|
||||||
rule.attrs = Object.fromEntries(value.attrs);
|
rule.attrs = Object.fromEntries(value.attrs);
|
||||||
rule.outboundTag = value.outboundTag == "" ? undefined : value.outboundTag;
|
rule.outboundTag = value.outboundTag;
|
||||||
rule.balancerTag = value.balancerTag == "" ? undefined : value.balancerTag;
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(rule)) {
|
for (const [key, value] of Object.entries(rule)) {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -14,7 +12,6 @@ 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"
|
||||||
)
|
)
|
||||||
@@ -26,6 +23,7 @@ type CheckClientIpJob struct {
|
|||||||
var job *CheckClientIpJob
|
var job *CheckClientIpJob
|
||||||
var ipFiles = []string{
|
var ipFiles = []string{
|
||||||
xray.GetIPLimitLogPath(),
|
xray.GetIPLimitLogPath(),
|
||||||
|
xray.GetIPLimitPrevLogPath(),
|
||||||
xray.GetIPLimitBannedLogPath(),
|
xray.GetIPLimitBannedLogPath(),
|
||||||
xray.GetIPLimitBannedPrevLogPath(),
|
xray.GetIPLimitBannedPrevLogPath(),
|
||||||
xray.GetAccessPersistentLogPath(),
|
xray.GetAccessPersistentLogPath(),
|
||||||
@@ -39,10 +37,8 @@ func NewCheckClientIpJob() *CheckClientIpJob {
|
|||||||
|
|
||||||
func (j *CheckClientIpJob) Run() {
|
func (j *CheckClientIpJob) Run() {
|
||||||
|
|
||||||
// create files and dirs required for iplimit if not exists
|
// create files required for iplimit if not exists
|
||||||
for i := 0; i < len(ipFiles); i++ {
|
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)
|
file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
@@ -53,37 +49,6 @@ func (j *CheckClientIpJob) Run() {
|
|||||||
j.checkFail2BanInstalled()
|
j.checkFail2BanInstalled()
|
||||||
j.processLogFile()
|
j.processLogFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !j.hasLimitIp() && xray.GetAccessLogPath() == "./access.log" {
|
|
||||||
go j.clearLogTime()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *CheckClientIpJob) clearLogTime() {
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Hour)
|
|
||||||
j.clearAccessLog()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *CheckClientIpJob) clearAccessLog() {
|
|
||||||
accessLogPath := xray.GetAccessLogPath()
|
|
||||||
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
|
||||||
j.checkError(err)
|
|
||||||
defer logAccessP.Close()
|
|
||||||
|
|
||||||
// reopen the access log file for reading
|
|
||||||
file, err := os.Open(accessLogPath)
|
|
||||||
j.checkError(err)
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// copy access log content to persistent file
|
|
||||||
_, err = io.Copy(logAccessP, file)
|
|
||||||
j.checkError(err)
|
|
||||||
|
|
||||||
// clean access log
|
|
||||||
err = os.Truncate(accessLogPath, 0)
|
|
||||||
j.checkError(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) hasLimitIp() bool {
|
func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||||
@@ -127,34 +92,24 @@ func (j *CheckClientIpJob) checkFail2BanInstalled() {
|
|||||||
|
|
||||||
func (j *CheckClientIpJob) processLogFile() {
|
func (j *CheckClientIpJob) processLogFile() {
|
||||||
accessLogPath := xray.GetAccessLogPath()
|
accessLogPath := xray.GetAccessLogPath()
|
||||||
|
|
||||||
if accessLogPath == "none" {
|
|
||||||
logger.Warning("Access log is set to 'none' check your Xray Configs")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if accessLogPath == "" {
|
if accessLogPath == "" {
|
||||||
logger.Warning("Access log doesn't exist in your Xray Configs")
|
logger.Warning("access.log doesn't exist in your config.json")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(accessLogPath)
|
data, err := os.ReadFile(accessLogPath)
|
||||||
j.checkError(err)
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
InboundClientIps := make(map[string][]string)
|
InboundClientIps := make(map[string][]string)
|
||||||
|
j.checkError(err)
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
lines := strings.Split(string(data), "\n")
|
||||||
for scanner.Scan() {
|
for _, line := range lines {
|
||||||
line := scanner.Text()
|
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
||||||
|
|
||||||
ipRegx, _ := regexp.Compile(`(\d+\.\d+\.\d+\.\d+).* accepted`)
|
|
||||||
emailRegx, _ := regexp.Compile(`email:.+`)
|
emailRegx, _ := regexp.Compile(`email:.+`)
|
||||||
|
|
||||||
matches := ipRegx.FindStringSubmatch(line)
|
matchesIp := ipRegx.FindString(line)
|
||||||
if len(matches) > 1 {
|
if len(matchesIp) > 0 {
|
||||||
ip := matches[1]
|
ip := string(matchesIp)
|
||||||
if ip == "127.0.0.1" {
|
if ip == "127.0.0.1" || ip == "1.1.1.1" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,14 +124,13 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
j.checkError(scanner.Err())
|
|
||||||
|
|
||||||
shouldCleanLog := false
|
shouldCleanLog := false
|
||||||
|
|
||||||
for clientEmail, ips := range InboundClientIps {
|
for clientEmail, ips := range InboundClientIps {
|
||||||
@@ -187,13 +141,27 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
} else {
|
} else {
|
||||||
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// added delay before cleaning logs to reduce chance of logging IP that already has been banned
|
// added delay before cleaning logs to reduce chance of logging IP that already has been banned
|
||||||
time.Sleep(time.Second * 2)
|
time.Sleep(time.Second * 2)
|
||||||
|
|
||||||
if shouldCleanLog {
|
if shouldCleanLog {
|
||||||
j.clearAccessLog()
|
// copy access log to persistent file
|
||||||
|
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
|
j.checkError(err)
|
||||||
|
input, err := os.ReadFile(accessLogPath)
|
||||||
|
j.checkError(err)
|
||||||
|
if _, err := logAccessP.Write(input); err != nil {
|
||||||
|
j.checkError(err)
|
||||||
|
}
|
||||||
|
defer logAccessP.Close()
|
||||||
|
|
||||||
|
// clean access log
|
||||||
|
if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
|
||||||
|
j.checkError(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ func NewClearLogsJob() *ClearLogsJob {
|
|||||||
// Here Run is an interface method of the Job interface
|
// Here Run is an interface method of the Job interface
|
||||||
func (j *ClearLogsJob) Run() {
|
func (j *ClearLogsJob) Run() {
|
||||||
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
|
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
|
||||||
logFilesPrev := []string{xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()}
|
logFilesPrev := []string{xray.GetIPLimitPrevLogPath(), xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()}
|
||||||
|
|
||||||
// clear old previous logs
|
// clear old previous logs
|
||||||
for i := 0; i < len(logFilesPrev); i++ {
|
for i := 0; i < len(logFilesPrev); i++ {
|
||||||
if err := os.Truncate(logFilesPrev[i], 0); err != nil {
|
if err := os.Truncate(logFilesPrev[i], 0); err != nil {
|
||||||
@@ -26,26 +26,25 @@ func (j *ClearLogsJob) Run() {
|
|||||||
|
|
||||||
// clear log files and copy to previous logs
|
// clear log files and copy to previous logs
|
||||||
for i := 0; i < len(logFiles); i++ {
|
for i := 0; i < len(logFiles); i++ {
|
||||||
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], os.O_CREATE|os.O_APPEND|os.O_RDWR, 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])
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("clear logs job err:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = logFilePrev.Write(logFile)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("clear logs job err:", err)
|
|
||||||
}
|
|
||||||
defer logFilePrev.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := os.Truncate(logFiles[i], 0)
|
logFile, err := os.ReadFile(logFiles[i])
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("clear logs job err:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = logFilePrev.Write(logFile)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("clear logs job err:", err)
|
||||||
|
}
|
||||||
|
defer logFilePrev.Close()
|
||||||
|
|
||||||
|
err = os.Truncate(logFiles[i], 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("clear logs job err:", err)
|
logger.Warning("clear logs job err:", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type XrayTrafficJob struct {
|
type XrayTrafficJob struct {
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
outboundService service.OutboundService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXrayTrafficJob() *XrayTrafficJob {
|
func NewXrayTrafficJob() *XrayTrafficJob {
|
||||||
@@ -25,15 +24,11 @@ func (j *XrayTrafficJob) Run() {
|
|||||||
logger.Warning("get xray traffic failed:", err)
|
logger.Warning("get xray traffic failed:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)
|
err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("add inbound traffic failed:", err)
|
logger.Warning("add traffic failed:", err)
|
||||||
}
|
}
|
||||||
err, needRestart1 := j.outboundService.AddTraffic(traffics, clientTraffics)
|
if needRestart {
|
||||||
if err != nil {
|
|
||||||
logger.Warning("add outbound traffic failed:", err)
|
|
||||||
}
|
|
||||||
if needRestart0 || needRestart1 {
|
|
||||||
j.xrayService.SetToNeedRestart()
|
j.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"access": "none",
|
"loglevel": "warning",
|
||||||
"dnsLog": false,
|
"error": "./error.log"
|
||||||
"error": "./error.log",
|
|
||||||
"loglevel": "warning"
|
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
@@ -45,9 +43,7 @@
|
|||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"statsInboundDownlink": true,
|
"statsInboundDownlink": true,
|
||||||
"statsInboundUplink": true,
|
"statsInboundUplink": true
|
||||||
"statsOutboundDownlink": true,
|
|
||||||
"statsOutboundUplink": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"routing": {
|
"routing": {
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
oldInbound.StreamSettings = inbound.StreamSettings
|
oldInbound.StreamSettings = inbound.StreamSettings
|
||||||
oldInbound.Sniffing = inbound.Sniffing
|
oldInbound.Sniffing = inbound.Sniffing
|
||||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||||
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
oldInbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
|
||||||
} else {
|
} else {
|
||||||
oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
||||||
}
|
}
|
||||||
@@ -682,7 +682,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 +694,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
|
||||||
}
|
}
|
||||||
@@ -1814,7 +1814,7 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
clients, ok := settings["clients"].([]interface{})
|
clients, ok := settings["clients"].([]interface{})
|
||||||
if ok {
|
if ok {
|
||||||
// Fix Client configuration problems
|
// Fix Clinet configuration problems
|
||||||
var newClients []interface{}
|
var newClients []interface{}
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]interface{})
|
||||||
@@ -1900,13 +1900,6 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
newStream, _ := json.MarshalIndent(stream, " ", " ")
|
newStream, _ := json.MarshalIndent(stream, " ", " ")
|
||||||
tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream)
|
tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Raw(`UPDATE inbounds
|
|
||||||
SET tag = REPLACE(tag, '0.0.0.0:', '')
|
|
||||||
WHERE INSTR(tag, '0.0.0.0:') > 0;`).Error
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) MigrateDB() {
|
func (s *InboundService) MigrateDB() {
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"x-ui/database"
|
|
||||||
"x-ui/database/model"
|
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/xray"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OutboundService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
|
||||||
var err error
|
|
||||||
db := database.GetDB()
|
|
||||||
tx := db.Begin()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
} else {
|
|
||||||
tx.Commit()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = s.addOutboundTraffic(tx, traffics)
|
|
||||||
if err != nil {
|
|
||||||
return err, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OutboundService) addOutboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
|
||||||
if len(traffics) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for _, traffic := range traffics {
|
|
||||||
if traffic.IsOutbound {
|
|
||||||
|
|
||||||
var outbound model.OutboundTraffics
|
|
||||||
|
|
||||||
err = tx.Model(&model.OutboundTraffics{}).Where("tag = ?", traffic.Tag).
|
|
||||||
FirstOrCreate(&outbound).Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
outbound.Tag = traffic.Tag
|
|
||||||
outbound.Up = outbound.Up + traffic.Up
|
|
||||||
outbound.Down = outbound.Down + traffic.Down
|
|
||||||
outbound.Total = outbound.Up + outbound.Down
|
|
||||||
|
|
||||||
err = tx.Save(&outbound).Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
var traffics []*model.OutboundTraffics
|
|
||||||
|
|
||||||
err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return traffics, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OutboundService) ResetOutboundTraffic(tag string) error {
|
|
||||||
db := database.GetDB()
|
|
||||||
|
|
||||||
whereText := "tag "
|
|
||||||
if tag == "-alltags-" {
|
|
||||||
whereText += " <> ?"
|
|
||||||
} else {
|
|
||||||
whereText += " = ?"
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.Model(model.OutboundTraffics{}).
|
|
||||||
Where(whereText, tag).
|
|
||||||
Updates(map[string]interface{}{"up": 0, "down": 0, "total": 0})
|
|
||||||
|
|
||||||
err := result.Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -57,9 +57,6 @@ var defaultValueMap = map[string]string{
|
|||||||
"subEncrypt": "true",
|
"subEncrypt": "true",
|
||||||
"subShowInfo": "true",
|
"subShowInfo": "true",
|
||||||
"subURI": "",
|
"subURI": "",
|
||||||
"subJsonPath": "/json/",
|
|
||||||
"subJsonURI": "",
|
|
||||||
"subJsonFragment": "",
|
|
||||||
"datepicker": "gregorian",
|
"datepicker": "gregorian",
|
||||||
"warp": "",
|
"warp": "",
|
||||||
}
|
}
|
||||||
@@ -390,11 +387,17 @@ func (s *SettingService) GetSubPort() (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubPath() (string, error) {
|
func (s *SettingService) GetSubPath() (string, error) {
|
||||||
return s.getString("subPath")
|
subPath, err := s.getString("subPath")
|
||||||
}
|
if err != nil {
|
||||||
|
return "", err
|
||||||
func (s *SettingService) GetSubJsonPath() (string, error) {
|
}
|
||||||
return s.getString("subJsonPath")
|
if !strings.HasPrefix(subPath, "/") {
|
||||||
|
subPath = "/" + subPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(subPath, "/") {
|
||||||
|
subPath += "/"
|
||||||
|
}
|
||||||
|
return subPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubDomain() (string, error) {
|
func (s *SettingService) GetSubDomain() (string, error) {
|
||||||
@@ -409,8 +412,8 @@ func (s *SettingService) GetSubKeyFile() (string, error) {
|
|||||||
return s.getString("subKeyFile")
|
return s.getString("subKeyFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubUpdates() (string, error) {
|
func (s *SettingService) GetSubUpdates() (int, error) {
|
||||||
return s.getString("subUpdates")
|
return s.getInt("subUpdates")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubEncrypt() (bool, error) {
|
func (s *SettingService) GetSubEncrypt() (bool, error) {
|
||||||
@@ -429,14 +432,6 @@ func (s *SettingService) GetSubURI() (string, error) {
|
|||||||
return s.getString("subURI")
|
return s.getString("subURI")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubJsonURI() (string, error) {
|
|
||||||
return s.getString("subJsonURI")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetSubJsonFragment() (string, error) {
|
|
||||||
return s.getString("subJsonFragment")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetDatepicker() (string, error) {
|
func (s *SettingService) GetDatepicker() (string, error) {
|
||||||
return s.getString("datepicker")
|
return s.getString("datepicker")
|
||||||
}
|
}
|
||||||
@@ -489,7 +484,6 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
|||||||
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
|
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
|
||||||
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
||||||
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
||||||
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
|
|
||||||
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
||||||
"datepicker": func() (interface{}, error) { return s.GetDatepicker() },
|
"datepicker": func() (interface{}, error) { return s.GetDatepicker() },
|
||||||
}
|
}
|
||||||
@@ -504,11 +498,10 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
|||||||
result[key] = value
|
result[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(string) == "") {
|
if result["subEnable"].(bool) && result["subURI"].(string) == "" {
|
||||||
subURI := ""
|
subURI := ""
|
||||||
subPort, _ := s.GetSubPort()
|
subPort, _ := s.GetSubPort()
|
||||||
subPath, _ := s.GetSubPath()
|
subPath, _ := s.GetSubPath()
|
||||||
subJsonPath, _ := s.GetSubJsonPath()
|
|
||||||
subDomain, _ := s.GetSubDomain()
|
subDomain, _ := s.GetSubDomain()
|
||||||
subKeyFile, _ := s.GetSubKeyFile()
|
subKeyFile, _ := s.GetSubKeyFile()
|
||||||
subCertFile, _ := s.GetSubCertFile()
|
subCertFile, _ := s.GetSubCertFile()
|
||||||
@@ -529,12 +522,12 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
|||||||
} else {
|
} else {
|
||||||
subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
|
subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
|
||||||
}
|
}
|
||||||
if result["subURI"].(string) == "" {
|
if subPath[0] == byte('/') {
|
||||||
result["subURI"] = subURI + subPath
|
subURI += subPath
|
||||||
}
|
} else {
|
||||||
if result["subJsonURI"].(string) == "" {
|
subURI += "/" + subPath
|
||||||
result["subJsonURI"] = subURI + subJsonPath
|
|
||||||
}
|
}
|
||||||
|
result["subURI"] = subURI
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
@@ -115,19 +115,14 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) NewBot(token string, proxyUrl string) (*telego.Bot, error) {
|
func (t *Tgbot) NewBot(token string, proxyUrl string) (*telego.Bot, error) {
|
||||||
if proxyUrl == "" {
|
if proxyUrl == "" || !strings.HasPrefix(proxyUrl, "socks5://") {
|
||||||
// No proxy URL provided, use default instance
|
logger.Warning("invalid socks5 url, start with default")
|
||||||
return telego.NewBot(token)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(proxyUrl, "socks5://") {
|
|
||||||
logger.Warning("Invalid socks5 URL, starting with default")
|
|
||||||
return telego.NewBot(token)
|
return telego.NewBot(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := url.Parse(proxyUrl)
|
_, err := url.Parse(proxyUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Can't parse proxy URL, using default instance for tgbot:", err)
|
logger.Warning("cant parse proxy url, use default instance for tgbot:", err)
|
||||||
return telego.NewBot(token)
|
return telego.NewBot(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,13 +197,9 @@ func (t *Tgbot) OnReceive() {
|
|||||||
}, th.AnyCallbackQueryWithMessage())
|
}, th.AnyCallbackQueryWithMessage())
|
||||||
|
|
||||||
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
|
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
|
||||||
if message.UsersShared != nil {
|
if message.UserShared != nil {
|
||||||
if checkAdmin(message.From.ID) {
|
if checkAdmin(message.From.ID) {
|
||||||
userIDsStr := ""
|
err := t.inboundService.SetClientTelegramUserID(message.UserShared.RequestID, strconv.FormatInt(message.UserShared.UserID, 10))
|
||||||
for _, userID := range message.UsersShared.UserIDs {
|
|
||||||
userIDsStr += strconv.FormatInt(userID, 10) + " "
|
|
||||||
}
|
|
||||||
err := t.inboundService.SetClientTelegramUserID(message.UsersShared.RequestID, userIDsStr)
|
|
||||||
output := ""
|
output := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output += t.I18nBot("tgbot.messages.selectUserFailed")
|
output += t.I18nBot("tgbot.messages.selectUserFailed")
|
||||||
@@ -269,7 +260,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
|||||||
msg += t.I18nBot("tgbot.commands.unknown")
|
msg += t.I18nBot("tgbot.commands.unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg != "" {
|
if msg != ""{
|
||||||
if onlyMessage {
|
if onlyMessage {
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
return
|
return
|
||||||
@@ -281,7 +272,7 @@ 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.Chat.ID
|
||||||
|
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
// get query from hash storage
|
// get query from hash storage
|
||||||
@@ -300,22 +291,22 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
t.searchClient(chatId, email)
|
t.searchClient(chatId, email)
|
||||||
case "client_refresh":
|
case "client_refresh":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clientRefreshSuccess", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clientRefreshSuccess", "Email=="+email))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "client_cancel":
|
case "client_cancel":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "ips_refresh":
|
case "ips_refresh":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.IpRefreshSuccess", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.IpRefreshSuccess", "Email=="+email))
|
||||||
t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClientIps(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "ips_cancel":
|
case "ips_cancel":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
||||||
t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClientIps(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "tgid_refresh":
|
case "tgid_refresh":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.TGIdRefreshSuccess", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.TGIdRefreshSuccess", "Email=="+email))
|
||||||
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
|
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "tgid_cancel":
|
case "tgid_cancel":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
||||||
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
|
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "reset_traffic":
|
case "reset_traffic":
|
||||||
inlineKeyboard := tu.InlineKeyboard(
|
inlineKeyboard := tu.InlineKeyboard(
|
||||||
tu.InlineKeyboardRow(
|
tu.InlineKeyboardRow(
|
||||||
@@ -325,13 +316,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic_c "+email)),
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic_c "+email)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
case "reset_traffic_c":
|
case "reset_traffic_c":
|
||||||
err := t.inboundService.ResetClientTrafficByEmail(email)
|
err := t.inboundService.ResetClientTrafficByEmail(email)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.xrayService.SetToNeedRestart()
|
t.xrayService.SetToNeedRestart()
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetTrafficSuccess", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetTrafficSuccess", "Email=="+email))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
} else {
|
} else {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
}
|
}
|
||||||
@@ -355,7 +346,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 40")),
|
tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 40")),
|
||||||
),
|
),
|
||||||
tu.InlineKeyboardRow(
|
tu.InlineKeyboardRow(
|
||||||
tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 50")),
|
tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")),
|
||||||
tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")),
|
tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")),
|
||||||
tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 80")),
|
tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 80")),
|
||||||
),
|
),
|
||||||
@@ -365,7 +356,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton("200 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 200")),
|
tu.InlineKeyboardButton("200 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 200")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
case "limit_traffic_c":
|
case "limit_traffic_c":
|
||||||
if len(dataArray) == 3 {
|
if len(dataArray) == 3 {
|
||||||
limitTraffic, err := strconv.Atoi(dataArray[2])
|
limitTraffic, err := strconv.Atoi(dataArray[2])
|
||||||
@@ -374,13 +365,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.xrayService.SetToNeedRestart()
|
t.xrayService.SetToNeedRestart()
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.setTrafficLimitSuccess", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.setTrafficLimitSuccess", "Email=="+email))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "limit_traffic_in":
|
case "limit_traffic_in":
|
||||||
if len(dataArray) >= 3 {
|
if len(dataArray) >= 3 {
|
||||||
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
||||||
@@ -436,12 +427,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
|
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "reset_exp":
|
case "reset_exp":
|
||||||
inlineKeyboard := tu.InlineKeyboard(
|
inlineKeyboard := tu.InlineKeyboard(
|
||||||
tu.InlineKeyboardRow(
|
tu.InlineKeyboardRow(
|
||||||
@@ -468,7 +459,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 12 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 365")),
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 12 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 365")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
case "reset_exp_c":
|
case "reset_exp_c":
|
||||||
if len(dataArray) == 3 {
|
if len(dataArray) == 3 {
|
||||||
days, err := strconv.Atoi(dataArray[2])
|
days, err := strconv.Atoi(dataArray[2])
|
||||||
@@ -503,13 +494,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.xrayService.SetToNeedRestart()
|
t.xrayService.SetToNeedRestart()
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.expireResetSuccess", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.expireResetSuccess", "Email=="+email))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "reset_exp_in":
|
case "reset_exp_in":
|
||||||
if len(dataArray) >= 3 {
|
if len(dataArray) >= 3 {
|
||||||
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
||||||
@@ -565,12 +556,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
|
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "ip_limit":
|
case "ip_limit":
|
||||||
inlineKeyboard := tu.InlineKeyboard(
|
inlineKeyboard := tu.InlineKeyboard(
|
||||||
tu.InlineKeyboardRow(
|
tu.InlineKeyboardRow(
|
||||||
@@ -599,7 +590,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton("10").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 10")),
|
tu.InlineKeyboardButton("10").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 10")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
case "ip_limit_c":
|
case "ip_limit_c":
|
||||||
if len(dataArray) == 3 {
|
if len(dataArray) == 3 {
|
||||||
count, err := strconv.Atoi(dataArray[2])
|
count, err := strconv.Atoi(dataArray[2])
|
||||||
@@ -608,13 +599,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.xrayService.SetToNeedRestart()
|
t.xrayService.SetToNeedRestart()
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetIpSuccess", "Email=="+email, "Count=="+strconv.Itoa(count)))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetIpSuccess", "Email=="+email, "Count=="+strconv.Itoa(count)))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "ip_limit_in":
|
case "ip_limit_in":
|
||||||
if len(dataArray) >= 3 {
|
if len(dataArray) >= 3 {
|
||||||
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
||||||
@@ -670,12 +661,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
|
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
case "clear_ips":
|
case "clear_ips":
|
||||||
inlineKeyboard := tu.InlineKeyboard(
|
inlineKeyboard := tu.InlineKeyboard(
|
||||||
tu.InlineKeyboardRow(
|
tu.InlineKeyboardRow(
|
||||||
@@ -685,12 +676,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmClearIps")).WithCallbackData(t.encodeQuery("clear_ips_c "+email)),
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmClearIps")).WithCallbackData(t.encodeQuery("clear_ips_c "+email)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
case "clear_ips_c":
|
case "clear_ips_c":
|
||||||
err := t.inboundService.ClearClientIps(email)
|
err := t.inboundService.ClearClientIps(email)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clearIpSuccess", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clearIpSuccess", "Email=="+email))
|
||||||
t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClientIps(chatId, email, callbackQuery.Message.MessageID)
|
||||||
} else {
|
} else {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
}
|
}
|
||||||
@@ -709,7 +700,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmRemoveTGUser")).WithCallbackData(t.encodeQuery("tgid_remove_c "+email)),
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmRemoveTGUser")).WithCallbackData(t.encodeQuery("tgid_remove_c "+email)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
case "tgid_remove_c":
|
case "tgid_remove_c":
|
||||||
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
||||||
if err != nil || traffic == nil {
|
if err != nil || traffic == nil {
|
||||||
@@ -719,7 +710,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
err = t.inboundService.SetClientTelegramUserID(traffic.Id, "")
|
err = t.inboundService.SetClientTelegramUserID(traffic.Id, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.removedTGUserSuccess", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.removedTGUserSuccess", "Email=="+email))
|
||||||
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
|
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID)
|
||||||
} else {
|
} else {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
}
|
}
|
||||||
@@ -732,7 +723,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmToggle")).WithCallbackData(t.encodeQuery("toggle_enable_c "+email)),
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmToggle")).WithCallbackData(t.encodeQuery("toggle_enable_c "+email)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
|
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
case "toggle_enable_c":
|
case "toggle_enable_c":
|
||||||
enabled, err := t.inboundService.ToggleClientEnableByEmail(email)
|
enabled, err := t.inboundService.ToggleClientEnableByEmail(email)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -742,7 +733,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
} else {
|
} else {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.disableSuccess", "Email=="+email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.disableSuccess", "Email=="+email))
|
||||||
}
|
}
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
|
||||||
} else {
|
} else {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
}
|
}
|
||||||
@@ -778,7 +769,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
t.onlineClients(chatId)
|
t.onlineClients(chatId)
|
||||||
case "onlines_refresh":
|
case "onlines_refresh":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
||||||
t.onlineClients(chatId, callbackQuery.Message.GetMessageID())
|
t.onlineClients(chatId, callbackQuery.Message.MessageID)
|
||||||
case "commands":
|
case "commands":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
||||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands"))
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands"))
|
||||||
@@ -1031,7 +1022,7 @@ func (t *Tgbot) getInboundUsages() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic, printEnabled bool, printOnline bool, printActive bool,
|
func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic, printEnabled bool, printOnline bool, printActive bool,
|
||||||
printDate bool, printTraffic bool, printRefreshed bool) string {
|
printDate bool, printTraffic bool, printRefreshed bool) string {
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
@@ -1219,13 +1210,13 @@ func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...
|
|||||||
t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
|
t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
|
||||||
} else {
|
} else {
|
||||||
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
|
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
|
||||||
requestUser := telego.KeyboardButtonRequestUsers{
|
requestUser := telego.KeyboardButtonRequestUser{
|
||||||
RequestID: int32(traffic.Id),
|
RequestID: int32(traffic.Id),
|
||||||
UserIsBot: new(bool),
|
UserIsBot: new(bool),
|
||||||
}
|
}
|
||||||
keyboard := tu.Keyboard(
|
keyboard := tu.Keyboard(
|
||||||
tu.KeyboardRow(
|
tu.KeyboardRow(
|
||||||
tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUsers(&requestUser),
|
tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUser(&requestUser),
|
||||||
),
|
),
|
||||||
tu.KeyboardRow(
|
tu.KeyboardRow(
|
||||||
tu.KeyboardButton(t.I18nBot("tgbot.buttons.closeKeyboard")),
|
tu.KeyboardButton(t.I18nBot("tgbot.buttons.closeKeyboard")),
|
||||||
@@ -1389,6 +1380,7 @@ func (t *Tgbot) getExhausted(chatId int64) {
|
|||||||
output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients"))
|
output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients"))
|
||||||
output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients)))
|
output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients)))
|
||||||
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(exhaustedCC))
|
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(exhaustedCC))
|
||||||
|
|
||||||
|
|
||||||
if exhaustedCC > 0 {
|
if exhaustedCC > 0 {
|
||||||
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.clients"))
|
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.clients"))
|
||||||
@@ -1498,6 +1490,7 @@ func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
|
|||||||
output := t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(onlinesCount))
|
output := t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(onlinesCount))
|
||||||
keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow(
|
keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow(
|
||||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("onlines_refresh"))))
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("onlines_refresh"))))
|
||||||
|
|
||||||
|
|
||||||
if onlinesCount > 0 {
|
if onlinesCount > 0 {
|
||||||
var buttons []telego.InlineKeyboardButton
|
var buttons []telego.InlineKeyboardButton
|
||||||
@@ -1572,44 +1565,30 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
|||||||
|
|
||||||
file, err := os.Open(xray.GetIPLimitBannedPrevLogPath())
|
file, err := os.Open(xray.GetIPLimitBannedPrevLogPath())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Check if the file is non-empty before attempting to upload
|
document := tu.Document(
|
||||||
fileInfo, _ := file.Stat()
|
tu.ID(chatId),
|
||||||
if fileInfo.Size() > 0 {
|
tu.File(file),
|
||||||
document := tu.Document(
|
)
|
||||||
tu.ID(chatId),
|
_, err = bot.SendDocument(document)
|
||||||
tu.File(file),
|
if err != nil {
|
||||||
)
|
logger.Error("Error in uploading backup: ", err)
|
||||||
_, err = bot.SendDocument(document)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Error in uploading IPLimitBannedPrevLog: ", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Warning("IPLimitBannedPrevLog file is empty, not uploading.")
|
|
||||||
}
|
}
|
||||||
file.Close()
|
|
||||||
} else {
|
} else {
|
||||||
logger.Error("Error in opening IPLimitBannedPrevLog file for backup: ", err)
|
logger.Error("Error in opening db file for backup: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err = os.Open(xray.GetIPLimitBannedLogPath())
|
file, err = os.Open(xray.GetIPLimitBannedLogPath())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Check if the file is non-empty before attempting to upload
|
document := tu.Document(
|
||||||
fileInfo, _ := file.Stat()
|
tu.ID(chatId),
|
||||||
if fileInfo.Size() > 0 {
|
tu.File(file),
|
||||||
document := tu.Document(
|
)
|
||||||
tu.ID(chatId),
|
_, err = bot.SendDocument(document)
|
||||||
tu.File(file),
|
if err != nil {
|
||||||
)
|
logger.Error("Error in uploading config.json: ", err)
|
||||||
_, err = bot.SendDocument(document)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Error in uploading IPLimitBannedLog: ", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Warning("IPLimitBannedLog file is empty, not uploading.")
|
|
||||||
}
|
}
|
||||||
file.Close()
|
|
||||||
} else {
|
} else {
|
||||||
logger.Error("Error in opening IPLimitBannedLog file for backup: ", err)
|
logger.Error("Error in opening config.json file for backup: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
if !clientTraffic.Enable {
|
if !clientTraffic.Enable {
|
||||||
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")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"online" = "Online"
|
"online" = "Online"
|
||||||
"domainName" = "Domain Name"
|
"domainName" = "Domain Name"
|
||||||
"monitor" = "Listen IP"
|
"monitor" = "Listen IP"
|
||||||
"certificate" = "Digital Certificate"
|
"certificate" = "Certificate"
|
||||||
"fail" = " Failed"
|
"fail" = " Failed"
|
||||||
"success" = " Successful"
|
"success" = " Successful"
|
||||||
"getVersion" = "Get Version"
|
"getVersion" = "Get Version"
|
||||||
@@ -52,14 +52,6 @@
|
|||||||
"secretToken" = "Secret Token"
|
"secretToken" = "Secret Token"
|
||||||
"remained" = "Remained"
|
"remained" = "Remained"
|
||||||
"security" = "Security"
|
"security" = "Security"
|
||||||
"secAlertTitle" = "Security Alert"
|
|
||||||
"secAlertSsl" = "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection."
|
|
||||||
"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"
|
||||||
@@ -70,7 +62,6 @@
|
|||||||
"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"
|
||||||
|
|
||||||
@@ -85,7 +76,7 @@
|
|||||||
"title" = "Overview"
|
"title" = "Overview"
|
||||||
"memory" = "RAM"
|
"memory" = "RAM"
|
||||||
"hard" = "Disk"
|
"hard" = "Disk"
|
||||||
"xrayStatus" = "Xray"
|
"xrayStatus" = "Status"
|
||||||
"stopXray" = "Stop"
|
"stopXray" = "Stop"
|
||||||
"restartXray" = "Restart"
|
"restartXray" = "Restart"
|
||||||
"xraySwitch" = "Version"
|
"xraySwitch" = "Version"
|
||||||
@@ -149,8 +140,10 @@
|
|||||||
"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"
|
||||||
"publicKey" = "Public Key"
|
"publicKeyPath" = "Public Key Path"
|
||||||
"privatekey" = "Private Key"
|
"publicKeyContent" = "Public Key Content"
|
||||||
|
"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"
|
||||||
@@ -309,8 +302,6 @@
|
|||||||
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
|
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
|
||||||
"subURI" = "Reverse Proxy URI"
|
"subURI" = "Reverse Proxy URI"
|
||||||
"subURIDesc" = "The URI path of the subscription URL for use behind proxies."
|
"subURIDesc" = "The URI path of the subscription URL for use behind proxies."
|
||||||
"fragment" = "Fragmentation"
|
|
||||||
"fragmentDesc" = "Enable fragmentation for TLS hello packet"
|
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Xray Configs"
|
"title" = "Xray Configs"
|
||||||
@@ -318,10 +309,8 @@
|
|||||||
"restart" = "Restart Xray"
|
"restart" = "Restart Xray"
|
||||||
"basicTemplate" = "Basics"
|
"basicTemplate" = "Basics"
|
||||||
"advancedTemplate" = "Advanced"
|
"advancedTemplate" = "Advanced"
|
||||||
"generalConfigs" = "General"
|
"generalConfigs" = "General Strategy"
|
||||||
"generalConfigsDesc" = "These options will determine general adjustments."
|
"generalConfigsDesc" = "These options will determine general strategy adjustments."
|
||||||
"logConfigs" = "Log"
|
|
||||||
"logConfigsDesc" = "Logs may affect your server's efficiency. It is recommended to enable it wisely only in case of your needs"
|
|
||||||
"blockConfigs" = "Protection Shield"
|
"blockConfigs" = "Protection Shield"
|
||||||
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
|
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
|
||||||
"blockCountryConfigs" = "Block Country"
|
"blockCountryConfigs" = "Block Country"
|
||||||
@@ -392,12 +381,6 @@
|
|||||||
"OpenAIWARPDesc" = "Routes traffic to ChatGPT via WARP."
|
"OpenAIWARPDesc" = "Routes traffic to ChatGPT via WARP."
|
||||||
"NetflixWARP" = "Netflix"
|
"NetflixWARP" = "Netflix"
|
||||||
"NetflixWARPDesc" = "Routes traffic to Netflix via WARP."
|
"NetflixWARPDesc" = "Routes traffic to Netflix via WARP."
|
||||||
"MetaWARP" = "Meta"
|
|
||||||
"MetaWARPDesc" = "Routes traffic to Meta (Instagram, Facebook, WhatsApp, Threads,...) via WARP."
|
|
||||||
"AppleWARP" = "Apple"
|
|
||||||
"AppleWARPDesc" = "Routes traffic to Apple via WARP."
|
|
||||||
"RedditWARP" = "Reddit"
|
|
||||||
"RedditWARPDesc" = "Routes traffic to Reddit via WARP."
|
|
||||||
"SpotifyWARP" = "Spotify"
|
"SpotifyWARP" = "Spotify"
|
||||||
"SpotifyWARPDesc" = "Routes traffic to Spotify via WARP."
|
"SpotifyWARPDesc" = "Routes traffic to Spotify via WARP."
|
||||||
"IRWARP" = "Iran domains"
|
"IRWARP" = "Iran domains"
|
||||||
@@ -405,17 +388,10 @@
|
|||||||
"Inbounds" = "Inbounds"
|
"Inbounds" = "Inbounds"
|
||||||
"InboundsDesc" = "Accepting the specific clients."
|
"InboundsDesc" = "Accepting the specific clients."
|
||||||
"Outbounds" = "Outbounds"
|
"Outbounds" = "Outbounds"
|
||||||
"Balancers" = "Balancers"
|
|
||||||
"OutboundsDesc" = "Set the outgoing traffic pathway."
|
"OutboundsDesc" = "Set the outgoing traffic pathway."
|
||||||
"Routings" = "Routing Rules"
|
"Routings" = "Routing Rules"
|
||||||
"RoutingsDesc" = "The priority of each rule is important!"
|
"RoutingsDesc" = "The priority of each rule is important!"
|
||||||
"completeTemplate" = "All"
|
"completeTemplate" = "All"
|
||||||
"logLevel" = "Log Level"
|
|
||||||
"logLevelDesc" = "The log level for error logs, indicating the information that needs to be recorded."
|
|
||||||
"accessLog" = "Access Log"
|
|
||||||
"accessLogDesc" = "The file path for the access log. The special value 'none' disabled access logs"
|
|
||||||
"errorLog" = "Error Log"
|
|
||||||
"errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs"
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "First"
|
"first" = "First"
|
||||||
@@ -426,7 +402,6 @@
|
|||||||
"dest" = "Destination"
|
"dest" = "Destination"
|
||||||
"inbound" = "Inbound"
|
"inbound" = "Inbound"
|
||||||
"outbound" = "Outbound"
|
"outbound" = "Outbound"
|
||||||
"balancer" = "Balancer"
|
|
||||||
"info" = "Info"
|
"info" = "Info"
|
||||||
"add" = "Add Rule"
|
"add" = "Add Rule"
|
||||||
"edit" = "Edit Rule"
|
"edit" = "Edit Rule"
|
||||||
@@ -447,15 +422,6 @@
|
|||||||
"portal" = "Portal"
|
"portal" = "Portal"
|
||||||
"intercon" = "Interconnection"
|
"intercon" = "Interconnection"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
|
||||||
"addBalancer" = "Add Balancer"
|
|
||||||
"editBalancer" = "Edit Balancer"
|
|
||||||
"balancerStrategy" = "Strategy"
|
|
||||||
"balancerSelectors" = "Selectors"
|
|
||||||
"tag" = "Tag"
|
|
||||||
"tagDesc" = "Unique Tag"
|
|
||||||
"balancerDesc" = "It is not possible to use balancerTag and outboundTag at the same time. If used at the same time, only outboundTag will work."
|
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "Secret Key"
|
"secretKey" = "Secret Key"
|
||||||
"publicKey" = "Public Key"
|
"publicKey" = "Public Key"
|
||||||
@@ -464,21 +430,6 @@
|
|||||||
"psk" = "PreShared Key"
|
"psk" = "PreShared Key"
|
||||||
"domainStrategy" = "Domain Strategy"
|
"domainStrategy" = "Domain Strategy"
|
||||||
|
|
||||||
[pages.xray.dns]
|
|
||||||
"enable" = "Enable DNS"
|
|
||||||
"enableDesc" = "Enable built-in DNS server"
|
|
||||||
"strategy" = "Query Strategy"
|
|
||||||
"strategyDesc" = "Overall strategy to resolve domain names"
|
|
||||||
"add" = "Add Server"
|
|
||||||
"edit" = "Edit Server"
|
|
||||||
"domains" = "Domains"
|
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
|
||||||
"add" = "Add Fake DNS"
|
|
||||||
"edit" = "Edit Fake DNS"
|
|
||||||
"ipPool" = "IP Pool Subnet"
|
|
||||||
"poolSize" = "Pool Size"
|
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Admin"
|
"admin" = "Admin"
|
||||||
"secret" = "Secret Token"
|
"secret" = "Secret Token"
|
||||||
@@ -501,7 +452,7 @@
|
|||||||
"wentWrong" = "❌ Something went wrong!"
|
"wentWrong" = "❌ Something went wrong!"
|
||||||
"noIpRecord" = "❗ No IP Record!"
|
"noIpRecord" = "❗ No IP Record!"
|
||||||
"noInbounds" = "❗ No inbound found!"
|
"noInbounds" = "❗ No inbound found!"
|
||||||
"unlimited" = "♾ Unlimited(Reset)"
|
"unlimited" = "♾ Unlimited"
|
||||||
"add" = "Add"
|
"add" = "Add"
|
||||||
"month" = "Month"
|
"month" = "Month"
|
||||||
"months" = "Months"
|
"months" = "Months"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"check" = "Verificar"
|
"check" = "Verificar"
|
||||||
"indefinite" = "Indefinido"
|
"indefinite" = "Indefinido"
|
||||||
"unlimited" = "Ilimitado"
|
"unlimited" = "Ilimitado"
|
||||||
"none" = "None"
|
"none" = "Ninguno"
|
||||||
"qrCode" = "Código QR"
|
"qrCode" = "Código QR"
|
||||||
"info" = "Más Información"
|
"info" = "Más Información"
|
||||||
"edit" = "Editar"
|
"edit" = "Editar"
|
||||||
@@ -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 Digital"
|
"certificate" = "Certificado"
|
||||||
"fail" = "Falló"
|
"fail" = "Falló"
|
||||||
"success" = "Éxito"
|
"success" = "Éxito"
|
||||||
"getVersion" = "Obtener versión"
|
"getVersion" = "Obtener versión"
|
||||||
@@ -52,25 +52,16 @@
|
|||||||
"secretToken" = "Token Secreto"
|
"secretToken" = "Token Secreto"
|
||||||
"remained" = "Restante"
|
"remained" = "Restante"
|
||||||
"security" = "Seguridad"
|
"security" = "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."
|
|
||||||
"secAlertConf" = "Certae occasus vulnerabiles sunt impetus. Commendatur ad securitatem protocolla roboranda ne interrupta potentiale."
|
|
||||||
"secAlertSSL" = "La panel carece de conexión segura. Por favor, instale un certificado TLS para la protección de datos."
|
|
||||||
"secAlertPanelPort" = "La puerto predeterminado del panel es vulnerable. Por favor, configure un puerto aleatorio o específico."
|
|
||||||
"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 predeterminada de la suscripción JSON no es segura. Por favor, configure una ruta URI compleja."
|
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Estado del Sistema"
|
"dashboard" = "Estado del Sistema"
|
||||||
"inbounds" = "Entradas"
|
"inbounds" = "Entradas"
|
||||||
"settings" = "Configuraciones"
|
"settings" = "Configuraciones"
|
||||||
"xray" = "Ajustes Xray"
|
"xray" = "Configuración Xray"
|
||||||
"logout" = "Cerrar Sesión"
|
"logout" = "Cerrar Sesión"
|
||||||
"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."
|
||||||
|
|
||||||
@@ -85,7 +76,7 @@
|
|||||||
"title" = "Estado del Sistema"
|
"title" = "Estado del Sistema"
|
||||||
"memory" = "Memoria"
|
"memory" = "Memoria"
|
||||||
"hard" = "Disco Duro"
|
"hard" = "Disco Duro"
|
||||||
"xrayStatus" = "Xray"
|
"xrayStatus" = "Estado de"
|
||||||
"stopXray" = "Detener"
|
"stopXray" = "Detener"
|
||||||
"restartXray" = "Reiniciar"
|
"restartXray" = "Reiniciar"
|
||||||
"xraySwitch" = "Versión"
|
"xraySwitch" = "Versión"
|
||||||
@@ -149,8 +140,10 @@
|
|||||||
"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" = "Ruta del Archivo"
|
||||||
"certificateContent" = "Contenido del Archivo"
|
"certificateContent" = "Contenido del Archivo"
|
||||||
"publicKey" = "llave Pública"
|
"publicKeyPath" = "Ruta de la Clave Pública"
|
||||||
"privatekey" = "llave Privada"
|
"publicKeyContent" = "Contenido de la Clave Pública"
|
||||||
|
"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"
|
||||||
@@ -201,7 +194,7 @@
|
|||||||
"last" = "Último"
|
"last" = "Último"
|
||||||
"prefix" = "Prefijo"
|
"prefix" = "Prefijo"
|
||||||
"postfix" = "Sufijo"
|
"postfix" = "Sufijo"
|
||||||
"delayedStart" = "Inicio Inicial"
|
"delayedStart" = "Iniciar después del primer uso"
|
||||||
"expireDays" = "Duratio"
|
"expireDays" = "Duratio"
|
||||||
"days" = "día(s)"
|
"days" = "día(s)"
|
||||||
"renew" = "Renovación automática"
|
"renew" = "Renovación automática"
|
||||||
@@ -309,8 +302,6 @@
|
|||||||
"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración."
|
"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración."
|
||||||
"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"
|
|
||||||
"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo TLS"
|
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Xray Configuración"
|
"title" = "Xray Configuración"
|
||||||
@@ -320,8 +311,6 @@
|
|||||||
"advancedTemplate" = "Plantilla Avanzada"
|
"advancedTemplate" = "Plantilla Avanzada"
|
||||||
"generalConfigs" = "Configuraciones Generales"
|
"generalConfigs" = "Configuraciones Generales"
|
||||||
"generalConfigsDesc" = "Estas opciones proporcionarán ajustes generales."
|
"generalConfigsDesc" = "Estas opciones proporcionarán ajustes generales."
|
||||||
"logConfigs" = "Registro"
|
|
||||||
"logConfigsDesc" = "Los registros pueden afectar la eficiencia de su servidor. Se recomienda habilitarlo 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"
|
||||||
@@ -386,36 +375,23 @@
|
|||||||
"GoogleIPv4Desc" = "Agregar enrutamiento para que Google se conecte con IPv4."
|
"GoogleIPv4Desc" = "Agregar enrutamiento para que Google se conecte con IPv4."
|
||||||
"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" = "Rutear Google a través de WARP."
|
||||||
"GoogleWARPDesc" = "Enruta el tráfico a Apple a través de WARP."
|
"GoogleWARPDesc" = "Agregar enrutamiento para Google a través de WARP."
|
||||||
"OpenAIWARP" = "OpenAI (ChatGPT)"
|
"OpenAIWARP" = "Rutear OpenAI (ChatGPT) a través de WARP."
|
||||||
"OpenAIWARPDesc" = "Enruta el tráfico a OpenAI (ChatGPT) a través de WARP."
|
"OpenAIWARPDesc" = "Agregar enrutamiento para OpenAI (ChatGPT) a través de WARP."
|
||||||
"NetflixWARP" = "Netflix"
|
"NetflixWARP" = "Rutear Netflix a través de WARP."
|
||||||
"NetflixWARPDesc" = "Enruta el tráfico a Netflix a través de WARP."
|
"NetflixWARPDesc" = "Agregar enrutamiento para Netflix a través de WARP."
|
||||||
"MetaWARP" = "Meta"
|
"SpotifyWARP" = "Rutear Spotify a través de WARP."
|
||||||
"MetaWARPDesc" = "Enruta el tráfico a Meta (Instagram, Facebook, WhatsApp, Threads,...) a través de WARP."
|
"SpotifyWARPDesc" = "Agregar enrutamiento para Spotify a través de WARP."
|
||||||
"AppleWARP" = "Apple"
|
|
||||||
"AppleWARPDesc" = "Enruta el tráfico a Apple a través de WARP."
|
|
||||||
"RedditWARP" = "Reddit"
|
|
||||||
"RedditWARPDesc" = "Enruta el tráfico a Reddit a través de WARP."
|
|
||||||
"SpotifyWARP" = "Spotify"
|
|
||||||
"SpotifyWARPDesc" = "Enruta el tráfico a Spotify a través de WARP."
|
|
||||||
"IRWARP" = "Rutear dominios de Irán a través de WARP."
|
"IRWARP" = "Rutear dominios de Irán a través de WARP."
|
||||||
"IRWARPDesc" = "Agregar enrutamiento para dominios de Irán a través de WARP."
|
"IRWARPDesc" = "Agregar enrutamiento para dominios de Irán a través de WARP."
|
||||||
"Inbounds" = "Entrante"
|
"Inbounds" = "Entrante"
|
||||||
"InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos."
|
"InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos."
|
||||||
"Outbounds" = "Salidas"
|
"Outbounds" = "Salidas"
|
||||||
"Balancers" = "Equilibradores"
|
|
||||||
"OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor."
|
"OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor."
|
||||||
"Routings" = "Reglas de enrutamiento"
|
"Routings" = "Reglas de enrutamiento"
|
||||||
"RoutingsDesc" = "¡La prioridad de cada regla es importante!"
|
"RoutingsDesc" = "¡La prioridad de cada regla es importante!"
|
||||||
"completeTemplate" = "Todos"
|
"completeTemplate" = "Todos"
|
||||||
"logLevel" = "Nivel de registro"
|
|
||||||
"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse."
|
|
||||||
"accessLog" = "Registro 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"
|
|
||||||
"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'ninguno' deshabilitó los registros de errores"
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Primero"
|
"first" = "Primero"
|
||||||
@@ -426,7 +402,6 @@
|
|||||||
"dest" = "Destino"
|
"dest" = "Destino"
|
||||||
"inbound" = "Entrante"
|
"inbound" = "Entrante"
|
||||||
"outbound" = "saliente"
|
"outbound" = "saliente"
|
||||||
"balancer" = "Balancín"
|
|
||||||
"info" = "Información"
|
"info" = "Información"
|
||||||
"add" = "Agregar regla"
|
"add" = "Agregar regla"
|
||||||
"edit" = "Editar regla"
|
"edit" = "Editar regla"
|
||||||
@@ -447,15 +422,6 @@
|
|||||||
"portal" = "portal"
|
"portal" = "portal"
|
||||||
"intercon" = "Interconexión"
|
"intercon" = "Interconexión"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
|
||||||
"addBalancer" = "Agregar equilibrador"
|
|
||||||
"editBalancer" = "Editar balanceador"
|
|
||||||
"balancerStrategy" = "Estrategia"
|
|
||||||
"balancerSelectors" = "Selectores"
|
|
||||||
"tag" = "Etiqueta"
|
|
||||||
"tagDesc" = "etiqueta única"
|
|
||||||
"balancerDesc" = "No es posible utilizar balancerTag y outboundTag al mismo tiempo. Si se utilizan al mismo tiempo, sólo funcionará outboundTag."
|
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "Llave secreta"
|
"secretKey" = "Llave secreta"
|
||||||
"publicKey" = "Llave pública"
|
"publicKey" = "Llave pública"
|
||||||
@@ -464,21 +430,6 @@
|
|||||||
"psk" = "Clave precompartida"
|
"psk" = "Clave precompartida"
|
||||||
"domainStrategy" = "Estrategia de dominio"
|
"domainStrategy" = "Estrategia de dominio"
|
||||||
|
|
||||||
[pages.xray.dns]
|
|
||||||
"enable" = "Habilitar DNS"
|
|
||||||
"enableDesc" = "Habilitar servidor DNS integrado"
|
|
||||||
"strategy" = "Estrategia de consulta"
|
|
||||||
"strategyDesc" = "Estrategia general para resolver nombres de dominio"
|
|
||||||
"add" = "Agregar servidor"
|
|
||||||
"edit" = "Editar servidor"
|
|
||||||
"domains" = "Dominios"
|
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
|
||||||
"add" = "Agregar DNS falso"
|
|
||||||
"edit" = "Editar DNS falso"
|
|
||||||
"ipPool" = "Subred del grupo de IP"
|
|
||||||
"poolSize" = "Tamaño del grupo"
|
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Administrador"
|
"admin" = "Administrador"
|
||||||
"secret" = "Token Secreto"
|
"secret" = "Token Secreto"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"online" = "آنلاین"
|
"online" = "آنلاین"
|
||||||
"domainName" = "آدرس دامنه"
|
"domainName" = "آدرس دامنه"
|
||||||
"monitor" = "آیپی اتصال"
|
"monitor" = "آیپی اتصال"
|
||||||
"certificate" = "گواهی دیجیتال"
|
"certificate" = "گواهی"
|
||||||
"fail" = "ناموفق"
|
"fail" = "ناموفق"
|
||||||
"success" = " موفق"
|
"success" = " موفق"
|
||||||
"getVersion" = "دریافت نسخه"
|
"getVersion" = "دریافت نسخه"
|
||||||
@@ -52,14 +52,6 @@
|
|||||||
"secretToken" = "توکن امنیتی"
|
"secretToken" = "توکن امنیتی"
|
||||||
"remained" = "باقیمانده"
|
"remained" = "باقیمانده"
|
||||||
"security" = "امنیت"
|
"security" = "امنیت"
|
||||||
"secAlertTitle" = "هشدارامنیتی"
|
|
||||||
"secAlertSsl" = "ایناتصالامن نیست. لطفا تازمانیکه تیالاس برای محافظت از دادهها فعال نشدهاست، از وارد کردن اطلاعات حساس خودداری کنید"
|
|
||||||
"secAlertConf" = "تنظیمات خاصی در برابر حملات آسیب پذیر هستند. توصیه میشود پروتکلهای امنیتی را برای جلوگیری از نفوذ احتمالی تقویت کنید"
|
|
||||||
"secAlertSSL" = "پنل فاقد ارتباط امن است. لطفاً یک گواهینامه تیالاس برای محافظت از دادهها نصب کنید"
|
|
||||||
"secAlertPanelPort" = "استفاده از پورت پیشفرض پنل ناامن است. لطفاً یک پورت تصادفی یا خاص تنظیم کنید"
|
|
||||||
"secAlertPanelURI" = "مسیر پیشفرض لینک پنل ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
|
||||||
"secAlertSubURI" = "مسیر پیشفرض لینک سابسکریپشن ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
|
||||||
"secAlertSubJsonURI" = "مسیر پیشفرض لینک سابسکریپشن جیسون ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "نمای کلی"
|
"dashboard" = "نمای کلی"
|
||||||
@@ -70,7 +62,6 @@
|
|||||||
"link" = "مدیریت"
|
"link" = "مدیریت"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
"hello" = "سلام"
|
|
||||||
"title" = "خوشآمدید"
|
"title" = "خوشآمدید"
|
||||||
"loginAgain" = "مدت زمان استفاده بهاتمامرسیده، لطفا دوباره وارد شوید"
|
"loginAgain" = "مدت زمان استفاده بهاتمامرسیده، لطفا دوباره وارد شوید"
|
||||||
|
|
||||||
@@ -85,7 +76,7 @@
|
|||||||
"title" = "نمای کلی"
|
"title" = "نمای کلی"
|
||||||
"memory" = "RAM"
|
"memory" = "RAM"
|
||||||
"hard" = "Disk"
|
"hard" = "Disk"
|
||||||
"xrayStatus" = "ایکسری"
|
"xrayStatus" = "وضعیتایکسری"
|
||||||
"stopXray" = "توقف"
|
"stopXray" = "توقف"
|
||||||
"restartXray" = "شروعمجدد"
|
"restartXray" = "شروعمجدد"
|
||||||
"xraySwitch" = "نسخه"
|
"xraySwitch" = "نسخه"
|
||||||
@@ -143,14 +134,16 @@
|
|||||||
"destinationPort" = "پورت مقصد"
|
"destinationPort" = "پورت مقصد"
|
||||||
"targetAddress" = "آدرس مقصد"
|
"targetAddress" = "آدرس مقصد"
|
||||||
"monitorDesc" = "بهطور پیشفرض خالیبگذارید"
|
"monitorDesc" = "بهطور پیشفرض خالیبگذارید"
|
||||||
"meansNoLimit" = "0 = واحد: گیگابایت) نامحدود)"
|
"meansNoLimit" = " = واحد: گیگابایت) نامحدود)"
|
||||||
"totalFlow" = "ترافیک کل"
|
"totalFlow" = "ترافیک کل"
|
||||||
"leaveBlankToNeverExpire" = "برای منقضینشدن خالیبگذارید"
|
"leaveBlankToNeverExpire" = "برای منقضینشدن خالیبگذارید"
|
||||||
"noRecommendKeepDefault" = "توصیهمیشود بهطور پیشفرض حفظشود"
|
"noRecommendKeepDefault" = "توصیهمیشود بهطور پیشفرض حفظشود"
|
||||||
"certificatePath" = "مسیر فایل"
|
"certificatePath" = "مسیر فایل"
|
||||||
"certificateContent" = "محتوای فایل"
|
"certificateContent" = "محتوای فایل"
|
||||||
"publicKey" = "کلید عمومی"
|
"publicKeyPath" = "مسیر کلید عمومی"
|
||||||
"privatekey" = "کلید خصوصی"
|
"publicKeyContent" = "محتوای کلید عمومی"
|
||||||
|
"keyPath" = "مسیر کلید خصوصی"
|
||||||
|
"keyContent" = "محتوای کلید خصوصی"
|
||||||
"clickOnQRcode" = "برای کپی بر روی کدتصویری کلیک کنید"
|
"clickOnQRcode" = "برای کپی بر روی کدتصویری کلیک کنید"
|
||||||
"client" = "کاربر"
|
"client" = "کاربر"
|
||||||
"export" = "استخراج لینکها"
|
"export" = "استخراج لینکها"
|
||||||
@@ -309,8 +302,6 @@
|
|||||||
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامههای کاربری نمایش میدهد"
|
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامههای کاربری نمایش میدهد"
|
||||||
"subURI" = "پروکسی معکوس URI مسیر"
|
"subURI" = "پروکسی معکوس URI مسیر"
|
||||||
"subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسیها تغییر میدهد URI مسیر"
|
"subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسیها تغییر میدهد URI مسیر"
|
||||||
"fragment" = "تکهتکه شدن"
|
|
||||||
"fragmentDesc" = "فعال کردن تکه تکه شدن برای بسته نخست تیالاس"
|
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "پیکربندی ایکسری"
|
"title" = "پیکربندی ایکسری"
|
||||||
@@ -320,8 +311,6 @@
|
|||||||
"advancedTemplate" = "پیشرفته"
|
"advancedTemplate" = "پیشرفته"
|
||||||
"generalConfigs" = "استراتژی کلی"
|
"generalConfigs" = "استراتژی کلی"
|
||||||
"generalConfigsDesc" = "این گزینهها استراتژی کلی ترافیک را تعیین میکنند"
|
"generalConfigsDesc" = "این گزینهها استراتژی کلی ترافیک را تعیین میکنند"
|
||||||
"logConfigs" = "گزارش"
|
|
||||||
"logConfigsDesc" = "گزارشها ممکن است بر کارایی سرور شما تأثیر بگذارد. توصیه می شود فقط در صورت نیاز آن را عاقلانه فعال کنید"
|
|
||||||
"blockConfigs" = "سپر محافظ"
|
"blockConfigs" = "سپر محافظ"
|
||||||
"blockConfigsDesc" = "این گزینهها ترافیک را بر اساس پروتکلهای درخواستی خاص، و وب سایتها مسدود میکند"
|
"blockConfigsDesc" = "این گزینهها ترافیک را بر اساس پروتکلهای درخواستی خاص، و وب سایتها مسدود میکند"
|
||||||
"blockCountryConfigs" = "مسدودسازی کشور"
|
"blockCountryConfigs" = "مسدودسازی کشور"
|
||||||
@@ -392,12 +381,6 @@
|
|||||||
"OpenAIWARPDesc" = "ترافیک را از طریق وارپ به چت جیپیتی هدایت میکند"
|
"OpenAIWARPDesc" = "ترافیک را از طریق وارپ به چت جیپیتی هدایت میکند"
|
||||||
"NetflixWARP" = "نتفلیکس"
|
"NetflixWARP" = "نتفلیکس"
|
||||||
"NetflixWARPDesc" = "ترافیک را از طریق وارپ به نتفلیکس هدایت میکند"
|
"NetflixWARPDesc" = "ترافیک را از طریق وارپ به نتفلیکس هدایت میکند"
|
||||||
"MetaWARP" = "متا"
|
|
||||||
"MetaWARPDesc" = "ترافیک را از طریق وارپ به متا (اینستاگرام، فیس بوک، واتساپ، تردز و...) هدایت می کند."
|
|
||||||
"AppleWARP" = "اپل"
|
|
||||||
"AppleWARPDesc" = " ترافیک را از طریق وارپ به اپل هدایت میکند"
|
|
||||||
"RedditWARP" = "ردیت"
|
|
||||||
"RedditWARPDesc" = " ترافیک را از طریق وارپ به ردیت هدایت میکند"
|
|
||||||
"SpotifyWARP" = "اسپاتیفای"
|
"SpotifyWARP" = "اسپاتیفای"
|
||||||
"SpotifyWARPDesc" = " ترافیک را از طریق وارپ به اسپاتیفای هدایت میکند"
|
"SpotifyWARPDesc" = " ترافیک را از طریق وارپ به اسپاتیفای هدایت میکند"
|
||||||
"IRWARP" = "دامنههای ایران"
|
"IRWARP" = "دامنههای ایران"
|
||||||
@@ -405,17 +388,10 @@
|
|||||||
"Inbounds" = "ورودیها"
|
"Inbounds" = "ورودیها"
|
||||||
"InboundsDesc" = "پذیرش کلاینت خاص"
|
"InboundsDesc" = "پذیرش کلاینت خاص"
|
||||||
"Outbounds" = "خروجیها"
|
"Outbounds" = "خروجیها"
|
||||||
"Balancers" = "بالانسرها"
|
|
||||||
"OutboundsDesc" = "مسیر ترافیک خروجی را تنظیم کنید"
|
"OutboundsDesc" = "مسیر ترافیک خروجی را تنظیم کنید"
|
||||||
"Routings" = "قوانین مسیریابی"
|
"Routings" = "قوانین مسیریابی"
|
||||||
"RoutingsDesc" = "اولویت هر قانون مهم است"
|
"RoutingsDesc" = "اولویت هر قانون مهم است"
|
||||||
"completeTemplate" = "کامل"
|
"completeTemplate" = "کامل"
|
||||||
"logLevel" = "سطح گزارش"
|
|
||||||
"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند."
|
|
||||||
"accessLog" = "مسیر گزارش"
|
|
||||||
"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارشهای دسترسی را غیرفعال میکند."
|
|
||||||
"errorLog" = "گزارش خطا"
|
|
||||||
"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند"
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "اولین"
|
"first" = "اولین"
|
||||||
@@ -426,7 +402,6 @@
|
|||||||
"dest" = "مقصد"
|
"dest" = "مقصد"
|
||||||
"inbound" = "ورودی"
|
"inbound" = "ورودی"
|
||||||
"outbound" = "خروجی"
|
"outbound" = "خروجی"
|
||||||
"balancer" = "بالانسر"
|
|
||||||
"info" = "اطلاعات"
|
"info" = "اطلاعات"
|
||||||
"add" = "افزودن قانون"
|
"add" = "افزودن قانون"
|
||||||
"edit" = "ویرایش قانون"
|
"edit" = "ویرایش قانون"
|
||||||
@@ -447,15 +422,6 @@
|
|||||||
"portal" = "پورتال"
|
"portal" = "پورتال"
|
||||||
"intercon" = "اتصال میانی"
|
"intercon" = "اتصال میانی"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
|
||||||
"addBalancer" = "افزودن بالانسر"
|
|
||||||
"editBalancer" = "ویرایش بالانسر"
|
|
||||||
"balancerStrategy" = "استراتژی"
|
|
||||||
"balancerSelectors" = "انتخابگرها"
|
|
||||||
"tag" = "برچسب"
|
|
||||||
"tagDesc" = "برچسب یگانه"
|
|
||||||
"balancerDesc" = "امکان استفاده همزمان balancerTag و outboundTag باهم وجود ندارد. درصورت استفاده همزمان فقط outboundTag عمل خواهد کرد."
|
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "کلید شخصی"
|
"secretKey" = "کلید شخصی"
|
||||||
"publicKey" = "کلید عمومی"
|
"publicKey" = "کلید عمومی"
|
||||||
@@ -464,21 +430,6 @@
|
|||||||
"psk" = "کلید مشترک"
|
"psk" = "کلید مشترک"
|
||||||
"domainStrategy" = "استراتژی حل دامنه"
|
"domainStrategy" = "استراتژی حل دامنه"
|
||||||
|
|
||||||
[pages.xray.dns]
|
|
||||||
"enable" = "فعال کردن حل دامنه"
|
|
||||||
"enableDesc" = "سرور حل دامنه داخلی را فعال کنید"
|
|
||||||
"strategy" = "استراتژی پرسوجو"
|
|
||||||
"strategyDesc" = "استراتژی کلی برای حل نام دامنه"
|
|
||||||
"add" = "افزودن سرور"
|
|
||||||
"edit" = "ویرایش سرور"
|
|
||||||
"domains" = "دامنهها"
|
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
|
||||||
"add" = "افزودن دیاناس جعلی"
|
|
||||||
"edit" = "ویرایش دیاناس جعلی"
|
|
||||||
"ipPool" = "زیرشبکه استخر آیپی"
|
|
||||||
"poolSize" = "اندازه استخر"
|
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "مدیر"
|
"admin" = "مدیر"
|
||||||
"secret" = "توکن مخفی"
|
"secret" = "توکن مخفی"
|
||||||
@@ -501,7 +452,7 @@
|
|||||||
"wentWrong" = "❌ مشکلی رخ داده است!"
|
"wentWrong" = "❌ مشکلی رخ داده است!"
|
||||||
"noIpRecord" = "❗ رکورد IP یافت نشد!"
|
"noIpRecord" = "❗ رکورد IP یافت نشد!"
|
||||||
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
||||||
"unlimited" = "♾ - نامحدود(ریست)"
|
"unlimited" = "♾ نامحدود"
|
||||||
"add" = "اضافه کردن"
|
"add" = "اضافه کردن"
|
||||||
"month" = "ماه"
|
"month" = "ماه"
|
||||||
"months" = "ماهها"
|
"months" = "ماهها"
|
||||||
|
|||||||
@@ -1,625 +0,0 @@
|
|||||||
"username" = "Nama Pengguna"
|
|
||||||
"password" = "Kata Sandi"
|
|
||||||
"login" = "Masuk"
|
|
||||||
"confirm" = "Konfirmasi"
|
|
||||||
"cancel" = "Batal"
|
|
||||||
"close" = "Tutup"
|
|
||||||
"copy" = "Salin"
|
|
||||||
"copied" = "Tersalin"
|
|
||||||
"download" = "Unduh"
|
|
||||||
"remark" = "Catatan"
|
|
||||||
"enable" = "Aktifkan"
|
|
||||||
"protocol" = "Protokol"
|
|
||||||
"search" = "Cari"
|
|
||||||
"filter" = "Filter"
|
|
||||||
"loading" = "Memuat..."
|
|
||||||
"second" = "Detik"
|
|
||||||
"minute" = "Menit"
|
|
||||||
"hour" = "Jam"
|
|
||||||
"day" = "Hari"
|
|
||||||
"check" = "Centang"
|
|
||||||
"indefinite" = "Tak Terbatas"
|
|
||||||
"unlimited" = "Tanpa Batas"
|
|
||||||
"none" = "None"
|
|
||||||
"qrCode" = "Kode QR"
|
|
||||||
"info" = "Informasi Lebih Lanjut"
|
|
||||||
"edit" = "Edit"
|
|
||||||
"delete" = "Hapus"
|
|
||||||
"reset" = "Reset"
|
|
||||||
"copySuccess" = "Berhasil Disalin"
|
|
||||||
"sure" = "Yakin"
|
|
||||||
"encryption" = "Enkripsi"
|
|
||||||
"transmission" = "Transmisi"
|
|
||||||
"host" = "Host"
|
|
||||||
"path" = "Jalur"
|
|
||||||
"camouflage" = "Obfuscation"
|
|
||||||
"status" = "Status"
|
|
||||||
"enabled" = "Aktif"
|
|
||||||
"disabled" = "Nonaktif"
|
|
||||||
"depleted" = "Habis"
|
|
||||||
"depletingSoon" = "Akan Habis"
|
|
||||||
"offline" = "Offline"
|
|
||||||
"online" = "Online"
|
|
||||||
"domainName" = "Nama Domain"
|
|
||||||
"monitor" = "IP Pemantauan"
|
|
||||||
"certificate" = "Sertifikat Digital"
|
|
||||||
"fail" = "Gagal"
|
|
||||||
"success" = "Berhasil"
|
|
||||||
"getVersion" = "Dapatkan Versi"
|
|
||||||
"install" = "Instal"
|
|
||||||
"clients" = "Klien"
|
|
||||||
"usage" = "Penggunaan"
|
|
||||||
"secretToken" = "Token Rahasia"
|
|
||||||
"remained" = "Tersisa"
|
|
||||||
"security" = "Keamanan"
|
|
||||||
"secAlertTitle" = "Peringatan keamanan"
|
|
||||||
"secAlertSsl" = "Koneksi ini tidak aman. Harap hindari memasukkan informasi sensitif sampai TLS diaktifkan untuk perlindungan data."
|
|
||||||
"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]
|
|
||||||
"dashboard" = "Ikhtisar"
|
|
||||||
"inbounds" = "Masuk"
|
|
||||||
"settings" = "Pengaturan Panel"
|
|
||||||
"xray" = "Konfigurasi Xray"
|
|
||||||
"logout" = "Keluar"
|
|
||||||
"link" = "Kelola"
|
|
||||||
|
|
||||||
[pages.login]
|
|
||||||
"hello" = "Halo"
|
|
||||||
"title" = "Selamat Datang"
|
|
||||||
"loginAgain" = "Sesi Anda telah berakhir, harap masuk kembali"
|
|
||||||
|
|
||||||
[pages.login.toasts]
|
|
||||||
"invalidFormData" = "Format data input tidak valid."
|
|
||||||
"emptyUsername" = "Nama Pengguna diperlukan"
|
|
||||||
"emptyPassword" = "Kata Sandi diperlukan"
|
|
||||||
"wrongUsernameOrPassword" = "Nama pengguna atau kata sandi tidak valid."
|
|
||||||
"successLogin" = "Login berhasil"
|
|
||||||
|
|
||||||
[pages.index]
|
|
||||||
"title" = "Ikhtisar"
|
|
||||||
"memory" = "RAM"
|
|
||||||
"hard" = "Disk"
|
|
||||||
"xrayStatus" = "Xray"
|
|
||||||
"stopXray" = "Stop"
|
|
||||||
"restartXray" = "Restart"
|
|
||||||
"xraySwitch" = "Versi"
|
|
||||||
"xraySwitchClick" = "Pilih versi yang ingin Anda pindah."
|
|
||||||
"xraySwitchClickDesk" = "Pilih dengan hati-hati, karena versi yang lebih lama mungkin tidak kompatibel dengan konfigurasi saat ini."
|
|
||||||
"operationHours" = "Waktu Aktif"
|
|
||||||
"systemLoad" = "Beban Sistem"
|
|
||||||
"systemLoadDesc" = "Rata-rata beban sistem selama 1, 5, dan 15 menit terakhir"
|
|
||||||
"connectionTcpCountDesc" = "Total koneksi TCP di seluruh sistem"
|
|
||||||
"connectionUdpCountDesc" = "Total koneksi UDP di seluruh sistem"
|
|
||||||
"connectionCount" = "Statistik Koneksi"
|
|
||||||
"upSpeed" = "Kecepatan unggah keseluruhan di seluruh sistem"
|
|
||||||
"downSpeed" = "Kecepatan unduh keseluruhan di seluruh sistem"
|
|
||||||
"totalSent" = "Total data terkirim di seluruh sistem sejak startup OS"
|
|
||||||
"totalReceive" = "Total data diterima di seluruh sistem sejak startup OS"
|
|
||||||
"xraySwitchVersionDialog" = "Ganti Versi Xray"
|
|
||||||
"xraySwitchVersionDialogDesc" = "Apakah Anda yakin ingin mengubah versi Xray menjadi"
|
|
||||||
"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini"
|
|
||||||
"logs" = "Log"
|
|
||||||
"config" = "Konfigurasi"
|
|
||||||
"backup" = "Cadangan & Pulihkan"
|
|
||||||
"backupTitle" = "Cadangan & Pulihkan Database"
|
|
||||||
"backupDescription" = "Disarankan untuk membuat cadangan sebelum memulihkan database."
|
|
||||||
"exportDatabase" = "Cadangkan"
|
|
||||||
"importDatabase" = "Pulihkan"
|
|
||||||
|
|
||||||
[pages.inbounds]
|
|
||||||
"title" = "Masuk"
|
|
||||||
"totalDownUp" = "Total Terkirim/Diterima"
|
|
||||||
"totalUsage" = "Penggunaan Total"
|
|
||||||
"inboundCount" = "Total Masuk"
|
|
||||||
"operate" = "Menu"
|
|
||||||
"enable" = "Aktifkan"
|
|
||||||
"remark" = "Catatan"
|
|
||||||
"protocol" = "Protokol"
|
|
||||||
"port" = "Port"
|
|
||||||
"traffic" = "Traffic"
|
|
||||||
"details" = "Rincian"
|
|
||||||
"transportConfig" = "Transport"
|
|
||||||
"expireDate" = "Durasi"
|
|
||||||
"resetTraffic" = "Reset Traffic"
|
|
||||||
"addInbound" = "Tambahkan Masuk"
|
|
||||||
"generalActions" = "Tindakan Umum"
|
|
||||||
"create" = "Buat"
|
|
||||||
"update" = "Perbarui"
|
|
||||||
"modifyInbound" = "Ubah Masuk"
|
|
||||||
"deleteInbound" = "Hapus Masuk"
|
|
||||||
"deleteInboundContent" = "Apakah Anda yakin ingin menghapus masuk?"
|
|
||||||
"deleteClient" = "Hapus Klien"
|
|
||||||
"deleteClientContent" = "Apakah Anda yakin ingin menghapus klien?"
|
|
||||||
"resetTrafficContent" = "Apakah Anda yakin ingin mereset traffic?"
|
|
||||||
"copyLink" = "Salin URL"
|
|
||||||
"address" = "Alamat"
|
|
||||||
"network" = "Jaringan"
|
|
||||||
"destinationPort" = "Port Tujuan"
|
|
||||||
"targetAddress" = "Alamat Target"
|
|
||||||
"monitorDesc" = "Biarkan kosong untuk mendengarkan semua IP"
|
|
||||||
"meansNoLimit" = " = Unlimited. (unit: GB)"
|
|
||||||
"totalFlow" = "Total Aliran"
|
|
||||||
"leaveBlankToNeverExpire" = "Biarkan kosong untuk tidak pernah kedaluwarsa"
|
|
||||||
"noRecommendKeepDefault" = "Disarankan untuk tetap menggunakan pengaturan default"
|
|
||||||
"certificatePath" = "Path Berkas"
|
|
||||||
"certificateContent" = "Konten Berkas"
|
|
||||||
"publicKey" = "Kunci Publik"
|
|
||||||
"privatekey" = "Kunci Pribadi"
|
|
||||||
"clickOnQRcode" = "Klik pada Kode QR untuk Menyalin"
|
|
||||||
"client" = "Klien"
|
|
||||||
"export" = "Ekspor Semua URL"
|
|
||||||
"clone" = "Duplikat"
|
|
||||||
"cloneInbound" = "Duplikat"
|
|
||||||
"cloneInboundContent" = "Semua pengaturan masuk ini, kecuali Port, Listening IP, dan Klien, akan diterapkan pada duplikat."
|
|
||||||
"cloneInboundOk" = "Duplikat"
|
|
||||||
"resetAllTraffic" = "Reset Semua Traffic Masuk"
|
|
||||||
"resetAllTrafficTitle" = "Reset Semua Traffic Masuk"
|
|
||||||
"resetAllTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua masuk?"
|
|
||||||
"resetInboundClientTraffics" = "Reset Traffic Klien Masuk"
|
|
||||||
"resetInboundClientTrafficTitle" = "Reset Traffic Klien Masuk"
|
|
||||||
"resetInboundClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic klien masuk ini?"
|
|
||||||
"resetAllClientTraffics" = "Reset Traffic Semua Klien"
|
|
||||||
"resetAllClientTrafficTitle" = "Reset Traffic Semua Klien"
|
|
||||||
"resetAllClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua klien?"
|
|
||||||
"delDepletedClients" = "Hapus Klien Habis"
|
|
||||||
"delDepletedClientsTitle" = "Hapus Klien Habis"
|
|
||||||
"delDepletedClientsContent" = "Apakah Anda yakin ingin menghapus semua klien yang habis?"
|
|
||||||
"email" = "Email"
|
|
||||||
"emailDesc" = "Harap berikan alamat email yang unik."
|
|
||||||
"IPLimit" = "Batas IP"
|
|
||||||
"IPLimitDesc" = "Menonaktifkan masuk jika jumlah melebihi nilai yang ditetapkan. (0 = nonaktif)"
|
|
||||||
"IPLimitlog" = "Log IP"
|
|
||||||
"IPLimitlogDesc" = "Log histori IP. (untuk mengaktifkan masuk setelah menonaktifkan, hapus log)"
|
|
||||||
"IPLimitlogclear" = "Hapus Log"
|
|
||||||
"setDefaultCert" = "Atur Sertifikat dari Panel"
|
|
||||||
"xtlsDesc" = "Xray harus versi 1.7.5"
|
|
||||||
"realityDesc" = "Xray harus versi 1.8.0+"
|
|
||||||
"telegramDesc" = "Harap berikan ID Telegram atau obrolan tanpa menggunakan '@'. (dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)"
|
|
||||||
"subscriptionDesc" = "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien."
|
|
||||||
"info" = "Info"
|
|
||||||
"same" = "Sama"
|
|
||||||
"inboundData" = "Data Masuk"
|
|
||||||
"exportInbound" = "Ekspor Masuk"
|
|
||||||
"import" = "Impor"
|
|
||||||
"importInbound" = "Impor Masuk"
|
|
||||||
|
|
||||||
[pages.client]
|
|
||||||
"add" = "Tambah Klien"
|
|
||||||
"edit" = "Edit Klien"
|
|
||||||
"submitAdd" = "Tambah Klien"
|
|
||||||
"submitEdit" = "Simpan Perubahan"
|
|
||||||
"clientCount" = "Jumlah Klien"
|
|
||||||
"bulk" = "Tambahkan Massal"
|
|
||||||
"method" = "Metode"
|
|
||||||
"first" = "Pertama"
|
|
||||||
"last" = "Terakhir"
|
|
||||||
"prefix" = "Awalan"
|
|
||||||
"postfix" = "Akhiran"
|
|
||||||
"delayedStart" = "Mulai Awal"
|
|
||||||
"expireDays" = "Durasi"
|
|
||||||
"days" = "Hari"
|
|
||||||
"renew" = "Perpanjang Otomatis"
|
|
||||||
"renewDesc" = "Perpanjangan otomatis setelah kedaluwarsa. (0 = nonaktif)(unit: hari)"
|
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
|
||||||
"obtain" = "Dapatkan"
|
|
||||||
|
|
||||||
[pages.inbounds.stream.general]
|
|
||||||
"request" = "Permintaan"
|
|
||||||
"response" = "Respons"
|
|
||||||
"name" = "Nama"
|
|
||||||
"value" = "Nilai"
|
|
||||||
|
|
||||||
[pages.inbounds.stream.tcp]
|
|
||||||
"version" = "Versi"
|
|
||||||
"method" = "Metode"
|
|
||||||
"path" = "Path"
|
|
||||||
"status" = "Status"
|
|
||||||
"statusDescription" = "Deskripsi Status"
|
|
||||||
"requestHeader" = "Header Permintaan"
|
|
||||||
"responseHeader" = "Header Respons"
|
|
||||||
|
|
||||||
[pages.inbounds.stream.quic]
|
|
||||||
"encryption" = "Enkripsi"
|
|
||||||
|
|
||||||
[pages.settings]
|
|
||||||
"title" = "Pengaturan Panel"
|
|
||||||
"save" = "Simpan"
|
|
||||||
"infoDesc" = "Setiap perubahan yang dibuat di sini perlu disimpan. Harap restart panel untuk menerapkan perubahan."
|
|
||||||
"restartPanel" = "Restart Panel"
|
|
||||||
"restartPanelDesc" = "Apakah Anda yakin ingin merestart panel? Jika Anda tidak dapat mengakses panel setelah merestart, lihat info log panel di server."
|
|
||||||
"actions" = "Tindakan"
|
|
||||||
"resetDefaultConfig" = "Reset ke Default"
|
|
||||||
"panelSettings" = "Umum"
|
|
||||||
"securitySettings" = "Otentikasi"
|
|
||||||
"TGBotSettings" = "Bot Telegram"
|
|
||||||
"panelListeningIP" = "IP Pendengar"
|
|
||||||
"panelListeningIPDesc" = "Alamat IP untuk panel web. (biarkan kosong untuk mendengarkan semua IP)"
|
|
||||||
"panelListeningDomain" = "Domain Pendengar"
|
|
||||||
"panelListeningDomainDesc" = "Nama domain untuk panel web. (biarkan kosong untuk mendengarkan semua domain dan IP)"
|
|
||||||
"panelPort" = "Port Pendengar"
|
|
||||||
"panelPortDesc" = "Nomor port untuk panel web. (harus menjadi port yang tidak digunakan)"
|
|
||||||
"publicKeyPath" = "Path Kunci Publik"
|
|
||||||
"publicKeyPathDesc" = "Path berkas kunci publik untuk panel web. (dimulai dengan ‘/‘)"
|
|
||||||
"privateKeyPath" = "Path Kunci Privat"
|
|
||||||
"privateKeyPathDesc" = "Path berkas kunci privat untuk panel web. (dimulai dengan ‘/‘)"
|
|
||||||
"panelUrlPath" = "URI Path"
|
|
||||||
"panelUrlPathDesc" = "URI path untuk panel web. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)"
|
|
||||||
"pageSize" = "Ukuran Halaman"
|
|
||||||
"pageSizeDesc" = "Tentukan ukuran halaman untuk tabel masuk. (0 = nonaktif)"
|
|
||||||
"remarkModel" = "Model Catatan & Karakter Pemisah"
|
|
||||||
"datepicker" = "Jenis Kalender"
|
|
||||||
"datepickerPlaceholder" = "Pilih tanggal"
|
|
||||||
"datepickerDescription" = "Tugas terjadwal akan berjalan berdasarkan kalender ini."
|
|
||||||
"sampleRemark" = "Contoh Catatan"
|
|
||||||
"oldUsername" = "Username Saat Ini"
|
|
||||||
"currentPassword" = "Kata Sandi Saat Ini"
|
|
||||||
"newUsername" = "Username Baru"
|
|
||||||
"newPassword" = "Kata Sandi Baru"
|
|
||||||
"telegramBotEnable" = "Aktifkan Bot Telegram"
|
|
||||||
"telegramBotEnableDesc" = "Mengaktifkan bot Telegram."
|
|
||||||
"telegramToken" = "Token Telegram"
|
|
||||||
"telegramTokenDesc" = "Token bot Telegram yang diperoleh dari '@BotFather'."
|
|
||||||
"telegramProxy" = "Proxy SOCKS"
|
|
||||||
"telegramProxyDesc" = "Mengaktifkan proxy SOCKS5 untuk terhubung ke Telegram. (sesuaikan pengaturan sesuai panduan)"
|
|
||||||
"telegramChatId" = "ID Obrolan Admin"
|
|
||||||
"telegramChatIdDesc" = "ID Obrolan Admin Telegram. (dipisahkan koma)(dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)"
|
|
||||||
"telegramNotifyTime" = "Waktu Notifikasi"
|
|
||||||
"telegramNotifyTimeDesc" = "Waktu notifikasi bot Telegram yang diatur untuk laporan berkala. (gunakan format waktu crontab)"
|
|
||||||
"tgNotifyBackup" = "Cadangan Database"
|
|
||||||
"tgNotifyBackupDesc" = "Kirim berkas cadangan database dengan laporan."
|
|
||||||
"tgNotifyLogin" = "Notifikasi Login"
|
|
||||||
"tgNotifyLoginDesc" = "Dapatkan notifikasi tentang username, alamat IP, dan waktu setiap kali seseorang mencoba masuk ke panel web Anda."
|
|
||||||
"sessionMaxAge" = "Durasi Sesi"
|
|
||||||
"sessionMaxAgeDesc" = "Durasi di mana Anda dapat tetap masuk. (unit: menit)"
|
|
||||||
"expireTimeDiff" = "Notifikasi Tanggal Kedaluwarsa"
|
|
||||||
"expireTimeDiffDesc" = "Dapatkan notifikasi tentang tanggal kedaluwarsa saat mencapai ambang batas ini. (unit: hari)"
|
|
||||||
"trafficDiff" = "Notifikasi Batas Traffic"
|
|
||||||
"trafficDiffDesc" = "Dapatkan notifikasi tentang batas traffic saat mencapai ambang batas ini. (unit: GB)"
|
|
||||||
"tgNotifyCpu" = "Notifikasi Beban CPU"
|
|
||||||
"tgNotifyCpuDesc" = "Dapatkan notifikasi jika beban CPU melebihi ambang batas ini. (unit: %)"
|
|
||||||
"timeZone" = "Zone Waktu"
|
|
||||||
"timeZoneDesc" = "Tugas terjadwal akan berjalan berdasarkan zona waktu ini."
|
|
||||||
"subSettings" = "Langganan"
|
|
||||||
"subEnable" = "Aktifkan Layanan Langganan"
|
|
||||||
"subEnableDesc" = "Mengaktifkan layanan langganan."
|
|
||||||
"subListen" = "IP Pendengar"
|
|
||||||
"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
|
|
||||||
"subPort" = "Port Pendengar"
|
|
||||||
"subPortDesc" = "Nomor port untuk layanan langganan. (harus menjadi port yang tidak digunakan)"
|
|
||||||
"subCertPath" = "Path Kunci Publik"
|
|
||||||
"subCertPathDesc" = "Path berkas kunci publik untuk layanan langganan. (dimulai dengan ‘/‘)"
|
|
||||||
"subKeyPath" = "Path Kunci Privat"
|
|
||||||
"subKeyPathDesc" = "Path berkas kunci privat untuk layanan langganan. (dimulai dengan ‘/‘)"
|
|
||||||
"subPath" = "URI Path"
|
|
||||||
"subPathDesc" = "URI path untuk layanan langganan. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)"
|
|
||||||
"subDomain" = "Domain Pendengar"
|
|
||||||
"subDomainDesc" = "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)"
|
|
||||||
"subUpdates" = "Interval Pembaruan"
|
|
||||||
"subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)"
|
|
||||||
"subEncrypt" = "Encode"
|
|
||||||
"subEncryptDesc" = "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64."
|
|
||||||
"subShowInfo" = "Tampilkan Info Penggunaan"
|
|
||||||
"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
|
|
||||||
"subURI" = "URI Proxy Terbalik"
|
|
||||||
"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy."
|
|
||||||
"fragment" = "Fragmentasi"
|
|
||||||
"fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS"
|
|
||||||
|
|
||||||
[pages.xray]
|
|
||||||
"title" = "Konfigurasi Xray"
|
|
||||||
"save" = "Simpan"
|
|
||||||
"restart" = "Restart Xray"
|
|
||||||
"basicTemplate" = "Dasar"
|
|
||||||
"advancedTemplate" = "Lanjutan"
|
|
||||||
"generalConfigs" = "Strategi Umum"
|
|
||||||
"generalConfigsDesc" = "Opsi ini akan menentukan penyesuaian strategi umum."
|
|
||||||
"logConfigs" = "Catatan"
|
|
||||||
"logConfigsDesc" = "Log dapat mempengaruhi efisiensi server Anda. Disarankan untuk mengaktifkannya dengan bijak hanya jika diperlukan"
|
|
||||||
"blockConfigs" = "Pelindung"
|
|
||||||
"blockConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan protokol dan situs web yang diminta."
|
|
||||||
"blockCountryConfigs" = "Blokir Negara"
|
|
||||||
"blockCountryConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan negara yang diminta."
|
|
||||||
"directCountryConfigs" = "Langsung ke Negara"
|
|
||||||
"directCountryConfigsDesc" = "Opsi ini akan langsung meneruskan lalu lintas berdasarkan negara yang diminta."
|
|
||||||
"ipv4Configs" = "Pengalihan IPv4"
|
|
||||||
"ipv4ConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui IPv4."
|
|
||||||
"warpConfigs" = "Pengalihan WARP"
|
|
||||||
"warpConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui WARP."
|
|
||||||
"Template" = "Template Konfigurasi Xray Lanjutan"
|
|
||||||
"TemplateDesc" = "File konfigurasi Xray akhir akan dibuat berdasarkan template ini."
|
|
||||||
"FreedomStrategy" = "Strategi Protokol Freedom"
|
|
||||||
"FreedomStrategyDesc" = "Atur strategi output untuk jaringan dalam Protokol Freedom."
|
|
||||||
"RoutingStrategy" = "Strategi Pengalihan Keseluruhan"
|
|
||||||
"RoutingStrategyDesc" = "Atur strategi pengalihan lalu lintas keseluruhan untuk menyelesaikan semua permintaan."
|
|
||||||
"Torrent" = "Blokir Protokol BitTorrent"
|
|
||||||
"TorrentDesc" = "Memblokir protokol BitTorrent."
|
|
||||||
"PrivateIp" = "Blokir Koneksi ke IP Pribadi"
|
|
||||||
"PrivateIpDesc" = "Memblokir pembentukan koneksi ke rentang IP pribadi."
|
|
||||||
"Ads" = "Blokir Iklan"
|
|
||||||
"AdsDesc" = "Memblokir situs web periklanan."
|
|
||||||
"Family" = "Proteksi Keluarga"
|
|
||||||
"FamilyDesc" = "Memblokir konten dewasa dan situs web berbahaya."
|
|
||||||
"Security" = "Pelindung Keamanan"
|
|
||||||
"SecurityDesc" = "Memblokir situs web malware, phishing, dan penambang kripto."
|
|
||||||
"Speedtest" = "Blokir Speedtest"
|
|
||||||
"SpeedtestDesc" = "Memblokir pembentukan koneksi ke situs web speedtest."
|
|
||||||
"IRIp" = "Blokir Koneksi ke IP Iran"
|
|
||||||
"IRIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Iran."
|
|
||||||
"IRDomain" = "Blokir Koneksi ke Domain Iran"
|
|
||||||
"IRDomainDesc" = "Memblokir pembentukan koneksi ke domain Iran."
|
|
||||||
"ChinaIp" = "Blokir Koneksi ke IP China"
|
|
||||||
"ChinaIpDesc" = "Memblokir pembentukan koneksi ke rentang IP China."
|
|
||||||
"ChinaDomain" = "Blokir Koneksi ke Domain China"
|
|
||||||
"ChinaDomainDesc" = "Memblokir pembentukan koneksi ke domain China."
|
|
||||||
"RussiaIp" = "Blokir Koneksi ke IP Rusia"
|
|
||||||
"RussiaIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Rusia."
|
|
||||||
"RussiaDomain" = "Blokir Koneksi ke Domain Rusia"
|
|
||||||
"RussiaDomainDesc" = "Memblokir pembentukan koneksi ke domain Rusia."
|
|
||||||
"VNIp" = "Blokir Koneksi ke IP Vietnam"
|
|
||||||
"VNIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Vietnam."
|
|
||||||
"VNDomain" = "Blokir Koneksi ke Domain Vietnam"
|
|
||||||
"VNDomainDesc" = "Memblokir pembentukan koneksi ke domain Vietnam."
|
|
||||||
"DirectIRIp" = "Koneksi Langsung ke IP Iran"
|
|
||||||
"DirectIRIpDesc" = "Membentuk koneksi langsung ke rentang IP Iran."
|
|
||||||
"DirectIRDomain" = "Koneksi Langsung ke Domain Iran"
|
|
||||||
"DirectIRDomainDesc" = "Membentuk koneksi langsung ke domain Iran."
|
|
||||||
"DirectChinaIp" = "Koneksi Langsung ke IP China"
|
|
||||||
"DirectChinaIpDesc" = "Membentuk koneksi langsung ke rentang IP China."
|
|
||||||
"DirectChinaDomain" = "Koneksi Langsung ke Domain China"
|
|
||||||
"DirectChinaDomainDesc" = "Membentuk koneksi langsung ke domain China."
|
|
||||||
"DirectRussiaIp" = "Koneksi Langsung ke IP Rusia"
|
|
||||||
"DirectRussiaIpDesc" = "Membentuk koneksi langsung ke rentang IP Rusia."
|
|
||||||
"DirectRussiaDomain" = "Koneksi Langsung ke Domain Rusia"
|
|
||||||
"DirectRussiaDomainDesc" = "Membentuk koneksi langsung ke domain Rusia."
|
|
||||||
"DirectVNIp" = "Koneksi Langsung ke IP Vietnam"
|
|
||||||
"DirectVNIpDesc" = "Membentuk koneksi langsung ke rentang IP Vietnam."
|
|
||||||
"DirectVNDomain" = "Koneksi Langsung ke Domain Vietnam"
|
|
||||||
"DirectVNDomainDesc" = "Membentuk koneksi langsung ke domain Vietnam."
|
|
||||||
"GoogleIPv4" = "Google"
|
|
||||||
"GoogleIPv4Desc" = "Rute lalu lintas ke Google melalui IPv4."
|
|
||||||
"NetflixIPv4" = "Netflix"
|
|
||||||
"NetflixIPv4Desc" = "Rute lalu lintas ke Netflix melalui IPv4."
|
|
||||||
"GoogleWARP" = "Google"
|
|
||||||
"GoogleWARPDesc" = "Tambahkan pengalihan untuk Google melalui WARP."
|
|
||||||
"OpenAIWARP" = "ChatGPT"
|
|
||||||
"OpenAIWARPDesc" = "Rute lalu lintas ke ChatGPT melalui WARP."
|
|
||||||
"NetflixWARP" = "Netflix"
|
|
||||||
"NetflixWARPDesc" = "Rute lalu lintas ke Netflix melalui WARP."
|
|
||||||
"MetaWARP" = "Meta"
|
|
||||||
"MetaWARPDesc" = "Merutekan lalu lintas ke Meta (Instagram, Facebook, WhatsApp, Threads,...) melalui WARP."
|
|
||||||
"AppleWARP" = "Apple"
|
|
||||||
"AppleWARPDesc" = "Merutekan lalu lintas ke Apple melalui WARP."
|
|
||||||
"RedditWARP" = "Reddit"
|
|
||||||
"RedditWARPDesc" = "Merutekan lalu lintas ke Reddit melalui WARP."
|
|
||||||
"SpotifyWARP" = "Spotify"
|
|
||||||
"SpotifyWARPDesc" = "Rute lalu lintas ke Spotify melalui WARP."
|
|
||||||
"IRWARP" = "Domain Iran"
|
|
||||||
"IRWARPDesc" = "Rute lalu lintas ke domain Iran melalui WARP."
|
|
||||||
"Inbounds" = "Masuk"
|
|
||||||
"InboundsDesc" = "Menerima klien tertentu."
|
|
||||||
"Outbounds" = "Keluar"
|
|
||||||
"Balancers" = "Penyeimbang"
|
|
||||||
"OutboundsDesc" = "Atur jalur lalu lintas keluar."
|
|
||||||
"Routings" = "Aturan Pengalihan"
|
|
||||||
"RoutingsDesc" = "Prioritas setiap aturan penting!"
|
|
||||||
"completeTemplate" = "Semua"
|
|
||||||
"logLevel" = "Tingkat Log"
|
|
||||||
"logLevelDesc" = "Tingkat log untuk log kesalahan, menunjukkan informasi yang perlu dicatat."
|
|
||||||
"accessLog" = "Log Akses"
|
|
||||||
"accessLogDesc" = "Jalur file untuk log akses. Nilai khusus 'tidak ada' menonaktifkan log akses"
|
|
||||||
"errorLog" = "Catatan eror"
|
|
||||||
"errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan"
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
|
||||||
"first" = "Pertama"
|
|
||||||
"last" = "Terakhir"
|
|
||||||
"up" = "Naik"
|
|
||||||
"down" = "Turun"
|
|
||||||
"source" = "Sumber"
|
|
||||||
"dest" = "Tujuan"
|
|
||||||
"inbound" = "Masuk"
|
|
||||||
"outbound" = "Keluar"
|
|
||||||
"balancer" = "Pengimbang"
|
|
||||||
"info" = "Info"
|
|
||||||
"add" = "Tambahkan Aturan"
|
|
||||||
"edit" = "Edit Aturan"
|
|
||||||
"useComma" = "Item yang dipisahkan koma"
|
|
||||||
|
|
||||||
[pages.xray.outbound]
|
|
||||||
"addOutbound" = "Tambahkan Keluar"
|
|
||||||
"addReverse" = "Tambahkan Revers"
|
|
||||||
"editOutbound" = "Edit Keluar"
|
|
||||||
"editReverse" = "Edit Revers"
|
|
||||||
"tag" = "Tag"
|
|
||||||
"tagDesc" = "Tag Unik"
|
|
||||||
"address" = "Alamat"
|
|
||||||
"reverse" = "Revers"
|
|
||||||
"domain" = "Domain"
|
|
||||||
"type" = "Tipe"
|
|
||||||
"bridge" = "Jembatan"
|
|
||||||
"portal" = "Portal"
|
|
||||||
"intercon" = "Interkoneksi"
|
|
||||||
|
|
||||||
[pages.xray.balancer]
|
|
||||||
"addBalancer" = "Tambahkan Penyeimbang"
|
|
||||||
"editBalancer" = "Sunting Penyeimbang"
|
|
||||||
"balancerStrategy" = "Strategi"
|
|
||||||
"balancerSelectors" = "Penyeleksi"
|
|
||||||
"tag" = "Menandai"
|
|
||||||
"tagDesc" = "Label Unik"
|
|
||||||
"balancerDesc" = "BalancerTag dan outboundTag tidak dapat digunakan secara bersamaan. Jika digunakan secara bersamaan, hanya outboundTag yang akan berfungsi."
|
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
|
||||||
"secretKey" = "Kunci Rahasia"
|
|
||||||
"publicKey" = "Kunci Publik"
|
|
||||||
"allowedIPs" = "IP yang Diizinkan"
|
|
||||||
"endpoint" = "Titik Akhir"
|
|
||||||
"psk" = "Kunci Pra-Bagi"
|
|
||||||
"domainStrategy" = "Strategi Domain"
|
|
||||||
|
|
||||||
[pages.xray.dns]
|
|
||||||
"enable" = "Aktifkan DNS"
|
|
||||||
"enableDesc" = "Aktifkan server DNS bawaan"
|
|
||||||
"strategy" = "Strategi Kueri"
|
|
||||||
"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain"
|
|
||||||
"add" = "Tambahkan Server"
|
|
||||||
"edit" = "Sunting Server"
|
|
||||||
"domains" = "Domains"
|
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
|
||||||
"add" = "Tambahkan DNS Palsu"
|
|
||||||
"edit" = "Edit DNS Palsu"
|
|
||||||
"ipPool" = "Subnet Kumpulan IP"
|
|
||||||
"poolSize" = "Ukuran Kolam"
|
|
||||||
|
|
||||||
[pages.settings.security]
|
|
||||||
"admin" = "Admin"
|
|
||||||
"secret" = "Token Rahasia"
|
|
||||||
"loginSecurity" = "Login Aman"
|
|
||||||
"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih."
|
|
||||||
"secretToken" = "Token Rahasia"
|
|
||||||
"secretTokenDesc" = "Simpan token ini dengan aman di tempat yang aman. Token ini diperlukan untuk login dan tidak dapat dipulihkan."
|
|
||||||
|
|
||||||
[pages.settings.toasts]
|
|
||||||
"modifySettings" = "Ubah Pengaturan"
|
|
||||||
"getSettings" = "Dapatkan Pengaturan"
|
|
||||||
"modifyUser" = "Ubah Admin"
|
|
||||||
"originalUserPassIncorrect" = "Username atau password saat ini tidak valid"
|
|
||||||
"userPassMustBeNotEmpty" = "Username dan password baru tidak boleh kosong"
|
|
||||||
|
|
||||||
[tgbot]
|
|
||||||
"keyboardClosed" = "❌ Papan ketik kustom ditutup!"
|
|
||||||
"noResult" = "❗ Tidak ada hasil!"
|
|
||||||
"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!"
|
|
||||||
"wentWrong" = "❌ Ada yang salah!"
|
|
||||||
"noIpRecord" = "❗ Tidak ada Catatan IP!"
|
|
||||||
"noInbounds" = "❗ Tidak ada masuk ditemukan!"
|
|
||||||
"unlimited" = "♾ Tak terbatas"
|
|
||||||
"add" = "Tambah"
|
|
||||||
"month" = "Bulan"
|
|
||||||
"months" = "Bulan"
|
|
||||||
"day" = "Hari"
|
|
||||||
"days" = "Hari"
|
|
||||||
"hours" = "Jam"
|
|
||||||
"unknown" = "Tidak diketahui"
|
|
||||||
"inbounds" = "Masuk"
|
|
||||||
"clients" = "Klien"
|
|
||||||
"offline" = "🔴 Offline"
|
|
||||||
"online" = "🟢 Online"
|
|
||||||
|
|
||||||
[tgbot.commands]
|
|
||||||
"unknown" = "❗ Perintah tidak dikenal."
|
|
||||||
"pleaseChoose" = "👇 Harap pilih:\r\n"
|
|
||||||
"help" = "🤖 Selamat datang di bot ini! Ini dirancang untuk menyediakan data tertentu dari panel web dan memungkinkan Anda melakukan modifikasi sesuai kebutuhan.\r\n\r\n"
|
|
||||||
"start" = "👋 Halo <i>{{ .Firstname }}</i>.\r\n"
|
|
||||||
"welcome" = "🤖 Selamat datang di <b>{{.Hostname }}</b> bot managemen.\r\n"
|
|
||||||
"status" = "✅ Bot dalam keadaan baik!"
|
|
||||||
"usage" = "❗ Harap berikan teks untuk mencari!"
|
|
||||||
"getID" = "🆔 ID Anda:<code>{{.ID }}</code>"
|
|
||||||
"helpAdminCommands" = "Untuk mencari email klien:\r\n<code>/usage [Email]</code>\r\n\r\nUntuk mencari masuk (dengan statistik klien):\r\n<code>/inbound [Remark]</code>"
|
|
||||||
"helpClientCommands" = "Untuk mencari statistik, gunakan perintah berikut:\r\n\r\n<code>/usage [Email]</code>"
|
|
||||||
|
|
||||||
[tgbot.messages]
|
|
||||||
"cpuThreshold" = "🔴 Beban CPU {{ .Percent }}% melebihi batas {{ .Threshold }}%"
|
|
||||||
"selectUserFailed" = "❌ Kesalahan dalam pemilihan pengguna!"
|
|
||||||
"userSaved" = "✅ Pengguna Telegram tersimpan."
|
|
||||||
"loginSuccess" = "✅ Berhasil masuk ke panel.\r\n"
|
|
||||||
"loginFailed" = "❗️ Gagal masuk ke panel.\r\n"
|
|
||||||
"report" = "🕰 Laporan Terjadwal: {{ .RunTime }}\r\n"
|
|
||||||
"datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n"
|
|
||||||
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
|
|
||||||
"version" = "🚀 Versi 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" = "⏳ Waktu Aktif: {{ .UpTime }} {{ .Unit }}\r\n"
|
|
||||||
"serverLoad" = "📈 Beban Sistem: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
|
||||||
"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n"
|
|
||||||
"tcpCount" = "🔹 TCP: {{ .Count }}\r\n"
|
|
||||||
"udpCount" = "🔸 UDP: {{ .Count }}\r\n"
|
|
||||||
"traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
|
||||||
"xrayStatus" = "ℹ️ Status: {{ .State }}\r\n"
|
|
||||||
"username" = "👤 Nama Pengguna: {{ .Username }}\r\n"
|
|
||||||
"time" = "⏰ Waktu: {{ .Time }}\r\n"
|
|
||||||
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
|
||||||
"port" = "🔌 Port: {{ .Port }}\r\n"
|
|
||||||
"expire" = "📅 Tanggal Kadaluarsa: {{ .Time }}\r\n"
|
|
||||||
"expireIn" = "📅 Kadaluarsa Dalam: {{ .Time }}\r\n"
|
|
||||||
"active" = "💡 Aktif: {{ .Enable }}\r\n"
|
|
||||||
"enabled" = "🚨 Diaktifkan: {{ .Enable }}\r\n"
|
|
||||||
"online" = "🌐 Status Koneksi: {{ .Status }}\r\n"
|
|
||||||
"email" = "📧 Email: {{ .Email }}\r\n"
|
|
||||||
"upload" = "🔼 Unggah: ↑{{ .Upload }}\r\n"
|
|
||||||
"download" = "🔽 Unduh: ↓{{ .Download }}\r\n"
|
|
||||||
"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n"
|
|
||||||
"TGUser" = "👤 Pengguna Telegram: {{ .TelegramID }}\r\n"
|
|
||||||
"exhaustedMsg" = "🚨 Habis {{ .Type }}:\r\n"
|
|
||||||
"exhaustedCount" = "🚨 Jumlah Habis {{ .Type }}:\r\n"
|
|
||||||
"onlinesCount" = "🌐 Klien Online: {{ .Count }}\r\n"
|
|
||||||
"disabled" = "🛑 Dinonaktifkan: {{ .Disabled }}\r\n"
|
|
||||||
"depleteSoon" = "🔜 Habis Sebentar: {{ .Deplete }}\r\n\r\n"
|
|
||||||
"backupTime" = "🗄 Waktu Backup: {{ .Time }}\r\n"
|
|
||||||
"refreshedOn" = "\r\n📋🔄 Diperbarui Pada: {{ .Time }}\r\n\r\n"
|
|
||||||
"yes" = "✅ Ya"
|
|
||||||
"no" = "❌ Tidak"
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
|
||||||
"closeKeyboard" = "❌ Tutup Papan Ketik"
|
|
||||||
"cancel" = "❌ Batal"
|
|
||||||
"cancelReset" = "❌ Batal Reset"
|
|
||||||
"cancelIpLimit" = "❌ Batal Batas IP"
|
|
||||||
"confirmResetTraffic" = "✅ Konfirmasi Reset Lalu Lintas?"
|
|
||||||
"confirmClearIps" = "✅ Konfirmasi Hapus IPs?"
|
|
||||||
"confirmRemoveTGUser" = "✅ Konfirmasi Hapus Pengguna Telegram?"
|
|
||||||
"confirmToggle" = "✅ Konfirmasi Aktifkan/Nonaktifkan Pengguna?"
|
|
||||||
"dbBackup" = "Dapatkan Cadangan DB"
|
|
||||||
"serverUsage" = "Penggunaan Server"
|
|
||||||
"getInbounds" = "Dapatkan Inbounds"
|
|
||||||
"depleteSoon" = "Habis Sebentar"
|
|
||||||
"clientUsage" = "Dapatkan Penggunaan"
|
|
||||||
"onlines" = "Klien Online"
|
|
||||||
"commands" = "Perintah"
|
|
||||||
"refresh" = "🔄 Perbarui"
|
|
||||||
"clearIPs" = "❌ Hapus IPs"
|
|
||||||
"removeTGUser" = "❌ Hapus Pengguna Telegram"
|
|
||||||
"selectTGUser" = "👤 Pilih Pengguna Telegram"
|
|
||||||
"selectOneTGUser" = "👤 Pilih Pengguna Telegram:"
|
|
||||||
"resetTraffic" = "📈 Reset Lalu Lintas"
|
|
||||||
"resetExpire" = "📅 Ubah Tanggal Kadaluarsa"
|
|
||||||
"ipLog" = "🔢 Log IP"
|
|
||||||
"ipLimit" = "🔢 Batas IP"
|
|
||||||
"setTGUser" = "👤 Set Pengguna Telegram"
|
|
||||||
"toggle" = "🔘 Aktifkan / Nonaktifkan"
|
|
||||||
"custom" = "🔢 Kustom"
|
|
||||||
"confirmNumber" = "✅ Konfirmasi: {{ .Num }}"
|
|
||||||
"confirmNumberAdd" = "✅ Konfirmasi menambahkan: {{ .Num }}"
|
|
||||||
"limitTraffic" = "🚧 Batas Lalu Lintas"
|
|
||||||
"getBanLogs" = "Dapatkan Log Pemblokiran"
|
|
||||||
|
|
||||||
[tgbot.answers]
|
|
||||||
"successfulOperation" = "✅ Operasi berhasil!"
|
|
||||||
"errorOperation" = "❗ Kesalahan dalam operasi."
|
|
||||||
"getInboundsFailed" = "❌ Gagal mendapatkan inbounds."
|
|
||||||
"canceled" = "❌ {{ .Email }}: Operasi dibatalkan."
|
|
||||||
"clientRefreshSuccess" = "✅ {{ .Email }}: Klien diperbarui dengan berhasil."
|
|
||||||
"IpRefreshSuccess" = "✅ {{ .Email }}: IP diperbarui dengan berhasil."
|
|
||||||
"TGIdRefreshSuccess" = "✅ {{ .Email }}: Pengguna Telegram Klien diperbarui dengan berhasil."
|
|
||||||
"resetTrafficSuccess" = "✅ {{ .Email }}: Lalu lintas direset dengan berhasil."
|
|
||||||
"setTrafficLimitSuccess" = "✅ {{ .Email }}: Batas lalu lintas disimpan dengan berhasil."
|
|
||||||
"expireResetSuccess" = "✅ {{ .Email }}: Hari kadaluarsa direset dengan berhasil."
|
|
||||||
"resetIpSuccess" = "✅ {{ .Email }}: Batas IP {{ .Count }} disimpan dengan berhasil."
|
|
||||||
"clearIpSuccess" = "✅ {{ .Email }}: IP dihapus dengan berhasil."
|
|
||||||
"getIpLog" = "✅ {{ .Email }}: Dapatkan Log IP."
|
|
||||||
"getUserInfo" = "✅ {{ .Email }}: Dapatkan Info Pengguna Telegram."
|
|
||||||
"removedTGUserSuccess" = "✅ {{ .Email }}: Pengguna Telegram dihapus dengan berhasil."
|
|
||||||
"enableSuccess" = "✅ {{ .Email }}: Diaktifkan dengan berhasil."
|
|
||||||
"disableSuccess" = "✅ {{ .Email }}: Dinonaktifkan dengan berhasil."
|
|
||||||
"askToAddUserId" = "Konfigurasi Anda tidak ditemukan!\r\nSilakan minta admin Anda untuk menggunakan ID Telegram Anda dalam konfigurasi Anda.\r\n\r\nID Pengguna Anda: <code>{{ .TgUserID }}</code>"
|
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"online" = "Онлайн"
|
"online" = "Онлайн"
|
||||||
"domainName" = "Домен"
|
"domainName" = "Домен"
|
||||||
"monitor" = "Порт IP"
|
"monitor" = "Порт IP"
|
||||||
"certificate" = "Цифровой сертификат"
|
"certificate" = "Сертификат"
|
||||||
"fail" = "Неудачно"
|
"fail" = "Неудачно"
|
||||||
"success" = "Успешно"
|
"success" = "Успешно"
|
||||||
"getVersion" = "Узнать версию"
|
"getVersion" = "Узнать версию"
|
||||||
@@ -52,14 +52,6 @@
|
|||||||
"secretToken" = "Секретный токен"
|
"secretToken" = "Секретный токен"
|
||||||
"remained" = "остались"
|
"remained" = "остались"
|
||||||
"security" = "Безопасность"
|
"security" = "Безопасность"
|
||||||
"secAlertTitle" = "Предупреждение системы безопасности"
|
|
||||||
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
|
|
||||||
"secAlertConf" = "Некоторые настройки уязвимы для атак. Рекомендуется усилить протоколы безопасности, чтобы предотвратить потенциальные нарушения."
|
|
||||||
"secAlertSSL" = "В панели отсутствует безопасное соединение. Пожалуйста, установите сертификат TLS для защиты данных."
|
|
||||||
"secAlertPanelPort" = "Порт по умолчанию панели небезопасен. Пожалуйста, настройте случайный или определенный порт."
|
|
||||||
"secAlertPanelURI" = "URI-путь по умолчанию панели небезопасен. Пожалуйста, настройте сложный URI-путь."
|
|
||||||
"secAlertSubURI" = "URI-путь по умолчанию подписки небезопасен. Пожалуйста, настройте сложный URI-путь."
|
|
||||||
"secAlertSubJsonURI" = "URI-путь по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-путь."
|
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Статус системы"
|
"dashboard" = "Статус системы"
|
||||||
@@ -70,7 +62,6 @@
|
|||||||
"link" = "менеджмент"
|
"link" = "менеджмент"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
"hello" = "Привет"
|
|
||||||
"title" = "Добро пожаловать"
|
"title" = "Добро пожаловать"
|
||||||
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова"
|
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова"
|
||||||
|
|
||||||
@@ -85,7 +76,7 @@
|
|||||||
"title" = "Статус системы"
|
"title" = "Статус системы"
|
||||||
"memory" = "Память"
|
"memory" = "Память"
|
||||||
"hard" = "Жесткий диск"
|
"hard" = "Жесткий диск"
|
||||||
"xrayStatus" = "Xray"
|
"xrayStatus" = "Статус"
|
||||||
"stopXray" = "Остановить"
|
"stopXray" = "Остановить"
|
||||||
"restartXray" = "Перезапустить"
|
"restartXray" = "Перезапустить"
|
||||||
"xraySwitch" = "Версия"
|
"xraySwitch" = "Версия"
|
||||||
@@ -149,8 +140,10 @@
|
|||||||
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
|
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
|
||||||
"certificatePath" = "Путь файла"
|
"certificatePath" = "Путь файла"
|
||||||
"certificateContent" = "Содержимое файла"
|
"certificateContent" = "Содержимое файла"
|
||||||
"publicKey" = "Публичный ключ"
|
"publicKeyPath" = "Путь к публичному ключу"
|
||||||
"privatekey" = "Приватный ключ"
|
"publicKeyContent" = "Содержимое публичного ключа"
|
||||||
|
"keyPath" = "Путь к приватному ключу"
|
||||||
|
"keyContent" = "Содержимое приватного ключа"
|
||||||
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
|
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
|
||||||
"client" = "Клиент"
|
"client" = "Клиент"
|
||||||
"export" = "Экспорт ключей"
|
"export" = "Экспорт ключей"
|
||||||
@@ -201,7 +194,7 @@
|
|||||||
"last" = "Последний"
|
"last" = "Последний"
|
||||||
"prefix" = "Префикс"
|
"prefix" = "Префикс"
|
||||||
"postfix" = "Постфикс"
|
"postfix" = "Постфикс"
|
||||||
"delayedStart" = "Начало использования"
|
"delayedStart" = "Начать с момента первого подключения"
|
||||||
"expireDays" = "Длительность"
|
"expireDays" = "Длительность"
|
||||||
"days" = "дней"
|
"days" = "дней"
|
||||||
"renew" = "Автопродление"
|
"renew" = "Автопродление"
|
||||||
@@ -309,8 +302,6 @@
|
|||||||
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
||||||
"subURI" = "URI обратного прокси"
|
"subURI" = "URI обратного прокси"
|
||||||
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
||||||
"fragment" = "Фрагментация"
|
|
||||||
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS"
|
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Настройки Xray"
|
"title" = "Настройки Xray"
|
||||||
@@ -320,8 +311,6 @@
|
|||||||
"advancedTemplate" = "Расширенный шаблон"
|
"advancedTemplate" = "Расширенный шаблон"
|
||||||
"generalConfigs" = "Основные настройки"
|
"generalConfigs" = "Основные настройки"
|
||||||
"generalConfigsDesc" = "Эти параметры описывают общие настройки"
|
"generalConfigsDesc" = "Эти параметры описывают общие настройки"
|
||||||
"logConfigs" = "Журнал"
|
|
||||||
"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их с умом только в случае ваших нужд!"
|
|
||||||
"blockConfigs" = "Блокировка конфигураций"
|
"blockConfigs" = "Блокировка конфигураций"
|
||||||
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
|
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
|
||||||
"blockCountryConfigs" = "Конфигурации блокировки страны"
|
"blockCountryConfigs" = "Конфигурации блокировки страны"
|
||||||
@@ -386,36 +375,23 @@
|
|||||||
"GoogleIPv4Desc" = "Добавить маршрутизацию для Google для подключения к IPv4"
|
"GoogleIPv4Desc" = "Добавить маршрутизацию для Google для подключения к IPv4"
|
||||||
"NetflixIPv4" = "Использовать IPv4 для Netflix"
|
"NetflixIPv4" = "Использовать IPv4 для Netflix"
|
||||||
"NetflixIPv4Desc" = "Добавить маршрутизацию для Netflix для подключения к IPv4"
|
"NetflixIPv4Desc" = "Добавить маршрутизацию для Netflix для подключения к IPv4"
|
||||||
"GoogleWARP" = "Google"
|
"GoogleWARP" = "Маршрутизация Google через WARP"
|
||||||
"GoogleWARPDesc" = "Направляет трафик в Google через WARP."
|
"GoogleWARPDesc" = "Добавить маршрутизацию для Google через WARP"
|
||||||
"OpenAIWARP" = "OpenAI (ChatGPT)"
|
"OpenAIWARP" = "Маршрутизация OpenAI (ChatGPT) через WARP"
|
||||||
"OpenAIWARPDesc" = "Направляет трафик в OpenAI (ChatGPT) через WARP."
|
"OpenAIWARPDesc" = "Добавить маршрутизацию для OpenAI (ChatGPT) через WARP"
|
||||||
"NetflixWARP" = "Netflix"
|
"NetflixWARP" = "Маршрутизация Netflix через WARP"
|
||||||
"NetflixWARPDesc" = "Направляет трафик в Apple через WARP."
|
"NetflixWARPDesc" = "Добавить маршрутизацию для Netflix через WARP"
|
||||||
"MetaWARP" = "Мета"
|
"SpotifyWARP" = "Маршрутизация Spotify через WARP"
|
||||||
"MetaWARPDesc" = "Направляет трафик в Meta (Instagram, Facebook, WhatsApp, Threads...) через WARP."
|
"SpotifyWARPDesc" = "Добавить маршрутизацию для Spotify через WARP"
|
||||||
"AppleWARP" = "Apple"
|
|
||||||
"AppleWARPDesc" = "Направляет трафик в Apple через WARP."
|
|
||||||
"RedditWARP" = "Reddit"
|
|
||||||
"RedditWARPDesc" = "Направляет трафик в Reddit через WARP."
|
|
||||||
"SpotifyWARP" = "Spotify"
|
|
||||||
"SpotifyWARPDesc" = "Направляет трафик в Spotify через WARP."
|
|
||||||
"IRWARP" = "Маршрутизация доменов Ирана через WARP"
|
"IRWARP" = "Маршрутизация доменов Ирана через WARP"
|
||||||
"IRWARPDesc" = "Добавить маршрутизацию для доменов Ирана через WARP"
|
"IRWARPDesc" = "Добавить маршрутизацию для доменов Ирана через WARP"
|
||||||
"Inbounds" = "Входящие"
|
"Inbounds" = "Входящие"
|
||||||
"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей"
|
"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей"
|
||||||
"Outbounds" = "Исходящие"
|
"Outbounds" = "Исходящие"
|
||||||
"Balancers" = "Балансиры"
|
|
||||||
"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера"
|
"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера"
|
||||||
"Routings" = "Правила маршрутизации"
|
"Routings" = "Правила маршрутизации"
|
||||||
"RoutingsDesc" = "Важен приоритет каждого правила!"
|
"RoutingsDesc" = "Важен приоритет каждого правила!"
|
||||||
"completeTemplate" = "Все"
|
"completeTemplate" = "Все"
|
||||||
"logLevel" = "Уровень журнала"
|
|
||||||
"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать."
|
|
||||||
"accessLog" = "Журнал доступа"
|
|
||||||
"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключило журналы доступа."
|
|
||||||
"errorLog" = "Журнал ошибок"
|
|
||||||
"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок."
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Первый"
|
"first" = "Первый"
|
||||||
@@ -426,7 +402,6 @@
|
|||||||
"dest" = "Пункт назначения"
|
"dest" = "Пункт назначения"
|
||||||
"inbound" = "Входящий"
|
"inbound" = "Входящий"
|
||||||
"outbound" = "Исходящий"
|
"outbound" = "Исходящий"
|
||||||
"balancer" = "балансир"
|
|
||||||
"info" = "Информация"
|
"info" = "Информация"
|
||||||
"add" = "Добавить правило"
|
"add" = "Добавить правило"
|
||||||
"edit" = "Редактировать правило"
|
"edit" = "Редактировать правило"
|
||||||
@@ -447,38 +422,14 @@
|
|||||||
"portal" = "Портал"
|
"portal" = "Портал"
|
||||||
"intercon" = "Соединение"
|
"intercon" = "Соединение"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
|
||||||
"addBalancer" = "Добавить балансир"
|
|
||||||
"editBalancer" = "Редактировать балансир"
|
|
||||||
"balancerStrategy" = "Стратегия"
|
|
||||||
"balancerSelectors" = "Селекторы"
|
|
||||||
"tag" = "Тег"
|
|
||||||
"tagDesc" = "уникальный тег"
|
|
||||||
"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
|
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "Секретный ключ"
|
"secretKey" = "Секретный ключ"
|
||||||
"publicKey" = "Публичный ключ"
|
"publicKey" = "Открытый ключ"
|
||||||
"allowedIPs" = "Разрешенные IP-адреса"
|
"allowedIPs" = "Разрешенные IP-адреса"
|
||||||
"endpoint" = "Конечная точка"
|
"endpoint" = "Конечная точка"
|
||||||
"psk" = "Общий ключ"
|
"psk" = "Общий ключ"
|
||||||
"domainStrategy" = "Стратегия домена"
|
"domainStrategy" = "Стратегия домена"
|
||||||
|
|
||||||
[pages.xray.dns]
|
|
||||||
"enable" = "Включить DNS"
|
|
||||||
"enableDesc" = "Включить встроенный DNS-сервер"
|
|
||||||
"strategy" = "Стратегия запроса"
|
|
||||||
"strategyDesc" = "Общая стратегия разрешения доменных имен"
|
|
||||||
"add" = "Добавить сервер"
|
|
||||||
"edit" = "Редактировать сервер"
|
|
||||||
"domains" = "Домены"
|
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
|
||||||
"add" = "Добавить поддельный DNS"
|
|
||||||
"edit" = "Редактировать поддельный DNS"
|
|
||||||
"ipPool" = "Подсеть пула IP"
|
|
||||||
"poolSize" = "Размер пула"
|
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Админ"
|
"admin" = "Админ"
|
||||||
"secret" = "Секретный токен"
|
"secret" = "Секретный токен"
|
||||||
|
|||||||
@@ -1,625 +0,0 @@
|
|||||||
"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" = "Взаємозв'язок"
|
|
||||||
|
|
||||||
[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-сервер"
|
|
||||||
"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>"
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"check" = "Kiểm tra"
|
"check" = "Kiểm tra"
|
||||||
"indefinite" = "Không xác định"
|
"indefinite" = "Không xác định"
|
||||||
"unlimited" = "Không giới hạn"
|
"unlimited" = "Không giới hạn"
|
||||||
"none" = "None"
|
"none" = "Không có"
|
||||||
"qrCode" = "Mã QR"
|
"qrCode" = "Mã QR"
|
||||||
"info" = "Thông tin thêm"
|
"info" = "Thông tin thêm"
|
||||||
"edit" = "Chỉnh sửa"
|
"edit" = "Chỉnh sửa"
|
||||||
@@ -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ỉ số"
|
"certificate" = "Chứng chỉ"
|
||||||
"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"
|
||||||
@@ -52,14 +52,6 @@
|
|||||||
"secretToken" = "Mã bí mật"
|
"secretToken" = "Mã bí mật"
|
||||||
"remained" = "Còn lại"
|
"remained" = "Còn lại"
|
||||||
"security" = "Bảo vệ"
|
"security" = "Bảo vệ"
|
||||||
"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"
|
|
||||||
"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"
|
||||||
@@ -70,7 +62,6 @@
|
|||||||
"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."
|
||||||
|
|
||||||
@@ -85,7 +76,7 @@
|
|||||||
"title" = "Trạng thái hệ thống"
|
"title" = "Trạng thái hệ thống"
|
||||||
"memory" = "Ram"
|
"memory" = "Ram"
|
||||||
"hard" = "Dung lượng"
|
"hard" = "Dung lượng"
|
||||||
"xrayStatus" = "Xray"
|
"xrayStatus" = "Trạng thái Xray"
|
||||||
"stopXray" = "Dừng lại"
|
"stopXray" = "Dừng lại"
|
||||||
"restartXray" = "Khởi động lại"
|
"restartXray" = "Khởi động lại"
|
||||||
"xraySwitch" = "Phiên bản"
|
"xraySwitch" = "Phiên bản"
|
||||||
@@ -149,8 +140,10 @@
|
|||||||
"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"
|
||||||
"publicKey" = "Khóa công khai"
|
"publicKeyPath" = "Đường dẫn khóa công khai"
|
||||||
"privatekey" = "Khóa cá nhân"
|
"publicKeyContent" = "Nội dung khóa công khai"
|
||||||
|
"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"
|
||||||
@@ -201,7 +194,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 ở Lần Đầu"
|
"delayedStart" = "Bắt đầu sau khi sử dụng 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"
|
||||||
@@ -309,8 +302,6 @@
|
|||||||
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
|
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
|
||||||
"subURI" = "URI proxy trung gian"
|
"subURI" = "URI proxy trung gian"
|
||||||
"subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian"
|
"subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian"
|
||||||
"fragment" = "Sự phân mảnh"
|
|
||||||
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
|
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Cài đặt Xray"
|
"title" = "Cài đặt Xray"
|
||||||
@@ -320,8 +311,6 @@
|
|||||||
"advancedTemplate" = "Mẫu Nâng cao"
|
"advancedTemplate" = "Mẫu Nâng cao"
|
||||||
"generalConfigs" = "Cấu hình Chung"
|
"generalConfigs" = "Cấu hình Chung"
|
||||||
"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát."
|
"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát."
|
||||||
"logConfigs" = "Nhật ký"
|
|
||||||
"logConfigsDesc" = "Nhật ký có thể ảnh hưởng đến hiệu suất máy chủ của bạn. Bạn chỉ nên kích hoạt nó một cách khôn ngoan trong trường hợp bạn cần"
|
|
||||||
"blockConfigs" = "Cấu hình Chặn"
|
"blockConfigs" = "Cấu hình Chặn"
|
||||||
"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể."
|
"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể."
|
||||||
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
|
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
|
||||||
@@ -386,36 +375,23 @@
|
|||||||
"GoogleIPv4Desc" = "Thêm định tuyến cho Google để kết nối qua IPv4."
|
"GoogleIPv4Desc" = "Thêm định tuyến cho Google để kết nối qua IPv4."
|
||||||
"NetflixIPv4" = "Sử dụng IPv4 cho Netflix"
|
"NetflixIPv4" = "Sử dụng IPv4 cho Netflix"
|
||||||
"NetflixIPv4Desc" = "Thêm định tuyến cho Netflix để kết nối qua IPv4."
|
"NetflixIPv4Desc" = "Thêm định tuyến cho Netflix để kết nối qua IPv4."
|
||||||
"GoogleWARP" = "Google"
|
"GoogleWARP" = "Định tuyến Google qua WARP."
|
||||||
"GoogleWARPDesc" = "Định tuyến lưu lượng truy cập tới Google thông qua WARP."
|
"GoogleWARPDesc" = "Thêm định tuyến cho Google qua WARP."
|
||||||
"OpenAIWARP" = "OpenAI (ChatGPT)"
|
"OpenAIWARP" = "Định tuyến OpenAI (ChatGPT) qua WARP."
|
||||||
"OpenAIWARPDesc" = "Định tuyến lưu lượng truy cập tới OpenAI (ChatGPT) thông qua WARP."
|
"OpenAIWARPDesc" = "Thêm định tuyến cho OpenAI (ChatGPT) qua WARP."
|
||||||
"NetflixWARP" = "Netflix"
|
"NetflixWARP" = "Định tuyến Netflix qua WARP."
|
||||||
"NetflixWARPDesc" = "Định tuyến lưu lượng truy cập tới Netflix thông qua WARP."
|
"NetflixWARPDesc" = "Thêm định tuyến cho Netflix qua WARP."
|
||||||
"MetaWARP" = "Meta"
|
"SpotifyWARP" = "Định tuyến Spotify qua WARP."
|
||||||
"MetaWARPDesc" = "Định tuyến lưu lượng truy cập tới Meta (Instagram, Facebook, WhatsApp, Threads,...) thông qua WARP."
|
"SpotifyWARPDesc" = "Thêm định tuyến cho Spotify qua WARP."
|
||||||
"AppleWARP" = "Apple"
|
|
||||||
"AppleWARPDesc" = "Định tuyến lưu lượng truy cập tới Apple thông qua WARP."
|
|
||||||
"RedditWARP" = "Reddit"
|
|
||||||
"RedditWARPDesc" = "Định tuyến lưu lượng truy cập tới Reddit thông qua WARP."
|
|
||||||
"SpotifyWARP" = "Spotify"
|
|
||||||
"SpotifyWARPDesc" = "Định tuyến lưu lượng truy cập tới Spotify thông qua WARP."
|
|
||||||
"IRWARP" = "Định tuyến tên miền của Iran qua WARP."
|
"IRWARP" = "Định tuyến tên miền của Iran qua WARP."
|
||||||
"IRWARPDesc" = "Thêm định tuyến cho các tên miền của Iran qua WARP."
|
"IRWARPDesc" = "Thêm định tuyến cho các tên miền của Iran qua WARP."
|
||||||
"Inbounds" = "Đầu vào"
|
"Inbounds" = "Đầu vào"
|
||||||
"InboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể."
|
"InboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể."
|
||||||
"Outbounds" = "Đầu ra"
|
"Outbounds" = "Đầu ra"
|
||||||
"Balancers" = "Cân bằng"
|
|
||||||
"OutboundsDesc" = "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này."
|
"OutboundsDesc" = "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này."
|
||||||
"Routings" = "Quy tắc định tuyến"
|
"Routings" = "Quy tắc định tuyến"
|
||||||
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
|
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
|
||||||
"completeTemplate" = "All"
|
"completeTemplate" = "All"
|
||||||
"logLevel" = "Mức đăng nhập"
|
|
||||||
"logLevelDesc" = "Cấp độ nhật ký cho nhật ký lỗi, cho biết thông tin cần được ghi lại."
|
|
||||||
"accessLog" = "Nhật ký truy cập"
|
|
||||||
"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập. Nhật ký truy cập bị vô hiệu hóa có giá trị đặc biệt 'không'"
|
|
||||||
"errorLog" = "Nhật ký lỗi"
|
|
||||||
"errorLogDesc" = "Đường dẫn tệp cho nhật ký lỗi. Nhật ký lỗi bị vô hiệu hóa có giá trị đặc biệt 'không'"
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Đầu tiên"
|
"first" = "Đầu tiên"
|
||||||
@@ -426,7 +402,6 @@
|
|||||||
"dest" = "Đích"
|
"dest" = "Đích"
|
||||||
"inbound" = "Vào"
|
"inbound" = "Vào"
|
||||||
"outbound" = "Ra"
|
"outbound" = "Ra"
|
||||||
"balancer" = "Cân bằng"
|
|
||||||
"info" = "Thông tin"
|
"info" = "Thông tin"
|
||||||
"add" = "Thêm quy tắc"
|
"add" = "Thêm quy tắc"
|
||||||
"edit" = "Chỉnh sửa quy tắc"
|
"edit" = "Chỉnh sửa quy tắc"
|
||||||
@@ -447,15 +422,6 @@
|
|||||||
"portal" = "Cổng thông tin"
|
"portal" = "Cổng thông tin"
|
||||||
"intercon" = "Kết nối"
|
"intercon" = "Kết nối"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
|
||||||
"addBalancer" = "Thêm cân bằng"
|
|
||||||
"editBalancer" = "Chỉnh sửa cân bằng"
|
|
||||||
"balancerStrategy" = "Chiến lược"
|
|
||||||
"balancerSelectors" = "Bộ chọn"
|
|
||||||
"tag" = "Thẻ"
|
|
||||||
"tagDesc" = "thẻ duy nhất"
|
|
||||||
"balancerDesc" = "Không thể sử dụng balancerTag và outboundTag cùng một lúc. Nếu sử dụng cùng lúc thì chỉ outboundTag mới hoạt động."
|
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "Khoá bí mật"
|
"secretKey" = "Khoá bí mật"
|
||||||
"publicKey" = "Khóa công khai"
|
"publicKey" = "Khóa công khai"
|
||||||
@@ -464,21 +430,6 @@
|
|||||||
"psk" = "Khóa chia sẻ"
|
"psk" = "Khóa chia sẻ"
|
||||||
"domainStrategy" = "Chiến lược tên miền"
|
"domainStrategy" = "Chiến lược tên miền"
|
||||||
|
|
||||||
[pages.xray.dns]
|
|
||||||
"enable" = "Kích hoạt DNS"
|
|
||||||
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
|
|
||||||
"strategy" = "Chiến lược truy vấn"
|
|
||||||
"strategyDesc" = "Chiến lược tổng thể để phân giải tên miền"
|
|
||||||
"add" = "Thêm máy chủ"
|
|
||||||
"edit" = "Chỉnh sửa máy chủ"
|
|
||||||
"domains" = "Tên miền"
|
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
|
||||||
"add" = "Thêm DNS giả"
|
|
||||||
"edit" = "Chỉnh sửa DNS giả"
|
|
||||||
"ipPool" = "Mạng con nhóm IP"
|
|
||||||
"poolSize" = "Kích thước bể bơi"
|
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Quản trị viên"
|
"admin" = "Quản trị viên"
|
||||||
"secret" = "Mã thông báo bí mật"
|
"secret" = "Mã thông báo bí mật"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"online" = "在线"
|
"online" = "在线"
|
||||||
"domainName" = "域名"
|
"domainName" = "域名"
|
||||||
"monitor" = "监听"
|
"monitor" = "监听"
|
||||||
"certificate" = "数字证书"
|
"certificate" = "证书"
|
||||||
"fail" = "失败"
|
"fail" = "失败"
|
||||||
"success" = "成功"
|
"success" = "成功"
|
||||||
"getVersion" = "获取版本"
|
"getVersion" = "获取版本"
|
||||||
@@ -52,14 +52,6 @@
|
|||||||
"secretToken" = "安全密钥"
|
"secretToken" = "安全密钥"
|
||||||
"remained" = "剩余"
|
"remained" = "剩余"
|
||||||
"security" = "安全"
|
"security" = "安全"
|
||||||
"secAlertTitle" = "安全警报"
|
|
||||||
"secAlertSsl" = "此连接不安全;在激活 TLS 进行数据保护之前,请勿输入敏感信息"
|
|
||||||
"secAlertConf" = "某些设置容易受到攻击。建议加强安全协议以防止潜在的违规行为。"
|
|
||||||
"secAlertSSL" = "面板缺乏安全连接。请安装 TLS 证书以保护数据。"
|
|
||||||
"secAlertPanelPort" = "面板默认端口存在漏洞。请配置随机或特定端口。"
|
|
||||||
"secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
|
||||||
"secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
|
||||||
"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "系统状态"
|
"dashboard" = "系统状态"
|
||||||
@@ -70,7 +62,6 @@
|
|||||||
"link" = "管理"
|
"link" = "管理"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
"hello" = "你好"
|
|
||||||
"title" = "欢迎"
|
"title" = "欢迎"
|
||||||
"loginAgain" = "登录时效已过,请重新登录"
|
"loginAgain" = "登录时效已过,请重新登录"
|
||||||
|
|
||||||
@@ -85,7 +76,7 @@
|
|||||||
"title" = "系统状态"
|
"title" = "系统状态"
|
||||||
"memory" = "内存"
|
"memory" = "内存"
|
||||||
"hard" = "硬盘"
|
"hard" = "硬盘"
|
||||||
"xrayStatus" = "Xray"
|
"xrayStatus" = "状态"
|
||||||
"stopXray" = "停止"
|
"stopXray" = "停止"
|
||||||
"restartXray" = "重启"
|
"restartXray" = "重启"
|
||||||
"xraySwitch" = "版本"
|
"xraySwitch" = "版本"
|
||||||
@@ -149,8 +140,10 @@
|
|||||||
"noRecommendKeepDefault" = "没有特殊需求保持默认即可"
|
"noRecommendKeepDefault" = "没有特殊需求保持默认即可"
|
||||||
"certificatePath" = "文件路径"
|
"certificatePath" = "文件路径"
|
||||||
"certificateContent" = "文件内容"
|
"certificateContent" = "文件内容"
|
||||||
"publicKey" = "公钥"
|
"publicKeyPath" = "公钥文件路径"
|
||||||
"privatekey" = "私钥"
|
"publicKeyContent" = "公钥内容"
|
||||||
|
"keyPath" = "密钥文件路径"
|
||||||
|
"keyContent" = "密钥内容"
|
||||||
"clickOnQRcode" = "点击二维码复制"
|
"clickOnQRcode" = "点击二维码复制"
|
||||||
"client" = "客户"
|
"client" = "客户"
|
||||||
"export" = "导出链接"
|
"export" = "导出链接"
|
||||||
@@ -309,8 +302,6 @@
|
|||||||
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
||||||
"subURI" = "反向代理 URI"
|
"subURI" = "反向代理 URI"
|
||||||
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
||||||
"fragment" = "碎片"
|
|
||||||
"fragmentDesc" = "启用 TLS hello 数据包分段"
|
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Xray 设置"
|
"title" = "Xray 设置"
|
||||||
@@ -320,8 +311,6 @@
|
|||||||
"advancedTemplate" = "高级模板部件"
|
"advancedTemplate" = "高级模板部件"
|
||||||
"generalConfigs" = "通用配置"
|
"generalConfigs" = "通用配置"
|
||||||
"generalConfigsDesc" = "这些选项将提供一般调整"
|
"generalConfigsDesc" = "这些选项将提供一般调整"
|
||||||
"logConfigs"="日志"
|
|
||||||
"logConfigsDesc" = "日志可能会影响您服务器的效率。建议仅在您需要时明智地启用它"
|
|
||||||
"blockConfigs" = "阻塞配置"
|
"blockConfigs" = "阻塞配置"
|
||||||
"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站"
|
"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站"
|
||||||
"blockCountryConfigs" = "阻止国家配置"
|
"blockCountryConfigs" = "阻止国家配置"
|
||||||
@@ -386,36 +375,23 @@
|
|||||||
"GoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
|
"GoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
|
||||||
"NetflixIPv4" = "为 Netflix 使用 IPv4"
|
"NetflixIPv4" = "为 Netflix 使用 IPv4"
|
||||||
"NetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
|
"NetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
|
||||||
"GoogleWARP" = "Google"
|
"GoogleWARP" = "将谷歌路由到 WARP"
|
||||||
"GoogleWARPDesc" = "通过 WARP 将流量路由到 Google。"
|
"GoogleWARPDesc" = "为谷歌添加路由到WARP"
|
||||||
"OpenAIWARP" = "OpenAI (ChatGPT)"
|
"OpenAIWARP" = "将 OpenAI (ChatGPT) 路由到 WARP"
|
||||||
"OpenAIWARPDesc" = "通过 WARP 将流量路由到 OpenAI (ChatGPT)。"
|
"OpenAIWARPDesc" = "将OpenAI(ChatGPT)路由添加到WARP"
|
||||||
"NetflixWARP" = "Netflix"
|
"NetflixWARP" = "将 Netflix 路由到 WARP"
|
||||||
"NetflixWARPDesc" = "通过 WARP 将流量路由到 Netflix。"
|
"NetflixWARPDesc" = "为Netflix添加路由到WARP"
|
||||||
"MetaWARP"="Meta"
|
"SpotifyWARP" = "将 Spotify 路由到 WARP"
|
||||||
"MetaWARPDesc" = "通过 WARP 将流量路由到 Meta(Instagram、Facebook、WhatsApp、Threads...)"
|
"SpotifyWARPDesc" = "为Spotify添加路由到WARP"
|
||||||
"AppleWARP" = "Apple"
|
|
||||||
"AppleWARPDesc" = "通过 WARP 将流量路由到 Apple。"
|
|
||||||
"RedditWARP" = "Reddit"
|
|
||||||
"RedditWARPDesc" = "通过 WARP 将流量路由到 Reddit。"
|
|
||||||
"SpotifyWARP" = "Spotify"
|
|
||||||
"SpotifyWARPDesc" = "通过 WARP 将流量路由到 Spotify。"
|
|
||||||
"IRWARP" = "将伊朗域名路由到 WARP"
|
"IRWARP" = "将伊朗域名路由到 WARP"
|
||||||
"IRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效"
|
"IRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效"
|
||||||
"Inbounds" = "入站"
|
"Inbounds" = "入站"
|
||||||
"InboundsDesc" = "更改配置模板接受特殊客户端"
|
"InboundsDesc" = "更改配置模板接受特殊客户端"
|
||||||
"Outbounds" = "出站"
|
"Outbounds" = "出站"
|
||||||
"Balancers" = "平衡器"
|
|
||||||
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式"
|
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式"
|
||||||
"Routings" = "路由规则"
|
"Routings" = "路由规则"
|
||||||
"RoutingsDesc" = "每条规则的优先级都很重要"
|
"RoutingsDesc" = "每条规则的优先级都很重要"
|
||||||
"completeTemplate" = "全部"
|
"completeTemplate" = "全部"
|
||||||
"logLevel" = "日志级别"
|
|
||||||
"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。"
|
|
||||||
"accessLog" = "访问日志"
|
|
||||||
"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志"
|
|
||||||
"errorLog" = "错误日志"
|
|
||||||
"errorLogDesc" = "错误日志的文件路径。 特殊值“none”禁用错误日志"
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "第一个"
|
"first" = "第一个"
|
||||||
@@ -426,7 +402,6 @@
|
|||||||
"dest" = "目的地"
|
"dest" = "目的地"
|
||||||
"inbound" = "入站"
|
"inbound" = "入站"
|
||||||
"outbound" = "出站"
|
"outbound" = "出站"
|
||||||
"balancer" = "平衡器"
|
|
||||||
"info" = "信息"
|
"info" = "信息"
|
||||||
"add" = "添加规则"
|
"add" = "添加规则"
|
||||||
"edit" = "编辑规则"
|
"edit" = "编辑规则"
|
||||||
@@ -447,15 +422,6 @@
|
|||||||
"portal" = "门户"
|
"portal" = "门户"
|
||||||
"intercon" = "互连"
|
"intercon" = "互连"
|
||||||
|
|
||||||
[pages.xray.balancer]
|
|
||||||
"addBalancer" = "添加平衡器"
|
|
||||||
"editBalancer" = "编辑平衡器"
|
|
||||||
"balancerStrategy" = "战略"
|
|
||||||
"balancerSelectors" = "选择器"
|
|
||||||
"tag" = "标签"
|
|
||||||
"tagDesc" = "唯一标记"
|
|
||||||
"balancerDesc" = "不能同时使用balancerTag和outboundTag。 如果同时使用,则只有outboundTag起作用。"
|
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "密钥"
|
"secretKey" = "密钥"
|
||||||
"publicKey" = "公钥"
|
"publicKey" = "公钥"
|
||||||
@@ -464,21 +430,6 @@
|
|||||||
"psk" = "共享密钥"
|
"psk" = "共享密钥"
|
||||||
"domainStrategy" = "域策略"
|
"domainStrategy" = "域策略"
|
||||||
|
|
||||||
[pages.xray.dns]
|
|
||||||
"enable" = "启用 DNS"
|
|
||||||
"enableDesc" = "启用内置 DNS 服务器"
|
|
||||||
"strategy" = "查询策略"
|
|
||||||
"strategyDesc" = "解析域名的总体策略"
|
|
||||||
"add" = "添加服务器"
|
|
||||||
"edit" = "编辑服务器"
|
|
||||||
"domains" = "域"
|
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
|
||||||
"add" = "添加假 DNS"
|
|
||||||
"edit" = "编辑假 DNS"
|
|
||||||
"ipPool" = "IP 池子网"
|
|
||||||
"poolSize" = "池大小"
|
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "管理员"
|
"admin" = "管理员"
|
||||||
"secret" = "密钥"
|
"secret" = "密钥"
|
||||||
|
|||||||
22
web/web.go
22
web/web.go
@@ -337,17 +337,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 {
|
||||||
c := &tls.Config{
|
listener.Close()
|
||||||
Certificates: []tls.Certificate{cert},
|
return err
|
||||||
}
|
|
||||||
listener = network.NewAutoHttpsListener(listener)
|
|
||||||
listener = tls.NewListener(listener, c)
|
|
||||||
logger.Info("web server run https on", listener.Addr())
|
|
||||||
} else {
|
|
||||||
logger.Error("error in loading certificates: ", err)
|
|
||||||
logger.Info("web server run http on", listener.Addr())
|
|
||||||
}
|
}
|
||||||
|
c := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
}
|
||||||
|
listener = network.NewAutoHttpsListener(listener)
|
||||||
|
listener = tls.NewListener(listener, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if certFile != "" || keyFile != "" {
|
||||||
|
logger.Info("web server run https on", listener.Addr())
|
||||||
} else {
|
} else {
|
||||||
logger.Info("web server run http on", listener.Addr())
|
logger.Info("web server run http on", listener.Addr())
|
||||||
}
|
}
|
||||||
|
|||||||
138
x-ui.sh
138
x-ui.sh
@@ -150,12 +150,6 @@ custom_version() {
|
|||||||
eval $install_command
|
eval $install_command
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to handle the deletion of the script file
|
|
||||||
delete_script() {
|
|
||||||
rm "$0" # Remove the script file itself
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
uninstall() {
|
uninstall() {
|
||||||
confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" "n"
|
confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" "n"
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
@@ -173,13 +167,12 @@ uninstall() {
|
|||||||
rm /usr/local/x-ui/ -rf
|
rm /usr/local/x-ui/ -rf
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "Uninstalled Successfully.\n"
|
echo -e "Uninstalled Successfully, If you want to remove this script, then after exiting the script run ${green}rm /usr/bin/x-ui -f${plain} to delete it."
|
||||||
echo "If you need to install this panel again, you can use below command:"
|
|
||||||
echo -e "${green}bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)${plain}"
|
|
||||||
echo ""
|
echo ""
|
||||||
# Trap the SIGTERM signal
|
|
||||||
trap delete_script SIGTERM
|
if [[ $# == 0 ]]; then
|
||||||
delete_script
|
before_show_menu
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_user() {
|
reset_user() {
|
||||||
@@ -345,47 +338,6 @@ show_banlog() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
bbr_menu() {
|
|
||||||
echo -e "${green}\t1.${plain} Enable BBR"
|
|
||||||
echo -e "${green}\t2.${plain} Disable BBR"
|
|
||||||
echo -e "${green}\t0.${plain} Back to Main Menu"
|
|
||||||
read -p "Choose an option: " choice
|
|
||||||
case "$choice" in
|
|
||||||
0)
|
|
||||||
show_menu
|
|
||||||
;;
|
|
||||||
1)
|
|
||||||
enable_bbr
|
|
||||||
;;
|
|
||||||
2)
|
|
||||||
disable_bbr
|
|
||||||
;;
|
|
||||||
*) echo "Invalid choice" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
disable_bbr() {
|
|
||||||
|
|
||||||
if ! grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf || ! grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
|
|
||||||
echo -e "${yellow}BBR is not currently enabled.${plain}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Replace BBR with CUBIC configurations
|
|
||||||
sed -i 's/net.core.default_qdisc=fq/net.core.default_qdisc=pfifo_fast/' /etc/sysctl.conf
|
|
||||||
sed -i 's/net.ipv4.tcp_congestion_control=bbr/net.ipv4.tcp_congestion_control=cubic/' /etc/sysctl.conf
|
|
||||||
|
|
||||||
# Apply changes
|
|
||||||
sysctl -p
|
|
||||||
|
|
||||||
# Verify that BBR is replaced with CUBIC
|
|
||||||
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "cubic" ]]; then
|
|
||||||
echo -e "${green}BBR has been replaced with CUBIC successfully.${plain}"
|
|
||||||
else
|
|
||||||
echo -e "${red}Failed to replace BBR with CUBIC. Please check your system configuration.${plain}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
enable_bbr() {
|
enable_bbr() {
|
||||||
if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
|
if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
|
||||||
echo -e "${green}BBR is already enabled!${plain}"
|
echo -e "${green}BBR is already enabled!${plain}"
|
||||||
@@ -531,33 +483,6 @@ show_xray_status() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
firewall_menu() {
|
|
||||||
echo -e "${green}\t1.${plain} Install Firewall & open ports"
|
|
||||||
echo -e "${green}\t2.${plain} Allowed List"
|
|
||||||
echo -e "${green}\t3.${plain} Delete Ports from List"
|
|
||||||
echo -e "${green}\t4.${plain} Disable Firewall"
|
|
||||||
echo -e "${green}\t0.${plain} Back to Main Menu"
|
|
||||||
read -p "Choose an option: " choice
|
|
||||||
case "$choice" in
|
|
||||||
0)
|
|
||||||
show_menu
|
|
||||||
;;
|
|
||||||
1)
|
|
||||||
open_ports
|
|
||||||
;;
|
|
||||||
2)
|
|
||||||
sudo ufw status
|
|
||||||
;;
|
|
||||||
3)
|
|
||||||
delete_ports
|
|
||||||
;;
|
|
||||||
4)
|
|
||||||
sudo ufw disable
|
|
||||||
;;
|
|
||||||
*) echo "Invalid choice" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
open_ports() {
|
open_ports() {
|
||||||
if ! command -v ufw &>/dev/null; then
|
if ! command -v ufw &>/dev/null; then
|
||||||
echo "ufw firewall is not installed. Installing now..."
|
echo "ufw firewall is not installed. Installing now..."
|
||||||
@@ -610,37 +535,6 @@ open_ports() {
|
|||||||
ufw status | grep $ports
|
ufw status | grep $ports
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_ports() {
|
|
||||||
# Prompt the user to enter the ports they want to delete
|
|
||||||
read -p "Enter the ports you want to delete (e.g. 80,443,2053 or range 400-500): " ports
|
|
||||||
|
|
||||||
# Check if the input is valid
|
|
||||||
if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
|
|
||||||
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Delete the specified ports using ufw
|
|
||||||
IFS=',' read -ra PORT_LIST <<<"$ports"
|
|
||||||
for port in "${PORT_LIST[@]}"; do
|
|
||||||
if [[ $port == *-* ]]; then
|
|
||||||
# Split the range into start and end ports
|
|
||||||
start_port=$(echo $port | cut -d'-' -f1)
|
|
||||||
end_port=$(echo $port | cut -d'-' -f2)
|
|
||||||
# Loop through the range and delete each port
|
|
||||||
for ((i = start_port; i <= end_port; i++)); do
|
|
||||||
ufw delete allow $i
|
|
||||||
done
|
|
||||||
else
|
|
||||||
ufw delete allow "$port"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Confirm that the ports are deleted
|
|
||||||
echo "Deleted the specified ports:"
|
|
||||||
ufw status | grep $ports
|
|
||||||
}
|
|
||||||
|
|
||||||
update_geo() {
|
update_geo() {
|
||||||
local defaultBinFolder="/usr/local/x-ui/bin"
|
local defaultBinFolder="/usr/local/x-ui/bin"
|
||||||
read -p "Please enter x-ui bin folder path. Leave blank for default. (Default: '${defaultBinFolder}')" binFolder
|
read -p "Please enter x-ui bin folder path. Leave blank for default. (Default: '${defaultBinFolder}')" binFolder
|
||||||
@@ -947,8 +841,8 @@ run_speedtest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create_iplimit_jails() {
|
create_iplimit_jails() {
|
||||||
# Use default bantime if not passed => 15 minutes
|
# Use default bantime if not passed => 30 minutes
|
||||||
local bantime="${1:-15}"
|
local bantime="${1:-30}"
|
||||||
|
|
||||||
# Uncomment 'allowipv6 = auto' in fail2ban.conf
|
# Uncomment 'allowipv6 = auto' in fail2ban.conf
|
||||||
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
|
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
|
||||||
@@ -959,8 +853,8 @@ enabled=true
|
|||||||
filter=3x-ipl
|
filter=3x-ipl
|
||||||
action=3x-ipl
|
action=3x-ipl
|
||||||
logpath=${iplimit_log_path}
|
logpath=${iplimit_log_path}
|
||||||
maxretry=2
|
maxretry=4
|
||||||
findtime=32
|
findtime=60
|
||||||
bantime=${bantime}m
|
bantime=${bantime}m
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -973,7 +867,7 @@ EOF
|
|||||||
|
|
||||||
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
|
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
|
||||||
[INCLUDES]
|
[INCLUDES]
|
||||||
before = iptables-allports.conf
|
before = iptables-common.conf
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
actionstart = <iptables> -N f2b-<name>
|
actionstart = <iptables> -N f2b-<name>
|
||||||
@@ -1230,10 +1124,10 @@ show_menu() {
|
|||||||
${green}17.${plain} Cloudflare SSL Certificate
|
${green}17.${plain} Cloudflare SSL Certificate
|
||||||
${green}18.${plain} IP Limit Management
|
${green}18.${plain} IP Limit Management
|
||||||
${green}19.${plain} WARP Management
|
${green}19.${plain} WARP Management
|
||||||
${green}20.${plain} Firewall Management
|
|
||||||
————————————————
|
————————————————
|
||||||
${green}21.${plain} Enable BBR
|
${green}20.${plain} Enable BBR
|
||||||
${green}22.${plain} Update Geo Files
|
${green}21.${plain} Update Geo Files
|
||||||
|
${green}22.${plain} Active Firewall and open ports
|
||||||
${green}23.${plain} Speedtest by Ookla
|
${green}23.${plain} Speedtest by Ookla
|
||||||
"
|
"
|
||||||
show_status
|
show_status
|
||||||
@@ -1301,13 +1195,13 @@ show_menu() {
|
|||||||
warp_cloudflare
|
warp_cloudflare
|
||||||
;;
|
;;
|
||||||
20)
|
20)
|
||||||
firewall_menu
|
enable_bbr
|
||||||
;;
|
;;
|
||||||
21)
|
21)
|
||||||
bbr_menu
|
update_geo
|
||||||
;;
|
;;
|
||||||
22)
|
22)
|
||||||
update_geo
|
open_ports
|
||||||
;;
|
;;
|
||||||
23)
|
23)
|
||||||
run_speedtest
|
run_speedtest
|
||||||
|
|||||||
@@ -213,7 +213,6 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isInbound := matchs[1] == "inbound"
|
isInbound := matchs[1] == "inbound"
|
||||||
isOutbound := matchs[1] == "outbound"
|
|
||||||
tag := matchs[2]
|
tag := matchs[2]
|
||||||
isDown := matchs[3] == "downlink"
|
isDown := matchs[3] == "downlink"
|
||||||
if tag == "api" {
|
if tag == "api" {
|
||||||
@@ -222,9 +221,8 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
|||||||
traffic, ok := tagTrafficMap[tag]
|
traffic, ok := tagTrafficMap[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
traffic = &Traffic{
|
traffic = &Traffic{
|
||||||
IsInbound: isInbound,
|
IsInbound: isInbound,
|
||||||
IsOutbound: isOutbound,
|
Tag: tag,
|
||||||
Tag: tag,
|
|
||||||
}
|
}
|
||||||
tagTrafficMap[tag] = traffic
|
tagTrafficMap[tag] = traffic
|
||||||
traffics = append(traffics, traffic)
|
traffics = append(traffics, traffic)
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ type Config struct {
|
|||||||
API json_util.RawMessage `json:"api"`
|
API json_util.RawMessage `json:"api"`
|
||||||
Stats json_util.RawMessage `json:"stats"`
|
Stats json_util.RawMessage `json:"stats"`
|
||||||
Reverse json_util.RawMessage `json:"reverse"`
|
Reverse json_util.RawMessage `json:"reverse"`
|
||||||
FakeDNS json_util.RawMessage `json:"fakedns"`
|
FakeDNS json_util.RawMessage `json:"fakeDns"`
|
||||||
Observatory json_util.RawMessage `json:"observatory"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Equals(other *Config) bool {
|
func (c *Config) Equals(other *Config) bool {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package xray
|
package xray
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
)
|
)
|
||||||
@@ -15,29 +14,37 @@ type LogWriter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (lw *LogWriter) Write(m []byte) (n int, err error) {
|
func (lw *LogWriter) Write(m []byte) (n int, err error) {
|
||||||
regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[([^\]]+)\] (.+)$`)
|
|
||||||
// Convert the data to a string
|
// Convert the data to a string
|
||||||
message := strings.TrimSpace(string(m))
|
message := strings.TrimSpace(string(m))
|
||||||
messages := strings.Split(message, "\n")
|
messages := strings.Split(message, "\n")
|
||||||
lw.lastLine = messages[len(messages)-1]
|
lw.lastLine = messages[len(messages)-1]
|
||||||
|
|
||||||
for _, msg := range messages {
|
for _, msg := range messages {
|
||||||
matches := regex.FindStringSubmatch(msg)
|
messageBody := msg
|
||||||
|
|
||||||
if len(matches) > 3 {
|
// Remove timestamp
|
||||||
level := matches[2]
|
splittedMsg := strings.SplitN(msg, " ", 3)
|
||||||
msgBody := matches[3]
|
if len(splittedMsg) > 2 {
|
||||||
|
messageBody = strings.TrimSpace(strings.SplitN(msg, " ", 3)[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find level in []
|
||||||
|
startIndex := strings.Index(messageBody, "[")
|
||||||
|
endIndex := strings.Index(messageBody, "]")
|
||||||
|
if startIndex != -1 && endIndex != -1 {
|
||||||
|
level := strings.TrimSpace(messageBody[startIndex+1 : endIndex])
|
||||||
|
msgBody := "XRAY: " + strings.TrimSpace(messageBody[endIndex+1:])
|
||||||
|
|
||||||
// Map the level to the appropriate logger function
|
// Map the level to the appropriate logger function
|
||||||
switch level {
|
switch level {
|
||||||
case "Debug":
|
case "Debug":
|
||||||
logger.Debug("XRAY: " + msgBody)
|
logger.Debug(msgBody)
|
||||||
case "Info":
|
case "Info":
|
||||||
logger.Info("XRAY: " + msgBody)
|
logger.Info(msgBody)
|
||||||
case "Warning":
|
case "Warning":
|
||||||
logger.Warning("XRAY: " + msgBody)
|
logger.Warning(msgBody)
|
||||||
case "Error":
|
case "Error":
|
||||||
logger.Error("XRAY: " + msgBody)
|
logger.Error(msgBody)
|
||||||
default:
|
default:
|
||||||
logger.Debug("XRAY: " + msg)
|
logger.Debug("XRAY: " + msg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ func GetIPLimitLogPath() string {
|
|||||||
return config.GetLogFolder() + "/3xipl.log"
|
return config.GetLogFolder() + "/3xipl.log"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetIPLimitPrevLogPath() string {
|
||||||
|
return config.GetLogFolder() + "/3xipl.prev.log"
|
||||||
|
}
|
||||||
|
|
||||||
func GetIPLimitBannedLogPath() string {
|
func GetIPLimitBannedLogPath() string {
|
||||||
return config.GetLogFolder() + "/3xipl-banned.log"
|
return config.GetLogFolder() + "/3xipl-banned.log"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package xray
|
package xray
|
||||||
|
|
||||||
type Traffic struct {
|
type Traffic struct {
|
||||||
IsInbound bool
|
IsInbound bool
|
||||||
IsOutbound bool
|
Tag string
|
||||||
Tag string
|
Up int64
|
||||||
Up int64
|
Down int64
|
||||||
Down int64
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user