diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 00ac9169..5c04e06a 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -11,15 +11,15 @@ jobs:
steps:
- name: Check out the code
- uses: actions/checkout@v4.1.1
+ uses: actions/checkout@v4
- name: Set up QEMU
- uses: docker/setup-qemu-action@v3.0.0
+ uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3.0.0
+ uses: docker/setup-buildx-action@v3
- name: Login to GHCR
- uses: docker/login-action@v3.0.0
+ uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -27,12 +27,12 @@ jobs:
- name: Docker meta
id: meta
- uses: docker/metadata-action@v5.5.1
+ uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
- name: Build and push Docker image
- uses: docker/build-push-action@v5.1.0
+ uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 62bc6e7c..790c3a1a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -20,12 +20,12 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout repository
- uses: actions/checkout@v4.1.1
+ uses: actions/checkout@v4
- name: Setup Go
- uses: actions/setup-go@v5.0.0
+ uses: actions/setup-go@v5
with:
- go-version: '1.21'
+ go-version: '1.22'
- name: Install dependencies
run: |
@@ -77,7 +77,7 @@ jobs:
cd x-ui/bin
# 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
wget ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip
@@ -116,12 +116,11 @@ jobs:
- name: Package
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
- - name: Upload
- uses: svenstaro/upload-release-action@2.7.0
+ - name: Upload files to GH release
+ uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: x-ui-linux-${{ matrix.platform }}.tar.gz
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
prerelease: true
- overwrite: true
diff --git a/DockerInit.sh b/DockerInit.sh
index a2224999..e9cccf49 100755
--- a/DockerInit.sh
+++ b/DockerInit.sh
@@ -27,7 +27,7 @@ case $1 in
esac
mkdir -p 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"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}"
diff --git a/Dockerfile b/Dockerfile
index 5f4a36c3..010d9578 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
# ========================================================
# Stage: Builder
# ========================================================
-FROM golang:1.21-alpine AS builder
+FROM golang:1.22-alpine AS builder
WORKDIR /app
ARG TARGETARCH
diff --git a/README.md b/README.md
index a1c902e2..14d569db 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# 3X-UI
+
+
**An Advanced Web Panel • Built on Xray Core**
[](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:
-
-
+
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
@@ -25,10 +26,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
## 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
@@ -69,7 +70,17 @@ certbot renew --dry-run
```sh
ARCH=$(uname -m)
-[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
+case "${ARCH}" in
+ x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
+ i*86 | x86) XUI_ARCH="386" ;;
+ armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
+ armv7* | armv7) XUI_ARCH="armv7" ;;
+ armv6* | armv6) XUI_ARCH="armv6" ;;
+ armv5* | armv5) XUI_ARCH="armv5" ;;
+ *) XUI_ARCH="amd64" ;;
+esac
+
+
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
```
@@ -77,7 +88,16 @@ wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI
```sh
ARCH=$(uname -m)
-[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
+case "${ARCH}" in
+ x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
+ i*86 | x86) XUI_ARCH="386" ;;
+ armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
+ armv7* | armv7) XUI_ARCH="armv7" ;;
+ armv6* | armv6) XUI_ARCH="armv6" ;;
+ armv5* | armv5) XUI_ARCH="armv5" ;;
+ *) XUI_ARCH="amd64" ;;
+esac
+
cd /root/
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
@@ -164,22 +184,26 @@ remove 3x-ui from docker
- AlmaLinux 9+
- Rockylinux 9+
-## Compatible Architectures & Devices
+## Supported Architectures and Devices
-Supports a variety of different architectures and devices. Here are some of the main architectures that we support:
+
+ Click for Supported Architectures and devices details
-- **amd64**: This is the most common architecture for personal computers and servers. It supports most modern operating systems.
+Our platform offers compatibility with a diverse range of architectures and devices, ensuring flexibility across various computing environments. The following are key architectures that we support:
-- **x86 / i386**: This architecture is prevalent in desktop and laptop computers. It's widely supported by various operating systems and applications. (Ex: Most Windows, macOS, and Linux systems)
+- **amd64**: This prevalent architecture is the standard for personal computers and servers, accommodating most modern operating systems seamlessly.
-- **armv8 / arm64 / aarch64**: This is the architecture for modern mobile and embedded devices, including smartphones and tablets. (Ex: Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS,...)
+- **x86 / i386**: Widely adopted in desktop and laptop computers, this architecture enjoys broad support from numerous operating systems and applications, including but not limited to Windows, macOS, and Linux systems.
-- **armv7 / arm / arm32**: This is the architecture for older mobile and embedded devices. It is still widely used in many devices. (Ex: Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2,...)
+- **armv8 / arm64 / aarch64**: Tailored for contemporary mobile and embedded devices, such as smartphones and tablets, this architecture is exemplified by devices like Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS, and more.
-- **armv6 / arm / arm32**: This is the architecture for very old embedded devices. While not as common as before, there are still some devices using this architecture. (Ex: Raspberry Pi 1, Raspberry Pi Zero/Zero W,...)
+- **armv7 / arm / arm32**: Serving as the architecture for older mobile and embedded devices, it remains widely utilized in devices like Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2, among others.
+
+- **armv6 / arm / arm32**: Geared towards very old embedded devices, this architecture, while less prevalent, is still in use. Devices such as Raspberry Pi 1, Raspberry Pi Zero/Zero W, rely on this architecture.
+
+- **armv5 / arm / arm32**: An older architecture primarily associated with early embedded systems, it is less common today but may still be found in legacy devices like early Raspberry Pi versions and some older smartphones.
+
-- **armv5 / arm / arm32**: This is an older architecture primarily used in early embedded systems. While it's less common today, some legacy devices may still rely on this architecture. (Ex: Early versions of Raspberry Pi, some older smartphones)
-
## Languages
- English
@@ -188,6 +212,8 @@ Supports a variety of different architectures and devices. Here are some of the
- Russian
- Vietnamese
- Spanish
+- Indonesian
+- Ukrainian
## Features
diff --git a/config/version b/config/version
index abae0d9a..fae692e4 100644
--- a/config/version
+++ b/config/version
@@ -1 +1 @@
-2.1.3
\ No newline at end of file
+2.2.1
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 722df287..f6566e55 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module x-ui
-go 1.21.4
+go 1.22.0
require (
github.com/Calidity/gin-sessions v1.3.1
@@ -14,10 +14,10 @@ require (
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.24.1
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
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/gorm v1.25.7
)
@@ -40,7 +40,7 @@ require (
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // 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/securecookie v1.1.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/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
- github.com/klauspost/compress v1.17.6 // indirect
- github.com/klauspost/cpuid/v2 v2.2.6 // indirect
+ github.com/klauspost/compress v1.17.7 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
github.com/mattn/go-isatty v0.0.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/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/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.40.1 // indirect
- github.com/refraction-networking/utls v1.6.0 // indirect
+ github.com/quic-go/quic-go v0.41.0 // indirect
+ github.com/refraction-networking/utls v1.6.3 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // 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/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // 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
golang.org/x/arch v0.6.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
- golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
- golang.org/x/mod v0.14.0 // indirect
+ golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
+ golang.org/x/mod v0.15.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.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/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
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
diff --git a/go.sum b/go.sum
index 0c5f04a0..5a92f611 100644
--- a/go.sum
+++ b/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/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.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
-github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
+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.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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/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-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8=
-github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
+github.com/google/pprof v0.0.0-20240225044709-fd706174c886 h1:JSJUTZTQT1Gzb2ROdAKOY3HwzBYcclS2GgumhMfHqjw=
+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 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
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/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/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
-github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
+github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
+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.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
-github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/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/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
-github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
+github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
+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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
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/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/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
-github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
-github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
-github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
+github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
+github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
+github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
+github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/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/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/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/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
-github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
-github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
-github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
-github.com/refraction-networking/utls v1.6.0 h1:X5vQMqVx7dY7ehxxqkFER/W6DSjy8TMqSItXm8hRDYQ=
-github.com/refraction-networking/utls v1.6.0/go.mod h1:kHJ6R9DFFA0WsRgBM35iiDku4O7AqPR6y79iuzW7b10=
+github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
+github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
+github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
+github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
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/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/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
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.0/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
+github.com/sagernet/sing v0.3.2 h1:CwWcxUBPkMvwgfe2/zUgY5oHG9qOL8Aob/evIFYK9jo=
+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/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
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/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/xray-core v1.8.7 h1:lb8O1l3/eAg3YAXA6tLm5M6N7BsX8wxW9sJLjU3dHkA=
-github.com/xtls/xray-core v1.8.7/go.mod h1:9rFpflfQbgFeH1VKJw7yUmEy7myOyDCgNXXl0bmmyOo=
+github.com/xtls/xray-core v1.8.8 h1:6ApBa5pNkPZ+I7jyJDZod3v5sadizS/BZr0pW7zcs8o=
+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/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
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/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-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
-golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
+golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
+golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
-golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
+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-20180826012351-8a410e7b638d/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-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.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
-golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
+golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
+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.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=
@@ -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-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/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
-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 h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo=
+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.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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
-google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
+google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
+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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
diff --git a/install.sh b/install.sh
index 9f46d4df..a9b6711b 100644
--- a/install.sh
+++ b/install.sh
@@ -82,16 +82,16 @@ fi
install_base() {
case "${release}" in
centos | almalinux | rocky)
- yum -y update && yum install -y -q wget curl tar
+ yum -y update && yum install -y -q wget curl tar tzdata
;;
fedora)
- dnf -y update && dnf install -y -q wget curl tar
+ dnf -y update && dnf install -y -q wget curl tar tzdata
;;
arch | manjaro)
- pacman -Syu && pacman -Syu --noconfirm wget curl tar
+ pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
;;
*)
- apt-get update && apt install -y -q wget curl tar
+ apt-get update && apt install -y -q wget curl tar tzdata
;;
esac
}
diff --git a/media/3X-UI.png b/media/3X-UI.png
new file mode 100644
index 00000000..8b1af8a7
Binary files /dev/null and b/media/3X-UI.png differ
diff --git a/sub/default.json b/sub/default.json
new file mode 100644
index 00000000..ba13f6fb
--- /dev/null
+++ b/sub/default.json
@@ -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": {}
+}
\ No newline at end of file
diff --git a/sub/sub.go b/sub/sub.go
index b642f7f2..2a4a37f4 100644
--- a/sub/sub.go
+++ b/sub/sub.go
@@ -47,11 +47,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine := gin.Default()
- subPath, err := s.settingService.GetSubPath()
- if err != nil {
- return nil, err
- }
-
subDomain, err := s.settingService.GetSubDomain()
if err != nil {
return nil, err
@@ -61,9 +56,44 @@ func (s *Server) initRouter() (*gin.Engine, error) {
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
}
diff --git a/sub/subController.go b/sub/subController.go
index 5f7c69cf..b5c5cbac 100644
--- a/sub/subController.go
+++ b/sub/subController.go
@@ -3,34 +3,56 @@ package sub
import (
"encoding/base64"
"strings"
- "x-ui/web/service"
"github.com/gin-gonic/gin"
)
type SUBController struct {
- subService SubService
- settingService service.SettingService
+ subPath string
+ subJsonPath string
+ subEncrypt bool
+ updateInterval string
+
+ subService *SubService
+ subJsonService *SubJsonService
}
-func NewSUBController(g *gin.RouterGroup) *SUBController {
- a := &SUBController{}
+func NewSUBController(
+ 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)
return a
}
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) {
- subEncrypt, _ := a.settingService.GetSubEncrypt()
- subShowInfo, _ := a.settingService.GetSubShowInfo()
subId := c.Param("subid")
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 {
c.String(400, "Error!")
} else {
@@ -40,14 +62,31 @@ func (a *SUBController) subs(c *gin.Context) {
}
// Add headers
- c.Writer.Header().Set("Subscription-Userinfo", headers[0])
- c.Writer.Header().Set("Profile-Update-Interval", headers[1])
- c.Writer.Header().Set("Profile-Title", headers[2])
+ c.Writer.Header().Set("Subscription-Userinfo", header)
+ c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
+ c.Writer.Header().Set("Profile-Title", subId)
- if subEncrypt {
+ if a.subEncrypt {
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
} else {
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)
+ }
+}
diff --git a/sub/subJsonService.go b/sub/subJsonService.go
new file mode 100644
index 00000000..8bc98dea
--- /dev/null
+++ b/sub/subJsonService.go
@@ -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"`
+}
diff --git a/sub/subService.go b/sub/subService.go
index ddf9692b..06d1ed0a 100644
--- a/sub/subService.go
+++ b/sub/subService.go
@@ -25,47 +25,42 @@ type SubService struct {
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.showInfo = showInfo
var result []string
- var headers []string
+ var header string
var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic
inbounds, err := s.getInboundsBySubId(subId)
if err != nil {
- return nil, nil, err
- }
- s.remarkModel, err = s.settingService.GetRemarkModel()
- if err != nil {
- s.remarkModel = "-ieo"
+ return nil, "", err
}
+
s.datepicker, err = s.settingService.GetDatepicker()
- if err != nil {
- s.datepicker = "gregorian"
- }
+ if err != nil {
+ s.datepicker = "gregorian"
+ }
for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound)
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 {
continue
}
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 {
- inbound.Listen = fallbackMaster.Listen
- inbound.Port = fallbackMaster.Port
- var stream map[string]interface{}
- json.Unmarshal([]byte(inbound.StreamSettings), &stream)
- var masterStream map[string]interface{}
- json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
- stream["security"] = masterStream["security"]
- stream["tlsSettings"] = masterStream["tlsSettings"]
- stream["externalProxy"] = masterStream["externalProxy"]
- modifiedStream, _ := json.MarshalIndent(stream, "", " ")
- inbound.StreamSettings = string(modifiedStream)
+ inbound.Listen = listen
+ inbound.Port = port
+ inbound.StreamSettings = streamSettings
}
}
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 {
if index == 0 {
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))
- updateInterval, _ := s.settingService.GetSubUpdates()
- headers = append(headers, fmt.Sprintf("%d", updateInterval))
- headers = append(headers, subId)
- return result, headers, nil
+ header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
+ return result, header, nil
}
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{}
}
-func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
+func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) {
db := database.GetDB()
var inbound *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).
Find(&inbound).Error
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 {
@@ -578,6 +582,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
+
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
diff --git a/util/random/random.go b/util/random/random.go
index b1dd2e09..1dd47ec9 100644
--- a/util/random/random.go
+++ b/util/random/random.go
@@ -2,7 +2,6 @@ package random
import (
"math/rand"
- "time"
)
var numSeq [10]rune
@@ -13,8 +12,6 @@ var numUpperSeq [36]rune
var allSeq [62]rune
func init() {
- rand.Seed(time.Now().UnixNano())
-
for i := 0; i < 10; i++ {
numSeq[i] = rune('0' + i)
}
@@ -41,3 +38,7 @@ func Seq(n int) string {
}
return string(runes)
}
+
+func Num(n int) int {
+ return rand.Intn(n)
+}
diff --git a/web/assets/codemirror/codemirror.js b/web/assets/codemirror/codemirror.js
index ef1810fa..a3cd26c1 100644
--- a/web/assets/codemirror/codemirror.js
+++ b/web/assets/codemirror/codemirror.js
@@ -538,7 +538,7 @@
var on = function(emitter, type, f) {
if (emitter.addEventListener) {
- emitter.addEventListener(type, f, false);
+ emitter.addEventListener(type, f, { passive: false });
} else if (emitter.attachEvent) {
emitter.attachEvent("on" + type, f);
} else {
diff --git a/web/assets/codemirror/xq.css b/web/assets/codemirror/xq.css
index 4f7c54cf..1e9eaaeb 100644
--- a/web/assets/codemirror/xq.css
+++ b/web/assets/codemirror/xq.css
@@ -45,12 +45,12 @@ THE SOFTWARE.
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
.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 div.CodeMirror-selected { background: rgba(0, 0, 0, 0.5); }
-.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: 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: rgba(39, 0, 122, 0.99); }
-.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid #2c3950; }
+.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: 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: var(--dark-color-codemirror-line-selection); }
+.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-subtle { color: rgb(255 255 255 / 70%); }
.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: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; }
.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; }
diff --git a/web/assets/css/custom.css b/web/assets/css/custom.css
index c41bcc16..4ab6c5a8 100644
--- a/web/assets/css/custom.css
+++ b/web/assets/css/custom.css
@@ -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,
body {
height: 100vh;
@@ -16,7 +100,7 @@ body {
font-feature-settings: "tnum";
}
html {
- --antd-wave-shadow-color: #008771;
+ --antd-wave-shadow-color: var(--color-primary-100);
line-height: 1.15;
text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
@@ -26,7 +110,7 @@ html {
}
::selection {
- color: #008771;
+ color: var(--color-primary-100);
background-color: #cfe8e4;
}
@@ -134,7 +218,7 @@ style attribute {
padding: 0.5rem;
}
.ant-modal-body {
- padding: 10px;
+ padding: 20px;
}
.ant-form-item-label {
line-height: 1.5;
@@ -218,7 +302,7 @@ style attribute {
.ant-menu-submenu-active,
.ant-menu-submenu-title:hover,
.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open {
- color: #008771;
+ color: var(--color-primary-100);
background-color: rgb(232 244 242);
border-radius: 0.5rem;
}
@@ -480,14 +564,14 @@ style attribute {
}
.normal-icon:hover {
- color: #008771;
+ color: var(--color-primary-100);
}
/* DARK THEME */
.dark ::selection {
color: #fff;
- background-color: #008771;
+ background-color: var(--color-primary-100);
}
.dark .normal-icon:hover {
@@ -502,13 +586,14 @@ style attribute {
.dark .ant-table,
.dark .ant-collapse-content,
.dark .ant-tabs {
- background-color: #151f31;
- color: #ffffffa6;
+ background-color: var(--dark-color-surface-100);
+ color: var(--dark-color-text-primary);
}
.dark .ant-card-hoverable: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,
@@ -518,8 +603,8 @@ style attribute {
.dark .ant-table-expanded-row:hover,
.dark .ant-table-expanded-row .ant-table-tbody,
.dark .ant-calendar {
- background-color: #101828;
- color: rgb(255 255 255 /65%);
+ background-color: var(--dark-color-background);
+ color: var(--dark-color-text-primary);
}
.dark .ant-table-expanded-row .ant-table-thead > tr:first-child > th {
@@ -528,7 +613,7 @@ style attribute {
.dark .ant-calendar,
.dark .ant-card-bordered {
- border-color: #151f31;
+ border-color: var(--dark-color-background);
}
.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-tbody > tr > td,
.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,
@@ -553,7 +638,7 @@ style attribute {
.dark .ant-popover-title,
.dark .ant-calendar-header,
.dark .ant-calendar-input-wrap {
- border-bottom-color: #2c3950;
+ border-bottom-color: var(--dark-color-surface-400);
}
.dark .ant-modal-footer,
@@ -561,7 +646,7 @@ style attribute {
.dark .ant-calendar-footer,
.dark .ant-divider-horizontal.ant-divider-with-text-center:before,
.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,
@@ -597,7 +682,7 @@ style attribute {
.dark .ant-calendar-year-panel-year,
.dark .ant-calendar-month-panel-month,
.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 {
@@ -623,13 +708,12 @@ style attribute {
.dark .ant-select-dropdown li,
.dark .ant-select-dropdown-menu-item,
.dark .ant-divider:not(.ant-divider-with-text-center),
-.dark .ant-calendar-input,
.dark .client-table-header,
.dark .ant-select-selection--multiple .ant-select-selection__choice,
.dark .ant-calendar-time-picker-inner {
- background-color: #222d42;
- border-color: #2c3950;
- color: rgba(255, 255, 255, 0.65);
+ background-color: var(--dark-color-surface-200);
+ border-color: var(--dark-color-surface-300);
+ color: var(--dark-color-text-primary);
}
.dark .ant-select-selection:hover,
@@ -639,34 +723,34 @@ style attribute {
.dark .ant-input:hover,
.dark .ant-input:focus {
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) {
- color: rgba(255, 255, 255, 0.65);
+ color: var(--dark-color-text-primary);
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:before {
- color: rgb(255 255 255 / 65%);
+ color: var(--dark-color-text-primary);
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:hover:not(.ant-btn-primary):not(.ant-btn-danger) {
color: #fff;
background-color: rgb(10 117 87 / 50%);
- border-color: #008771;
+ border-color: var(--color-primary-100);
}
.dark .ant-btn-primary[disabled],
.dark .ant-btn-danger[disabled],
.dark .ant-calendar-ok-btn-disabled {
color: rgb(255 255 255 / 35%);
- background-color: #2c3950;
+ background-color: var(--dark-color-surface-300);
border-color: #42516c;
}
@@ -675,7 +759,7 @@ style attribute {
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
> td,
.dark .client-table-odd-row {
- background-color: #00877122;
+ background-color: var(--dark-color-table-hover);
}
.dark .ant-table-row-expand-icon {
@@ -685,38 +769,36 @@ style attribute {
}
.dark .ant-table-row-expand-icon:hover {
- color: #008771;
+ color: var(--color-primary-100);
background-color: #fff0;
- border-color: #008771;
+ border-color: var(--color-primary-100);
}
.dark .ant-switch:not(.ant-switch-checked),
.dark .ant-progress-line .ant-progress-inner {
- background-color: #2c3950;
+ background-color: var(--dark-color-surface-500);
}
.dark .ant-progress-circle-trail {
- stroke: #2c3950 !important;
+ stroke: var(--dark-color-stroke) !important;
}
-.ant-dropdown-menu-dark,
.dark .ant-popover-inner {
- background-color: #222d42;
+ background-color: var(--dark-color-surface-500);
}
.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,
.dark .ant-select-dropdown-menu-item-selected,
-.dark .ant-select-dropdown-menu-item:hover,
.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 {
- background-color: #2c3950;
+ background-color: var(--dark-color-surface-300);
}
.dark .ant-alert-message {
@@ -724,61 +806,61 @@ style attribute {
}
.dark .ant-tag {
- color: rgba(255, 255, 255, 0.65);
- background-color: #ffffff0a;
- border-color: #344461;
+ color: var(--dark-color-tag-color);
+ background-color: var(--dark-color-tag-bg);
+ border-color: var(--dark-color-tag-border);
}
.dark .ant-tag-blue {
- background-color: #111a2c;
- border-color: #0f367e;
- color: #3c89e8;
+ background-color: var(--dark-color-tag-blue-bg);
+ border-color: var(--dark-color-tag-blue-border);
+ color: var(--dark-color-tag-blue-color);
}
.dark .ant-tag-red,
.dark .ant-alert-error {
- background-color: #291515;
- border-color: #5c2626;
- color: #e04141;
+ background-color: var(--dark-color-tag-red-bg);
+ border-color: var(--dark-color-tag-red-border);
+ color: var(--dark-color-tag-red-color);
}
.dark .ant-tag-orange,
.dark .ant-alert-warning {
- background-color: #312313;
- border-color: #593914;
- color: #ffa031;
+ background-color: var(--dark-color-tag-orange-bg);
+ border-color: var(--dark-color-tag-orange-border);
+ color: var(--dark-color-tag-orange-color);
}
.dark .ant-tag-green {
- background-color: #112421;
- border-color: #144840;
- color: #33bca5;
+ background-color: var(--dark-color-tag-green-bg);
+ border-color: var(--dark-color-tag-green-border);
+ color: var(--dark-color-tag-green-color);
}
.dark .ant-tag-purple {
- background-color: #2c1e32;
- border-color: #49394e;
- color: #cfb9cc;
+ background-color: var(--dark-color-tag-purple-bg);
+ border-color: var(--dark-color-tag-purple-border);
+ color: var(--dark-color-tag-purple-color);
}
.dark .ant-modal-content,
.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-last-month-cell .ant-calendar-date {
- color: #2c3950;
+ color: var(--dark-color-surface-300);
}
.dark .ant-calendar-selected-day .ant-calendar-date {
- background-color: #008771 !important;
+ background-color: var(--color-primary-100) !important;
color: #fff;
}
.dark .ant-calendar-date:hover,
.dark .ant-calendar-time-picker-select li:hover {
- background-color: #313f5a;
+ background-color: var(--dark-color-surface-600);
color: #fff;
}
@@ -792,13 +874,15 @@ style attribute {
color: #fff;
font-weight: 600;
outline: none;
- background-color: #008771;
+ background-color: var(--color-primary-100);
}
.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:hover {
background-color: #ffeee1;
@@ -813,6 +897,8 @@ style attribute {
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:hover {
border-color: #784e1d;
@@ -828,7 +914,7 @@ style attribute {
}
.dark .has-success .anticon {
- color: #61bf39;
+ color: var(--color-primary-100);
animation-name: diffZoomIn1 !important;
}
@@ -874,19 +960,19 @@ style attribute {
}
.ant-calendar-today .ant-calendar-date {
- color: #008771;
+ color: var(--color-primary-100);
font-weight: 700;
- border-color: #008771;
+ border-color: var(--color-primary-100);
}
.dark .ant-calendar-today .ant-calendar-date {
color: #fff;
font-weight: 700;
- border-color: #008771;
+ border-color: var(--color-primary-100);
}
.ant-calendar-selected-day .ant-calendar-date {
- background: #008771;
+ background: var(--color-primary-100);
color: #ffffff;
}
@@ -920,7 +1006,7 @@ li.ant-select-dropdown-menu-item:empty:after {
.ant-select-dropdown.ant-select-dropdown--multiple
.ant-select-dropdown-menu-item-selected:hover
.ant-select-selected-icon {
- color: #008771;
+ color: var(--color-primary-100);
}
.ant-select-selection:hover,
.ant-input-number-focused,
@@ -929,7 +1015,7 @@ li.ant-select-dropdown-menu-item:empty:after {
}
.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,
@@ -957,7 +1043,7 @@ li.ant-select-dropdown-menu-item:empty:after {
}
.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,
@@ -965,10 +1051,11 @@ li.ant-select-dropdown-menu-item:empty:after {
color: rgba(255, 255, 255, 0.35);
}
+.ant-dropdown-menu-dark,
.dark .ant-calendar-year-panel-year:hover,
.dark .ant-calendar-month-panel-month:hover,
.dark .ant-calendar-decade-panel-decade:hover {
- background-color: #222d42;
+ background-color: var(--dark-color-surface-200);
}
.dark .ant-calendar-header a:hover {
@@ -976,13 +1063,13 @@ li.ant-select-dropdown-menu-item:empty:after {
}
.dark .ant-calendar-month-panel-header {
- background-color: #101828;
- border-bottom: 1px solid #222d42;
+ background-color: var(--dark-color-background);
+ border-bottom: 1px solid var(--dark-color-surface-200);
}
.dark .ant-calendar-year-panel,
.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,
@@ -1000,7 +1087,7 @@ li.ant-select-dropdown-menu-item:empty:after {
.ant-calendar-decade-panel-selected-cell
.ant-calendar-decade-panel-decade:hover {
color: #fff;
- background-color: #008771;
+ background-color: var(--color-primary-100);
}
.dark .ant-calendar-last-month-cell .ant-calendar-date,
@@ -1014,8 +1101,8 @@ li.ant-select-dropdown-menu-item:empty:after {
.dark .ant-calendar-today .ant-calendar-date:hover {
color: #fff;
- border-color: #008771;
- background-color: #008771;
+ border-color: var(--color-primary-100);
+ background-color: var(--color-primary-100);
}
.dark
@@ -1028,8 +1115,8 @@ li.ant-select-dropdown-menu-item:empty:after {
}
.dark .ant-calendar-decade-panel-header {
- border-bottom: 1px solid #222d42;
- background-color: #101828;
+ border-bottom: 1px solid var(--dark-color-surface-200);
+ background-color: var(--dark-color-background);
}
.dark .ant-checkbox-inner {
@@ -1038,12 +1125,13 @@ li.ant-select-dropdown-menu-item:empty:after {
}
.dark .ant-checkbox-checked .ant-checkbox-inner {
- background-color: #008771;
- border-color: #008771;
+ background-color: var(--color-primary-100);
+ border-color: var(--color-primary-100);
}
.dark .ant-calendar-input {
- background-color: #101828;
+ background-color: var(--dark-color-background);
+ color: var(--dark-color-text-primary);
}
.dark .ant-calendar-input::placeholder {
@@ -1099,11 +1187,10 @@ li.ant-select-dropdown-menu-item:empty:after {
background-color: rgb(232 244 242);
}
-.dark .ant-dropdown-menu-item:hover,
.dark .ant-dropdown-menu-submenu-title:hover,
.dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),
.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) {
- background-color: #313f5a;
+ background-color: var(--dark-color-surface-600);
}
.ant-select-dropdown,
@@ -1116,6 +1203,8 @@ li.ant-select-dropdown-menu-item:empty:after {
}
.qr-bg {
+ width: 100%;
+ height: 100%;
background-color: #fff;
display: flex;
justify-content: center;
@@ -1124,7 +1213,7 @@ li.ant-select-dropdown-menu-item:empty:after {
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;
}
@@ -1135,3 +1224,36 @@ li.ant-select-dropdown-menu-item:empty:after {
b, strong {
font-weight: 500;
}
+
+.ant-collapse>.ant-collapse-item>.ant-collapse-header {
+ padding: 10px 16px 10px 40px;
+}
+
+.dark .ant-message-notice-content {
+ background-color: var(--dark-color-surface-200);
+ border: 1px solid var(--dark-color-surface-300);
+ color: var(--dark-color-text-primary);
+}
+
+.ant-btn-danger {
+ background-color: var(--dark-color-btn-danger);
+ border-color: var(--dark-color-btn-danger-border);
+}
+
+.ant-btn-danger:focus, .ant-btn-danger:hover {
+ background-color: var(--dark-color-btn-danger-hover);
+ border-color: var(--dark-color-btn-danger-hover);
+}
+
+.dark .ant-alert-close-icon .anticon-close:hover {
+ color: rgb(255 255 255);
+}
+
+.ant-empty-small {
+ margin: 4px 0;
+ background-color: transparent !important;
+}
+
+.ant-empty-small .ant-empty-image {
+ height: 20px;
+}
diff --git a/web/assets/favicon.ico b/web/assets/favicon.ico
deleted file mode 100644
index 99b108ab..00000000
Binary files a/web/assets/favicon.ico and /dev/null differ
diff --git a/web/assets/js/langs.js b/web/assets/js/langs.js
index 42fa49ff..a7364bcb 100644
--- a/web/assets/js/langs.js
+++ b/web/assets/js/langs.js
@@ -34,6 +34,11 @@ const supportLangs = [
value: 'id-ID',
icon: '🇮🇩',
},
+ {
+ name: 'Український',
+ value: 'uk-UA',
+ icon: '🇺🇦',
+ },
];
function getLang() {
diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js
index 6a52563d..05248b77 100644
--- a/web/assets/js/model/outbound.js
+++ b/web/assets/js/model/outbound.js
@@ -957,7 +957,7 @@ Outbound.WireguardSettings = class extends CommonClass {
address: this.address ? this.address.split(",") : [],
workers: this.workers?? undefined,
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
- reserved: this.reserved ? this.reserved.split(",") : undefined,
+ reserved: this.reserved ? this.reserved.split(",").map(Number) : undefined,
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
kernelMode: this.kernelMode,
};
diff --git a/web/assets/js/model/setting.js b/web/assets/js/model/setting.js
index ddf1a0e1..637830e8 100644
--- a/web/assets/js/model/setting.js
+++ b/web/assets/js/model/setting.js
@@ -28,6 +28,7 @@ class AllSetting {
this.subListen = "";
this.subPort = "2096";
this.subPath = "/sub/";
+ this.subJsonPath = "/json/";
this.subDomain = "";
this.subCertFile = "";
this.subKeyFile = "";
@@ -35,6 +36,8 @@ class AllSetting {
this.subEncrypt = true;
this.subShowInfo = false;
this.subURI = '';
+ this.subJsonURI = '';
+ this.subJsonFragment = '';
this.timeLocation = "Asia/Tehran";
diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js
index e9676252..791e8533 100644
--- a/web/assets/js/model/xray.js
+++ b/web/assets/js/model/xray.js
@@ -1146,10 +1146,6 @@ class Inbound extends XrayCommonClass {
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
}
- canSniffing() {
- return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
- }
-
reset() {
this.port = RandomUtil.randomIntRange(10000, 60000);
this.listen = '';
@@ -1534,6 +1530,28 @@ class Inbound extends XrayCommonClass {
return url.toString();
}
+ getWireguardLink(address, port, remark, peerId) {
+ let txt = `[Interface]\n`
+ txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n`
+ txt += `Address = ${this.settings.peers[peerId].allowedIPs[0]}\n`
+ txt += `DNS = 1.1.1.1, 1.0.0.1\n`
+ if (this.settings.mtu) {
+ txt += `MTU = ${this.settings.mtu}\n`
+ }
+ txt += `\n# ${remark}\n`
+ txt += `[Peer]\n`
+ txt += `PublicKey = ${this.settings.pubKey}\n`
+ txt += `AllowedIPs = 0.0.0.0/0, ::/0\n`
+ txt += `Endpoint = ${address}:${port}`
+ if (this.settings.peers[peerId].psk) {
+ txt += `\nPresharedKey = ${this.settings.peers[peerId].psk}`
+ }
+ if (this.settings.peers[peerId].keepAlive) {
+ txt += `\nPersistentKeepalive = ${this.settings.peers[peerId].keepAlive}\n`
+ }
+ return txt;
+ }
+
genLink(address='', port=this.port, forceTls='same', remark='', client) {
switch (this.protocol) {
case Protocols.VMESS:
@@ -1557,7 +1575,7 @@ class Inbound extends XrayCommonClass {
const orderChars = remarkModel.slice(1);
let orders = {
'i': remark,
- 'e': client ? client.email : '',
+ 'e': email,
'o': '',
};
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
@@ -1580,6 +1598,7 @@ class Inbound extends XrayCommonClass {
}
genInboundLinks(remark = '', remarkModel = '-ieo') {
+ let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
if(this.clients){
let links = [];
this.clients.forEach((client) => {
@@ -1589,7 +1608,14 @@ class Inbound extends XrayCommonClass {
});
return links.join('\r\n');
} else {
- if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(this.listen, this.port, 'same', remark);
+ if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(addr, this.port, 'same', remark);
+ if(this.protocol == Protocols.WIREGUARD) {
+ let links = [];
+ this.settings.peers.forEach((p,index) => {
+ links.push(this.getWireguardLink(addr,this.port,remark + remarkModel.charAt(0) + (index+1),index));
+ });
+ return links.join('\r\n');
+ }
return '';
}
}
@@ -2269,7 +2295,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
}
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) {
@@ -2297,16 +2323,24 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
};
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
- constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], keepAlive=0) {
+ constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.2/32'], keepAlive=0) {
super();
+ this.privateKey = privateKey
this.publicKey = publicKey;
+ if (!this.publicKey){
+ [this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
+ }
this.psk = psk;
+ allowedIPs.forEach((a,index) => {
+ if (a.length>0 && !a.includes('/')) allowedIPs[index] += '/32';
+ })
this.allowedIPs = allowedIPs;
this.keepAlive = keepAlive;
}
static fromJson(json={}){
return new Inbound.WireguardSettings.Peer(
+ json.privateKey,
json.publicKey,
json.preSharedKey,
json.allowedIPs,
@@ -2315,7 +2349,11 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
}
toJson() {
+ this.allowedIPs.forEach((a,index) => {
+ if (a.length>0 && !a.includes('/')) this.allowedIPs[index] += '/32';
+ });
return {
+ privateKey: this.privateKey,
publicKey: this.publicKey,
preSharedKey: this.psk.length>0 ? this.psk : undefined,
allowedIPs: this.allowedIPs,
diff --git a/web/assets/js/sw.js b/web/assets/js/sw.js
deleted file mode 100644
index 46cf61c4..00000000
--- a/web/assets/js/sw.js
+++ /dev/null
@@ -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();
\ No newline at end of file
diff --git a/web/assets/manifest.json b/web/assets/manifest.json
deleted file mode 100644
index 04c63c1d..00000000
--- a/web/assets/manifest.json
+++ /dev/null
@@ -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"
- }
- ]
-}
diff --git a/web/assets/persian-datepicker/persian-datepicker.min.css b/web/assets/persian-datepicker/persian-datepicker.min.css
index 9e99974a..133f80d0 100644
--- a/web/assets/persian-datepicker/persian-datepicker.min.css
+++ b/web/assets/persian-datepicker/persian-datepicker.min.css
@@ -33,7 +33,8 @@ jdp-container :before {
}
jdp-container .jdp-icon-minus,
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;
cursor: pointer;
display: flex;
@@ -205,15 +206,15 @@ jdp-container .jdp-day-name {
}
jdp-container .jdp-day-name.today,
jdp-container .jdp-day.today {
- border-color: #008771;
- color: #008771;
+ border-color: var(--color-primary-100);
+ color: var(--color-primary-100);
font-weight: 700;
}
.dark jdp-container .jdp-day-name.selected,
.dark jdp-container .jdp-day.selected,
jdp-container .jdp-day-name.selected,
jdp-container .jdp-day.selected {
- background-color: #008771 !important;
+ background-color: var(--color-primary-100) !important;
color: #fff !important;
opacity: 1 !important;
}
@@ -266,7 +267,7 @@ jdp-container .jdp-btn-empty,
jdp-container .jdp-btn-today {
background: #00877000;
border-radius: 5px;
- color: #008771;
+ color: var(--color-primary-100);
cursor: pointer;
display: inline-block;
font-size: 90%;
@@ -368,26 +369,26 @@ jdp-container .jdp-time-container.jdp-only-time .jdp-time:after {
}
.dark jdp-container .jdp-days {
- border-color: #313f5a;
+ border-color: var(--dark-color-surface-400);
}
.dark jdp-overlay {
background-color: #181f2c;
}
.dark jdp-container {
- background: #101828;
+ background: var(--dark-color-background);
border-color: #2c3950;
box-shadow: 0 2px 8px rgba(0,0,0,.15);
color: #fff;
}
.dark jdp-container .jdp-icon-minus,
.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-plus:hover {
- background-color: #313f5a;
+ background-color: var(--dark-color-surface-600);
}
.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 input,
.dark jdp-container .jdp-year select {
- background: #101828;
- color: rgb(255 255 255 / 65%);
+ background: var(--dark-color-background);
+ color: var(--dark-color-text-primary);
}
.dark jdp-container .jdp-day,
.dark jdp-container .jdp-day-name {
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.today {
- border-color: #008771;
+ border-color: var(--color-primary-100);
}
.dark jdp-container .jdp-day.disabled-day {
opacity: 0.15;
}
.dark jdp-container .jdp-day:not(.disabled-day):hover {
- background-color: #313f5a;
+ background-color: var(--dark-color-surface-600);
color: #fff;
}
.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-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 {
- background-color: #313f5a;
+ background-color: var(--dark-color-surface-600);
color: #fff;
}
.dark jdp-container .jdp-time-container .jdp-time select {
- border: 1px solid rgb(49 63 90);
+ border: 1px solid var(--dark-color-surface-600);
}
diff --git a/web/controller/inbound.go b/web/controller/inbound.go
index 86da9813..d613453f 100644
--- a/web/controller/inbound.go
+++ b/web/controller/inbound.go
@@ -86,7 +86,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
user := session.GetLoginUser(c)
inbound.UserId = user.Id
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 {
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.UserId = user.Id
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 {
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
}
diff --git a/web/controller/xray_setting.go b/web/controller/xray_setting.go
index 28f55b54..2dddb44b 100644
--- a/web/controller/xray_setting.go
+++ b/web/controller/xray_setting.go
@@ -81,7 +81,6 @@ func (a *XraySettingController) warp(c *gin.Context) {
resp, err = a.XraySettingService.RegWarp(skey, pkey)
case "license":
license := c.PostForm("license")
- println(license)
resp, err = a.XraySettingService.SetWarpLicence(license)
}
diff --git a/web/entity/entity.go b/web/entity/entity.go
index 8ab06399..06850128 100644
--- a/web/entity/entity.go
+++ b/web/entity/entity.go
@@ -48,6 +48,9 @@ type AllSetting struct {
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
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"`
}
@@ -105,6 +108,13 @@ func (s *AllSetting) CheckValid() error {
s.SubPath += "/"
}
+ if !strings.HasPrefix(s.SubJsonPath, "/") {
+ s.SubJsonPath = "/" + s.SubJsonPath
+ }
+ if !strings.HasSuffix(s.SubJsonPath, "/") {
+ s.SubJsonPath += "/"
+ }
+
_, err := time.LoadLocation(s.TimeLocation)
if err != nil {
return common.NewError("time location not exist:", s.TimeLocation)
diff --git a/web/html/common/head.html b/web/html/common/head.html
index e20cdc24..b2533098 100644
--- a/web/html/common/head.html
+++ b/web/html/common/head.html
@@ -7,18 +7,6 @@
-
-
-
-
-
{{ .host }}-{{ i18n .title}}
+
{{end}}
\ No newline at end of file
diff --git a/web/html/common/prompt_modal.html b/web/html/common/prompt_modal.html
index edfad682..b91ede03 100644
--- a/web/html/common/prompt_modal.html
+++ b/web/html/common/prompt_modal.html
@@ -1,6 +1,7 @@
{{define "promptModal"}}
- Subscription
-
+ {{ i18n "pages.settings.subSettings"}}
+
+
+ {{ i18n "pages.settings.subSettings"}} Json
+
+
{{ i18n "pages.inbounds.client" }}
[[ row.remark ]]
-
+
@@ -35,12 +45,21 @@
this.client = client;
this.subId = '';
this.qrcodes = [];
- this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
- this.qrcodes.push({
- remark: l.remark,
- link: l.link
+ if (this.inbound.protocol == Protocols.WIREGUARD){
+ this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l,index) =>{
+ this.qrcodes.push({
+ remark: "Peer " + (index+1),
+ link: l
+ });
});
- });
+ } else {
+ this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
+ this.qrcodes.push({
+ remark: l.remark,
+ link: l.link
+ });
+ });
+ }
this.visible = true;
},
close: function () {
@@ -73,12 +92,16 @@
},
genSubLink(subID) {
return app.subSettings.subURI+subID;
+ },
+ genSubJsonLink(subID) {
+ return app.subSettings.subJsonURI+subID;
}
},
updated() {
if (qrModal.client && qrModal.client.subId) {
qrModal.subId = qrModal.client.subId;
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
+ this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
}
qrModal.qrcodes.forEach((element, index) => {
this.setQrCode("qrCode-" + index, element.link);
diff --git a/web/html/common/text_modal.html b/web/html/common/text_modal.html
index 4fe2f175..d668c792 100644
--- a/web/html/common/text_modal.html
+++ b/web/html/common/text_modal.html
@@ -1,15 +1,16 @@
{{define "textModal"}}
-
- {{ i18n "download" }} [[ txtModal.fileName ]]
-
-
+ :closable="true"
+ :class="themeSwitcher.currentTheme">
+
+ [[ txtModal.fileName ]]
+
+ {{ i18n "copy" }}
+
+
-{{end}}
\ No newline at end of file
+{{end}}
diff --git a/web/html/login.html b/web/html/login.html
index b622a080..7655c66f 100644
--- a/web/html/login.html
+++ b/web/html/login.html
@@ -2,9 +2,14 @@
{{template "head" .}}
-
+
-
-
-
-
+
+
+
+
-
- {{ i18n "pages.login.title" }}
-
+
+
+
+ {{ i18n "pages.login.hello" }}
+ {{ i18n "pages.login.title" }}
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [[ loading ? '' : '{{ i18n "login" }}' ]]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [[ loading ? '' : '{{ i18n "login" }}' ]]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ultra
+
+
+
+
+
+
-
-
-
+
+
+
-
+
{{template "js" .}}
{{template "component/themeSwitcher" .}}
{{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');
+ }
+ });
diff --git a/web/html/xui/client_bulk_modal.html b/web/html/xui/client_bulk_modal.html
index 9ae84e5a..cb05cdc3 100644
--- a/web/html/xui/client_bulk_modal.html
+++ b/web/html/xui/client_bulk_modal.html
@@ -220,7 +220,7 @@
clientsBulkModal.visible = false;
clientsBulkModal.loading(false);
},
- loading(loading) {
+ loading(loading=true) {
clientsBulkModal.confirmLoading = loading;
},
};
diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html
index 4b270607..83fa71a3 100644
--- a/web/html/xui/client_modal.html
+++ b/web/html/xui/client_modal.html
@@ -72,7 +72,7 @@
clientModal.visible = false;
clientModal.loading(false);
},
- loading(loading) {
+ loading(loading=true) {
clientModal.confirmLoading = loading;
},
};
diff --git a/web/html/xui/common_sider.html b/web/html/xui/common_sider.html
index 13d5bd49..78b833b9 100644
--- a/web/html/xui/common_sider.html
+++ b/web/html/xui/common_sider.html
@@ -15,10 +15,6 @@
{{ i18n "menu.xray"}}
-
-
-
-
{{ i18n "menu.logout"}}
@@ -31,7 +27,13 @@
-
+
+
+
+ Ultra
+
-
+
+
+
+ Ultra
+
+{{end}}
+
+{{define "component/sortableTable"}}
+
+
+
+{{end}}
diff --git a/web/html/xui/component/themeSwitch.html b/web/html/xui/component/themeSwitch.html
index 3bc1ad7d..2301da94 100644
--- a/web/html/xui/component/themeSwitch.html
+++ b/web/html/xui/component/themeSwitch.html
@@ -10,27 +10,48 @@
-{{end}}
\ No newline at end of file
+{{end}}
diff --git a/web/html/xui/dns_modal.html b/web/html/xui/dns_modal.html
new file mode 100644
index 00000000..3035e9e1
--- /dev/null
+++ b/web/html/xui/dns_modal.html
@@ -0,0 +1,86 @@
+{{define "dnsModal"}}
+
+
+
+
+
+
+ +
+
+
+ -
+
+
+
+
+
+
+ [[ l ]]
+
+
+
+
+
+
+{{end}}
diff --git a/web/html/xui/fakedns_modal.html b/web/html/xui/fakedns_modal.html
new file mode 100644
index 00000000..1429b686
--- /dev/null
+++ b/web/html/xui/fakedns_modal.html
@@ -0,0 +1,57 @@
+{{define "fakednsModal"}}
+
+
+
+
+
+
+
+
+
+
+
+{{end}}
diff --git a/web/html/xui/form/inbound.html b/web/html/xui/form/inbound.html
index 6f3705ff..03df4926 100644
--- a/web/html/xui/form/inbound.html
+++ b/web/html/xui/form/inbound.html
@@ -1,6 +1,6 @@
{{define "form/inbound"}}
-
+
@@ -114,7 +114,7 @@
-
+
{{template "form/sniffing"}}
{{end}}
diff --git a/web/html/xui/form/outbound.html b/web/html/xui/form/outbound.html
index fb6a7af7..469c42b9 100644
--- a/web/html/xui/form/outbound.html
+++ b/web/html/xui/form/outbound.html
@@ -134,28 +134,10 @@
-
-
-
-
- {{ i18n "reset" }}
-
- {{ i18n "pages.xray.wireguard.publicKey" }}
-
-
-
+
-
-
-
-
- {{ i18n "reset" }}
-
- {{ i18n "pages.xray.wireguard.psk" }}
-
-
-
+
@@ -189,7 +171,6 @@
-
@@ -212,9 +193,8 @@
-
-
+
@@ -369,13 +349,15 @@
-
+
None
[[ key ]]
-
[[ alpn ]]
@@ -391,7 +373,8 @@
-
+
[[ key ]]
diff --git a/web/html/xui/form/protocol/dokodemo.html b/web/html/xui/form/protocol/dokodemo.html
index a5309597..70ffe7e0 100644
--- a/web/html/xui/form/protocol/dokodemo.html
+++ b/web/html/xui/form/protocol/dokodemo.html
@@ -1,5 +1,5 @@
{{define "form/dokodemo"}}
-
+
diff --git a/web/html/xui/form/protocol/http.html b/web/html/xui/form/protocol/http.html
index ff266de5..a14418a2 100644
--- a/web/html/xui/form/protocol/http.html
+++ b/web/html/xui/form/protocol/http.html
@@ -1,6 +1,6 @@
{{define "form/http"}}
-
-
+
+
{{ i18n "username" }}
{{ i18n "password" }}
@@ -18,4 +18,4 @@
-{{end}}
\ No newline at end of file
+{{end}}
diff --git a/web/html/xui/form/protocol/shadowsocks.html b/web/html/xui/form/protocol/shadowsocks.html
index 8e4861cf..b55a3d5c 100644
--- a/web/html/xui/form/protocol/shadowsocks.html
+++ b/web/html/xui/form/protocol/shadowsocks.html
@@ -20,7 +20,7 @@
-
+
[[ method_name ]]
diff --git a/web/html/xui/form/protocol/socks.html b/web/html/xui/form/protocol/socks.html
index e9a937e2..105603e5 100644
--- a/web/html/xui/form/protocol/socks.html
+++ b/web/html/xui/form/protocol/socks.html
@@ -1,5 +1,5 @@
{{define "form/socks"}}
-
+
@@ -11,7 +11,7 @@
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'">
-
+
{{ i18n "username" }}
{{ i18n "password" }}
diff --git a/web/html/xui/form/protocol/trojan.html b/web/html/xui/form/protocol/trojan.html
index 3f1d830b..fe1db139 100644
--- a/web/html/xui/form/protocol/trojan.html
+++ b/web/html/xui/form/protocol/trojan.html
@@ -19,27 +19,23 @@
-
+
-
-
- +
-
-
+ +
-
+
Fallback [[ index + 1 ]]
inbound.settings.delFallback(index)"
- style="color: rgb(255, 77, 79);cursor: pointer;"/>
+ style="color: rgb(255, 77, 79);cursor: pointer;" />
-
+
@@ -53,6 +49,6 @@
-
+
{{end}}
diff --git a/web/html/xui/form/protocol/vless.html b/web/html/xui/form/protocol/vless.html
index 73859ad5..5ccec3fe 100644
--- a/web/html/xui/form/protocol/vless.html
+++ b/web/html/xui/form/protocol/vless.html
@@ -21,27 +21,23 @@
-
+
-
-
- +
-
-
+ +
-
+
Fallback [[ index + 1 ]]
inbound.settings.delFallback(index)"
- style="color: rgb(255, 77, 79);cursor: pointer;"/>
+ style="color: rgb(255, 77, 79);cursor: pointer;" />
-
+
diff --git a/web/html/xui/form/protocol/wireguard.html b/web/html/xui/form/protocol/wireguard.html
index ea2c3427..a1ac5628 100644
--- a/web/html/xui/form/protocol/wireguard.html
+++ b/web/html/xui/form/protocol/wireguard.html
@@ -1,5 +1,5 @@
{{define "form/wireguard"}}
-
+
@@ -26,7 +26,7 @@
+
-
+
Peer [[ index + 1 ]]
inbound.settings.delPeer(index)"
@@ -38,10 +38,16 @@
{{ i18n "reset" }}
- {{ i18n "pages.xray.wireguard.publicKey" }}
-
+ {{ i18n "pages.xray.wireguard.secretKey" }}
+
+
+
+
+
+ {{ i18n "pages.xray.wireguard.publicKey" }}
+
@@ -51,7 +57,7 @@
{{ i18n "reset" }}
{{ i18n "pages.xray.wireguard.psk" }}
-
+
diff --git a/web/html/xui/form/stream/stream_grpc.html b/web/html/xui/form/stream/stream_grpc.html
index 11c1ec5d..fcefdff9 100644
--- a/web/html/xui/form/stream/stream_grpc.html
+++ b/web/html/xui/form/stream/stream_grpc.html
@@ -1,5 +1,5 @@
{{define "form/streamGRPC"}}
-
+
diff --git a/web/html/xui/form/stream/stream_http.html b/web/html/xui/form/stream/stream_http.html
index 3d854743..3a05655f 100644
--- a/web/html/xui/form/stream/stream_http.html
+++ b/web/html/xui/form/stream/stream_http.html
@@ -1,5 +1,5 @@
{{define "form/streamHTTP"}}
-
+
diff --git a/web/html/xui/form/stream/stream_kcp.html b/web/html/xui/form/stream/stream_kcp.html
index 39ae6ca2..5cdee701 100644
--- a/web/html/xui/form/stream/stream_kcp.html
+++ b/web/html/xui/form/stream/stream_kcp.html
@@ -1,7 +1,7 @@
{{define "form/streamKCP"}}
-
+
-
+
None
SRTP
uTP
diff --git a/web/html/xui/form/stream/stream_quic.html b/web/html/xui/form/stream/stream_quic.html
index b92167bf..c7c5800a 100644
--- a/web/html/xui/form/stream/stream_quic.html
+++ b/web/html/xui/form/stream/stream_quic.html
@@ -1,5 +1,5 @@
{{define "form/streamQUIC"}}
-
+
None
@@ -20,7 +20,7 @@
-
+
None
SRTP
uTP
diff --git a/web/html/xui/form/stream/stream_settings.html b/web/html/xui/form/stream/stream_settings.html
index a2887de0..af81651d 100644
--- a/web/html/xui/form/stream/stream_settings.html
+++ b/web/html/xui/form/stream/stream_settings.html
@@ -1,8 +1,8 @@
{{define "form/streamSettings"}}
-
+
-
TCP
mKCP
diff --git a/web/html/xui/form/tls_settings.html b/web/html/xui/form/tls_settings.html
index b670226c..a2d9bd77 100644
--- a/web/html/xui/form/tls_settings.html
+++ b/web/html/xui/form/tls_settings.html
@@ -34,16 +34,16 @@
-
+
[[ key ]]
-
+
[[ key ]]
-
None
[[ key ]]
@@ -73,10 +73,10 @@
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-
-
+
-
+
@@ -85,10 +85,10 @@
-
+
-
+
@@ -124,10 +124,10 @@
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-
-
+
-
+
@@ -136,10 +136,10 @@
-
+
-
+
@@ -154,7 +154,7 @@
-
[[ key ]]
@@ -180,10 +180,10 @@
-
+
-
+
diff --git a/web/html/xui/inbound_client_table.html b/web/html/xui/inbound_client_table.html
index d7619dde..c4c405ec 100644
--- a/web/html/xui/inbound_client_table.html
+++ b/web/html/xui/inbound_client_table.html
@@ -40,7 +40,7 @@
-
+
{{ i18n "online" }}
@@ -52,7 +52,7 @@
{{ i18n "depleted" }}
{{ i18n "disabled" }}
- {{ i18n "online" }}
+ {{ i18n "online" }}
diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html
index cdcabad9..c8341651 100644
--- a/web/html/xui/inbound_info_modal.html
+++ b/web/html/xui/inbound_info_modal.html
@@ -166,7 +166,7 @@
Subscription URL
- [[ infoModal.subLink ]]
+ SUB: [[ infoModal.subLink ]]
@@ -175,14 +175,24 @@
+
+ JSON: [[ infoModal.subJsonLink ]]
+
+
+
+
+
+
+
+
Telegram ID
- @[[ infoModal.clientSettings.tgId ]]
+ [[ infoModal.clientSettings.tgId ]]
-
+
@@ -283,24 +293,50 @@
- Peer [[ index + 1 ]]
+ Peer [[ index + 1 ]]
+ {{ i18n "pages.xray.wireguard.secretKey" }}
+ [[ peer.privateKey ]]
+
+
{{ i18n "pages.xray.wireguard.publicKey" }}
[[ peer.publicKey ]]
-
+
{{ i18n "pages.xray.wireguard.psk" }}
[[ peer.psk ]]
-
+
{{ i18n "pages.xray.wireguard.allowedIPs" }}
[[ peer.allowedIPs.join(",") ]]
-
+
Keep Alive
[[ peer.keepAlive ]]
+
+
+
+
+ Config
+
+
+
+
+
+
+
+
+
+
+
+
@@ -319,7 +355,7 @@
index: null,
isExpired: false,
subLink: '',
- tgLink: '',
+ subJsonLink: '',
show(dbInbound, index) {
this.index = index;
this.inbound = dbInbound.toInbound();
@@ -327,13 +363,15 @@
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
- this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
+ if (this.inbound.protocol == Protocols.WIREGUARD){
+ this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
+ } else {
+ this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
+ }
if (this.clientSettings) {
if (this.clientSettings.subId) {
this.subLink = this.genSubLink(this.clientSettings.subId);
- }
- if (this.clientSettings.tgId) {
- this.tgLink = "https://t.me/" + this.clientSettings.tgId;
+ this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
}
}
this.visible = true;
@@ -343,6 +381,9 @@
},
genSubLink(subID) {
return app.subSettings.subURI+subID;
+ },
+ genSubJsonLink(subID) {
+ return app.subSettings.subJsonURI+subID;
}
};
diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html
index ab42e09c..fa89fada 100644
--- a/web/html/xui/inbound_modal.html
+++ b/web/html/xui/inbound_modal.html
@@ -40,7 +40,7 @@
inModal.visible = false;
inModal.loading(false);
},
- loading(loading) {
+ loading(loading=true) {
inModal.confirmLoading = loading;
},
};
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index c986e3fd..28d6b6e8 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -56,9 +56,13 @@
-
- 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
-
+
+
@@ -133,6 +137,10 @@
{{ i18n "pages.inbounds.export" }}
+
+
+ {{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
+
{{ i18n "pages.inbounds.resetAllTraffic" }}
@@ -141,7 +149,7 @@
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
-
+
{{ i18n "pages.inbounds.delDepletedClients" }}
@@ -178,7 +186,7 @@
-
{{ i18n "edit" }}
-
+
{{ i18n "qrCode" }}
@@ -217,7 +225,11 @@
{{ i18n "pages.inbounds.export"}}
-
+
+
+ {{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
+
+
{{ i18n "pages.inbounds.delDepletedClients" }}
@@ -496,7 +508,7 @@
scopedSlots: { customRender: 'expiryTime' },
}];
- const mobileColums = [{
+ const mobileColumns = [{
title: "ID",
align: 'right',
dataIndex: "id",
@@ -559,11 +571,13 @@
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
subSettings: {
enable : false,
- subURI : ''
+ subURI : '',
+ subJsonURI : '',
},
remarkModel: '-ieo',
datepicker: 'gregorian',
tgBotEnable: false,
+ showAlert: false,
pageSize: 0,
isMobile: window.innerWidth <= 768,
},
@@ -578,6 +592,7 @@
this.refreshing = false;
return;
}
+
await this.getOnlineUsers();
this.setInbounds(msg.obj);
setTimeout(() => {
@@ -604,7 +619,8 @@
this.tgBotEnable = tgBotEnable;
this.subSettings = {
enable : subEnable,
- subURI: subURI
+ subURI: subURI,
+ subJsonURI: subJsonURI
};
this.pageSize = pageSize;
this.remarkModel = remarkModel;
@@ -642,8 +658,12 @@
clientCount = clients.length;
if (dbInbound.enable) {
clients.forEach(client => {
- client.enable ? active.push(client.email) : deactive.push(client.email);
- if(this.isClientOnline(client.email)) online.push(client.email);
+ if (client.enable) {
+ active.push(client.email);
+ if (this.isClientOnline(client.email)) online.push(client.email);
+ } else {
+ deactive.push(client.email);
+ }
});
clientStats.forEach(client => {
if (!client.enable) {
@@ -668,6 +688,7 @@
online: online,
};
},
+
searchInbounds(key) {
if (ObjectUtil.isEmpty(key)) {
this.searchedInbounds = this.dbInbounds.slice();
@@ -731,6 +752,9 @@
case "export":
this.exportAllLinks();
break;
+ case "subs":
+ this.exportAllSubs();
+ break;
case "resetInbounds":
this.resetAllTraffic();
break;
@@ -762,6 +786,9 @@
case "export":
this.inboundLinks(dbInbound.id);
break;
+ case "subs":
+ this.exportSubs(dbInbound.id);
+ break;
case "clipboard":
this.copyToClipboard(dbInbound.id);
break;
@@ -811,7 +838,7 @@
protocol: baseInbound.protocol,
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
streamSettings: baseInbound.stream.toString(),
- sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
+ sniffing: baseInbound.sniffing.toString(),
};
await this.submit('/panel/inbound/add', data, inModal);
},
@@ -821,9 +848,7 @@
okText: '{{ i18n "pages.inbounds.create"}}',
cancelText: '{{ i18n "close" }}',
confirm: async (inbound, dbInbound) => {
- inModal.loading();
- await this.addInbound(inbound, dbInbound);
- inModal.close();
+ await this.addInbound(inbound, dbInbound, inModal);
},
isEdit: false
});
@@ -838,9 +863,7 @@
inbound: inbound,
dbInbound: dbInbound,
confirm: async (inbound, dbInbound) => {
- inModal.loading();
await this.updateInbound(inbound, dbInbound);
- inModal.close();
},
isEdit: true
});
@@ -860,7 +883,7 @@
settings: inbound.settings.toString(),
};
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
- if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
+ data.sniffing = inbound.sniffing.toString();
await this.submit('/panel/inbound/add', data, inModal);
},
@@ -879,7 +902,7 @@
settings: inbound.settings.toString(),
};
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
- if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
+ data.sniffing = inbound.sniffing.toString();
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
},
@@ -890,9 +913,7 @@
okText: '{{ i18n "pages.client.submitAdd"}}',
dbInbound: dbInbound,
confirm: async (clients, dbInboundId) => {
- clientModal.loading();
- await this.addClient(clients, dbInboundId);
- clientModal.close();
+ await this.addClient(clients, dbInboundId, clientModal);
},
isEdit: false
});
@@ -904,9 +925,7 @@
okText: '{{ i18n "pages.client.bulk"}}',
dbInbound: dbInbound,
confirm: async (clients, dbInboundId) => {
- clientsBulkModal.loading();
- await this.addClient(clients, dbInboundId);
- clientsBulkModal.close();
+ await this.addClient(clients, dbInboundId, clientsBulkModal);
},
});
},
@@ -935,19 +954,19 @@
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
}
},
- async addClient(clients, dbInboundId) {
+ async addClient(clients, dbInboundId, modal) {
const data = {
id: dbInboundId,
settings: '{"clients": [' + clients.toString() + ']}',
};
- await this.submit(`/panel/inbound/addClient`, data);
+ await this.submit(`/panel/inbound/addClient`, data, modal);
},
async updateClient(client, dbInboundId, clientId) {
const data = {
id: dbInboundId,
settings: '{"clients": [' + client.toString() + ']}',
};
- await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
+ await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
},
resetTraffic(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
@@ -967,7 +986,7 @@
},
delInbound(dbInboundId) {
this.$confirm({
- title: '{{ i18n "pages.inbounds.deleteInbound"}}',
+ title: '{{ i18n "pages.inbounds.deleteInbound"}}' + ' #' + dbInboundId,
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}',
@@ -980,7 +999,7 @@
clientId = this.getClientId(dbInbound.protocol, client);
if (confirmation){
this.$confirm({
- title: '{{ i18n "pages.inbounds.deleteClient"}}',
+ title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}',
@@ -1050,8 +1069,8 @@
await this.updateClient(clients[index], dbInboundId, clientId);
this.loading(false);
},
- async submit(url, data) {
- const msg = await HttpUtil.postWithModal(url, data);
+ async submit(url, data, modal) {
+ const msg = await HttpUtil.postWithModal(url, data, modal);
if (msg.success) {
await this.getDBInbounds();
}
@@ -1186,6 +1205,22 @@
newDbInbound = this.checkFallback(dbInbound);
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), newDbInbound.remark);
},
+ exportSubs(dbInboundId) {
+ const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
+ const clients = this.getInboundClients(dbInbound);
+ let subLinks = []
+ if (clients != null){
+ clients.forEach(c => {
+ if (c.subId && c.subId.length>0){
+ subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
+ }
+ })
+ }
+ txtModal.show(
+ '{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
+ [...new Set(subLinks)].join('\n'),
+ dbInbound.remark + "-Subs");
+ },
importInbound() {
promptModal.open({
title: '{{ i18n "pages.inbounds.importInbound" }}',
@@ -1194,10 +1229,26 @@
okText: '{{ i18n "pages.inbounds.import" }}',
confirm: async (dbInboundText) => {
await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal);
- promptModal.close();
},
});
},
+ exportAllSubs() {
+ let subLinks = []
+ for (const dbInbound of this.dbInbounds) {
+ const clients = this.getInboundClients(dbInbound);
+ if (clients != null){
+ clients.forEach(c => {
+ if (c.subId && c.subId.length>0){
+ subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
+ }
+ })
+ }
+ }
+ txtModal.show(
+ '{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
+ [...new Set(subLinks)].join('\r\n'),
+ 'All-Inbounds-Subs');
+ },
exportAllLinks() {
let copyText = [];
for (const dbInbound of this.dbInbounds) {
@@ -1238,7 +1289,7 @@
pagination(obj){
if (this.pageSize > 0 && obj.length>this.pageSize) {
// Set page options based on object size
- sizeOptions = []
+ sizeOptions = [];
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
sizeOptions.push(i.toString());
}
@@ -1251,8 +1302,8 @@
position: 'bottom',
pageSize: this.pageSize,
pageSizeOptions: sizeOptions
- }
- return p
+ };
+ return p;
}
return false
},
@@ -1266,6 +1317,9 @@
}, 500)
},
mounted() {
+ if (window.location.protocol !== "https:") {
+ this.showAlert = true;
+ }
window.addEventListener('resize', this.onResize);
this.onResize();
this.loading();
@@ -1303,7 +1357,6 @@
}
},
});
-
{{template "inboundModal"}}
@@ -1313,6 +1366,5 @@
{{template "inboundInfoModal"}}
{{template "clientsModal"}}
{{template "clientsBulkModal"}}
-