mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-09-09 19:56:19 +00:00
Merge branch 'MHSanaei:main' into main
This commit is contained in:
commit
fd3497b4ce
78 changed files with 2917 additions and 767 deletions
12
.github/workflows/docker.yml
vendored
12
.github/workflows/docker.yml
vendored
|
@ -11,15 +11,15 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the code
|
- name: Check out the code
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.0.0
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@v3.0.0
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
@ -27,12 +27,12 @@ jobs:
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5.5.1
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v5.1.0
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
@ -20,10 +20,10 @@ jobs:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5.0.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.22'
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ jobs:
|
||||||
cd x-ui/bin
|
cd x-ui/bin
|
||||||
|
|
||||||
# Download dependencies
|
# Download dependencies
|
||||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.7/"
|
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.8/"
|
||||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||||
wget ${Xray_URL}Xray-linux-64.zip
|
wget ${Xray_URL}Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
|
@ -117,7 +117,7 @@ jobs:
|
||||||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||||
|
|
||||||
- name: Upload files to GH release
|
- name: Upload files to GH release
|
||||||
uses: MHSanaei/upload-release-action@2.8.0
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
|
|
|
@ -27,7 +27,7 @@ case $1 in
|
||||||
esac
|
esac
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
cd build/bin
|
cd build/bin
|
||||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.7/Xray-linux-${ARCH}.zip"
|
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.8/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}"
|
||||||
|
|
10
README.md
10
README.md
|
@ -1,5 +1,7 @@
|
||||||
# 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)
|
||||||
|
@ -12,8 +14,7 @@
|
||||||
|
|
||||||
**If this project is helpful to you, you may wish to give it a**:star2:
|
**If this project is helpful to you, you may wish to give it a**:star2:
|
||||||
|
|
||||||
<a href="#">
|
<p align="left"><a href="#"><img width="125" src="https://github.com/MHSanaei/3x-ui/assets/115543613/7aa895dd-048a-42e7-989b-afd41a74e2e1" alt="Image"></a></p>
|
||||||
<img width="125" alt="image" src="https://github.com/MHSanaei/3x-ui/assets/115543613/7aa895dd-048a-42e7-989b-afd41a74e2e1.jpg"></a>
|
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
|
|
||||||
|
@ -25,10 +26,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||||
|
|
||||||
## Install Custom Version
|
## Install Custom Version
|
||||||
|
|
||||||
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.1.3`:
|
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.2.1`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.3
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.1
|
||||||
```
|
```
|
||||||
|
|
||||||
## SSL Certificate
|
## SSL Certificate
|
||||||
|
@ -212,6 +213,7 @@ Our platform offers compatibility with a diverse range of architectures and devi
|
||||||
- Vietnamese
|
- Vietnamese
|
||||||
- Spanish
|
- Spanish
|
||||||
- Indonesian
|
- Indonesian
|
||||||
|
- Ukrainian
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
2.1.3
|
2.2.1
|
29
go.mod
29
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.22
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Calidity/gin-sessions v1.3.1
|
github.com/Calidity/gin-sessions v1.3.1
|
||||||
|
@ -14,10 +14,10 @@ require (
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1
|
github.com/shirou/gopsutil/v3 v3.24.1
|
||||||
github.com/valyala/fasthttp v1.52.0
|
github.com/valyala/fasthttp v1.52.0
|
||||||
github.com/xtls/xray-core v1.8.7
|
github.com/xtls/xray-core v1.8.8
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
google.golang.org/grpc v1.61.1
|
google.golang.org/grpc v1.62.0
|
||||||
gorm.io/driver/sqlite v1.5.5
|
gorm.io/driver/sqlite v1.5.5
|
||||||
gorm.io/gorm v1.25.7
|
gorm.io/gorm v1.25.7
|
||||||
)
|
)
|
||||||
|
@ -40,7 +40,7 @@ require (
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // indirect
|
github.com/google/pprof v0.0.0-20240225044709-fd706174c886 // indirect
|
||||||
github.com/gorilla/context v1.1.2 // indirect
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.2.2 // indirect
|
github.com/gorilla/sessions v1.2.2 // indirect
|
||||||
|
@ -49,23 +49,22 @@ require (
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.6 // indirect
|
github.com/klauspost/compress v1.17.7 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.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.13.2 // indirect
|
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
|
||||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
github.com/quic-go/quic-go v0.41.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.40.1 // indirect
|
github.com/refraction-networking/utls v1.6.3 // indirect
|
||||||
github.com/refraction-networking/utls v1.6.0 // indirect
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
github.com/sagernet/sing v0.3.0 // indirect
|
github.com/sagernet/sing v0.3.2 // 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
|
||||||
|
@ -85,15 +84,15 @@ require (
|
||||||
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.19.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
golang.org/x/mod v0.15.0 // indirect
|
||||||
golang.org/x/net v0.21.0 // indirect
|
golang.org/x/net v0.21.0 // indirect
|
||||||
golang.org/x/sys v0.17.0 // indirect
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.16.1 // indirect
|
golang.org/x/tools v0.18.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // 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
|
||||||
|
|
66
go.sum
66
go.sum
|
@ -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.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
@ -111,8 +111,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8=
|
github.com/google/pprof v0.0.0-20240225044709-fd706174c886 h1:JSJUTZTQT1Gzb2ROdAKOY3HwzBYcclS2GgumhMfHqjw=
|
||||||
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20240225044709-fd706174c886/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
@ -138,11 +138,11 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||||
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
@ -169,8 +169,8 @@ 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.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||||
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
@ -183,10 +183,10 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
|
||||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
|
@ -208,12 +208,10 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
|
||||||
github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
|
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
|
||||||
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
|
||||||
github.com/refraction-networking/utls v1.6.0 h1:X5vQMqVx7dY7ehxxqkFER/W6DSjy8TMqSItXm8hRDYQ=
|
|
||||||
github.com/refraction-networking/utls v1.6.0/go.mod h1:kHJ6R9DFFA0WsRgBM35iiDku4O7AqPR6y79iuzW7b10=
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
@ -223,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.0 h1:PIDVFZHnQAAYRL1UYqNM+0k5s8f/tb1lUW6UDcQiOc8=
|
github.com/sagernet/sing v0.3.2 h1:CwWcxUBPkMvwgfe2/zUgY5oHG9qOL8Aob/evIFYK9jo=
|
||||||
github.com/sagernet/sing v0.3.0/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
|
github.com/sagernet/sing v0.3.2/go.mod h1:qHySJ7u8po9DABtMYEkNBcOumx7ZZJf/fbv2sfTkNHE=
|
||||||
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=
|
||||||
|
@ -303,8 +301,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.7 h1:lb8O1l3/eAg3YAXA6tLm5M6N7BsX8wxW9sJLjU3dHkA=
|
github.com/xtls/xray-core v1.8.8 h1:6ApBa5pNkPZ+I7jyJDZod3v5sadizS/BZr0pW7zcs8o=
|
||||||
github.com/xtls/xray-core v1.8.7/go.mod h1:9rFpflfQbgFeH1VKJw7yUmEy7myOyDCgNXXl0bmmyOo=
|
github.com/xtls/xray-core v1.8.8/go.mod h1:Zp33A8cxnhP5Kt6nguQrMgNH4A/tgq7LE8cBedeNje8=
|
||||||
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=
|
||||||
|
@ -326,13 +324,13 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
||||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.15.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=
|
||||||
|
@ -392,8 +390,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
|
@ -411,14 +409,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-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
|
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
||||||
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.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=
|
||||||
|
|
BIN
media/3X-UI.png
Normal file
BIN
media/3X-UI.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 198 KiB |
105
sub/default.json
Normal file
105
sub/default.json
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
{
|
||||||
|
"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,11 +47,6 @@ 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
|
||||||
|
@ -61,9 +56,44 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
||||||
}
|
}
|
||||||
|
|
||||||
g := engine.Group(subPath)
|
LinksPath, err := s.settingService.GetSubPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
s.sub = NewSUBController(g)
|
JsonPath, err := s.settingService.GetSubJsonPath()
|
||||||
|
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,34 +3,56 @@ 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 {
|
||||||
subService SubService
|
subPath string
|
||||||
settingService service.SettingService
|
subJsonPath string
|
||||||
|
subEncrypt bool
|
||||||
|
updateInterval string
|
||||||
|
|
||||||
|
subService *SubService
|
||||||
|
subJsonService *SubJsonService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
func NewSUBController(
|
||||||
a := &SUBController{}
|
g *gin.RouterGroup,
|
||||||
|
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) {
|
||||||
g = g.Group("/")
|
gLink := g.Group(a.subPath)
|
||||||
|
gJson := g.Group(a.subJsonPath)
|
||||||
|
|
||||||
g.GET("/:subid", a.subs)
|
gLink.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, headers, err := a.subService.GetSubs(subId, host, subShowInfo)
|
subs, header, err := a.subService.GetSubs(subId, host)
|
||||||
if err != nil || len(subs) == 0 {
|
if err != nil || len(subs) == 0 {
|
||||||
c.String(400, "Error!")
|
c.String(400, "Error!")
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,14 +62,31 @@ func (a *SUBController) subs(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add headers
|
// Add headers
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
c.Writer.Header().Set("Subscription-Userinfo", header)
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
||||||
c.Writer.Header().Set("Profile-Title", headers[2])
|
c.Writer.Header().Set("Profile-Title", subId)
|
||||||
|
|
||||||
if subEncrypt {
|
if a.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
355
sub/subJsonService.go
Normal file
355
sub/subJsonService.go
Normal file
|
@ -0,0 +1,355 @@
|
||||||
|
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,47 +25,42 @@ type SubService struct {
|
||||||
settingService service.SettingService
|
settingService service.SettingService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
|
func NewSubService(showInfo bool, remarkModel string) *SubService {
|
||||||
|
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 headers []string
|
var header 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, nil, err
|
return nil, "", err
|
||||||
}
|
|
||||||
s.remarkModel, err = s.settingService.GetRemarkModel()
|
|
||||||
if err != nil {
|
|
||||||
s.remarkModel = "-ieo"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.datepicker, err = s.settingService.GetDatepicker()
|
s.datepicker, err = s.settingService.GetDatepicker()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.datepicker = "gregorian"
|
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 - GetSub: Unable to get clients from inbound")
|
logger.Error("SubService - GetClients: 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] == '@' {
|
||||||
fallbackMaster, err := s.getFallbackMaster(inbound.Listen)
|
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
inbound.Listen = fallbackMaster.Listen
|
inbound.Listen = listen
|
||||||
inbound.Port = fallbackMaster.Port
|
inbound.Port = port
|
||||||
var stream map[string]interface{}
|
inbound.StreamSettings = streamSettings
|
||||||
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 {
|
||||||
|
@ -76,6 +71,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
@ -97,11 +94,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
|
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
||||||
updateInterval, _ := s.settingService.GetSubUpdates()
|
return result, header, nil
|
||||||
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) {
|
||||||
|
@ -130,7 +124,7 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
|
||||||
return xray.ClientTraffic{}
|
return xray.ClientTraffic{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
|
func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, 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{}).
|
||||||
|
@ -138,9 +132,19 @@ func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
|
||||||
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 nil, err
|
return "", 0, "", 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 {
|
||||||
|
@ -578,6 +582,7 @@ 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 {
|
||||||
|
|
|
@ -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, false);
|
emitter.addEventListener(type, f, { passive: false });
|
||||||
} else if (emitter.attachEvent) {
|
} else if (emitter.attachEvent) {
|
||||||
emitter.attachEvent("on" + type, f);
|
emitter.attachEvent("on" + type, f);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -45,12 +45,12 @@ THE SOFTWARE.
|
||||||
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
|
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
|
||||||
.cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; }
|
.cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; }
|
||||||
|
|
||||||
.dark .cm-s-xq.CodeMirror { background-color: #222D42; border-color: #2c3950; color: rgb(255 255 255 / 65%); }
|
.dark .cm-s-xq.CodeMirror { background-color: var(--dark-color-surface-200); border-color: var(--dark-color-surface-300); color: rgb(255 255 255 / 65%); }
|
||||||
.dark .cm-s-xq.CodeMirror:hover { background-color: rgb(0 50 42 / 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: rgba(0, 0, 0, 0.5); }
|
.dark .cm-s-xq div.CodeMirror-selected { background: var(--dark-color-codemirror-line-selection); }
|
||||||
.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: rgba(39, 0, 122, 0.99); }
|
.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: var(--dark-color-codemirror-line-selection); }
|
||||||
.dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 0, 122, 0.99); }
|
.dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: var(--dark-color-codemirror-line-selection); }
|
||||||
.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid #2c3950; }
|
.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid var(--dark-color-surface-300); }
|
||||||
.dark .cm-s-xq .CodeMirror-guttermarker { color: #FFBD40; }
|
.dark .cm-s-xq .CodeMirror-guttermarker { color: #FFBD40; }
|
||||||
.dark .cm-s-xq .CodeMirror-guttermarker-subtle { color: rgb(255 255 255 / 70%); }
|
.dark .cm-s-xq .CodeMirror-guttermarker-subtle { color: rgb(255 255 255 / 70%); }
|
||||||
.dark .cm-s-xq .CodeMirror-linenumber { color: rgb(255 255 255 / 50%); }
|
.dark .cm-s-xq .CodeMirror-linenumber { color: rgb(255 255 255 / 50%); }
|
||||||
|
@ -80,7 +80,7 @@ THE SOFTWARE.
|
||||||
|
|
||||||
.Line-Hover{transition: all .2s;}
|
.Line-Hover{transition: all .2s;}
|
||||||
.Line-Hover:hover{ background-color: rgba(0, 102, 85, 0.05) !important; }
|
.Line-Hover:hover{ background-color: rgba(0, 102, 85, 0.05) !important; }
|
||||||
.dark .Line-Hover:hover{ background-color: rgb(0 0 0 / 20%) !important; }
|
.dark .Line-Hover:hover{ background-color: var(--dark-color-codemirror-line-hover) !important; }
|
||||||
|
|
||||||
.CodeMirror-foldmarker { color: #fc8800; text-shadow: #ffd8aa 1px 1px 2px, #ffd8aa -1px -1px 2px, #ffd8aa 1px -1px 2px, #ffd8aa -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
.CodeMirror-foldmarker { color: #fc8800; text-shadow: #ffd8aa 1px 1px 2px, #ffd8aa -1px -1px 2px, #ffd8aa 1px -1px 2px, #ffd8aa -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
||||||
.dark .CodeMirror-foldmarker { color: #ffffff; text-shadow: #bbb 1px 1px 2px, #bbb -1px -1px 2px, #bbb 1px -1px 2px, #bbb -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
.dark .CodeMirror-foldmarker { color: #ffffff; text-shadow: #bbb 1px 1px 2px, #bbb -1px -1px 2px, #bbb 1px -1px 2px, #bbb -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
||||||
|
|
|
@ -1,3 +1,87 @@
|
||||||
|
: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;
|
||||||
|
@ -16,7 +100,7 @@ body {
|
||||||
font-feature-settings: "tnum";
|
font-feature-settings: "tnum";
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
--antd-wave-shadow-color: #008771;
|
--antd-wave-shadow-color: var(--color-primary-100);
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
text-size-adjust: 100%;
|
text-size-adjust: 100%;
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
|
@ -26,7 +110,7 @@ html {
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
background-color: #cfe8e4;
|
background-color: #cfe8e4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +218,7 @@ style attribute {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
.ant-modal-body {
|
.ant-modal-body {
|
||||||
padding: 10px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
.ant-form-item-label {
|
.ant-form-item-label {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
@ -218,7 +302,7 @@ style attribute {
|
||||||
.ant-menu-submenu-active,
|
.ant-menu-submenu-active,
|
||||||
.ant-menu-submenu-title:hover,
|
.ant-menu-submenu-title:hover,
|
||||||
.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open {
|
.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
background-color: rgb(232 244 242);
|
background-color: rgb(232 244 242);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
@ -480,14 +564,14 @@ style attribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
.normal-icon:hover {
|
.normal-icon:hover {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DARK THEME */
|
/* DARK THEME */
|
||||||
|
|
||||||
.dark ::selection {
|
.dark ::selection {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .normal-icon:hover {
|
.dark .normal-icon:hover {
|
||||||
|
@ -502,13 +586,14 @@ style attribute {
|
||||||
.dark .ant-table,
|
.dark .ant-table,
|
||||||
.dark .ant-collapse-content,
|
.dark .ant-collapse-content,
|
||||||
.dark .ant-tabs {
|
.dark .ant-tabs {
|
||||||
background-color: #151f31;
|
background-color: var(--dark-color-surface-100);
|
||||||
color: #ffffffa6;
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.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,
|
||||||
|
@ -518,8 +603,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: #101828;
|
background-color: var(--dark-color-background);
|
||||||
color: rgb(255 255 255 /65%);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-expanded-row .ant-table-thead > tr:first-child > th {
|
.dark .ant-table-expanded-row .ant-table-thead > tr:first-child > th {
|
||||||
|
@ -528,7 +613,7 @@ style attribute {
|
||||||
|
|
||||||
.dark .ant-calendar,
|
.dark .ant-calendar,
|
||||||
.dark .ant-card-bordered {
|
.dark .ant-card-bordered {
|
||||||
border-color: #151f31;
|
border-color: var(--dark-color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-bordered,
|
.dark .ant-table-bordered,
|
||||||
|
@ -540,7 +625,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: #2c3950;
|
border-color: var(--dark-color-surface-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-tbody > tr > td,
|
.dark .ant-table-tbody > tr > td,
|
||||||
|
@ -553,7 +638,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: #2c3950;
|
border-bottom-color: var(--dark-color-surface-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-modal-footer,
|
.dark .ant-modal-footer,
|
||||||
|
@ -561,7 +646,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: #2c3950;
|
border-top-color: var(--dark-color-surface-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-progress-text,
|
.dark .ant-progress-text,
|
||||||
|
@ -597,7 +682,7 @@ style attribute {
|
||||||
.dark .ant-calendar-year-panel-year,
|
.dark .ant-calendar-year-panel-year,
|
||||||
.dark .ant-calendar-month-panel-month,
|
.dark .ant-calendar-month-panel-month,
|
||||||
.dark .ant-calendar-decade-panel-decade {
|
.dark .ant-calendar-decade-panel-decade {
|
||||||
color: rgba(255, 255, 255, 0.65);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-list-item-meta-description {
|
.dark .ant-list-item-meta-description {
|
||||||
|
@ -623,13 +708,12 @@ 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: #222d42;
|
background-color: var(--dark-color-surface-200);
|
||||||
border-color: #2c3950;
|
border-color: var(--dark-color-surface-300);
|
||||||
color: rgba(255, 255, 255, 0.65);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-select-selection:hover,
|
.dark .ant-select-selection:hover,
|
||||||
|
@ -639,34 +723,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: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) {
|
.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) {
|
||||||
color: rgba(255, 255, 255, 0.65);
|
color: var(--dark-color-text-primary);
|
||||||
background-color: rgb(10 117 87 / 30%);
|
background-color: rgb(10 117 87 / 30%);
|
||||||
border: 1px solid #008771;
|
border: 1px solid var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-radio-button-wrapper,
|
.dark .ant-radio-button-wrapper,
|
||||||
.dark .ant-radio-button-wrapper:before {
|
.dark .ant-radio-button-wrapper:before {
|
||||||
color: rgb(255 255 255 / 65%);
|
color: var(--dark-color-text-primary);
|
||||||
background-color: rgba(0, 135, 113, 0.3);
|
background-color: rgba(0, 135, 113, 0.3);
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),
|
.dark .ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),
|
||||||
.dark .ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger) {
|
.dark .ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger) {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: rgb(10 117 87 / 50%);
|
background-color: rgb(10 117 87 / 50%);
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-btn-primary[disabled],
|
.dark .ant-btn-primary[disabled],
|
||||||
.dark .ant-btn-danger[disabled],
|
.dark .ant-btn-danger[disabled],
|
||||||
.dark .ant-calendar-ok-btn-disabled {
|
.dark .ant-calendar-ok-btn-disabled {
|
||||||
color: rgb(255 255 255 / 35%);
|
color: rgb(255 255 255 / 35%);
|
||||||
background-color: #2c3950;
|
background-color: var(--dark-color-surface-300);
|
||||||
border-color: #42516c;
|
border-color: #42516c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -675,7 +759,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: #00877122;
|
background-color: var(--dark-color-table-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-row-expand-icon {
|
.dark .ant-table-row-expand-icon {
|
||||||
|
@ -685,38 +769,37 @@ style attribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-table-row-expand-icon:hover {
|
.dark .ant-table-row-expand-icon:hover {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
background-color: #fff0;
|
background-color: #fff0;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-switch:not(.ant-switch-checked),
|
.dark .ant-switch:not(.ant-switch-checked),
|
||||||
.dark .ant-progress-line .ant-progress-inner {
|
.dark .ant-progress-line .ant-progress-inner {
|
||||||
background-color: #2c3950;
|
background-color: var(--dark-color-surface-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-progress-circle-trail {
|
.dark .ant-progress-circle-trail {
|
||||||
stroke: #2c3950 !important;
|
stroke: var(--dark-color-stroke) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-dropdown-menu-dark,
|
|
||||||
.dark .ant-popover-inner {
|
.dark .ant-popover-inner {
|
||||||
background-color: #222d42;
|
background-color: var(--dark-color-surface-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark > .ant-popover-content > .ant-popover-arrow {
|
.dark > .ant-popover-content > .ant-popover-arrow {
|
||||||
border-color: #222d42;
|
border-color: var(--dark-color-surface-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.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: #313f5a;
|
background-color: var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-dark .ant-menu-item:hover {
|
.ant-menu-dark .ant-menu-item:hover {
|
||||||
background-color: #2c3950;
|
background-color: var(--dark-color-surface-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-alert-message {
|
.dark .ant-alert-message {
|
||||||
|
@ -724,61 +807,61 @@ style attribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag {
|
.dark .ant-tag {
|
||||||
color: rgba(255, 255, 255, 0.65);
|
color: var(--dark-color-tag-color);
|
||||||
background-color: #ffffff0a;
|
background-color: var(--dark-color-tag-bg);
|
||||||
border-color: #344461;
|
border-color: var(--dark-color-tag-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-blue {
|
.dark .ant-tag-blue {
|
||||||
background-color: #111a2c;
|
background-color: var(--dark-color-tag-blue-bg);
|
||||||
border-color: #0f367e;
|
border-color: var(--dark-color-tag-blue-border);
|
||||||
color: #3c89e8;
|
color: var(--dark-color-tag-blue-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-red,
|
.dark .ant-tag-red,
|
||||||
.dark .ant-alert-error {
|
.dark .ant-alert-error {
|
||||||
background-color: #291515;
|
background-color: var(--dark-color-tag-red-bg);
|
||||||
border-color: #5c2626;
|
border-color: var(--dark-color-tag-red-border);
|
||||||
color: #e04141;
|
color: var(--dark-color-tag-red-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-orange,
|
.dark .ant-tag-orange,
|
||||||
.dark .ant-alert-warning {
|
.dark .ant-alert-warning {
|
||||||
background-color: #312313;
|
background-color: var(--dark-color-tag-orange-bg);
|
||||||
border-color: #593914;
|
border-color: var(--dark-color-tag-orange-border);
|
||||||
color: #ffa031;
|
color: var(--dark-color-tag-orange-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-green {
|
.dark .ant-tag-green {
|
||||||
background-color: #112421;
|
background-color: var(--dark-color-tag-green-bg);
|
||||||
border-color: #144840;
|
border-color: var(--dark-color-tag-green-border);
|
||||||
color: #33bca5;
|
color: var(--dark-color-tag-green-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-tag-purple {
|
.dark .ant-tag-purple {
|
||||||
background-color: #2c1e32;
|
background-color: var(--dark-color-tag-purple-bg);
|
||||||
border-color: #49394e;
|
border-color: var(--dark-color-tag-purple-border);
|
||||||
color: #cfb9cc;
|
color: var(--dark-color-tag-purple-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-modal-content,
|
.dark .ant-modal-content,
|
||||||
.dark .ant-modal-header {
|
.dark .ant-modal-header {
|
||||||
background-color: #181f2c;
|
background-color: var(--dark-color-surface-700);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-next-month-btn-day .ant-calendar-date,
|
.dark .ant-calendar-next-month-btn-day .ant-calendar-date,
|
||||||
.dark .ant-calendar-last-month-cell .ant-calendar-date {
|
.dark .ant-calendar-last-month-cell .ant-calendar-date {
|
||||||
color: #2c3950;
|
color: var(--dark-color-surface-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-selected-day .ant-calendar-date {
|
.dark .ant-calendar-selected-day .ant-calendar-date {
|
||||||
background-color: #008771 !important;
|
background-color: var(--color-primary-100) !important;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-date:hover,
|
.dark .ant-calendar-date:hover,
|
||||||
.dark .ant-calendar-time-picker-select li:hover {
|
.dark .ant-calendar-time-picker-select li:hover {
|
||||||
background-color: #313f5a;
|
background-color: var(--dark-color-surface-600);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -792,13 +875,15 @@ style attribute {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-time-picker-select {
|
.dark .ant-calendar-time-picker-select {
|
||||||
border-right-color: #2c3950;
|
border-right-color: var(--dark-color-surface-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-warning .ant-select-selection,
|
||||||
|
.has-warning .ant-select-selection:hover,
|
||||||
.has-warning .ant-input,
|
.has-warning .ant-input,
|
||||||
.has-warning .ant-input:hover {
|
.has-warning .ant-input:hover {
|
||||||
background-color: #ffeee1;
|
background-color: #ffeee1;
|
||||||
|
@ -813,6 +898,8 @@ style attribute {
|
||||||
border-color: #fec093;
|
border-color: #fec093;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .has-warning .ant-select-selection,
|
||||||
|
.dark .has-warning .ant-select-selection:hover,
|
||||||
.dark .has-warning .ant-input,
|
.dark .has-warning .ant-input,
|
||||||
.dark .has-warning .ant-input:hover {
|
.dark .has-warning .ant-input:hover {
|
||||||
border-color: #784e1d;
|
border-color: #784e1d;
|
||||||
|
@ -828,7 +915,7 @@ style attribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .has-success .anticon {
|
.dark .has-success .anticon {
|
||||||
color: #61bf39;
|
color: var(--color-primary-100);
|
||||||
animation-name: diffZoomIn1 !important;
|
animation-name: diffZoomIn1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -874,19 +961,19 @@ style attribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-calendar-today .ant-calendar-date {
|
.ant-calendar-today .ant-calendar-date {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-today .ant-calendar-date {
|
.dark .ant-calendar-today .ant-calendar-date {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-calendar-selected-day .ant-calendar-date {
|
.ant-calendar-selected-day .ant-calendar-date {
|
||||||
background: #008771;
|
background: var(--color-primary-100);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -920,7 +1007,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
.ant-select-dropdown.ant-select-dropdown--multiple
|
.ant-select-dropdown.ant-select-dropdown--multiple
|
||||||
.ant-select-dropdown-menu-item-selected:hover
|
.ant-select-dropdown-menu-item-selected:hover
|
||||||
.ant-select-selected-icon {
|
.ant-select-selected-icon {
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
.ant-select-selection:hover,
|
.ant-select-selection:hover,
|
||||||
.ant-input-number-focused,
|
.ant-input-number-focused,
|
||||||
|
@ -929,7 +1016,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-input-number-handler:active {
|
.dark .ant-input-number-handler:active {
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-input-number-handler:hover .ant-input-number-handler-down-inner,
|
.dark .ant-input-number-handler:hover .ant-input-number-handler-down-inner,
|
||||||
|
@ -957,7 +1044,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 #222d42;
|
border-bottom: 1px solid var(--dark-color-surface-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.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,
|
||||||
|
@ -965,10 +1052,11 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
color: rgba(255, 255, 255, 0.35);
|
color: rgba(255, 255, 255, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-dark,
|
||||||
.dark .ant-calendar-year-panel-year:hover,
|
.dark .ant-calendar-year-panel-year:hover,
|
||||||
.dark .ant-calendar-month-panel-month:hover,
|
.dark .ant-calendar-month-panel-month:hover,
|
||||||
.dark .ant-calendar-decade-panel-decade:hover {
|
.dark .ant-calendar-decade-panel-decade:hover {
|
||||||
background-color: #222d42;
|
background-color: var(--dark-color-surface-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-header a:hover {
|
.dark .ant-calendar-header a:hover {
|
||||||
|
@ -976,13 +1064,13 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-month-panel-header {
|
.dark .ant-calendar-month-panel-header {
|
||||||
background-color: #101828;
|
background-color: var(--dark-color-background);
|
||||||
border-bottom: 1px solid #222d42;
|
border-bottom: 1px solid var(--dark-color-surface-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-year-panel,
|
.dark .ant-calendar-year-panel,
|
||||||
.dark .ant-calendar table {
|
.dark .ant-calendar table {
|
||||||
background-color: #101828;
|
background-color: var(--dark-color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year,
|
.dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year,
|
||||||
|
@ -1000,7 +1088,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
.ant-calendar-decade-panel-selected-cell
|
.ant-calendar-decade-panel-selected-cell
|
||||||
.ant-calendar-decade-panel-decade:hover {
|
.ant-calendar-decade-panel-decade:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-last-month-cell .ant-calendar-date,
|
.dark .ant-calendar-last-month-cell .ant-calendar-date,
|
||||||
|
@ -1014,8 +1102,8 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
|
|
||||||
.dark .ant-calendar-today .ant-calendar-date:hover {
|
.dark .ant-calendar-today .ant-calendar-date:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark
|
.dark
|
||||||
|
@ -1028,8 +1116,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 #222d42;
|
border-bottom: 1px solid var(--dark-color-surface-200);
|
||||||
background-color: #101828;
|
background-color: var(--dark-color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-checkbox-inner {
|
.dark .ant-checkbox-inner {
|
||||||
|
@ -1038,12 +1126,13 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-checkbox-checked .ant-checkbox-inner {
|
.dark .ant-checkbox-checked .ant-checkbox-inner {
|
||||||
background-color: #008771;
|
background-color: var(--color-primary-100);
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-input {
|
.dark .ant-calendar-input {
|
||||||
background-color: #101828;
|
background-color: var(--dark-color-background);
|
||||||
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-calendar-input::placeholder {
|
.dark .ant-calendar-input::placeholder {
|
||||||
|
@ -1099,11 +1188,10 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
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: #313f5a;
|
background-color: var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-select-dropdown,
|
.ant-select-dropdown,
|
||||||
|
@ -1116,6 +1204,8 @@ 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;
|
||||||
|
@ -1124,7 +1214,7 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-group-addon:not(:first-child):not(:last-child), .ant-input-group-wrap:not(:first-child):not(:last-child), .ant-input-group>.ant-input:not(:first-child):not(:last-child) {
|
.ant-input-group-addon:not(:first-child):not(:last-child) {
|
||||||
border-radius: 0rem 1rem 1rem 0rem;
|
border-radius: 0rem 1rem 1rem 0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1135,3 +1225,27 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
b, strong {
|
b, strong {
|
||||||
font-weight: 500;
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,11 @@ const supportLangs = [
|
||||||
value: 'id-ID',
|
value: 'id-ID',
|
||||||
icon: '🇮🇩',
|
icon: '🇮🇩',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Український',
|
||||||
|
value: 'uk-UA',
|
||||||
|
icon: '🇺🇦',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getLang() {
|
function getLang() {
|
||||||
|
|
|
@ -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(",") : undefined,
|
reserved: this.reserved ? this.reserved.split(",").map(Number) : undefined,
|
||||||
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
||||||
kernelMode: this.kernelMode,
|
kernelMode: this.kernelMode,
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,6 +28,7 @@ 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 = "";
|
||||||
|
@ -35,6 +36,8 @@ 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";
|
||||||
|
|
||||||
|
|
|
@ -2295,7 +2295,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
addPeer() {
|
addPeer() {
|
||||||
this.peers.push(new Inbound.WireguardSettings.Peer());
|
this.peers.push(new Inbound.WireguardSettings.Peer(null,null,'',['10.0.0.' + (this.peers.length+2)]));
|
||||||
}
|
}
|
||||||
|
|
||||||
delPeer(index) {
|
delPeer(index) {
|
||||||
|
@ -2323,7 +2323,7 @@ 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.0/24'], keepAlive=0) {
|
constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.2/32'], keepAlive=0) {
|
||||||
super();
|
super();
|
||||||
this.privateKey = privateKey
|
this.privateKey = privateKey
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
|
@ -2331,6 +2331,9 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
||||||
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
|
[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;
|
||||||
}
|
}
|
||||||
|
@ -2346,6 +2349,9 @@ 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,
|
privateKey: this.privateKey,
|
||||||
publicKey: this.publicKey,
|
publicKey: this.publicKey,
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var filesToCache = [
|
|
||||||
'/'
|
|
||||||
];
|
|
||||||
|
|
||||||
self.addEventListener('install', function (e) {
|
|
||||||
e.waitUntil(
|
|
||||||
caches.open('3xPanel').then(function (cache) {
|
|
||||||
return cache.addAll(filesToCache);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
self.addEventListener('activate', function (event) {
|
|
||||||
event.waitUntil(
|
|
||||||
caches.keys().then(function (cacheNames) {
|
|
||||||
return Promise.all(
|
|
||||||
cacheNames.filter(function (cacheName) {
|
|
||||||
}).map(function (cacheName) {
|
|
||||||
return caches.delete(cacheName);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
self.addEventListener('fetch', function (event) {
|
|
||||||
event.respondWith(
|
|
||||||
caches.open('mysite-dynamic').then(function (cache) {
|
|
||||||
return cache.match(event.request).then(function (response) {
|
|
||||||
return response || fetch(event.request).then(function (response) {
|
|
||||||
cache.put(event.request, response.clone());
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.clear();
|
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"name": "3x-UI Panel",
|
|
||||||
"short_name": "3xPanel",
|
|
||||||
"description": "3x-ui panel converted to PWA",
|
|
||||||
"start_url": "/",
|
|
||||||
"background_color": "#F4F4F4",
|
|
||||||
"display": "fullscreen",
|
|
||||||
"theme_color": "#293343",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "./assets/icons/16.png",
|
|
||||||
"sizes": "16x16",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "./assets/icons/24.png",
|
|
||||||
"sizes": "24x24",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "./assets/icons/32.png",
|
|
||||||
"sizes": "32x32",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "./assets/icons/64.png",
|
|
||||||
"sizes": "64x64",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "./assets/icons/192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "./assets/icons/512.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -33,7 +33,8 @@ jdp-container :before {
|
||||||
}
|
}
|
||||||
jdp-container .jdp-icon-minus,
|
jdp-container .jdp-icon-minus,
|
||||||
jdp-container .jdp-icon-plus {
|
jdp-container .jdp-icon-plus {
|
||||||
border: 1px solid rgb(232 244 242);
|
outline: 1px solid rgb(232 244 242);
|
||||||
|
outline-offset: -1px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -205,15 +206,15 @@ jdp-container .jdp-day-name {
|
||||||
}
|
}
|
||||||
jdp-container .jdp-day-name.today,
|
jdp-container .jdp-day-name.today,
|
||||||
jdp-container .jdp-day.today {
|
jdp-container .jdp-day.today {
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-day-name.selected,
|
.dark jdp-container .jdp-day-name.selected,
|
||||||
.dark jdp-container .jdp-day.selected,
|
.dark jdp-container .jdp-day.selected,
|
||||||
jdp-container .jdp-day-name.selected,
|
jdp-container .jdp-day-name.selected,
|
||||||
jdp-container .jdp-day.selected {
|
jdp-container .jdp-day.selected {
|
||||||
background-color: #008771 !important;
|
background-color: var(--color-primary-100) !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
@ -266,7 +267,7 @@ jdp-container .jdp-btn-empty,
|
||||||
jdp-container .jdp-btn-today {
|
jdp-container .jdp-btn-today {
|
||||||
background: #00877000;
|
background: #00877000;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
color: #008771;
|
color: var(--color-primary-100);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
|
@ -368,26 +369,26 @@ jdp-container .jdp-time-container.jdp-only-time .jdp-time:after {
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-container .jdp-days {
|
.dark jdp-container .jdp-days {
|
||||||
border-color: #313f5a;
|
border-color: var(--dark-color-surface-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-overlay {
|
.dark jdp-overlay {
|
||||||
background-color: #181f2c;
|
background-color: #181f2c;
|
||||||
}
|
}
|
||||||
.dark jdp-container {
|
.dark jdp-container {
|
||||||
background: #101828;
|
background: var(--dark-color-background);
|
||||||
border-color: #2c3950;
|
border-color: #2c3950;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,.15);
|
box-shadow: 0 2px 8px rgba(0,0,0,.15);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-icon-minus,
|
.dark jdp-container .jdp-icon-minus,
|
||||||
.dark jdp-container .jdp-icon-plus {
|
.dark jdp-container .jdp-icon-plus {
|
||||||
border-color: #313f5a;
|
outline-color: var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-container .jdp-icon-minus:hover,
|
.dark jdp-container .jdp-icon-minus:hover,
|
||||||
.dark jdp-container .jdp-icon-plus:hover {
|
.dark jdp-container .jdp-icon-plus:hover {
|
||||||
background-color: #313f5a;
|
background-color: var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-container .jdp-months,
|
.dark jdp-container .jdp-months,
|
||||||
|
@ -404,27 +405,27 @@ jdp-container .jdp-time-container.jdp-only-time .jdp-time:after {
|
||||||
.dark jdp-container .jdp-year,
|
.dark jdp-container .jdp-year,
|
||||||
.dark jdp-container .jdp-year input,
|
.dark jdp-container .jdp-year input,
|
||||||
.dark jdp-container .jdp-year select {
|
.dark jdp-container .jdp-year select {
|
||||||
background: #101828;
|
background: var(--dark-color-background);
|
||||||
color: rgb(255 255 255 / 65%);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-day,
|
.dark jdp-container .jdp-day,
|
||||||
.dark jdp-container .jdp-day-name {
|
.dark jdp-container .jdp-day-name {
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
color: rgba(255, 255, 255, 0.65);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-day-name.today,
|
.dark jdp-container .jdp-day-name.today,
|
||||||
.dark jdp-container .jdp-day.today {
|
.dark jdp-container .jdp-day.today {
|
||||||
border-color: #008771;
|
border-color: var(--color-primary-100);
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-day.disabled-day {
|
.dark jdp-container .jdp-day.disabled-day {
|
||||||
opacity: 0.15;
|
opacity: 0.15;
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-day:not(.disabled-day):hover {
|
.dark jdp-container .jdp-day:not(.disabled-day):hover {
|
||||||
background-color: #313f5a;
|
background-color: var(--dark-color-surface-600);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-footer {
|
.dark jdp-container .jdp-footer {
|
||||||
border-color: #313f5a;
|
border-color: var(--dark-color-surface-400);
|
||||||
}
|
}
|
||||||
.dark jdp-container .jdp-btn-close,
|
.dark jdp-container .jdp-btn-close,
|
||||||
.dark jdp-container .jdp-btn-empty,
|
.dark jdp-container .jdp-btn-empty,
|
||||||
|
@ -445,10 +446,10 @@ jdp-container .jdp-time-container.jdp-only-time .jdp-time:after {
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-container .jdp-time-container .jdp-time select:hover {
|
.dark jdp-container .jdp-time-container .jdp-time select:hover {
|
||||||
background-color: #313f5a;
|
background-color: var(--dark-color-surface-600);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark jdp-container .jdp-time-container .jdp-time select {
|
.dark jdp-container .jdp-time-container .jdp-time select {
|
||||||
border: 1px solid rgb(49 63 90);
|
border: 1px solid var(--dark-color-surface-600);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-0.0.0.0:%v", inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%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-0.0.0.0:%v", inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,9 @@ 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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +108,13 @@ 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,16 +7,6 @@
|
||||||
<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="manifest" href="{{ .base_path }}assets/manifest.json">
|
|
||||||
|
|
||||||
<script>
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
navigator.serviceWorker.register('{{ .base_path }}assets/js/sw.js')
|
|
||||||
.then(function () {
|
|
||||||
console.log('Service Worker Registered');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
<style>
|
||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -38,4 +28,5 @@
|
||||||
</style>
|
</style>
|
||||||
<title>{{ .host }}-{{ i18n .title}}</title>
|
<title>{{ .host }}-{{ i18n .title}}</title>
|
||||||
</head>
|
</head>
|
||||||
|
<div id="message"></div>
|
||||||
{{end}}
|
{{end}}
|
|
@ -1,6 +1,7 @@
|
||||||
{{define "promptModal"}}
|
{{define "promptModal"}}
|
||||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||||
|
:confirm-loading="promptModal.confirmLoading"
|
||||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||||
v-model="promptModal.value"
|
v-model="promptModal.value"
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
value: '',
|
value: '',
|
||||||
okText: '{{ i18n "sure"}}',
|
okText: '{{ i18n "sure"}}',
|
||||||
visible: false,
|
visible: false,
|
||||||
|
confirmLoading: false,
|
||||||
keyEnter(e) {
|
keyEnter(e) {
|
||||||
if (this.type !== 'textarea') {
|
if (this.type !== 'textarea') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -30,7 +32,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ok() {
|
ok() {
|
||||||
promptModal.close();
|
|
||||||
promptModal.confirm(promptModal.value);
|
promptModal.confirm(promptModal.value);
|
||||||
},
|
},
|
||||||
confirm() {},
|
confirm() {},
|
||||||
|
@ -53,7 +54,10 @@
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
}
|
},
|
||||||
|
loading(loading=true) {
|
||||||
|
this.confirmLoading = loading;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const promptModalApp = new Vue({
|
const promptModalApp = new Vue({
|
||||||
|
|
|
@ -8,13 +8,23 @@
|
||||||
{{ 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>Subscription</a-divider>
|
<a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
|
||||||
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas></div>
|
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
|
||||||
|
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>
|
||||||
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas></div>
|
<canvas @click="copyToClipboard('qrCode-'+index, row.link)"
|
||||||
|
:id="'qrCode-'+index"
|
||||||
|
class="qr-bg"></canvas>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
|
@ -82,12 +92,16 @@
|
||||||
},
|
},
|
||||||
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,15 +1,16 @@
|
||||||
{{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" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
:closable="true"
|
||||||
:class="themeSwitcher.currentTheme"
|
:class="themeSwitcher.currentTheme">
|
||||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
<template slot="footer">
|
||||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" icon="download"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||||
:download="txtModal.fileName">
|
:download="txtModal.fileName">[[ txtModal.fileName ]]
|
||||||
{{ i18n "download" }} [[ txtModal.fileName ]]
|
</a-button>
|
||||||
</a-button>
|
<a-button type="primary" id="copy-btn">{{ i18n "copy" }}</a-button>
|
||||||
<a-input type="textarea" v-model="txtModal.content"
|
</template>
|
||||||
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
<a-input style="overflow-y: auto;" type="textarea" v-model="txtModal.content"
|
||||||
|
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -28,7 +29,7 @@
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
textModalApp.$nextTick(() => {
|
textModalApp.$nextTick(() => {
|
||||||
if (this.clipboard === null) {
|
if (this.clipboard === null) {
|
||||||
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
|
this.clipboard = new ClipboardJS('#copy-btn', {
|
||||||
text: () => this.content,
|
text: () => this.content,
|
||||||
});
|
});
|
||||||
this.clipboard.on('success', () => {
|
this.clipboard.on('success', () => {
|
||||||
|
@ -52,4 +53,4 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -2,9 +2,14 @@
|
||||||
<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 {
|
||||||
|
@ -31,7 +36,9 @@
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-weight: 600;
|
}
|
||||||
|
.title b {
|
||||||
|
font-weight: bold !important;
|
||||||
}
|
}
|
||||||
#app {
|
#app {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -42,6 +49,9 @@
|
||||||
border-radius: 2rem;
|
border-radius: 2rem;
|
||||||
padding: 3rem;
|
padding: 3rem;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
user-select:none;
|
||||||
|
-webkit-user-select:none;
|
||||||
|
-moz-user-select: none;
|
||||||
}
|
}
|
||||||
#login:hover {
|
#login:hover {
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||||
|
@ -61,13 +71,13 @@
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
.dark .under {
|
.dark .under {
|
||||||
background-color: #0f2d32;
|
background-color: var(--dark-color-login-wave);
|
||||||
}
|
}
|
||||||
.dark #login {
|
.dark #login {
|
||||||
background-color: #151f31;
|
background-color: var(--dark-color-surface-100);
|
||||||
}
|
}
|
||||||
.dark h1 {
|
.dark h1 {
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: rgba(255, 255, 255);
|
||||||
}
|
}
|
||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
@ -192,7 +202,7 @@
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
.dark .waves-header {
|
.dark .waves-header {
|
||||||
background-color: #101828;
|
background-color: var(--dark-color-login-background);
|
||||||
}
|
}
|
||||||
.waves-inner-header {
|
.waves-inner-header {
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
|
@ -212,7 +222,7 @@
|
||||||
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
|
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
|
||||||
}
|
}
|
||||||
.dark .parallax > use {
|
.dark .parallax > use {
|
||||||
fill: #0f2d32;
|
fill: var(--dark-color-login-wave);
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(1) {
|
.parallax > use:nth-child(1) {
|
||||||
animation-delay: -2s;
|
animation-delay: -2s;
|
||||||
|
@ -247,91 +257,225 @@
|
||||||
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" viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
|
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
<defs>
|
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
|
||||||
<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>
|
<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" />
|
||||||
<g class="parallax">
|
</defs>
|
||||||
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(0, 135, 113, 0.08)" />
|
<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="#c7ebe2" />
|
<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>
|
<a-col style="width: 100%;">
|
||||||
<h1 class="title">{{ i18n "pages.login.title" }}</h1>
|
<h1 class="title headline zoom">
|
||||||
</a-col>
|
<span class="words-wrapper">
|
||||||
|
<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 slot="prefix" type="user" style="font-size: 16px;"></a-icon>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<password-input icon="lock" v-model.trim="user.password"
|
<password-input icon="lock" v-model.trim="user.password"
|
||||||
placeholder='{{ i18n "password" }}' @keydown.enter.native="login">
|
placeholder='{{ i18n "password" }}'
|
||||||
</password-input>
|
@keydown.enter.native="login">
|
||||||
</a-form-item>
|
</password-input>
|
||||||
<a-form-item v-if="secretEnable">
|
</a-form-item>
|
||||||
<password-input icon="key" v-model.trim="user.loginSecret"
|
<a-form-item v-if="secretEnable">
|
||||||
placeholder='{{ i18n "secretToken" }}' @keydown.enter.native="login">
|
<password-input icon="key" v-model.trim="user.loginSecret"
|
||||||
</password-input>
|
placeholder='{{ i18n "secretToken" }}'
|
||||||
</a-input>
|
@keydown.enter.native="login">
|
||||||
</a-form-item>
|
</password-input>
|
||||||
<a-form-item>
|
</a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-form-item>
|
||||||
<div class="wave-btn-bg wave-btn-bg-cl" :style="loading ? { width: '52px' } : { display: 'inline-block' }">
|
<a-row justify="center" class="centered">
|
||||||
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined">
|
<div style="height: 50px;" class="wave-btn-bg wave-btn-bg-cl"
|
||||||
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
:style="loading ? { width: '52px' } : { display: 'inline-block' }">
|
||||||
</a-button>
|
<a-button class="ant-btn-primary-login" type="primary"
|
||||||
</div>
|
:loading="loading" @click="login"
|
||||||
</a-row>
|
:icon="loading ? 'poweroff' : undefined">
|
||||||
</a-form-item>
|
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||||
<a-form-item>
|
</a-button>
|
||||||
<a-row justify="center" class="centered">
|
</div>
|
||||||
<a-col :span="24">
|
</a-row>
|
||||||
<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-form-item>
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<a-row justify="center" class="centered">
|
||||||
<span v-text="l.name"></span>
|
<a-col :span="24">
|
||||||
</a-select-option>
|
<a-select ref="selectLang" v-model="lang"
|
||||||
</a-select>
|
@change="setLang(lang)" style="width: 150px;"
|
||||||
</a-col>
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
</a-row>
|
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
||||||
</a-form-item>
|
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||||
<a-form-item>
|
<span v-text="l.name"></span>
|
||||||
<a-row justify="center" class="centered">
|
</a-select-option>
|
||||||
<a-col>
|
</a-select>
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
</a-col>
|
||||||
</a-col>
|
</a-row>
|
||||||
<a-col>
|
</a-form-item>
|
||||||
<theme-switch />
|
<a-form-item>
|
||||||
</a-col>
|
<a-row justify="center" class="centered">
|
||||||
</a-row>
|
<a-col>
|
||||||
</a-form-item>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
</a-form>
|
</a-col>
|
||||||
</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" .}}
|
||||||
|
@ -377,6 +521,42 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
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) {
|
loading(loading=true) {
|
||||||
clientsBulkModal.confirmLoading = loading;
|
clientsBulkModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
clientModal.visible = false;
|
clientModal.visible = false;
|
||||||
clientModal.loading(false);
|
clientModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
clientModal.confirmLoading = loading;
|
clientModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,10 +15,6 @@
|
||||||
<a-icon type="tool"></a-icon>
|
<a-icon type="tool"></a-icon>
|
||||||
<span><b>{{ i18n "menu.xray"}}</b></span>
|
<span><b>{{ i18n "menu.xray"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
|
||||||
<!-- <a-icon type="laptop"></a-icon>-->
|
|
||||||
<!-- <span>Client</span>-->
|
|
||||||
<!--</a-menu-item>-->
|
|
||||||
<a-menu-item key="{{ .base_path }}logout">
|
<a-menu-item key="{{ .base_path }}logout">
|
||||||
<a-icon type="logout"></a-icon>
|
<a-icon type="logout"></a-icon>
|
||||||
<span><b>{{ i18n "menu.logout"}}</b></span>
|
<span><b>{{ i18n "menu.logout"}}</b></span>
|
||||||
|
@ -31,7 +27,13 @@
|
||||||
<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 }}']"
|
||||||
|
@ -50,7 +52,13 @@
|
||||||
<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 }}']"
|
||||||
|
|
224
web/html/xui/component/sortableTable.html
Normal file
224
web/html/xui/component/sortableTable.html
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
{{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,27 +10,48 @@
|
||||||
<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: () => ({ themeSwitcher }),
|
data: () => ({
|
||||||
|
themeSwitcher
|
||||||
|
}),
|
||||||
|
mounted() {
|
||||||
|
this.$message.config({
|
||||||
|
getContainer: () => document.getElementById('message')
|
||||||
|
});
|
||||||
|
document.getElementById('message').className = themeSwitcher.currentTheme;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
|
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
|
||||||
:closable="true" :mask-closable="false"
|
:closable="true" :mask-closable="false"
|
||||||
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
||||||
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
57
web/html/xui/fakedns_modal.html
Normal file
57
web/html/xui/fakedns_modal.html
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{{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:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "enable" }}'>
|
<a-form-item label='{{ i18n "enable" }}'>
|
||||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{define "form/dokodemo"}}
|
{{define "form/dokodemo"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
||||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{{define "form/http"}}
|
{{define "form/http"}}
|
||||||
<a-form>
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
<table style="width: 100%; text-align: center; margin: 1rem 0;">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
|
@ -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:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
|
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{define "form/socks"}}
|
{{define "form/socks"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
||||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.settings.auth === 'password'">
|
<template v-if="inbound.settings.auth === 'password'">
|
||||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
<table style="width: 100%; text-align: center; margin: 1rem 0;">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
|
|
|
@ -19,27 +19,23 @@
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
||||||
<a-button type="primary" size="small"
|
|
||||||
@click="inbound.settings.addFallback()">
|
|
||||||
+
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- trojan fallbacks -->
|
<!-- trojan fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }"
|
||||||
|
:wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;">
|
<a-divider style="margin:0;">
|
||||||
Fallback [[ index + 1 ]]
|
Fallback [[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;" />
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='SNI'>
|
<a-form-item label='SNI'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
</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>
|
||||||
|
@ -53,6 +49,6 @@
|
||||||
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider style="margin:0;"></a-divider>
|
<a-divider style="margin:5px 0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -21,27 +21,23 @@
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
||||||
<a-button type="primary" size="small"
|
|
||||||
@click="inbound.settings.addFallback()">
|
|
||||||
+
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- vless fallbacks -->
|
<!-- vless fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }"
|
||||||
|
:wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;">
|
<a-divider style="margin:0;">
|
||||||
Fallback [[ index + 1 ]]
|
Fallback [[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;" />
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='SNI'>
|
<a-form-item label='SNI'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
</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:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
<a-form-item label="Peers">
|
<a-form-item label="Peers">
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
|
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;">
|
<a-divider style="margin:0;">
|
||||||
Peer [[ index + 1 ]]
|
Peer [[ index + 1 ]]
|
||||||
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
|
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{{define "form/sniffing"}}
|
{{define "form/sniffing"}}
|
||||||
<a-divider style="margin:5px 0 0;"></a-divider>
|
<a-divider style="margin:5px 0 0;"></a-divider>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Sniffing
|
Sniffing
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{define "form/streamGRPC"}}
|
{{define "form/streamGRPC"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Service Name">
|
<a-form-item label="Service Name">
|
||||||
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{define "form/streamHTTP"}}
|
{{define "form/streamHTTP"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.kcp.type" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None</a-select-option>
|
<a-select-option value="none">None</a-select-option>
|
||||||
<a-select-option value="srtp">SRTP</a-select-option>
|
<a-select-option value="srtp">SRTP</a-select-option>
|
||||||
<a-select-option value="utp">uTP</a-select-option>
|
<a-select-option value="utp">uTP</a-select-option>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{define "form/streamQUIC"}}
|
{{define "form/streamQUIC"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None</a-select-option>
|
<a-select-option value="none">None</a-select-option>
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.quic.type" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None</a-select-option>
|
<a-select-option value="none">None</a-select-option>
|
||||||
<a-select-option value="srtp">SRTP</a-select-option>
|
<a-select-option value="srtp">SRTP</a-select-option>
|
||||||
<a-select-option value="utp">uTP</a-select-option>
|
<a-select-option value="utp">uTP</a-select-option>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{{define "form/streamSettings"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
<a-select v-model="inbound.stream.network" style="width: 50%" @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>
|
||||||
|
|
|
@ -34,16 +34,16 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Min/Max Version">
|
<a-form-item label="Min/Max Version">
|
||||||
<a-input-group compact>
|
<a-input-group compact>
|
||||||
<a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.tls.minVersion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<a-form-item label="uTLS">
|
||||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 50%"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value=''>None</a-select-option>
|
<a-select-option value=''>None</a-select-option>
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
|
@ -73,10 +73,10 @@
|
||||||
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="cert.useFile">
|
<template v-if="cert.useFile">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input v-model.trim="cert.certFile"></a-input>
|
<a-input v-model.trim="cert.certFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input v-model.trim="cert.keyFile"></a-input>
|
<a-input v-model.trim="cert.keyFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
|
@ -85,10 +85,10 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
@ -124,10 +124,10 @@
|
||||||
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="cert.useFile">
|
<template v-if="cert.useFile">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input v-model.trim="cert.certFile"></a-input>
|
<a-input v-model.trim="cert.certFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input v-model.trim="cert.keyFile"></a-input>
|
<a-input v-model.trim="cert.keyFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
|
@ -136,10 +136,10 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
@ -154,7 +154,7 @@
|
||||||
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='uTLS'>
|
<a-form-item label='uTLS'>
|
||||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 50%"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
@ -180,10 +180,10 @@
|
||||||
<a-form-item label='SpiderX'>
|
<a-form-item label='SpiderX'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Private Key'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Public Key'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
|
|
|
@ -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"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
<a-col :sx="24" :md="22">SUB: <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,6 +175,16 @@
|
||||||
</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>
|
||||||
|
@ -345,6 +355,7 @@
|
||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
subLink: '',
|
subLink: '',
|
||||||
|
subJsonLink: '',
|
||||||
show(dbInbound, index) {
|
show(dbInbound, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
|
@ -360,6 +371,7 @@
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
|
@ -369,6 +381,9 @@
|
||||||
},
|
},
|
||||||
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) {
|
loading(loading=true) {
|
||||||
inModal.confirmLoading = loading;
|
inModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -56,9 +56,13 @@
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-tag v-if="false" color="red" style="margin-bottom: 10px">
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
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
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
</a-tag>
|
color="red"
|
||||||
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
|
show-icon closable
|
||||||
|
>
|
||||||
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
|
@ -182,7 +186,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 ? mobileColums : columns" :row-key="dbInbound => dbInbound.id"
|
<a-table :columns="isMobile ? mobileColumns : 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)
|
||||||
|
@ -504,7 +508,7 @@
|
||||||
scopedSlots: { customRender: 'expiryTime' },
|
scopedSlots: { customRender: 'expiryTime' },
|
||||||
}];
|
}];
|
||||||
|
|
||||||
const mobileColums = [{
|
const mobileColumns = [{
|
||||||
title: "ID",
|
title: "ID",
|
||||||
align: 'right',
|
align: 'right',
|
||||||
dataIndex: "id",
|
dataIndex: "id",
|
||||||
|
@ -567,11 +571,13 @@
|
||||||
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,
|
||||||
},
|
},
|
||||||
|
@ -613,7 +619,8 @@
|
||||||
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;
|
||||||
|
@ -841,9 +848,7 @@
|
||||||
okText: '{{ i18n "pages.inbounds.create"}}',
|
okText: '{{ i18n "pages.inbounds.create"}}',
|
||||||
cancelText: '{{ i18n "close" }}',
|
cancelText: '{{ i18n "close" }}',
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (inbound, dbInbound) => {
|
||||||
inModal.loading();
|
await this.addInbound(inbound, dbInbound, inModal);
|
||||||
await this.addInbound(inbound, dbInbound);
|
|
||||||
inModal.close();
|
|
||||||
},
|
},
|
||||||
isEdit: false
|
isEdit: false
|
||||||
});
|
});
|
||||||
|
@ -858,9 +863,7 @@
|
||||||
inbound: inbound,
|
inbound: inbound,
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (inbound, dbInbound) => {
|
||||||
inModal.loading();
|
|
||||||
await this.updateInbound(inbound, dbInbound);
|
await this.updateInbound(inbound, dbInbound);
|
||||||
inModal.close();
|
|
||||||
},
|
},
|
||||||
isEdit: true
|
isEdit: true
|
||||||
});
|
});
|
||||||
|
@ -910,9 +913,7 @@
|
||||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (clients, dbInboundId) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientModal.loading();
|
await this.addClient(clients, dbInboundId, clientModal);
|
||||||
await this.addClient(clients, dbInboundId);
|
|
||||||
clientModal.close();
|
|
||||||
},
|
},
|
||||||
isEdit: false
|
isEdit: false
|
||||||
});
|
});
|
||||||
|
@ -924,9 +925,7 @@
|
||||||
okText: '{{ i18n "pages.client.bulk"}}',
|
okText: '{{ i18n "pages.client.bulk"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (clients, dbInboundId) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientsBulkModal.loading();
|
await this.addClient(clients, dbInboundId, clientsBulkModal);
|
||||||
await this.addClient(clients, dbInboundId);
|
|
||||||
clientsBulkModal.close();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -955,19 +954,19 @@
|
||||||
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async addClient(clients, dbInboundId) {
|
async addClient(clients, dbInboundId, modal) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + clients.toString() + ']}',
|
settings: '{"clients": [' + clients.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/addClient`, data);
|
await this.submit(`/panel/inbound/addClient`, data, modal);
|
||||||
},
|
},
|
||||||
async updateClient(client, dbInboundId, clientId) {
|
async updateClient(client, dbInboundId, clientId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + client.toString() + ']}',
|
settings: '{"clients": [' + client.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
|
await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
|
||||||
},
|
},
|
||||||
resetTraffic(dbInboundId) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
@ -987,7 +986,7 @@
|
||||||
},
|
},
|
||||||
delInbound(dbInboundId) {
|
delInbound(dbInboundId) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}' + ' #' + dbInboundId,
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
|
@ -1000,7 +999,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"}}',
|
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
|
||||||
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
|
@ -1070,8 +1069,8 @@
|
||||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
async submit(url, data) {
|
async submit(url, data, modal) {
|
||||||
const msg = await HttpUtil.postWithModal(url, data);
|
const msg = await HttpUtil.postWithModal(url, data, modal);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
await this.getDBInbounds();
|
await this.getDBInbounds();
|
||||||
}
|
}
|
||||||
|
@ -1230,7 +1229,6 @@
|
||||||
okText: '{{ i18n "pages.inbounds.import" }}',
|
okText: '{{ i18n "pages.inbounds.import" }}',
|
||||||
confirm: async (dbInboundText) => {
|
confirm: async (dbInboundText) => {
|
||||||
await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal);
|
await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal);
|
||||||
promptModal.close();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1291,7 +1289,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());
|
||||||
}
|
}
|
||||||
|
@ -1304,8 +1302,8 @@
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
pageSize: this.pageSize,
|
pageSize: this.pageSize,
|
||||||
pageSizeOptions: sizeOptions
|
pageSizeOptions: sizeOptions
|
||||||
}
|
};
|
||||||
return p
|
return p;
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
@ -1319,6 +1317,9 @@
|
||||||
}, 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();
|
||||||
|
@ -1356,7 +1357,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{template "inboundModal"}}
|
{{template "inboundModal"}}
|
||||||
|
@ -1366,6 +1366,5 @@
|
||||||
{{template "inboundInfoModal"}}
|
{{template "inboundInfoModal"}}
|
||||||
{{template "clientsModal"}}
|
{{template "clientsModal"}}
|
||||||
{{template "clientsBulkModal"}}
|
{{template "clientsBulkModal"}}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -10,21 +10,11 @@
|
||||||
margin-inline: 0.3rem;
|
margin-inline: 0.3rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark h2 {
|
.ant-card-dark h2 {
|
||||||
color: hsla(0, 0%, 100%, .65);
|
color: var(--dark-color-text-primary);
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tag-df {
|
|
||||||
color: rgb(0 0 0 / 80%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .ant-tag-df {
|
|
||||||
color: rgb(255 255 255 / 80%);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -34,6 +24,15 @@
|
||||||
<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>
|
||||||
|
@ -93,8 +92,8 @@
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
||||||
<a-tag class="ant-tag-df">Xray [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
<a-tag :color="status.xray.color">Xray: [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
<a-tag class="ant-tag-df">OS [[ formatSecond(status.uptime) ]]</a-tag>
|
<a-tag color="green">OS: [[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :lg="12">
|
||||||
|
@ -128,7 +127,7 @@
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>{{ i18n "pages.index.systemLoad" }}:</b>
|
<b>{{ i18n "pages.index.systemLoad" }}:</b>
|
||||||
<a-tag class="ant-tag-df">
|
<a-tag color="green">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
@ -141,11 +140,11 @@
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>{{ i18n "usage"}}:</b>
|
<b>{{ i18n "usage"}}:</b>
|
||||||
<a-tag class="ant-tag-df">
|
<a-tag color="green">
|
||||||
RAM [[ sizeFormat(status.appStats.mem) ]]
|
RAM: [[ sizeFormat(status.appStats.mem) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tag class="ant-tag-df">
|
<a-tag color="green">
|
||||||
Threads [[ status.appStats.threads ]]
|
Threads: [[ status.appStats.threads ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
@ -153,7 +152,7 @@
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag class="ant-tag-df">
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="global"></a-icon> IPv4
|
<a-icon type="global"></a-icon> IPv4
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
@ -163,7 +162,7 @@
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag class="ant-tag-df">
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="global"></a-icon> IPv6
|
<a-icon type="global"></a-icon> IPv6
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
@ -179,7 +178,7 @@
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag class="ant-tag-df">
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
|
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
@ -189,7 +188,7 @@
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag class="ant-tag-df">
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
|
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
@ -205,7 +204,7 @@
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag class="ant-tag-df">
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="arrow-up"></a-icon>
|
<a-icon type="arrow-up"></a-icon>
|
||||||
Up: [[ sizeFormat(status.netIO.up) ]]/s
|
Up: [[ sizeFormat(status.netIO.up) ]]/s
|
||||||
|
@ -216,7 +215,7 @@
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag class="ant-tag-df">
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="arrow-down"></a-icon>
|
<a-icon type="arrow-down"></a-icon>
|
||||||
Down: [[ sizeFormat(status.netIO.down) ]]/s
|
Down: [[ sizeFormat(status.netIO.down) ]]/s
|
||||||
|
@ -233,7 +232,7 @@
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag class="ant-tag-df">
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
<a-icon type="cloud-upload"></a-icon>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
@ -243,7 +242,7 @@
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag class="ant-tag-df">
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="cloud-download"></a-icon>
|
<a-icon type="cloud-download"></a-icon>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
@ -276,45 +275,45 @@
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="log-modal" v-model="logModal.visible" title="Logs"
|
<a-modal id="log-modal" v-model="logModal.visible"
|
||||||
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
:closable="true" @cancel="() => logModal.visible = false"
|
||||||
:class="themeSwitcher.currentTheme"
|
:class="themeSwitcher.currentTheme"
|
||||||
width="800px"
|
width="800px" footer="">
|
||||||
footer="">
|
<template slot="title">
|
||||||
|
{{ 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 label="Count">
|
<a-form-item>
|
||||||
<a-select v-model="logModal.rows"
|
<a-input-group compact>
|
||||||
style="width: 80px"
|
<a-select v-model="logModal.rows" style="width:70px;"
|
||||||
@change="openLogs()"
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="50">50</a-select-option>
|
||||||
<a-select-option value="50">50</a-select-option>
|
<a-select-option value="100">100</a-select-option>
|
||||||
<a-select-option value="100">100</a-select-option>
|
</a-select>
|
||||||
</a-select>
|
<a-select v-model="logModal.level" style="width:100px;"
|
||||||
</a-form-item>
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-form-item label="Log Level">
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
<a-select v-model="logModal.level"
|
<a-select-option value="info">Info</a-select-option>
|
||||||
style="width: 120px"
|
<a-select-option value="notice">Notice</a-select-option>
|
||||||
@change="openLogs()"
|
<a-select-option value="warning">Warning</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select-option value="err">Error</a-select-option>
|
||||||
<a-select-option value="debug">Debug</a-select-option>
|
</a-select>
|
||||||
<a-select-option value="info">Info</a-select-option>
|
</a-input-group>
|
||||||
<a-select-option value="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-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-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item style="float: right;">
|
||||||
<a-button type="primary" style="margin-bottom: 10px;"
|
<a-button type="primary" icon="download"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs)" 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>
|
||||||
|
@ -322,8 +321,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" :class="themeSwitcher.currentTheme"
|
:closable="true" footer=""
|
||||||
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
:class="themeSwitcher.currentTheme">
|
||||||
<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,7 +445,7 @@
|
||||||
|
|
||||||
const logModal = {
|
const logModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
logs: '',
|
logs: [],
|
||||||
rows: 20,
|
rows: 20,
|
||||||
level: 'info',
|
level: 'info',
|
||||||
syslog: false,
|
syslog: false,
|
||||||
|
@ -532,6 +531,7 @@
|
||||||
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 +655,14 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
let retries = 0;
|
if (window.location.protocol !== "https:") {
|
||||||
while (retries < 5) {
|
this.showAlert = true;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
try {
|
try {
|
||||||
await this.getStatus();
|
await this.getStatus();
|
||||||
retries = 0;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error occurred while fetching status:", e);
|
console.error(e);
|
||||||
retries++;
|
|
||||||
}
|
}
|
||||||
await PromiseUtil.sleep(2000);
|
await PromiseUtil.sleep(2000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,19 @@
|
||||||
<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; overflow-x: hidden;">
|
||||||
<a-row style="display: flex; flex-wrap: wrap; align-items: center;">
|
<a-row style="display: flex; flex-wrap: wrap; align-items: center;">
|
||||||
|
@ -87,16 +100,12 @@
|
||||||
<a-col :xs="24" :sm="14">
|
<a-col :xs="24" :sm="14">
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<template>
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
||||||
<div>
|
</a-back-top>
|
||||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
<a-alert type="warning" style="float: right; width: fit-content"
|
||||||
</a-back-top>
|
message='{{ i18n "pages.settings.infoDesc" }}'
|
||||||
<a-alert type="warning" style="float: right; width: fit-content"
|
show-icon
|
||||||
message='{{ i18n "pages.settings.infoDesc" }}'
|
>
|
||||||
show-icon
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
@ -164,7 +173,6 @@
|
||||||
<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
|
||||||
|
@ -234,7 +242,6 @@
|
||||||
<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>
|
||||||
|
@ -250,15 +257,13 @@
|
||||||
<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>
|
||||||
|
@ -285,6 +290,17 @@
|
||||||
<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>
|
||||||
|
@ -310,11 +326,28 @@
|
||||||
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('') : [];
|
||||||
|
@ -443,14 +476,59 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
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(600);
|
await PromiseUtil.sleep(1000);
|
||||||
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:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :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) {
|
loading(loading=true) {
|
||||||
this.confirmLoading = loading;
|
this.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
async getData(){
|
async getData(){
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css?{{ .cur_ver }}">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .cur_ver }}">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .cur_ver }}">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
|
||||||
|
|
||||||
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/codemirror.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/codemirror.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
|
||||||
|
@ -63,10 +63,19 @@
|
||||||
<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>
|
<a-row style="display: flex; flex-wrap: wrap; align-items: center;">
|
||||||
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
<a-col :xs="24" :sm="10" style="padding: 4px;">
|
||||||
<a-space direction="horizontal">
|
<a-space direction="horizontal">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.xray.save" }}</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.xray.save" }}</a-button>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">{{ i18n "pages.xray.restart" }}</a-button>
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">{{ i18n "pages.xray.restart" }}</a-button>
|
||||||
|
@ -80,7 +89,7 @@
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="16">
|
<a-col :xs="24" :sm="14">
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
||||||
|
@ -99,7 +108,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="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
<a-button type="danger" @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"}}'>
|
||||||
|
@ -269,8 +278,11 @@
|
||||||
<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 style="margin: 10px 0;" @click="showWarp">WARP {{ i18n "pages.xray.rules.outbound" }}</a-button>
|
<a-button v-else type="primary" icon="cloud" style="margin: 15px 20px;" @click="showWarp()">WARP</a-button>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
@ -278,15 +290,19 @@
|
||||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||||
message='{{ i18n "pages.xray.RoutingsDesc"}}' show-icon></a-alert>
|
message='{{ i18n "pages.xray.RoutingsDesc"}}' show-icon></a-alert>
|
||||||
<a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
|
<a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
|
||||||
<a-table :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
|
<a-table-sortable :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
:data-source="routingRuleData"
|
:data-source="routingRuleData"
|
||||||
:scroll="isMobile ? {} : { x: 1000 }"
|
:scroll="isMobile ? {} : { x: 1000 }"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
:indent-size="0"
|
:indent-size="0"
|
||||||
:style="isMobile ? 'padding: 5px 0' : 'margin-top: 10px;'">
|
:style="isMobile ? 'padding: 5px 0' : 'margin-top: 10px;'"
|
||||||
|
v-on:onSort="replaceRule">
|
||||||
<template slot="action" slot-scope="text, rule, index">
|
<template slot="action" slot-scope="text, rule, index">
|
||||||
[[ index+1 ]]
|
<table-sort-trigger :item-index="index"></table-sort-trigger>
|
||||||
|
<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">
|
||||||
|
@ -381,6 +397,10 @@
|
||||||
<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;">
|
||||||
|
@ -388,18 +408,25 @@
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table-sortable>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="12" :sm="12" :lg="12">
|
<a-col :xs="12" :sm="12" :lg="12">
|
||||||
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n
|
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n
|
||||||
"pages.xray.outbound.addOutbound" }}</a-button>
|
"pages.xray.outbound.addOutbound" }}</a-button>
|
||||||
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
<a-button type="primary" icon="cloud" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
||||||
<a-icon type="sync" :spin="refreshing" @click="refreshOutboundTraffic()" style="margin: 0 5px;"></a-icon>
|
<a-icon type="sync" :spin="refreshing" @click="refreshOutboundTraffic()" style="margin: 0 5px;"></a-icon>
|
||||||
<a-icon type="retweet" @click="resetOutboundTraffic(-1)"></a-icon>
|
<a-popconfirm placement="topRight" @confirm="resetOutboundTraffic(-1)"
|
||||||
|
title='{{ i18n "pages.inbounds.resetTrafficContent"}}'
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "reset"}}'
|
||||||
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
|
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: #008771' : 'color: #008771'"></a-icon>
|
||||||
|
<a-icon type="retweet" style="cursor: pointer;"></a-icon>
|
||||||
|
</a-popconfirm>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-table :columns="outboundColumns" bordered
|
<a-table :columns="outboundColumns" bordered
|
||||||
|
@ -453,7 +480,7 @@
|
||||||
</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
|
<a-table :columns="reverseColumns" bordered v-if="reverseData.length>0"
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
:data-source="reverseData"
|
:data-source="reverseData"
|
||||||
:scroll="isMobile ? {} : { x: 200 }"
|
:scroll="isMobile ? {} : { x: 200 }"
|
||||||
|
@ -479,9 +506,11 @@
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-balancers" tab='{{ i18n "pages.xray.Balancers"}}' style="padding-top: 20px;" force-render="true">
|
<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-button type="primary" icon="plus" @click="addBalancer()" style="margin-bottom: 10px;">{{ i18n "pages.xray.balancer.addBalancer"}}</a-button>
|
||||||
<a-table :columns="balancerColumns" bordered
|
<a-table :columns="balancerColumns" bordered v-if="balancersData.length>0"
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
:data-source="balancersData"
|
:data-source="balancersData"
|
||||||
:scroll="isMobile ? {} : { x: 200 }"
|
:scroll="isMobile ? {} : { x: 200 }"
|
||||||
|
@ -512,9 +541,9 @@
|
||||||
<template slot="selector" slot-scope="text, balancer, index">
|
<template slot="selector" slot-scope="text, balancer, index">
|
||||||
<a-tag class="info-large-tag" style="margin:1;" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
|
<a-tag class="info-large-tag" style="margin:1;" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-5" tab='DNS' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-6" tab='DNS' style="padding-top: 20px;" force-render="true">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item>
|
||||||
<template v-if="enableDNS">
|
<template v-if="enableDNS">
|
||||||
<a-list-item>
|
<a-list-item>
|
||||||
|
@ -534,6 +563,7 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-list-item>
|
</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-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"
|
<a-table :columns="dnsColumns" bordered v-if="dnsServers.length>0"
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
|
@ -567,6 +597,30 @@
|
||||||
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
|
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</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>
|
</template>
|
||||||
</a-tab-pane>
|
</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">
|
||||||
|
@ -587,11 +641,14 @@
|
||||||
</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 "balancerModal"}}
|
||||||
|
{{template "dnsModal"}}
|
||||||
|
{{template "fakednsModal"}}
|
||||||
{{template "warpModal"}}
|
{{template "warpModal"}}
|
||||||
<script>
|
<script>
|
||||||
const rulesColumns = [
|
const rulesColumns = [
|
||||||
|
@ -642,6 +699,12 @@
|
||||||
{ title: '{{ i18n "pages.xray.dns.domains"}}', align: 'center', width: 50, scopedSlots: { customRender: 'domain' } },
|
{ 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 = [
|
const balancerColumns = [
|
||||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
{ 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.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
||||||
|
@ -664,6 +727,7 @@
|
||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
restartResult: '',
|
restartResult: '',
|
||||||
|
showAlert: false,
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
advSettings: 'xraySetting',
|
advSettings: 'xraySetting',
|
||||||
cm: null,
|
cm: null,
|
||||||
|
@ -729,6 +793,9 @@
|
||||||
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$"
|
||||||
|
@ -1214,7 +1281,7 @@
|
||||||
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(){
|
addDNSServer(){
|
||||||
|
@ -1247,6 +1314,36 @@
|
||||||
newDnsServers.splice(index,1);
|
newDnsServers.splice(index,1);
|
||||||
this.dnsServers = newDnsServers;
|
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"}}',
|
||||||
|
@ -1293,6 +1390,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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();
|
await this.getOutboundsTraffic();
|
||||||
|
@ -1857,6 +1957,42 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
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);
|
||||||
|
@ -1896,6 +2032,14 @@
|
||||||
newTemplateSettings.dns.servers = newValue;
|
newTemplateSettings.dns.servers = newValue;
|
||||||
this.templateSettings = newTemplateSettings;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
:ok-text="balancerModal.okText"
|
:ok-text="balancerModal.okText"
|
||||||
cancel-text='{{ i18n "close" }}'
|
cancel-text='{{ i18n "close" }}'
|
||||||
:class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
|
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
|
||||||
:validate-status="balancerModal.duplicateTag? 'warning' : 'success'">
|
:validate-status="balancerModal.duplicateTag? 'warning' : 'success'">
|
||||||
<a-input v-model.trim="balancerModal.balancer.tag" @change="balancerModal.check()"
|
<a-input v-model.trim="balancerModal.balancer.tag" @change="balancerModal.check()"
|
||||||
|
@ -23,7 +23,8 @@
|
||||||
<a-select-option value="roundRobin">Round Robin</a-select-option>
|
<a-select-option value="roundRobin">Round Robin</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback :validate-status="balancerModal.emptySelector? 'warning' : 'success'">
|
<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()"
|
<a-select v-model="balancerModal.balancer.selector" mode="tags" @change="balancerModal.checkSelector()"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||||
|
@ -74,17 +75,18 @@
|
||||||
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
|
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
|
||||||
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);
|
||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.check()
|
this.check();
|
||||||
|
this.checkSelector();
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
balancerModal.visible = false;
|
this.visible = false;
|
||||||
balancerModal.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
balancerModal.confirmLoading = loading;
|
this.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
check() {
|
check() {
|
||||||
if (balancerModal.balancer.tag == '' || balancerModal.balancerTags.includes(balancerModal.balancer.tag)) {
|
if (this.balancer.tag == '' || this.balancerTags.includes(this.balancer.tag)) {
|
||||||
this.duplicateTag = true;
|
this.duplicateTag = true;
|
||||||
this.isValid = false;
|
this.isValid = false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -93,7 +95,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
checkSelector() {
|
checkSelector() {
|
||||||
balancerModal.emptySelector = balancerModal.balancer.selector.length == 0;
|
this.emptySelector = this.balancer.selector.length == 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
outModal.visible = false;
|
outModal.visible = false;
|
||||||
outModal.loading(false);
|
outModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
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:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
<a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
|
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
reverseModal.visible = false;
|
reverseModal.visible = false;
|
||||||
reverseModal.loading(false);
|
reverseModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
reverseModal.confirmLoading = loading;
|
reverseModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
|
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
|
||||||
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='Domain Matcher'>
|
<a-form-item label='Domain Matcher'>
|
||||||
<a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
|
<a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
|
||||||
|
@ -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">Source Port
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
|
@ -174,7 +174,7 @@
|
||||||
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 : ""
|
this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : "";
|
||||||
} else {
|
} else {
|
||||||
this.rule = {
|
this.rule = {
|
||||||
domainMatcher: "",
|
domainMatcher: "",
|
||||||
|
@ -195,7 +195,7 @@
|
||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||||
this.inboundTags.push(...app.inboundTags);
|
this.inboundTags.push(...app.inboundTags);
|
||||||
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));
|
||||||
|
@ -204,14 +204,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.templateSettings.routing && app.templateSettings.routing.balancers) {
|
if (app.templateSettings.routing && app.templateSettings.routing.balancers) {
|
||||||
this.balancerTags = app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)
|
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) {
|
loading(loading=true) {
|
||||||
ruleModal.confirmLoading = loading;
|
ruleModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
getResult() {
|
getResult() {
|
||||||
|
@ -230,8 +230,8 @@
|
||||||
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;
|
rule.outboundTag = value.outboundTag == "" ? undefined : value.outboundTag;
|
||||||
rule.balancerTag = value.balancerTag;
|
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 (
|
||||||
|
|
|
@ -14,6 +14,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
@ -38,8 +39,10 @@ func NewCheckClientIpJob() *CheckClientIpJob {
|
||||||
|
|
||||||
func (j *CheckClientIpJob) Run() {
|
func (j *CheckClientIpJob) Run() {
|
||||||
|
|
||||||
// create files required for iplimit if not exists
|
// create files and dirs 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()
|
||||||
|
|
|
@ -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-0.0.0.0:%v", inbound.Port)
|
oldInbound.Tag = fmt.Sprintf("inbound-%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)
|
||||||
}
|
}
|
||||||
|
@ -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 Clinet configuration problems
|
// Fix Client configuration problems
|
||||||
var newClients []interface{}
|
var newClients []interface{}
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
@ -1900,6 +1900,13 @@ 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() {
|
||||||
|
|
|
@ -57,6 +57,9 @@ 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": "",
|
||||||
}
|
}
|
||||||
|
@ -387,17 +390,11 @@ func (s *SettingService) GetSubPort() (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubPath() (string, error) {
|
func (s *SettingService) GetSubPath() (string, error) {
|
||||||
subPath, err := s.getString("subPath")
|
return s.getString("subPath")
|
||||||
if err != nil {
|
}
|
||||||
return "", err
|
|
||||||
}
|
func (s *SettingService) GetSubJsonPath() (string, error) {
|
||||||
if !strings.HasPrefix(subPath, "/") {
|
return s.getString("subJsonPath")
|
||||||
subPath = "/" + subPath
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(subPath, "/") {
|
|
||||||
subPath += "/"
|
|
||||||
}
|
|
||||||
return subPath, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubDomain() (string, error) {
|
func (s *SettingService) GetSubDomain() (string, error) {
|
||||||
|
@ -412,8 +409,8 @@ func (s *SettingService) GetSubKeyFile() (string, error) {
|
||||||
return s.getString("subKeyFile")
|
return s.getString("subKeyFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubUpdates() (int, error) {
|
func (s *SettingService) GetSubUpdates() (string, error) {
|
||||||
return s.getInt("subUpdates")
|
return s.getString("subUpdates")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubEncrypt() (bool, error) {
|
func (s *SettingService) GetSubEncrypt() (bool, error) {
|
||||||
|
@ -432,6 +429,14 @@ 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")
|
||||||
}
|
}
|
||||||
|
@ -484,6 +489,7 @@ 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() },
|
||||||
}
|
}
|
||||||
|
@ -498,10 +504,11 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||||
result[key] = value
|
result[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
if result["subEnable"].(bool) && result["subURI"].(string) == "" {
|
if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(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()
|
||||||
|
@ -522,12 +529,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 subPath[0] == byte('/') {
|
if result["subURI"].(string) == "" {
|
||||||
subURI += subPath
|
result["subURI"] = subURI + subPath
|
||||||
} else {
|
}
|
||||||
subURI += "/" + subPath
|
if result["subJsonURI"].(string) == "" {
|
||||||
|
result["subJsonURI"] = subURI + subJsonPath
|
||||||
}
|
}
|
||||||
result["subURI"] = subURI
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
"online" = "Online"
|
"online" = "Online"
|
||||||
"domainName" = "Domain Name"
|
"domainName" = "Domain Name"
|
||||||
"monitor" = "Listen IP"
|
"monitor" = "Listen IP"
|
||||||
"certificate" = "Certificate"
|
"certificate" = "Digital Certificate"
|
||||||
"fail" = " Failed"
|
"fail" = " Failed"
|
||||||
"success" = " Successful"
|
"success" = " Successful"
|
||||||
"getVersion" = "Get Version"
|
"getVersion" = "Get Version"
|
||||||
|
@ -52,6 +52,14 @@
|
||||||
"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"
|
||||||
|
@ -62,6 +70,7 @@
|
||||||
"link" = "Manage"
|
"link" = "Manage"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "Hello"
|
||||||
"title" = "Welcome"
|
"title" = "Welcome"
|
||||||
"loginAgain" = "Your session has expired, please log in again"
|
"loginAgain" = "Your session has expired, please log in again"
|
||||||
|
|
||||||
|
@ -140,10 +149,8 @@
|
||||||
"noRecommendKeepDefault" = "It is recommended to keep the default"
|
"noRecommendKeepDefault" = "It is recommended to keep the default"
|
||||||
"certificatePath" = "File Path"
|
"certificatePath" = "File Path"
|
||||||
"certificateContent" = "File Content"
|
"certificateContent" = "File Content"
|
||||||
"publicKeyPath" = "Public Key Path"
|
"publicKey" = "Public Key"
|
||||||
"publicKeyContent" = "Public Key Content"
|
"privatekey" = "Private Key"
|
||||||
"keyPath" = "Private Key Path"
|
|
||||||
"keyContent" = "Private Key Content"
|
|
||||||
"clickOnQRcode" = "Click on QR Code to Copy"
|
"clickOnQRcode" = "Click on QR Code to Copy"
|
||||||
"client" = "Client"
|
"client" = "Client"
|
||||||
"export" = "Export All URLs"
|
"export" = "Export All URLs"
|
||||||
|
@ -302,6 +309,8 @@
|
||||||
"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"
|
||||||
|
@ -383,6 +392,12 @@
|
||||||
"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"
|
||||||
|
@ -458,6 +473,12 @@
|
||||||
"edit" = "Edit Server"
|
"edit" = "Edit Server"
|
||||||
"domains" = "Domains"
|
"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"
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
"online" = "en línea"
|
"online" = "en línea"
|
||||||
"domainName" = "Nombre de dominio"
|
"domainName" = "Nombre de dominio"
|
||||||
"monitor" = "Listening IP"
|
"monitor" = "Listening IP"
|
||||||
"certificate" = "Certificado"
|
"certificate" = "Certificado Digital"
|
||||||
"fail" = "Falló"
|
"fail" = "Falló"
|
||||||
"success" = "Éxito"
|
"success" = "Éxito"
|
||||||
"getVersion" = "Obtener versión"
|
"getVersion" = "Obtener versión"
|
||||||
|
@ -52,6 +52,14 @@
|
||||||
"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"
|
||||||
|
@ -62,6 +70,7 @@
|
||||||
"link" = "Gestionar"
|
"link" = "Gestionar"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "Hola"
|
||||||
"title" = "Bienvenido"
|
"title" = "Bienvenido"
|
||||||
"loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente."
|
"loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente."
|
||||||
|
|
||||||
|
@ -140,10 +149,8 @@
|
||||||
"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"
|
||||||
"publicKeyPath" = "Ruta de la Clave Pública"
|
"publicKey" = "llave Pública"
|
||||||
"publicKeyContent" = "Contenido de la Clave Pública"
|
"privatekey" = "llave Privada"
|
||||||
"keyPath" = "Ruta de la Clave Privada"
|
|
||||||
"keyContent" = "Contenido de la Clave Privada"
|
|
||||||
"clickOnQRcode" = "Haz clic en el Código QR para Copiar"
|
"clickOnQRcode" = "Haz clic en el Código QR para Copiar"
|
||||||
"client" = "Cliente"
|
"client" = "Cliente"
|
||||||
"export" = "Exportar Enlaces"
|
"export" = "Exportar Enlaces"
|
||||||
|
@ -194,7 +201,7 @@
|
||||||
"last" = "Último"
|
"last" = "Último"
|
||||||
"prefix" = "Prefijo"
|
"prefix" = "Prefijo"
|
||||||
"postfix" = "Sufijo"
|
"postfix" = "Sufijo"
|
||||||
"delayedStart" = "Iniciar después del primer uso"
|
"delayedStart" = "Inicio Inicial"
|
||||||
"expireDays" = "Duratio"
|
"expireDays" = "Duratio"
|
||||||
"days" = "día(s)"
|
"days" = "día(s)"
|
||||||
"renew" = "Renovación automática"
|
"renew" = "Renovación automática"
|
||||||
|
@ -302,6 +309,8 @@
|
||||||
"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"
|
||||||
|
@ -377,14 +386,20 @@
|
||||||
"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" = "Rutear Google a través de WARP."
|
"GoogleWARP" = "Google"
|
||||||
"GoogleWARPDesc" = "Agregar enrutamiento para Google a través de WARP."
|
"GoogleWARPDesc" = "Enruta el tráfico a Apple a través de WARP."
|
||||||
"OpenAIWARP" = "Rutear OpenAI (ChatGPT) a través de WARP."
|
"OpenAIWARP" = "OpenAI (ChatGPT)"
|
||||||
"OpenAIWARPDesc" = "Agregar enrutamiento para OpenAI (ChatGPT) a través de WARP."
|
"OpenAIWARPDesc" = "Enruta el tráfico a OpenAI (ChatGPT) a través de WARP."
|
||||||
"NetflixWARP" = "Rutear Netflix a través de WARP."
|
"NetflixWARP" = "Netflix"
|
||||||
"NetflixWARPDesc" = "Agregar enrutamiento para Netflix a través de WARP."
|
"NetflixWARPDesc" = "Enruta el tráfico a Netflix a través de WARP."
|
||||||
"SpotifyWARP" = "Rutear Spotify a través de WARP."
|
"MetaWARP" = "Meta"
|
||||||
"SpotifyWARPDesc" = "Agregar enrutamiento para Spotify a través de WARP."
|
"MetaWARPDesc" = "Enruta el tráfico a Meta (Instagram, Facebook, WhatsApp, Threads,...) 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"
|
||||||
|
@ -458,6 +473,12 @@
|
||||||
"edit" = "Editar servidor"
|
"edit" = "Editar servidor"
|
||||||
"domains" = "Dominios"
|
"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,6 +52,14 @@
|
||||||
"secretToken" = "توکن امنیتی"
|
"secretToken" = "توکن امنیتی"
|
||||||
"remained" = "باقیمانده"
|
"remained" = "باقیمانده"
|
||||||
"security" = "امنیت"
|
"security" = "امنیت"
|
||||||
|
"secAlertTitle" = "هشدارامنیتی"
|
||||||
|
"secAlertSsl" = "ایناتصالامن نیست. لطفا تازمانیکه تیالاس برای محافظت از دادهها فعال نشدهاست، از وارد کردن اطلاعات حساس خودداری کنید"
|
||||||
|
"secAlertConf" = "تنظیمات خاصی در برابر حملات آسیب پذیر هستند. توصیه میشود پروتکلهای امنیتی را برای جلوگیری از نفوذ احتمالی تقویت کنید"
|
||||||
|
"secAlertSSL" = "پنل فاقد ارتباط امن است. لطفاً یک گواهینامه تیالاس برای محافظت از دادهها نصب کنید"
|
||||||
|
"secAlertPanelPort" = "استفاده از پورت پیشفرض پنل ناامن است. لطفاً یک پورت تصادفی یا خاص تنظیم کنید"
|
||||||
|
"secAlertPanelURI" = "مسیر پیشفرض لینک پنل ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
||||||
|
"secAlertSubURI" = "مسیر پیشفرض لینک سابسکریپشن ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
||||||
|
"secAlertSubJsonURI" = "مسیر پیشفرض لینک سابسکریپشن جیسون ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "نمای کلی"
|
"dashboard" = "نمای کلی"
|
||||||
|
@ -62,6 +70,7 @@
|
||||||
"link" = "مدیریت"
|
"link" = "مدیریت"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "سلام"
|
||||||
"title" = "خوشآمدید"
|
"title" = "خوشآمدید"
|
||||||
"loginAgain" = "مدت زمان استفاده بهاتمامرسیده، لطفا دوباره وارد شوید"
|
"loginAgain" = "مدت زمان استفاده بهاتمامرسیده، لطفا دوباره وارد شوید"
|
||||||
|
|
||||||
|
@ -134,16 +143,14 @@
|
||||||
"destinationPort" = "پورت مقصد"
|
"destinationPort" = "پورت مقصد"
|
||||||
"targetAddress" = "آدرس مقصد"
|
"targetAddress" = "آدرس مقصد"
|
||||||
"monitorDesc" = "بهطور پیشفرض خالیبگذارید"
|
"monitorDesc" = "بهطور پیشفرض خالیبگذارید"
|
||||||
"meansNoLimit" = " = واحد: گیگابایت) نامحدود)"
|
"meansNoLimit" = "0 = واحد: گیگابایت) نامحدود)"
|
||||||
"totalFlow" = "ترافیک کل"
|
"totalFlow" = "ترافیک کل"
|
||||||
"leaveBlankToNeverExpire" = "برای منقضینشدن خالیبگذارید"
|
"leaveBlankToNeverExpire" = "برای منقضینشدن خالیبگذارید"
|
||||||
"noRecommendKeepDefault" = "توصیهمیشود بهطور پیشفرض حفظشود"
|
"noRecommendKeepDefault" = "توصیهمیشود بهطور پیشفرض حفظشود"
|
||||||
"certificatePath" = "مسیر فایل"
|
"certificatePath" = "مسیر فایل"
|
||||||
"certificateContent" = "محتوای فایل"
|
"certificateContent" = "محتوای فایل"
|
||||||
"publicKeyPath" = "مسیر کلید عمومی"
|
"publicKey" = "کلید عمومی"
|
||||||
"publicKeyContent" = "محتوای کلید عمومی"
|
"privatekey" = "کلید خصوصی"
|
||||||
"keyPath" = "مسیر کلید خصوصی"
|
|
||||||
"keyContent" = "محتوای کلید خصوصی"
|
|
||||||
"clickOnQRcode" = "برای کپی بر روی کدتصویری کلیک کنید"
|
"clickOnQRcode" = "برای کپی بر روی کدتصویری کلیک کنید"
|
||||||
"client" = "کاربر"
|
"client" = "کاربر"
|
||||||
"export" = "استخراج لینکها"
|
"export" = "استخراج لینکها"
|
||||||
|
@ -302,6 +309,8 @@
|
||||||
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامههای کاربری نمایش میدهد"
|
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامههای کاربری نمایش میدهد"
|
||||||
"subURI" = "پروکسی معکوس URI مسیر"
|
"subURI" = "پروکسی معکوس URI مسیر"
|
||||||
"subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسیها تغییر میدهد URI مسیر"
|
"subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسیها تغییر میدهد URI مسیر"
|
||||||
|
"fragment" = "تکهتکه شدن"
|
||||||
|
"fragmentDesc" = "فعال کردن تکه تکه شدن برای بسته نخست تیالاس"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "پیکربندی ایکسری"
|
"title" = "پیکربندی ایکسری"
|
||||||
|
@ -383,6 +392,12 @@
|
||||||
"OpenAIWARPDesc" = "ترافیک را از طریق وارپ به چت جیپیتی هدایت میکند"
|
"OpenAIWARPDesc" = "ترافیک را از طریق وارپ به چت جیپیتی هدایت میکند"
|
||||||
"NetflixWARP" = "نتفلیکس"
|
"NetflixWARP" = "نتفلیکس"
|
||||||
"NetflixWARPDesc" = "ترافیک را از طریق وارپ به نتفلیکس هدایت میکند"
|
"NetflixWARPDesc" = "ترافیک را از طریق وارپ به نتفلیکس هدایت میکند"
|
||||||
|
"MetaWARP" = "متا"
|
||||||
|
"MetaWARPDesc" = "ترافیک را از طریق وارپ به متا (اینستاگرام، فیس بوک، واتساپ، تردز و...) هدایت می کند."
|
||||||
|
"AppleWARP" = "اپل"
|
||||||
|
"AppleWARPDesc" = " ترافیک را از طریق وارپ به اپل هدایت میکند"
|
||||||
|
"RedditWARP" = "ردیت"
|
||||||
|
"RedditWARPDesc" = " ترافیک را از طریق وارپ به ردیت هدایت میکند"
|
||||||
"SpotifyWARP" = "اسپاتیفای"
|
"SpotifyWARP" = "اسپاتیفای"
|
||||||
"SpotifyWARPDesc" = " ترافیک را از طریق وارپ به اسپاتیفای هدایت میکند"
|
"SpotifyWARPDesc" = " ترافیک را از طریق وارپ به اسپاتیفای هدایت میکند"
|
||||||
"IRWARP" = "دامنههای ایران"
|
"IRWARP" = "دامنههای ایران"
|
||||||
|
@ -458,6 +473,12 @@
|
||||||
"edit" = "ویرایش سرور"
|
"edit" = "ویرایش سرور"
|
||||||
"domains" = "دامنهها"
|
"domains" = "دامنهها"
|
||||||
|
|
||||||
|
[pages.xray.fakedns]
|
||||||
|
"add" = "افزودن دیاناس جعلی"
|
||||||
|
"edit" = "ویرایش دیاناس جعلی"
|
||||||
|
"ipPool" = "زیرشبکه استخر آیپی"
|
||||||
|
"poolSize" = "اندازه استخر"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "مدیر"
|
"admin" = "مدیر"
|
||||||
"secret" = "توکن مخفی"
|
"secret" = "توکن مخفی"
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
"online" = "Online"
|
"online" = "Online"
|
||||||
"domainName" = "Nama Domain"
|
"domainName" = "Nama Domain"
|
||||||
"monitor" = "IP Pemantauan"
|
"monitor" = "IP Pemantauan"
|
||||||
"certificate" = "Sertifikat"
|
"certificate" = "Sertifikat Digital"
|
||||||
"fail" = "Gagal"
|
"fail" = "Gagal"
|
||||||
"success" = "Berhasil"
|
"success" = "Berhasil"
|
||||||
"getVersion" = "Dapatkan Versi"
|
"getVersion" = "Dapatkan Versi"
|
||||||
|
@ -52,6 +52,14 @@
|
||||||
"secretToken" = "Token Rahasia"
|
"secretToken" = "Token Rahasia"
|
||||||
"remained" = "Tersisa"
|
"remained" = "Tersisa"
|
||||||
"security" = "Keamanan"
|
"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]
|
[menu]
|
||||||
"dashboard" = "Ikhtisar"
|
"dashboard" = "Ikhtisar"
|
||||||
|
@ -62,6 +70,7 @@
|
||||||
"link" = "Kelola"
|
"link" = "Kelola"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "Halo"
|
||||||
"title" = "Selamat Datang"
|
"title" = "Selamat Datang"
|
||||||
"loginAgain" = "Sesi Anda telah berakhir, harap masuk kembali"
|
"loginAgain" = "Sesi Anda telah berakhir, harap masuk kembali"
|
||||||
|
|
||||||
|
@ -140,10 +149,8 @@
|
||||||
"noRecommendKeepDefault" = "Disarankan untuk tetap menggunakan pengaturan default"
|
"noRecommendKeepDefault" = "Disarankan untuk tetap menggunakan pengaturan default"
|
||||||
"certificatePath" = "Path Berkas"
|
"certificatePath" = "Path Berkas"
|
||||||
"certificateContent" = "Konten Berkas"
|
"certificateContent" = "Konten Berkas"
|
||||||
"publicKeyPath" = "Path Kunci Publik"
|
"publicKey" = "Kunci Publik"
|
||||||
"publicKeyContent" = "Konten Kunci Publik"
|
"privatekey" = "Kunci Pribadi"
|
||||||
"keyPath" = "Path Kunci Privat"
|
|
||||||
"keyContent" = "Konten Kunci Privat"
|
|
||||||
"clickOnQRcode" = "Klik pada Kode QR untuk Menyalin"
|
"clickOnQRcode" = "Klik pada Kode QR untuk Menyalin"
|
||||||
"client" = "Klien"
|
"client" = "Klien"
|
||||||
"export" = "Ekspor Semua URL"
|
"export" = "Ekspor Semua URL"
|
||||||
|
@ -194,7 +201,7 @@
|
||||||
"last" = "Terakhir"
|
"last" = "Terakhir"
|
||||||
"prefix" = "Awalan"
|
"prefix" = "Awalan"
|
||||||
"postfix" = "Akhiran"
|
"postfix" = "Akhiran"
|
||||||
"delayedStart" = "Mulai saat Penggunaan Awal"
|
"delayedStart" = "Mulai Awal"
|
||||||
"expireDays" = "Durasi"
|
"expireDays" = "Durasi"
|
||||||
"days" = "Hari"
|
"days" = "Hari"
|
||||||
"renew" = "Perpanjang Otomatis"
|
"renew" = "Perpanjang Otomatis"
|
||||||
|
@ -302,6 +309,8 @@
|
||||||
"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
|
"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
|
||||||
"subURI" = "URI Proxy Terbalik"
|
"subURI" = "URI Proxy Terbalik"
|
||||||
"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy."
|
"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy."
|
||||||
|
"fragment" = "Fragmentasi"
|
||||||
|
"fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Konfigurasi Xray"
|
"title" = "Konfigurasi Xray"
|
||||||
|
@ -383,6 +392,12 @@
|
||||||
"OpenAIWARPDesc" = "Rute lalu lintas ke ChatGPT melalui WARP."
|
"OpenAIWARPDesc" = "Rute lalu lintas ke ChatGPT melalui WARP."
|
||||||
"NetflixWARP" = "Netflix"
|
"NetflixWARP" = "Netflix"
|
||||||
"NetflixWARPDesc" = "Rute lalu lintas ke Netflix melalui WARP."
|
"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"
|
"SpotifyWARP" = "Spotify"
|
||||||
"SpotifyWARPDesc" = "Rute lalu lintas ke Spotify melalui WARP."
|
"SpotifyWARPDesc" = "Rute lalu lintas ke Spotify melalui WARP."
|
||||||
"IRWARP" = "Domain Iran"
|
"IRWARP" = "Domain Iran"
|
||||||
|
@ -458,6 +473,12 @@
|
||||||
"edit" = "Sunting Server"
|
"edit" = "Sunting Server"
|
||||||
"domains" = "Domains"
|
"domains" = "Domains"
|
||||||
|
|
||||||
|
[pages.xray.fakedns]
|
||||||
|
"add" = "Tambahkan DNS Palsu"
|
||||||
|
"edit" = "Edit DNS Palsu"
|
||||||
|
"ipPool" = "Subnet Kumpulan IP"
|
||||||
|
"poolSize" = "Ukuran Kolam"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Admin"
|
"admin" = "Admin"
|
||||||
"secret" = "Token Rahasia"
|
"secret" = "Token Rahasia"
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
"online" = "Онлайн"
|
"online" = "Онлайн"
|
||||||
"domainName" = "Домен"
|
"domainName" = "Домен"
|
||||||
"monitor" = "Порт IP"
|
"monitor" = "Порт IP"
|
||||||
"certificate" = "Сертификат"
|
"certificate" = "Цифровой сертификат"
|
||||||
"fail" = "Неудачно"
|
"fail" = "Неудачно"
|
||||||
"success" = "Успешно"
|
"success" = "Успешно"
|
||||||
"getVersion" = "Узнать версию"
|
"getVersion" = "Узнать версию"
|
||||||
|
@ -52,6 +52,14 @@
|
||||||
"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" = "Статус системы"
|
||||||
|
@ -62,6 +70,7 @@
|
||||||
"link" = "менеджмент"
|
"link" = "менеджмент"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "Привет"
|
||||||
"title" = "Добро пожаловать"
|
"title" = "Добро пожаловать"
|
||||||
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова"
|
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова"
|
||||||
|
|
||||||
|
@ -140,10 +149,8 @@
|
||||||
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
|
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
|
||||||
"certificatePath" = "Путь файла"
|
"certificatePath" = "Путь файла"
|
||||||
"certificateContent" = "Содержимое файла"
|
"certificateContent" = "Содержимое файла"
|
||||||
"publicKeyPath" = "Путь к публичному ключу"
|
"publicKey" = "Публичный ключ"
|
||||||
"publicKeyContent" = "Содержимое публичного ключа"
|
"privatekey" = "Приватный ключ"
|
||||||
"keyPath" = "Путь к приватному ключу"
|
|
||||||
"keyContent" = "Содержимое приватного ключа"
|
|
||||||
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
|
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
|
||||||
"client" = "Клиент"
|
"client" = "Клиент"
|
||||||
"export" = "Экспорт ключей"
|
"export" = "Экспорт ключей"
|
||||||
|
@ -194,7 +201,7 @@
|
||||||
"last" = "Последний"
|
"last" = "Последний"
|
||||||
"prefix" = "Префикс"
|
"prefix" = "Префикс"
|
||||||
"postfix" = "Постфикс"
|
"postfix" = "Постфикс"
|
||||||
"delayedStart" = "Начать с момента первого подключения"
|
"delayedStart" = "Начало использования"
|
||||||
"expireDays" = "Длительность"
|
"expireDays" = "Длительность"
|
||||||
"days" = "дней"
|
"days" = "дней"
|
||||||
"renew" = "Автопродление"
|
"renew" = "Автопродление"
|
||||||
|
@ -302,6 +309,8 @@
|
||||||
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
||||||
"subURI" = "URI обратного прокси"
|
"subURI" = "URI обратного прокси"
|
||||||
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
||||||
|
"fragment" = "Фрагментация"
|
||||||
|
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Настройки Xray"
|
"title" = "Настройки Xray"
|
||||||
|
@ -377,14 +386,20 @@
|
||||||
"GoogleIPv4Desc" = "Добавить маршрутизацию для Google для подключения к IPv4"
|
"GoogleIPv4Desc" = "Добавить маршрутизацию для Google для подключения к IPv4"
|
||||||
"NetflixIPv4" = "Использовать IPv4 для Netflix"
|
"NetflixIPv4" = "Использовать IPv4 для Netflix"
|
||||||
"NetflixIPv4Desc" = "Добавить маршрутизацию для Netflix для подключения к IPv4"
|
"NetflixIPv4Desc" = "Добавить маршрутизацию для Netflix для подключения к IPv4"
|
||||||
"GoogleWARP" = "Маршрутизация Google через WARP"
|
"GoogleWARP" = "Google"
|
||||||
"GoogleWARPDesc" = "Добавить маршрутизацию для Google через WARP"
|
"GoogleWARPDesc" = "Направляет трафик в Google через WARP."
|
||||||
"OpenAIWARP" = "Маршрутизация OpenAI (ChatGPT) через WARP"
|
"OpenAIWARP" = "OpenAI (ChatGPT)"
|
||||||
"OpenAIWARPDesc" = "Добавить маршрутизацию для OpenAI (ChatGPT) через WARP"
|
"OpenAIWARPDesc" = "Направляет трафик в OpenAI (ChatGPT) через WARP."
|
||||||
"NetflixWARP" = "Маршрутизация Netflix через WARP"
|
"NetflixWARP" = "Netflix"
|
||||||
"NetflixWARPDesc" = "Добавить маршрутизацию для Netflix через WARP"
|
"NetflixWARPDesc" = "Направляет трафик в Apple через WARP."
|
||||||
"SpotifyWARP" = "Маршрутизация Spotify через WARP"
|
"MetaWARP" = "Мета"
|
||||||
"SpotifyWARPDesc" = "Добавить маршрутизацию для Spotify через WARP"
|
"MetaWARPDesc" = "Направляет трафик в Meta (Instagram, Facebook, WhatsApp, Threads...) через 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" = "Входящие"
|
||||||
|
@ -443,7 +458,7 @@
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "Секретный ключ"
|
"secretKey" = "Секретный ключ"
|
||||||
"publicKey" = "Открытый ключ"
|
"publicKey" = "Публичный ключ"
|
||||||
"allowedIPs" = "Разрешенные IP-адреса"
|
"allowedIPs" = "Разрешенные IP-адреса"
|
||||||
"endpoint" = "Конечная точка"
|
"endpoint" = "Конечная точка"
|
||||||
"psk" = "Общий ключ"
|
"psk" = "Общий ключ"
|
||||||
|
@ -458,6 +473,12 @@
|
||||||
"edit" = "Редактировать сервер"
|
"edit" = "Редактировать сервер"
|
||||||
"domains" = "Домены"
|
"domains" = "Домены"
|
||||||
|
|
||||||
|
[pages.xray.fakedns]
|
||||||
|
"add" = "Добавить поддельный DNS"
|
||||||
|
"edit" = "Редактировать поддельный DNS"
|
||||||
|
"ipPool" = "Подсеть пула IP"
|
||||||
|
"poolSize" = "Размер пула"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Админ"
|
"admin" = "Админ"
|
||||||
"secret" = "Секретный токен"
|
"secret" = "Секретный токен"
|
||||||
|
|
625
web/translation/translate.uk_UA.toml
Normal file
625
web/translation/translate.uk_UA.toml
Normal file
|
@ -0,0 +1,625 @@
|
||||||
|
"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>"
|
|
@ -42,7 +42,7 @@
|
||||||
"online" = "Trực tuyến"
|
"online" = "Trực tuyến"
|
||||||
"domainName" = "Tên miền"
|
"domainName" = "Tên miền"
|
||||||
"monitor" = "Listening IP"
|
"monitor" = "Listening IP"
|
||||||
"certificate" = "Chứng chỉ"
|
"certificate" = "Chứng chỉ số"
|
||||||
"fail" = "Thất bại"
|
"fail" = "Thất bại"
|
||||||
"success" = "Thành công"
|
"success" = "Thành công"
|
||||||
"getVersion" = "Lấy phiên bản"
|
"getVersion" = "Lấy phiên bản"
|
||||||
|
@ -52,6 +52,14 @@
|
||||||
"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"
|
||||||
|
@ -62,6 +70,7 @@
|
||||||
"link" = "Quản lý"
|
"link" = "Quản lý"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "Xin chào"
|
||||||
"title" = "Chào mừng"
|
"title" = "Chào mừng"
|
||||||
"loginAgain" = "Thời hạn đăng nhập đã hết. Vui lòng đăng nhập lại."
|
"loginAgain" = "Thời hạn đăng nhập đã hết. Vui lòng đăng nhập lại."
|
||||||
|
|
||||||
|
@ -140,10 +149,8 @@
|
||||||
"noRecommendKeepDefault" = "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định"
|
"noRecommendKeepDefault" = "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định"
|
||||||
"certificatePath" = "Đường dẫn tập"
|
"certificatePath" = "Đường dẫn tập"
|
||||||
"certificateContent" = "Nội dung tập"
|
"certificateContent" = "Nội dung tập"
|
||||||
"publicKeyPath" = "Đường dẫn khóa công khai"
|
"publicKey" = "Khóa công khai"
|
||||||
"publicKeyContent" = "Nội dung khóa công khai"
|
"privatekey" = "Khóa cá nhân"
|
||||||
"keyPath" = "Đường dẫn khóa riêng tư"
|
|
||||||
"keyContent" = "Nội dung khóa riêng tư"
|
|
||||||
"clickOnQRcode" = "Nhấn vào Mã QR để sao chép"
|
"clickOnQRcode" = "Nhấn vào Mã QR để sao chép"
|
||||||
"client" = "Người dùng"
|
"client" = "Người dùng"
|
||||||
"export" = "Xuất liên kết"
|
"export" = "Xuất liên kết"
|
||||||
|
@ -194,7 +201,7 @@
|
||||||
"last" = "Cuối cùng"
|
"last" = "Cuối cùng"
|
||||||
"prefix" = "Tiền tố"
|
"prefix" = "Tiền tố"
|
||||||
"postfix" = "Hậu tố"
|
"postfix" = "Hậu tố"
|
||||||
"delayedStart" = "Bắt đầu sau khi sử dụng lần đầu"
|
"delayedStart" = "Bắt đầu ở Lần Đầu"
|
||||||
"expireDays" = "Khoảng thời gian"
|
"expireDays" = "Khoảng thời gian"
|
||||||
"days" = "ngày"
|
"days" = "ngày"
|
||||||
"renew" = "Tự động gia hạn"
|
"renew" = "Tự động gia hạn"
|
||||||
|
@ -302,6 +309,8 @@
|
||||||
"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"
|
||||||
|
@ -377,14 +386,20 @@
|
||||||
"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" = "Định tuyến Google qua WARP."
|
"GoogleWARP" = "Google"
|
||||||
"GoogleWARPDesc" = "Thêm định tuyến cho Google qua WARP."
|
"GoogleWARPDesc" = "Định tuyến lưu lượng truy cập tới Google thông qua WARP."
|
||||||
"OpenAIWARP" = "Định tuyến OpenAI (ChatGPT) qua WARP."
|
"OpenAIWARP" = "OpenAI (ChatGPT)"
|
||||||
"OpenAIWARPDesc" = "Thêm định tuyến cho OpenAI (ChatGPT) qua WARP."
|
"OpenAIWARPDesc" = "Định tuyến lưu lượng truy cập tới OpenAI (ChatGPT) thông qua WARP."
|
||||||
"NetflixWARP" = "Định tuyến Netflix qua WARP."
|
"NetflixWARP" = "Netflix"
|
||||||
"NetflixWARPDesc" = "Thêm định tuyến cho Netflix qua WARP."
|
"NetflixWARPDesc" = "Định tuyến lưu lượng truy cập tới Netflix thông qua WARP."
|
||||||
"SpotifyWARP" = "Định tuyến Spotify qua WARP."
|
"MetaWARP" = "Meta"
|
||||||
"SpotifyWARPDesc" = "Thêm định tuyến cho 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."
|
||||||
|
"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"
|
||||||
|
@ -458,6 +473,12 @@
|
||||||
"edit" = "Chỉnh sửa máy chủ"
|
"edit" = "Chỉnh sửa máy chủ"
|
||||||
"domains" = "Tên miền"
|
"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,6 +52,14 @@
|
||||||
"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" = "系统状态"
|
||||||
|
@ -62,6 +70,7 @@
|
||||||
"link" = "管理"
|
"link" = "管理"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
|
"hello" = "你好"
|
||||||
"title" = "欢迎"
|
"title" = "欢迎"
|
||||||
"loginAgain" = "登录时效已过,请重新登录"
|
"loginAgain" = "登录时效已过,请重新登录"
|
||||||
|
|
||||||
|
@ -140,10 +149,8 @@
|
||||||
"noRecommendKeepDefault" = "没有特殊需求保持默认即可"
|
"noRecommendKeepDefault" = "没有特殊需求保持默认即可"
|
||||||
"certificatePath" = "文件路径"
|
"certificatePath" = "文件路径"
|
||||||
"certificateContent" = "文件内容"
|
"certificateContent" = "文件内容"
|
||||||
"publicKeyPath" = "公钥文件路径"
|
"publicKey" = "公钥"
|
||||||
"publicKeyContent" = "公钥内容"
|
"privatekey" = "私钥"
|
||||||
"keyPath" = "密钥文件路径"
|
|
||||||
"keyContent" = "密钥内容"
|
|
||||||
"clickOnQRcode" = "点击二维码复制"
|
"clickOnQRcode" = "点击二维码复制"
|
||||||
"client" = "客户"
|
"client" = "客户"
|
||||||
"export" = "导出链接"
|
"export" = "导出链接"
|
||||||
|
@ -302,6 +309,8 @@
|
||||||
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
||||||
"subURI" = "反向代理 URI"
|
"subURI" = "反向代理 URI"
|
||||||
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
||||||
|
"fragment" = "碎片"
|
||||||
|
"fragmentDesc" = "启用 TLS hello 数据包分段"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Xray 设置"
|
"title" = "Xray 设置"
|
||||||
|
@ -377,14 +386,20 @@
|
||||||
"GoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
|
"GoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
|
||||||
"NetflixIPv4" = "为 Netflix 使用 IPv4"
|
"NetflixIPv4" = "为 Netflix 使用 IPv4"
|
||||||
"NetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
|
"NetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
|
||||||
"GoogleWARP" = "将谷歌路由到 WARP"
|
"GoogleWARP" = "Google"
|
||||||
"GoogleWARPDesc" = "为谷歌添加路由到WARP"
|
"GoogleWARPDesc" = "通过 WARP 将流量路由到 Google。"
|
||||||
"OpenAIWARP" = "将 OpenAI (ChatGPT) 路由到 WARP"
|
"OpenAIWARP" = "OpenAI (ChatGPT)"
|
||||||
"OpenAIWARPDesc" = "将OpenAI(ChatGPT)路由添加到WARP"
|
"OpenAIWARPDesc" = "通过 WARP 将流量路由到 OpenAI (ChatGPT)。"
|
||||||
"NetflixWARP" = "将 Netflix 路由到 WARP"
|
"NetflixWARP" = "Netflix"
|
||||||
"NetflixWARPDesc" = "为Netflix添加路由到WARP"
|
"NetflixWARPDesc" = "通过 WARP 将流量路由到 Netflix。"
|
||||||
"SpotifyWARP" = "将 Spotify 路由到 WARP"
|
"MetaWARP"="Meta"
|
||||||
"SpotifyWARPDesc" = "为Spotify添加路由到WARP"
|
"MetaWARPDesc" = "通过 WARP 将流量路由到 Meta(Instagram、Facebook、WhatsApp、Threads...)"
|
||||||
|
"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" = "入站"
|
||||||
|
@ -458,6 +473,12 @@
|
||||||
"edit" = "编辑服务器"
|
"edit" = "编辑服务器"
|
||||||
"domains" = "域"
|
"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,19 +337,17 @@ func (s *Server) Start() (err error) {
|
||||||
}
|
}
|
||||||
if certFile != "" || keyFile != "" {
|
if certFile != "" || keyFile != "" {
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
listener.Close()
|
c := &tls.Config{
|
||||||
return err
|
Certificates: []tls.Certificate{cert},
|
||||||
|
}
|
||||||
|
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())
|
||||||
}
|
}
|
||||||
|
|
53
x-ui.sh
53
x-ui.sh
|
@ -345,6 +345,47 @@ 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}"
|
||||||
|
@ -906,8 +947,8 @@ run_speedtest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
create_iplimit_jails() {
|
create_iplimit_jails() {
|
||||||
# Use default bantime if not passed => 30 minutes
|
# Use default bantime if not passed => 15 minutes
|
||||||
local bantime="${1:-30}"
|
local bantime="${1:-15}"
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -918,8 +959,8 @@ enabled=true
|
||||||
filter=3x-ipl
|
filter=3x-ipl
|
||||||
action=3x-ipl
|
action=3x-ipl
|
||||||
logpath=${iplimit_log_path}
|
logpath=${iplimit_log_path}
|
||||||
maxretry=4
|
maxretry=2
|
||||||
findtime=60
|
findtime=32
|
||||||
bantime=${bantime}m
|
bantime=${bantime}m
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
@ -932,7 +973,7 @@ EOF
|
||||||
|
|
||||||
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
|
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
|
||||||
[INCLUDES]
|
[INCLUDES]
|
||||||
before = iptables-common.conf
|
before = iptables-allports.conf
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
actionstart = <iptables> -N f2b-<name>
|
actionstart = <iptables> -N f2b-<name>
|
||||||
|
@ -1263,7 +1304,7 @@ show_menu() {
|
||||||
firewall_menu
|
firewall_menu
|
||||||
;;
|
;;
|
||||||
21)
|
21)
|
||||||
enable_bbr
|
bbr_menu
|
||||||
;;
|
;;
|
||||||
22)
|
22)
|
||||||
update_geo
|
update_geo
|
||||||
|
|
|
@ -16,7 +16,8 @@ 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 {
|
||||||
|
|
Loading…
Reference in a new issue