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 +

Image

+ **An Advanced Web Panel • Built on Xray Core** [![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](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: - - image +

Image

- 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"}} {{ i18n "pages.inbounds.client" }} @@ -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"> + + -{{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" }} + +

+
- - - - - - - - - - - - - - - - - - -
- -
-
-
- - - - - - -    - - - - - - - - -   - - - - - - -
-
+ + + + + + + + + + + + + + + + + +
+ +
+
+
+ + + + + + +    + + + + + + + + +   + + + + + 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 @@ -